[
  {
    "path": ".gitignore",
    "content": "bin\n.settings\n.classpath\n.project\n.gradle\n.idea\ntest-output\nreport.log\n/~$card_checklist.xlsx\nbuild/\nclasses/\n*.iml\n\ncards/src/main/resources/metastone.properties\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    {description}\n    Copyright (C) {year}  {fullname}\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  {signature of Ty Coon}, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n\n"
  },
  {
    "path": "README.md",
    "content": "# MetaStone #\r\n\r\n### What is it? ###\r\nMetaStone is a simulator for the online collectible card game (CCG) Hearthstone&reg; by Activison Blizzard written in Java. It strives to be a useful tool for card value analyzation, deck building and performance evaluation. There is also support for custom cards, allowing users to implement their own card inventions and testing them within the simulator engine. MetaStone tries to re-implement all game mechanics and rules from the original game as accurately as possible. \r\n\r\n### What is it not? ###\r\nThis is no Hearthstone replacement or clone. Please do not request better graphical effects, sounds or anything which makes it feel more like Hearthstone. There won't be any mode to battle against other human players. This is a tool meant for advanced players; if you just want to play Hearthstone, please play the real game.\r\n\r\n### How do I run it on Linux? ###\r\n* Go to [Releases](https://github.com/demilich1/metastone/releases) and download the latest release (`metastone-X_Y_Z_jar.zip`).\r\n* Extract the contents of the .zip file.\r\n* Open the Terminal (`Ctrl+Alt+T` on Ubuntu) and access `../MetaStone-X.Y.Z/bin`.\r\n* Execute this command: `./MetaStone`.\r\n    * Executing `sudo ./MetaStone` will execute the file as Root (\"Super User\"), this is not necessary though.\r\n    * You might need to make the file executable (On Ubuntu: Right Click the File -> Properties -> Permissions -> Allow executing file as program).\r\n\r\n### Can I contribute? ###\r\nSure! There is still a lot to do and anybody willing to contribute is welcome\r\n\r\n### What needs to be done? ###\r\n- UI improvements in general are welcome\r\n- We always need more unit tests! If you don't know what to test, take a look at http://hearthstone.gamepedia.com/Advanced_rulebook and just pick an example of card interaction from that wiki page\r\n- Code refactorings to make the code simpler and/or faster\r\n- There is a bug in the code and you know how to fix it? Great!\r\n- Better AI: at the moment the most advanced AI is 'Game State Value', however it is very subpar compared to human players. A more sophisticated AI would be a huge boon\r\n- Also consider having a look at the open issues\r\n- Anything else you would like to improve\r\n\r\n### How do I compile the code on my machine? ###\r\n* NOTE **JDK 1.8 is required!**\r\n* Clone the repo.  See [https://help.github.com/articles/cloning-a-repository/](https://help.github.com/articles/cloning-a-repository/) for help.\r\n* Open a terminal / command prompt and nagivate to your to your git repo location\r\n* Run the application from the command line: \r\n   * Linux/Mac OSX `./gradlew run`\r\n   * Windows `gradlew.bat run`\r\n   * Note: this will download all dependecies, compile and assemble all modules and then run the app.\r\n* Download dependecies and compile: \r\n   * Linux/Mac OSX `./gradlew compileJava`\r\n   * Windows `gradlew.bat compileJava`\r\n   * Note: this will download all dependecies and compile all modules.  Usefull when developing.\r\n* Get a list of all gradle tasks: \r\n   * Linux/Mac OSX `./gradlew tasks --all`\r\n   * Windows `gradlew.bat tasks --all`\r\n\r\n#### Building with an IDE\r\n* If you want to build from Eclipse, create the Eclipse project files: \r\n   * Linux/Mac OSX `./gradlew eclipse`\r\n   * Windows `gradlew.bat eclipse`\r\n   * _The above gradle task will automatically generate the `BuildConfig.java` file._\r\n   * Open Eclipse and choose `File > Import > General > Existing projects into workspace`\r\n   * Select the `Search for nested project` checkbox on the `Import Projects` screen.\r\n   * Change `Eclipse > Window > Preferences > Java > Compiler > Compiler Complience Level` to 1.8\r\n   * Change `Eclipse > Window > Preferences > Java > Compiler > Building > Circular dependencies` from `Error` to `Warning`.  There is a [known bug](https://issues.gradle.org/browse/GRADLE-2200) with importing multi-module gradle projects into Eclipse. The IDE of choice for working with gradle projects is [IntelliJ IDEA](https://www.jetbrains.com/idea/).\r\n* If you want to build from IntelliJ IDEA, create the IntelliJ project files:\r\n   * Linux/Mac OSX `./gradlew idea`\r\n   * Windows `gradlew.bat idea`\r\n   * _The above gradle task will automatically generate the `BuildConfig.java` file._\r\n   * Open IntelliJ and select `File > Open` then navigate to the project root dir.\r\n* ***Optionally*** (advanced option), you can choose to import the project into your respective IDE from the `build.gradle` files. When doing so, you **must** manually generate the `BuildConfig.java` file.  Otherwise your IDE will complain about unresolved references to `BuildConfig.java`.\r\n   * Linux/Mac OSX `./gradlew compileBuildConfig`\r\n   * Windows `gradlew.bat compileBuildConfig`\r\n\r\n### Project structure\r\n* MetaStone is made up of a handfull of source modules.  Here's what the top level structure looks like:\r\n```\r\nmetastone\r\n ├── app    // Application UI code and resources. Depends on 'game' and 'cards' modules.\r\n ├── game   // Game source code. Depends on 'shared' module.\r\n ├── shared // Shared code between 'app' and 'game' modules.\r\n └── cards  // Cards, decks and deckFormat data files.\r\n```\r\n* Each module can be built separately.  Their respective dependencies will get compiled and pulled in at build time. For example:\r\n* To produce a `cards.jar` file which contains all the cards, decks and deckFormat data files:\r\n   * Linux/Mac OSX `./gradlew cards:assemble`\r\n   * Windows `gradlew.bat cards:assemble`\r\n* To build the game module and produce a `game.jar` file:\r\n   * Linux/Mac OSX `./gradlew game:assemble`\r\n   * Windows `gradlew.bat game:assemble`\r\n* To produce a standalone distributable app binary:\r\n   * Linux/Mac OSX `./gradlew app:assemble`\r\n   * Windows `gradlew.bat app:assemble`\r\n\r\n### How do I build my own cards? ###\r\n**This feature is in very early stages and there is no official support yet.** There is no documentation at all. If you really want to start right now, here's how you can start:\r\n- You can build your own cards or modify existing cards without having to fork the project!\r\n- Card files are located in the `metastone/cards` directory.  **Use these as reference!**\r\n   * Linux/Mac OSX `~/metastone/cards`\r\n   * Windows `C:\\Users\\[username]\\Documents\\metastone\\cards`\r\n   * You can override the default metastone home dir by setting an environment varialble `USER_HOME_METASTONE` and specifying a new path.\r\n   * You must launch the app at least once for card data files to be copied.\r\n- Any `.json` files you place in your `metastone/cards` folder will be parsed and treated like built-in cards.\r\n- To learn the cards format it is highly recommended that you copy an existing card, change the `filename` and the `id` attribute (**<-- important!**) and make small changes.\r\n- Restart MetaStone for new cards to be detected.\r\n- If you are building out official cards or fixing existing cards, you will need to fork the project then make your changes in your repo's `metastone/cards/src/main/resources/cards` dir.  Then open a [Pull Request](https://help.github.com/articles/using-pull-requests/) into the project [master](https://github.com/demilich1/metastone/tree/master) branch with your changes.\r\n- Make sure to validate that the cards you added are well formed and can be parsed! Run the following command: \r\n   - Linux/Mac OSX `./gradlew cards:test` \r\n   - Windows `gradlew.bat cards:test`\r\n- **The card format is subject to change; cards you create now MAY NOT work in future versions**\r\n- In the rare chance that your card files get messed up beyond repair,  you can always force the app to overwrite your local card files with the versions distributed with the app in `cards.jar`.\r\n   * _Option 1_: Delete the `~/metastone` dir.  \r\n      * You **WILL LOOSE** all your changes, including **ALL new files** you may have added. DANGEROUS! MAKE A BACKUP!!\r\n      * Linux/Mac OSX `rm -rf ~/metastone`\r\n      * Windows `rmdir /s C:\\Users\\[username]\\Documents\\metastone`\r\n      * Card data files will be copied in their prestine state after you restart the app.\r\n   * _Option 2_: Edit the `~/metastone/metastone.properties` file and update the `cards.copied` property.\r\n      * delete the `cards.copied` property and save the file\r\n      * New files you may have added will NOT be affected.\r\n      * All card files that are distributed with the app will be overritten after you restart the app.\r\n\r\n### Running tests\r\n* The easiest way to run tests is from the command line.\r\n   * Linux/Mac OSX `./gradlew game:test`\r\n   * Windows `gradlew.bat game:test`\r\n* You can also run tests from your favorite IDE. For example:\r\n   * In IntelliJ right click on `src/test` folder in a given module and select `Run All Tests`\r\n* You can also run individual tests using the `-Dtest.single=[TEST NAME]` command line option.\r\n   * From the command line\r\n      * Linux/Mac OSX `./gradlew game:test -Dtest.single=SecretTest`\r\n      * Windows `gradlew.bat game:test -Dtest.single=SecretTest`\r\n   * From your IDE\r\n      * Right click on the individual test file and select `Run Test`\r\n* If you encounter test failures open the test report file `build/reports/tests/index.html` for details on the failures\r\n* Look [**here**](/game/src/test/java/net/demilich/metastone/tests) for list of existing game tests.\r\n\r\n"
  },
  {
    "path": "app/build.fxbuild",
    "content": "<?xml version=\"1.0\" encoding=\"ASCII\"?>\r\n<anttasks:AntTask xmi:version=\"2.0\" xmlns:xmi=\"http://www.omg.org/XMI\" xmlns:anttasks=\"http://org.eclipse.fx.ide.jdt/1.0\" buildDirectory=\"${project}/build\" cssToBin=\"true\">\r\n  <deploy height=\"768\" width=\"1024\" packagingFormat=\"exe\">\r\n    <application name=\"JavaFxTest\" mainclass=\"net.demilich.metastone.MetaStone\" version=\"0.9\" toolkit=\"fx\"/>\r\n    <info title=\"Metastone\" vendor=\"demilich\"/>\r\n  </deploy>\r\n  <signjar/>\r\n</anttasks:AntTask>\r\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'java'\r\napply plugin: 'application'\r\napply plugin: 'javafx-gradle-plugin'\r\n\r\nbuildscript {\r\n    dependencies {\r\n        classpath group: 'de.dynamicfiles.projects.gradle.plugins', name: 'javafx-gradle-plugin', version: '8.8.2'\r\n    }\r\n    repositories {\r\n        jcenter()\r\n    }\r\n}\r\n\r\nmainClassName = 'net.demilich.metastone.MetaStone'\r\n\r\njar {\r\n\tmanifest {\r\n\t\tattributes 'Implementation-Title': rootProject.name.capitalize(),\r\n\t\t\t'Implementation-Version': project.version\r\n\t}\r\n}\r\n\r\ndependencies {\r\n\tcompile project(':game')\r\n\tcompile project(':cards')\r\n\tcompile files('lib/controlsfx-8.40.10-20151003.010657-492.jar')\r\n\tcompile group: 'org.apache.commons', name: 'commons-lang3', version: '3.4'\r\n\tcompile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5'\r\n\tcompile 'org.jsoup:jsoup:1.10.2'\r\n\tcompile 'org.controlsfx:openjfx-dialogs:1.0.2'\r\n\ttestCompile group: 'org.testng', name: 'testng', version: '6.+'\r\n}\r\n\r\njfx {\r\n    // minimal requirement for jfxJar-task\r\n    mainClass = 'JavaFXDemo'\r\n    \r\n    // minimal requirement for jfxNative-task\r\n    vendor = ''\r\n}\r\n\r\ntest {\r\n\t// enable TestNG support (default is JUnit)\r\n\tuseTestNG()\r\n\ttestLogging {\r\n\t\tevents \"standardError\"\r\n\t}\r\n}\r\n"
  },
  {
    "path": "app/javafx.plugin",
    "content": "/*\n * Bootstrap script for the Gradle JavaFX Plugin.\n * (based on http://plugins.jasoft.fi/vaadin.plugin)\n *\n * The script will add the plugin to the build script\n * dependencies and apply the plugin to the project. If you do not want\n * this behavior you can copy and paste the below configuration into your\n * own build script and define your own repository and version for the plugin.\n */\n\nimport org.gradle.api.GradleException;\n\n\n\nbuildscript {\n    repositories {\n        mavenLocal()\n        maven {\n            name = 'BinTray'\n            url = 'http://dl.bintray.com/content/shemnon/javafx-gradle/'\n        }\n        maven {\n            name = 'CloudBees Snapshot'\n            url = 'http://repository-javafx-gradle-plugin.forge.cloudbees.com/snapshot'\n        }\n        ivy {\n            url = 'http://repository-javafx-gradle-plugin.forge.cloudbees.com/snapshot'\n        }\n        mavenCentral()\n    }\n    dependencies {\n        try {\n            assert (jfxrtDir != null)\n        } catch (RuntimeException re) {\n            ext.jfxrtDir = \".\"\n        }\n\n        ext.searchFile = {Map<String, Closure> places, List<String> searchPaths, String searchID ->\n            File result = null;\n            places.each { k, v ->\n                if (result != null) return;\n                project.logger.debug(\"Looking for $searchID in $k\")\n                def dir = v()\n                if (dir == null) {\n                    project.logger.debug(\"$k not set\")\n                } else {\n                    project.logger.debug(\"$k is $dir\")\n                    searchPaths.each { s ->\n                        if (result != null) return;\n                        File f = new File(dir, s);\n                        project.logger.debug(\"Trying $f.path\")\n                        if (f.exists() && f.file) {\n                            project.logger.debug(\"found $searchID as $result\")\n                            result = f;\n                        }\n                    }\n                }\n            }\n            if (!result?.file) {\n                throw new GradleException(\"Could not find $searchID, please set one of ${places.keySet()}\");\n            } else {\n                project.logger.info(\"$searchID: ${result}\")\n                return result\n            }\n        }\n        ext.findJFXJar = {\n            return searchFile([\n                    'jfxrtDir in Gradle Properties': {jfxrtDir},\n                    'JFXRT_HOME in System Environment': {System.env['JFXRT_HOME']},\n                    'JAVA_HOME in System Environment': {System.env['JAVA_HOME']},\n                    'java.home in JVM properties': {System.properties['java.home']}\n            ],\n                    ['jfxrt.jar', 'lib/jfxrt.jar', 'lib/ext/jfxrt.jar', 'jre/lib/jfxrt.jar', 'jre/lib/ext/jfxrt.jar'],\n                    'JavaFX Runtime Jar')\n        }\n\n        ext.findAntJavaFXJar = {\n            return searchFile([\n                    'jfxrtDir in Gradle Properties': {jfxrtDir},\n                    'JFXRT_HOME in System Environment': {System.env['JFXRT_HOME']},\n                    'JAVA_HOME in System Environment': {System.env['JAVA_HOME']},\n                    'java.home in JVM properties': {System.properties['java.home']}\n            ],\n                    ['ant-javafx.jar', 'lib/ant-javafx.jar', '../lib/ant-javafx.jar'],\n                    'JavaFX Packager Tools')\n        }\n\n\n        classpath 'org.bitbucket.shemnon.javafxplugin:gradle-javafx-plugin:8.1.2-SNAPSHOT'\n        classpath project.files(findAntJavaFXJar())\n        classpath project.files(findJFXJar())\n    }\n}\n\nif (!project.plugins.findPlugin(org.bitbucket.shemnon.javafxplugin.JavaFXPlugin)) {\n    project.apply(plugin: org.bitbucket.shemnon.javafxplugin.JavaFXPlugin)\n}\n"
  },
  {
    "path": "app/manifest.json",
    "content": "{\r\n\t\"version\" : \"1.2.0\",\r\n\t\"whatsNew\" : [\r\n\t\t\"- added all 'One Night in Karazhan' cards\"\r\n\t]\r\n\t\r\n}\r\n"
  },
  {
    "path": "app/src/deploy/package/windows/Metastone.iss",
    "content": ";This file will be executed next to the application bundle image\r\n;I.e. current directory will contain folder Metastone with application files\r\n[Setup]\r\nAppId={{fxApplication}}\r\nAppName=Metastone\r\nAppVersion=1.2.0\r\nAppVerName=Metastone\r\nAppPublisher=demilich\r\nAppComments=MetaStone\r\nAppCopyright=Copyright (C) 2016\r\n;AppPublisherURL=http://java.com/\r\n;AppSupportURL=http://java.com/\r\n;AppUpdatesURL=http://java.com/\r\nDefaultDirName={localappdata}\\Metastone\r\nDisableStartupPrompt=Yes\r\nDisableDirPage=No\r\nDisableProgramGroupPage=Yes\r\nDisableReadyPage=Yes\r\nDisableFinishedPage=No\r\nDisableWelcomePage=No\r\nDefaultGroupName=MetaStone\r\n;Optional License\r\nLicenseFile=\r\n;WinXP or above\r\nMinVersion=0,5.1 \r\nOutputBaseFilename=Metastone_Installer\r\nCompression=lzma\r\nSolidCompression=yes\r\nPrivilegesRequired=lowest\r\nSetupIconFile=Metastone\\Metastone.ico\r\nUninstallDisplayIcon={app}\\Metastone.ico\r\nUninstallDisplayName=Metastone\r\nWizardImageStretch=No\r\nWizardSmallImageFile=Metastone-setup-icon.bmp   \r\nArchitecturesInstallIn64BitMode=\r\n\r\n[Languages]\r\nName: \"english\"; MessagesFile: \"compiler:Default.isl\"\r\n\r\n[Files]\r\nSource: \"Metastone\\Metastone.exe\"; DestDir: \"{app}\"; Flags: ignoreversion\r\nSource: \"Metastone\\*\"; DestDir: \"{app}\"; Flags: ignoreversion recursesubdirs createallsubdirs\r\n\r\n[Icons]\r\nName: \"{group}\\Metastone\"; Filename: \"{app}\\Metastone.exe\"; IconFilename: \"{app}\\Metastone.ico\"; Check: returnTrue()\r\nName: \"{commondesktop}\\Metastone\"; Filename: \"{app}\\Metastone.exe\";  IconFilename: \"{app}\\Metastone.ico\"; Check: returnFalse()\r\n\r\n[Run]\r\nFilename: \"{app}\\Metastone.exe\"; Description: \"{cm:LaunchProgram,Metastone}\"; Flags: nowait postinstall skipifsilent; Check: returnTrue()\r\nFilename: \"{app}\\Metastone.exe\"; Parameters: \"-install -svcName \"\"Metastone\"\" -svcDesc \"\"Metastone\"\" -mainExe \"\"Metastone.exe\"\"  \"; Check: returnFalse()\r\n\r\n[UninstallRun]\r\nFilename: \"{app}\\Metastone.exe \"; Parameters: \"-uninstall -svcName Metastone -stopOnUninstall\"; Check: returnFalse()\r\n\r\n[Code]\r\nfunction returnTrue(): Boolean;\r\nbegin\r\n  Result := True;\r\nend;\r\n\r\nfunction returnFalse(): Boolean;\r\nbegin\r\n  Result := False;\r\nend;\r\n\r\nfunction InitializeSetup(): Boolean;\r\nbegin\r\n// Possible future improvements:\r\n//   if version less or same => just launch app\r\n//   if upgrade => check if same app is running and wait for it to exit\r\n//   Add pack200/unpack200 support? \r\n  Result := True;\r\nend;  \r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/ApplicationFacade.java",
    "content": "package net.demilich.metastone;\n\nimport net.demilich.nittygrittymvc.Facade;\nimport net.demilich.nittygrittymvc.interfaces.IFacade;\nimport net.demilich.metastone.gui.autoupdate.CheckForUpdateCommand;\nimport net.demilich.metastone.gui.battleofdecks.StartBattleOfDecksCommand;\nimport net.demilich.metastone.gui.deckbuilder.AddCardToDeckCommand;\nimport net.demilich.metastone.gui.deckbuilder.ChangeDeckNameCommand;\nimport net.demilich.metastone.gui.deckbuilder.DeleteDeckCommand;\nimport net.demilich.metastone.gui.deckbuilder.FillDeckWithRandomCardsCommand;\nimport net.demilich.metastone.gui.deckbuilder.FilterCardsCommand;\nimport net.demilich.metastone.gui.deckbuilder.ImportDeckCommand;\nimport net.demilich.metastone.gui.deckbuilder.LoadDeckFormatsCommand;\nimport net.demilich.metastone.gui.deckbuilder.LoadDecksCommand;\nimport net.demilich.metastone.gui.deckbuilder.RemoveCardFromDeckCommand;\nimport net.demilich.metastone.gui.deckbuilder.SaveDeckCommand;\nimport net.demilich.metastone.gui.deckbuilder.SetActiveDeckCommand;\nimport net.demilich.metastone.gui.deckbuilder.metadeck.AddDeckToMetaDeckCommand;\nimport net.demilich.metastone.gui.deckbuilder.metadeck.RemoveDeckFromMetaDeckCommand;\nimport net.demilich.metastone.gui.playmode.StartGameCommand;\nimport net.demilich.metastone.gui.playmode.animation.AnimationCompletedCommand;\nimport net.demilich.metastone.gui.playmode.animation.AnimationLockCommand;\nimport net.demilich.metastone.gui.playmode.animation.AnimationStartedCommand;\nimport net.demilich.metastone.gui.playmode.config.RequestDeckFormatsCommand;\nimport net.demilich.metastone.gui.playmode.config.RequestDecksCommand;\nimport net.demilich.metastone.gui.sandboxmode.commands.CreateNewSandboxCommand;\nimport net.demilich.metastone.gui.sandboxmode.commands.ModifyPlayerDeckCommand;\nimport net.demilich.metastone.gui.sandboxmode.commands.ModifyPlayerHandCommand;\nimport net.demilich.metastone.gui.sandboxmode.commands.PerformActionCommand;\nimport net.demilich.metastone.gui.sandboxmode.commands.SelectPlayerCommand;\nimport net.demilich.metastone.gui.sandboxmode.commands.SpawnMinionCommand;\nimport net.demilich.metastone.gui.sandboxmode.commands.StartPlaySandboxCommand;\nimport net.demilich.metastone.gui.sandboxmode.commands.StopPlaySandboxCommand;\nimport net.demilich.metastone.gui.simulationmode.SimulateGamesCommand;\nimport net.demilich.metastone.gui.trainingmode.PerformTrainingCommand;\nimport net.demilich.metastone.gui.trainingmode.RequestTrainingDataCommand;\nimport net.demilich.metastone.gui.trainingmode.SaveTrainingDataCommand;\n\npublic class ApplicationFacade extends Facade<GameNotification> {\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static IFacade<GameNotification> getInstance() {\n\t\tif (instance == null) {\n\t\t\tinstance = new ApplicationFacade();\n\t\t}\n\n\t\treturn instance;\n\t}\n\n\tpublic ApplicationFacade() {\n\t\tNotificationProxy.init(this);\n\n\t\tregisterCommand(GameNotification.APPLICATION_STARTUP, new ApplicationStartupCommand());\n\t\tregisterCommand(GameNotification.START_GAME, new StartGameCommand());\n\t\tregisterCommand(GameNotification.PLAY_GAME, new PlayGameCommand());\n\t\tregisterCommand(GameNotification.SIMULATE_GAMES, new SimulateGamesCommand());\n\t\tregisterCommand(GameNotification.START_TRAINING, new PerformTrainingCommand());\n\t\tregisterCommand(GameNotification.COMMIT_BATTLE_OF_DECKS_CONFIG, new StartBattleOfDecksCommand());\n\t\t\n\t\tregisterCommand(GameNotification.CHECK_FOR_UPDATE, new CheckForUpdateCommand());\n\n\t\tregisterCommand(GameNotification.SET_ACTIVE_DECK, new SetActiveDeckCommand());\n\t\tregisterCommand(GameNotification.ADD_CARD_TO_DECK, new AddCardToDeckCommand());\n\t\tregisterCommand(GameNotification.REMOVE_CARD_FROM_DECK, new RemoveCardFromDeckCommand());\n\t\tregisterCommand(GameNotification.SAVE_ACTIVE_DECK, new SaveDeckCommand());\n\t\tregisterCommand(GameNotification.LOAD_DECKS, new LoadDecksCommand());\n\t\tregisterCommand(GameNotification.LOAD_DECK_FORMATS, new LoadDeckFormatsCommand());\n\t\tregisterCommand(GameNotification.FILTER_CARDS, new FilterCardsCommand());\n\t\tregisterCommand(GameNotification.FILL_DECK_WITH_RANDOM_CARDS, new FillDeckWithRandomCardsCommand());\n\t\tregisterCommand(GameNotification.IMPORT_DECK_FROM_URL, new ImportDeckCommand());\n\t\tregisterCommand(GameNotification.CHANGE_DECK_NAME, new ChangeDeckNameCommand());\n\t\tregisterCommand(GameNotification.ADD_DECK_TO_META_DECK, new AddDeckToMetaDeckCommand());\n\t\tregisterCommand(GameNotification.REMOVE_DECK_FROM_META_DECK, new RemoveDeckFromMetaDeckCommand());\n\t\tregisterCommand(GameNotification.DELETE_DECK, new DeleteDeckCommand());\n\n\t\tregisterCommand(GameNotification.REQUEST_DECKS, new RequestDecksCommand());\n\t\tregisterCommand(GameNotification.REQUEST_DECK_FORMATS, new RequestDeckFormatsCommand());\n\n\t\tregisterCommand(GameNotification.CREATE_NEW_SANDBOX, new CreateNewSandboxCommand());\n\t\tregisterCommand(GameNotification.MODIFY_PLAYER_DECK, new ModifyPlayerDeckCommand());\n\t\tregisterCommand(GameNotification.MODIFY_PLAYER_HAND, new ModifyPlayerHandCommand());\n\t\tregisterCommand(GameNotification.SELECT_PLAYER, new SelectPlayerCommand());\n\t\tregisterCommand(GameNotification.SPAWN_MINION, new SpawnMinionCommand());\n\t\tregisterCommand(GameNotification.PERFORM_ACTION, new PerformActionCommand());\n\t\tregisterCommand(GameNotification.START_PLAY_SANDBOX, new StartPlaySandboxCommand());\n\t\tregisterCommand(GameNotification.STOP_PLAY_SANDBOX, new StopPlaySandboxCommand());\n\n\t\tregisterCommand(GameNotification.GAME_STATE_UPDATE, new AnimationLockCommand());\n\t\tregisterCommand(GameNotification.ANIMATION_STARTED, new AnimationStartedCommand());\n\t\tregisterCommand(GameNotification.ANIMATION_COMPLETED, new AnimationCompletedCommand());\n\n\t\tregisterCommand(GameNotification.SAVE_TRAINING_DATA, new SaveTrainingDataCommand());\n\t\tregisterCommand(GameNotification.REQUEST_TRAINING_DATA, new RequestTrainingDataCommand());\n\t}\n\n\tpublic void startUp() {\n\t\tsendNotification(GameNotification.APPLICATION_STARTUP);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/ApplicationStartupCommand.java",
    "content": "package net.demilich.metastone;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.gui.cards.CardProxy;\nimport net.demilich.metastone.gui.autoupdate.AutoUpdateMediator;\nimport net.demilich.metastone.gui.deckbuilder.DeckFormatProxy;\nimport net.demilich.metastone.gui.deckbuilder.DeckProxy;\nimport net.demilich.metastone.gui.dialog.DialogMediator;\nimport net.demilich.metastone.gui.main.ApplicationMediator;\nimport net.demilich.metastone.gui.playmode.animation.AnimationProxy;\nimport net.demilich.metastone.gui.sandboxmode.SandboxProxy;\nimport net.demilich.metastone.gui.trainingmode.TrainingProxy;\n\npublic class ApplicationStartupCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tgetFacade().registerMediator(new DialogMediator());\n\t\t\n\t\tgetFacade().registerProxy(new CardProxy());\n\t\tgetFacade().registerProxy(new DeckProxy());\n\t\tgetFacade().registerProxy(new DeckFormatProxy());\n\t\tgetFacade().registerProxy(new TrainingProxy());\n\t\tgetFacade().registerProxy(new SandboxProxy());\n\t\tgetFacade().registerProxy(new AnimationProxy());\n\n\t\tgetFacade().registerMediator(new ApplicationMediator());\n\t\tgetFacade().registerMediator(new AutoUpdateMediator());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/DevCardTools.java",
    "content": "package net.demilich.metastone;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileReader;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.PrintWriter;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.List;\n\nimport javax.script.ScriptEngine;\nimport javax.script.ScriptEngineManager;\nimport javax.script.ScriptException;\n\nimport org.apache.commons.io.FileUtils;\n\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\n\n/**\n * TODO:  not sure how this is used.\n * Paths in this file are no longer valid since cards, decks and deckformat are loaded from jar resources\n */\npublic class DevCardTools {\n\n\tpublic static void assignUniqueIdToEachCard() {\n\t\tfinal String path = \"./src/\" + Card.class.getPackage().getName().replace(\".\", \"/\") + \"/concrete/\";\n\t\tfinal String idExpression = \"public int getTypeId()\";\n\t\tFile folder = new File(path);\n\t\tint uniqueId = 1;\n\t\tHashSet<Integer> assignedIds = new HashSet<>();\n\t\tList<File> filesWithoutId = new ArrayList<>();\n\t\tfor (File file : FileUtils.listFiles(folder, new String[] { \"java\" }, true)) {\n\t\t\ttry {\n\t\t\t\t// System.out.println(\"Processing \" + file.getName() + \"...\");\n\t\t\t\tList<String> lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);\n\t\t\t\tint lineIndex = containsExpression(lines, idExpression);\n\t\t\t\tif (lineIndex != -1) {\n\t\t\t\t\t// System.out.println(\"Skipping \" + file.getName() +\n\t\t\t\t\t// \" because it already has an id assigned\");\n\t\t\t\t\tint id = extractId(lines.get(lineIndex + 1));\n\t\t\t\t\tassignedIds.add(id);\n\t\t\t\t\tcontinue;\n\t\t\t\t} else {\n\t\t\t\t\tfilesWithoutId.add(file);\n\t\t\t\t}\n\n\t\t\t} catch (IOException e) {\n\t\t\t\tSystem.err.println(\"Error while parsing file: \" + file.getName());\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\n\t\twhile (assignedIds.contains(uniqueId)) {\n\t\t\tuniqueId++;\n\t\t}\n\n\t\tfor (File file : filesWithoutId) {\n\t\t\ttry {\n\t\t\t\tList<String> lines;\n\t\t\t\tlines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);\n\t\t\t\tfor (int i = lines.size() - 1; i > 0; i--) {\n\t\t\t\t\tString line = lines.get(i);\n\t\t\t\t\tif (line.contains(\"}\")) {\n\t\t\t\t\t\tlines.add(i, \"\\t}\");\n\t\t\t\t\t\tlines.add(i, \"\\t\\treturn \" + uniqueId + \";\");\n\t\t\t\t\t\tlines.add(i, \"\\tpublic int getTypeId() {\");\n\t\t\t\t\t\tlines.add(i, \"\\t@Override\");\n\t\t\t\t\t\tlines.add(i, \"\\n\");\n\t\t\t\t\t\tSystem.out.println(\"Assigning id \" + uniqueId + \" to \" + file.getName());\n\t\t\t\t\t\tuniqueId++;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tFiles.write(file.toPath(), lines, StandardCharsets.UTF_8);\n\t\t\t} catch (IOException e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic static void cardListFromImages(String path) throws IOException {\n\t\tFile folder = new File(path);\n\n\t\tPrintWriter out = new PrintWriter(new FileWriter(\"cards_all\"));\n\n\t\tfor (File file : folder.listFiles()) {\n\t\t\tif (file.isFile()) {\n\t\t\t\tout.println(file.getName().replace(\".png\", \"\"));\n\t\t\t}\n\t\t}\n\t\tout.close();\n\t}\n\n\tprivate static String changeFileNameToClassName(String name) {\n\t\tif (name == null) {\n\t\t\tthrow new IllegalArgumentException(\"File Name == null\");\n\t\t}\n\t\tString className = name.replace(\".java\", \"\");\n\n\t\tclassName = className.replace('/', '.');\n\t\tclassName = className.replace('\\\\', '.');\n\t\tclassName = className.replace(\"..src.\", \"\");\n\n\t\treturn className;\n\t}\n\n\tpublic static void compareClassesWithCardList(String path) throws IOException {\n\t\tFile folder = new File(path);\n\t\tBufferedReader reader = new BufferedReader(new FileReader(\"cards_all\"));\n\t\tList<String> allCards = new ArrayList<String>();\n\t\tString line;\n\t\twhile ((line = reader.readLine()) != null) {\n\t\t\tallCards.add(toCanonName(line));\n\t\t}\n\t\treader.close();\n\n\t\tList<String> allClasses = new ArrayList<String>();\n\n\t\tfor (File file : FileUtils.listFiles(folder, new String[] { \"java\" }, true)) {\n\t\t\tString canonName = toCanonName(file.getName());\n\t\t\tallClasses.add(canonName);\n\t\t}\n\n\t\tint missing = 0;\n\t\tfor (String card : allCards) {\n\t\t\tif (allClasses.contains(card)) {\n\t\t\t\t// System.out.println(\"Card found: \" + card);\n\t\t\t} else {\n\t\t\t\tmissing++;\n\t\t\t\tSystem.out.println(\"Card missing: \" + card);\n\t\t\t}\n\t\t}\n\t\tSystem.out.println(\"There are \" + missing + \" cards missing\");\n\t}\n\n\tprivate static int containsExpression(List<String> lines, String expression) {\n\t\tint i = 0;\n\t\tfor (String line : lines) {\n\t\t\tif (line.contains(expression)) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t\ti++;\n\t\t}\n\t\treturn -1;\n\t}\n\n\tprivate static int extractId(String line) {\n\t\tString result = \"\";\n\t\tfor (int i = 0; i < line.length(); i++) {\n\t\t\tchar c = line.charAt(i);\n\t\t\tif (Character.isDigit(c)) {\n\t\t\t\tresult += c;\n\t\t\t}\n\t\t}\n\t\treturn Integer.parseInt(result);\n\t}\n\n\tpublic static void formatJsons() {\n\t\tFile folder = new File(\"./cards/\");\n\n\t\tCollection<File> files = FileUtils.listFiles(folder, new String[] { \"json\" }, true);\n\t\tint i = 0;\n\t\tfor (File file : files) {\n\t\t\ttry {\n\t\t\t\tSystem.out.println(\"Processing \" + file.getName() + \" (\" + ++i + \" of \" + files.size() + \" files)\");\n\t\t\t\tprettyPrintFile(file);\n\t\t\t} catch (IOException e) {\n\t\t\t\t// TODO Auto-generated catch block\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static List<String> getImplementedCardsAsLines() {\n\t\tfinal String expression = \"cards.add(new %s());\";\n\t\tfinal String path = \"./src/\" + Card.class.getPackage().getName().replace(\".\", \"/\") + \"/concrete/\";\n\t\tList<String> lines = new ArrayList<String>();\n\t\tFile folder = new File(path);\n\t\tfor (File file : FileUtils.listFiles(folder, new String[] { \"java\" }, true)) {\n\t\t\tString cardFileName = file.getPath();\n\t\t\tString cardClassName = changeFileNameToClassName(cardFileName);\n\t\t\t// System.out.println(changeFileNameToClassName(cardName));\n\t\t\tlines.add(String.format(expression, cardClassName));\n\t\t}\n\t\treturn lines;\n\t}\n\n\tprivate static void prettyPrintFile(File file) throws IOException {\n\t\tPath path = Paths.get(file.getPath());\n\t\tString content = new String(Files.readAllBytes(path));\n\n\t\tScriptEngineManager manager = new ScriptEngineManager();\n\t\tScriptEngine scriptEngine = manager.getEngineByName(\"JavaScript\");\n\n\t\tscriptEngine.put(\"jsonString\", content);\n\t\ttry {\n\t\t\tscriptEngine.eval(\"result = JSON.stringify(JSON.parse(jsonString), null, \\\"\\t\\\")\");\n\t\t} catch (ScriptException e) {\n\t\t\t// TODO Auto-generated catch block\n\t\t\te.printStackTrace();\n\t\t}\n\t\tString prettyPrintedJson = (String) scriptEngine.get(\"result\");\n\t\tFiles.write(path, prettyPrintedJson.getBytes());\n\t}\n\n\tprivate static String toCanonName(String name) {\n\t\treturn name.toLowerCase().replace(\".java\", \"\").replace(\".png\", \"\").replace(\"_\", \"\").replace(\"-\", \"\");\n\t}\n\n\tpublic static void updateCardCatalogue() {\n\t\tfinal String cataloguePathStr = \"./src/\" + CardCatalogue.class.getPackage().getName().replace(\".\", \"/\") + \"/CardCatalogue.java\";\n\t\tPath cataloguePath = Paths.get(cataloguePathStr);\n\t\tList<String> lines = new ArrayList<>();\n\t\ttry (BufferedReader reader = Files.newBufferedReader(cataloguePath)) {\n\t\t\tString line = null;\n\t\t\tList<String> implementedCards = getImplementedCardsAsLines();\n\t\t\tboolean insideRelevantCodeBlock = false;\n\t\t\twhile ((line = reader.readLine()) != null) {\n\t\t\t\tif (line.contains(\"static {\")) {\n\t\t\t\t\tlines.add(line);\n\t\t\t\t\tinsideRelevantCodeBlock = true;\n\t\t\t\t\tlines.addAll(implementedCards);\n\t\t\t\t} else if (line.contains(\"}\")) {\n\t\t\t\t\tinsideRelevantCodeBlock = false;\n\t\t\t\t}\n\n\t\t\t\tif (!insideRelevantCodeBlock) {\n\t\t\t\t\tlines.add(line);\n\t\t\t\t}\n\n\t\t\t}\n\t\t} catch (IOException x) {\n\t\t\tSystem.err.format(\"IOException: %s%n\", x);\n\t\t}\n\t\ttry {\n\t\t\tFiles.write(cataloguePath, lines);\n\t\t} catch (IOException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\tSystem.out.println(\"CardCatalogue has been successfully updated\");\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/MetaStone.java",
    "content": "package net.demilich.metastone;\r\n\r\nimport javafx.application.Application;\r\nimport javafx.geometry.Pos;\r\nimport javafx.scene.Scene;\r\nimport javafx.scene.image.Image;\r\nimport javafx.scene.layout.StackPane;\r\nimport javafx.stage.Stage;\r\nimport javafx.stage.StageStyle;\r\nimport net.demilich.metastone.gui.IconFactory;\r\nimport net.demilich.metastone.utils.UserHomeMetastone;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport java.io.IOException;\r\nimport java.nio.file.Files;\r\nimport java.nio.file.Paths;\r\n\r\npublic class MetaStone extends Application {\r\n\r\n\tprivate static Logger logger = LoggerFactory.getLogger(MetaStone.class);\r\n\r\n\tpublic static void main(String[] args) {\r\n\t\t//DevCardTools.formatJsons();\r\n\r\n\t\ttry {\r\n\t\t\t// ensure that the user home metastone dir exists\r\n\t\t\tFiles.createDirectories(Paths.get(UserHomeMetastone.getPath()));\r\n\t\t} catch (IOException e) {\r\n\t\t\tlogger.error(\"Trouble creating \" +  Paths.get(UserHomeMetastone.getPath()));\r\n\t\t\te.printStackTrace();\r\n\t\t}\r\n\r\n\t\tlaunch(args);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void start(Stage primaryStage) throws Exception {\r\n\t\tprimaryStage.setTitle(\"MetaStone\");\r\n\t\tprimaryStage.initStyle(StageStyle.UNIFIED);\r\n\t\tprimaryStage.setResizable(false);\r\n\t\tprimaryStage.getIcons().add(new Image(IconFactory.getImageUrl(\"ui/app_icon.png\")));\r\n\r\n\t\tApplicationFacade facade = (ApplicationFacade) ApplicationFacade.getInstance();\r\n\t\tfacade.startUp();\r\n\r\n\t\tStackPane root = new StackPane();\r\n\t\troot.setAlignment(Pos.CENTER);\r\n\t\tScene scene = new Scene(root);\r\n\t\tscene.getStylesheets().add(getClass().getResource(\"/css/main.css\").toExternalForm());\r\n\t\tprimaryStage.setScene(scene);\r\n\t\t\r\n\t\t// implementing potential visual fix for JavaFX\r\n\t\t// setting the visual opacity to zero, and then\r\n\t\t// once the stage is shown, setting the opacity to one.\r\n\t\t// this fixes an issue where some users would only see a blank\r\n\t\t// screen on application startup\r\n\t\tprimaryStage.setOpacity(0.0);\r\n\t\t\r\n\t\tfacade.sendNotification(GameNotification.CANVAS_CREATED, root);\r\n\t\tfacade.sendNotification(GameNotification.MAIN_MENU);\r\n\t\tprimaryStage.show();\r\n\t\t\r\n\t\t// setting opacity to one for JavaFX hotfix\r\n\t\tprimaryStage.setOpacity(1.0);\r\n\t\t\r\n\t\tfacade.sendNotification(GameNotification.CHECK_FOR_UPDATE);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/PlayGameCommand.java",
    "content": "package net.demilich.metastone;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.game.GameContext;\n\npublic class PlayGameCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tGameContext context = (GameContext) notification.getBody();\n\t\tcontext.play();\n\t\tgetFacade().sendNotification(GameNotification.GAME_OVER, context);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/DigitFactory.java",
    "content": "package net.demilich.metastone.gui;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.HashMap;\n\nimport javax.imageio.ImageIO;\n\nimport javafx.embed.swing.SwingFXUtils;\nimport javafx.scene.Group;\nimport javafx.scene.Node;\nimport javafx.scene.Scene;\nimport javafx.scene.SnapshotParameters;\nimport javafx.scene.effect.Blend;\nimport javafx.scene.effect.BlendMode;\nimport javafx.scene.effect.ColorAdjust;\nimport javafx.scene.effect.ColorInput;\nimport javafx.scene.effect.Effect;\nimport javafx.scene.effect.ImageInput;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.image.WritableImage;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.paint.Color;\nimport javafx.stage.Stage;\nimport javafx.stage.StageStyle;\n\npublic class DigitFactory {\n\n\tprivate final static HashMap<Character, Image> digits = new HashMap<>();\n\n\tstatic {\n\t\tdigits.put('-', new Image(IconFactory.RESOURCE_PATH + \"/img/common/digits/-.png\"));\n\t\tfor (int i = 0; i < 10; i++) {\n\t\t\tchar digitToChar = Character.forDigit(i, 10);\n\t\t\tdigits.put(digitToChar, new Image(IconFactory.RESOURCE_PATH + \"/img/common/digits/\" + digitToChar + \".png\"));\n\t\t}\n\t}\n\n\tprivate static void applyFontColor(ImageView image, Color color) {\n\t\tColorAdjust monochrome = new ColorAdjust();\n\t\tmonochrome.setSaturation(-1.0);\n\t\tEffect colorInput = new ColorInput(0, 0, image.getImage().getWidth(), image.getImage().getHeight(), color);\n\t\tBlend blend = new Blend(BlendMode.MULTIPLY, new ImageInput(image.getImage()), colorInput);\n\t\timage.setClip(new ImageView(image.getImage()));\n\t\timage.setEffect(blend);\n\t\timage.setCache(true);\n\t}\n\n\tprivate static Node getCachedDigitImage(int number, Color color) {\n\t\tString numberString = String.valueOf(number);\n\t\tif (numberString.length() == 1) {\n\t\t\tchar digitToChar = Character.forDigit(number, 10);\n\t\t\tImageView image = new ImageView(digits.get(digitToChar));\n\t\t\tapplyFontColor(image, color);\n\t\t\treturn image;\n\t\t}\n\n\t\tHBox layoutPane = new HBox(-4);\n\t\tfor (int i = 0; i < numberString.length(); i++) {\n\t\t\tchar digitToChar = numberString.charAt(i);\n\t\t\tImageView image = new ImageView(digits.get(digitToChar));\n\t\t\tapplyFontColor(image, color);\n\t\t\tlayoutPane.getChildren().add(image);\n\t\t}\n\t\treturn layoutPane;\n\t}\n\n\tpublic static void saveAllDigits() {\n\t\tStage stage = new Stage(StageStyle.TRANSPARENT);\n\t\tDigitTemplate root = new DigitTemplate();\n\t\tScene scene = new Scene(root);\n\t\tstage.setScene(scene);\n\t\tstage.show();\n\n\t\tSnapshotParameters snapshotParams = new SnapshotParameters();\n\t\tsnapshotParams.setFill(Color.TRANSPARENT);\n\t\troot.digit.setText(\"-\");\n\t\tfor (int i = 0; i <= 10; i++) {\n\t\t\tWritableImage image = root.digit.snapshot(snapshotParams, null);\n\n\t\t\tFile file = new File(\"src/\" + IconFactory.RESOURCE_PATH + \"/img/common/digits/\" + root.digit.getText() + \".png\");\n\n\t\t\ttry {\n\t\t\t\tImageIO.write(SwingFXUtils.fromFXImage(image, null), \"png\", file);\n\t\t\t} catch (IOException e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t\troot.digit.setText(\"\" + i);\n\t\t}\n\n\t\tstage.close();\n\t}\n\n\tpublic static void showPreRenderedDigits(Group group, int number) {\n\t\tshowPreRenderedDigits(group, number, Color.WHITE);\n\t}\n\n\tpublic static void showPreRenderedDigits(Group group, int number, Color color) {\n\t\tgroup.getChildren().clear();\n\t\tgroup.getChildren().add(DigitFactory.getCachedDigitImage(number, color));\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/DigitTemplate.java",
    "content": "package net.demilich.metastone.gui;\n\nimport java.io.IOException;\n\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.text.Text;\n\npublic class DigitTemplate extends HBox {\n\n\t@FXML\n\tpublic Text digit;\n\n\tpublic DigitTemplate() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/DigitTemplate.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/IconFactory.java",
    "content": "package net.demilich.metastone.gui;\n\nimport javafx.scene.image.Image;\nimport javafx.scene.paint.Color;\nimport net.demilich.metastone.game.cards.Rarity;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.heroes.powers.HeroPower;\nimport net.demilich.metastone.gui.dialog.DialogType;\n\npublic class IconFactory {\n\n\t//public static final String RESOURCE_PATH = \"/net/demilich/metastone/resources\";\n\tpublic static final String RESOURCE_PATH = \"\";\n\n\tpublic static Image getClassIcon(HeroClass heroClass) {\n\t\tString iconPath = RESOURCE_PATH + \"/img/classes/\";\n\t\ticonPath += heroClass.toString().toLowerCase();\n\t\ticonPath += \".png\";\n\t\treturn new Image(iconPath);\n\t}\n\n\tpublic static Image getDefaultCardBack() {\n\t\tString iconPath = RESOURCE_PATH + \"/img/common/card_back_default.png\";\n\t\treturn new Image(iconPath);\n\t}\n\n\tpublic static Image getDialogIcon(DialogType dialogType) {\n\t\tString iconPath = RESOURCE_PATH + \"/img/ui/\";\n\t\tswitch (dialogType) {\n\t\tcase CONFIRM:\n\t\t\ticonPath += \"confirm.png\";\n\t\t\tbreak;\n\t\tcase ERROR:\n\t\t\ticonPath += \"error.png\";\n\t\t\tbreak;\n\t\tcase INFO:\n\t\t\ticonPath += \"info.png\";\n\t\t\tbreak;\n\t\tcase WARNING:\n\t\t\ticonPath += \"warning.png\";\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\n\t\t}\n\t\treturn new Image(iconPath);\n\t}\n\n\tpublic static String getHeroIconUrl(HeroClass heroClass) {\n\t\tString iconPath = RESOURCE_PATH + \"/img/heroes/\";\n\t\tswitch (heroClass) {\n\t\tcase DRUID:\n\t\t\ticonPath += \"malfurion\";\n\t\t\tbreak;\n\t\tcase HUNTER:\n\t\t\ticonPath += \"rexxar\";\n\t\t\tbreak;\n\t\tcase MAGE:\n\t\t\ticonPath += \"jaina\";\n\t\t\tbreak;\n\t\tcase PALADIN:\n\t\t\ticonPath += \"uther\";\n\t\t\tbreak;\n\t\tcase PRIEST:\n\t\t\ticonPath += \"anduin\";\n\t\t\tbreak;\n\t\tcase ROGUE:\n\t\t\ticonPath += \"valeera\";\n\t\t\tbreak;\n\t\tcase SHAMAN:\n\t\t\ticonPath += \"thrall\";\n\t\t\tbreak;\n\t\tcase WARLOCK:\n\t\t\ticonPath += \"guldan\";\n\t\t\tbreak;\n\t\tcase WARRIOR:\n\t\t\ticonPath += \"garrosh\";\n\t\t\tbreak;\n\t\tdefault:\n\t\tcase ANY:\n\t\t\ticonPath += \"unknown\";\n\t\t\tbreak;\n\n\t\t}\n\t\treturn iconPath + \".png\";\n\t}\n\n\tpublic static String getHeroPowerIconUrl(HeroPower heroPower) {\n\t\tString iconPath = RESOURCE_PATH + \"/img/powers/\";\n\t\tswitch (heroPower.getHeroClass()) {\n\t\tcase DRUID:\n\t\t\ticonPath += \"shapeshift\";\n\t\t\tbreak;\n\t\tcase HUNTER:\n\t\t\ticonPath += \"steady_shot\";\n\t\t\tbreak;\n\t\tcase MAGE:\n\t\t\ticonPath += \"fireblast\";\n\t\t\tbreak;\n\t\tcase PALADIN:\n\t\t\ticonPath += \"reinforce\";\n\t\t\tbreak;\n\t\tcase PRIEST:\n\t\t\ticonPath += \"lesser_heal\";\n\t\t\tbreak;\n\t\tcase ROGUE:\n\t\t\ticonPath += \"dagger_mastery\";\n\t\t\tbreak;\n\t\tcase SHAMAN:\n\t\t\ticonPath += \"totemic_call\";\n\t\t\tbreak;\n\t\tcase WARLOCK:\n\t\t\ticonPath += \"life_tap\";\n\t\t\tbreak;\n\t\tcase WARRIOR:\n\t\t\ticonPath += \"armor_up\";\n\t\t\tbreak;\n\t\tdefault:\n\t\t\ticonPath += \"unknown\";\n\t\t\tbreak;\n\n\t\t}\n\t\ticonPath += \".png\";\n\t\treturn iconPath;\n\t}\n\n\tpublic static String getImageUrl(String imageName) {\n\t\t//System.out.println(new File(\"\").getAbsolutePath());\n\t\treturn RESOURCE_PATH + \"/img/\" + imageName;\n\t}\n\n\tpublic static Color getRarityColor(Rarity rarity) {\n\t\tColor color = Color.BLACK;\n\t\tswitch (rarity) {\n\t\tcase COMMON:\n\t\t\tcolor = Color.WHITE;\n\t\t\tbreak;\n\t\tcase EPIC:\n\t\t\t// a335ee\n\t\t\tcolor = Color.rgb(163, 53, 238);\n\t\t\tbreak;\n\t\tcase LEGENDARY:\n\t\t\t// ff8000\n\t\t\tcolor = Color.rgb(255, 128, 0);\n\t\t\tbreak;\n\t\tcase RARE:\n\t\t\t// 0070dd\n\t\t\tcolor = Color.rgb(0, 112, 221);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tcolor = Color.GRAY;\n\t\t\tbreak;\n\t\t}\n\t\treturn color;\n\n\t}\n\n\tpublic static Image getSummonHelper() {\n\t\tString iconPath = RESOURCE_PATH + \"/img/common/arrow_down_blue.png\";\n\t\treturn new Image(iconPath);\n\t}\n\n\tpublic static Image getTargetIcon() {\n\t\tString iconPath = RESOURCE_PATH + \"/img/common/target.png\";\n\t\treturn new Image(iconPath);\n\t}\n\n\tprivate IconFactory() {\n\t}\n}"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/autoupdate/AutoUpdateMediator.java",
    "content": "package net.demilich.metastone.gui.autoupdate;\r\n\r\nimport java.awt.Desktop;\r\nimport java.io.IOException;\r\nimport java.net.URI;\r\nimport java.net.URISyntaxException;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\nimport org.controlsfx.control.Notifications;\r\n\r\nimport javafx.application.Platform;\r\nimport javafx.event.ActionEvent;\r\nimport javafx.geometry.Pos;\r\nimport javafx.scene.Node;\r\nimport javafx.scene.image.ImageView;\r\nimport javafx.util.Duration;\r\nimport net.demilich.metastone.GameNotification;\r\nimport net.demilich.metastone.gui.IconFactory;\r\nimport net.demilich.metastone.gui.dialog.DialogType;\r\nimport net.demilich.metastone.utils.VersionInfo;\r\nimport net.demilich.nittygrittymvc.Mediator;\r\nimport net.demilich.nittygrittymvc.interfaces.INotification;\r\n\r\npublic class AutoUpdateMediator extends Mediator<GameNotification> {\r\n\t\r\n\tpublic static final String NAME = \"AutoUpdateMediator\";\r\n\t\r\n\tprivate Node view;\r\n\r\n\tpublic AutoUpdateMediator() {\r\n\t\tsuper(NAME);\r\n\t}\r\n\t\r\n\t@Override\r\n\tpublic void handleNotification(final INotification<GameNotification> notification) {\r\n\t\tswitch (notification.getId()) {\r\n\t\tcase CANVAS_CREATED:\r\n\t\t\tview = (Node) notification.getBody();\r\n\t\t\tbreak;\r\n\t\tcase NEW_VERSION_AVAILABLE:\r\n\t\t\tVersionInfo versionInfo = (VersionInfo) notification.getBody();\r\n\t\t\tPlatform.runLater(() -> showUpdateNotification(versionInfo));\r\n\t\t\tbreak;\r\n\t\tdefault:\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic List<GameNotification> listNotificationInterests() {\r\n\t\tList<GameNotification> notificationInterests = new ArrayList<GameNotification>();\r\n\t\tnotificationInterests.add(GameNotification.CANVAS_CREATED);\r\n\t\tnotificationInterests.add(GameNotification.NEW_VERSION_AVAILABLE);\r\n\t\treturn notificationInterests;\r\n\t}\r\n\t\r\n\tprivate void showUpdateNotification(VersionInfo versionInfo) {\r\n\t\tImageView icon = new ImageView(IconFactory.getDialogIcon(DialogType.INFO));\r\n\t\ticon.setFitWidth(64);\r\n\t\ticon.setFitHeight(64);\r\n\t\tNotifications.create()\r\n        .title(\"New version available\")\r\n        .text(\"MetaStone '\" + versionInfo.version + \"' is ready for download\")\r\n        .graphic(icon)\r\n        .position(Pos.BOTTOM_CENTER)\r\n        .hideAfter(Duration.seconds(5))\r\n        .owner(view)\r\n        .darkStyle()\r\n        .onAction(this::onNotificationClicked)\r\n        .show();\r\n\t}\r\n\t\r\n\tprivate void onNotificationClicked(ActionEvent event) {\r\n\t\ttry {\r\n\t\t\tDesktop.getDesktop().browse(new URI(\"http://www.demilich.net/metastone/download.html\"));\r\n\t\t} catch (IOException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t} catch (URISyntaxException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/autoupdate/CheckForUpdateCommand.java",
    "content": "package net.demilich.metastone.gui.autoupdate;\r\n\r\nimport org.apache.http.HttpEntity;\r\nimport org.apache.http.client.config.RequestConfig;\r\nimport org.apache.http.client.methods.CloseableHttpResponse;\r\nimport org.apache.http.client.methods.HttpGet;\r\nimport org.apache.http.impl.client.CloseableHttpClient;\r\nimport org.apache.http.impl.client.HttpClientBuilder;\r\nimport org.apache.http.util.EntityUtils;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.google.gson.Gson;\r\n\r\nimport net.demilich.metastone.BuildConfig;\r\nimport net.demilich.metastone.GameNotification;\r\nimport net.demilich.metastone.utils.VersionInfo;\r\nimport net.demilich.nittygrittymvc.SimpleCommand;\r\nimport net.demilich.nittygrittymvc.interfaces.INotification;\r\n\r\npublic class CheckForUpdateCommand extends SimpleCommand<GameNotification> {\r\n\r\n\tprivate static Logger logger = LoggerFactory.getLogger(CheckForUpdateCommand\t.class);\r\n\r\n\tprivate static final String MANIFEST_URL = \"http://demilich.net/metastone/version/manifest.json\";\r\n\r\n\t@Override\r\n\tpublic void execute(INotification<GameNotification> notification) {\r\n\t\tnew Thread(this::check).start();\r\n\t}\r\n\r\n\tprivate void check() {\r\n\t\ttry {\r\n\t\t\tRequestConfig globalConfig = RequestConfig.custom().setCircularRedirectsAllowed(true).build();\r\n\t\t\tCloseableHttpClient httpclient = HttpClientBuilder.create().build();\r\n\t\t\tlogger.debug(\"Requesting: \" + MANIFEST_URL);\r\n\r\n\t\t\tHttpGet httpGet = new HttpGet(MANIFEST_URL);\r\n\t\t\thttpGet.setConfig(globalConfig);\r\n\r\n\t\t\tCloseableHttpResponse response = httpclient.execute(httpGet);\r\n\t\t\ttry {\r\n\r\n\t\t\t\tHttpEntity entity = response.getEntity();\r\n\t\t\t\tString htmlContent = EntityUtils.toString(entity);\r\n\t\t\t\tEntityUtils.consume(entity);\r\n\t\t\t\tGson gson = new Gson();\r\n\t\t\t\tVersionInfo versionInfo = gson.fromJson(htmlContent, VersionInfo.class);\r\n\t\t\t\tif (versionInfo.isNewerVersionAvailable(BuildConfig.VERSION)) {\r\n\t\t\t\t\tlogger.debug(\"Newer version available: {}\" + versionInfo.version);\r\n\t\t\t\t\tgetFacade().sendNotification(GameNotification.NEW_VERSION_AVAILABLE, versionInfo);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tlogger.debug(\"Version up-to-date\");\r\n\t\t\t\t}\r\n\t\t\t} finally {\r\n\t\t\t\tresponse.close();\r\n\t\t\t}\r\n\r\n\t\t} catch (Exception e) {\r\n\t\t\tlogger.warn(\"Auto updater version check failed: \" + e.toString());\r\n\t\t}\r\n\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/battleofdecks/BattleBatchResult.java",
    "content": "package net.demilich.metastone.gui.battleofdecks;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.statistics.GameStatistics;\nimport net.demilich.metastone.game.statistics.Statistic;\n\npublic class BattleBatchResult {\n\n\tprivate final int numberOfGames;\n\tprivate final GameStatistics player1Results = new GameStatistics();\n\tprivate final GameStatistics player2Results = new GameStatistics();\n\tprivate int gamesCompleted;\n\tprivate final Deck deck1;\n\tprivate final Deck deck2;\n\tprivate boolean completed;\n\n\tpublic BattleBatchResult(Deck deck1, Deck deck2, int numberOfGames) {\n\t\tthis.deck1 = deck1;\n\t\tthis.deck2 = deck2;\n\t\tthis.numberOfGames = numberOfGames;\n\t}\n\n\tpublic Deck getDeck1() {\n\t\treturn deck1;\n\t}\n\n\tpublic double getDeck1Winrate() {\n\t\treturn getPlayer1Results().getDouble(Statistic.WIN_RATE);\n\t}\n\n\tpublic Deck getDeck2() {\n\t\treturn deck2;\n\t}\n\n\tpublic double getDeck2Winrate() {\n\t\treturn getPlayer2Results().getDouble(Statistic.WIN_RATE);\n\t}\n\n\tpublic int getNumberOfGames() {\n\t\treturn numberOfGames;\n\t}\n\n\tpublic GameStatistics getPlayer1Results() {\n\t\treturn player1Results;\n\t}\n\n\tpublic GameStatistics getPlayer2Results() {\n\t\treturn player2Results;\n\t}\n\n\tpublic double getProgress() {\n\t\treturn gamesCompleted / (double) numberOfGames;\n\t}\n\n\tpublic boolean isCompleted() {\n\t\treturn completed;\n\t}\n\n\tpublic void onGameEnded(GameContext result) {\n\t\tgetPlayer1Results().merge(result.getPlayer1().getStatistics());\n\t\tgetPlayer2Results().merge(result.getPlayer2().getStatistics());\n\n\t\tif (++gamesCompleted == numberOfGames) {\n\t\t\tsetCompleted(true);\n\t\t}\n\t}\n\n\tpublic void setCompleted(boolean completed) {\n\t\tthis.completed = completed;\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/battleofdecks/BattleBatchResultToken.java",
    "content": "package net.demilich.metastone.gui.battleofdecks;\n\nimport java.io.IOException;\n\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ProgressBar;\nimport javafx.scene.control.ProgressIndicator;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.layout.BorderPane;\nimport net.demilich.metastone.gui.IconFactory;\n\npublic class BattleBatchResultToken extends BorderPane {\n\n\t@FXML\n\tprivate Label deck1Label;\n\t@FXML\n\tprivate ImageView deck1Icon;\n\t@FXML\n\tprivate Label deck2Label;\n\t@FXML\n\tprivate ImageView deck2Icon;\n\n\t@FXML\n\tprivate ProgressBar winrate1Bar;\n\t@FXML\n\tprivate Label winrate1Label;\n\t@FXML\n\tprivate ProgressBar winrate2Bar;\n\t@FXML\n\tprivate Label winrate2Label;\n\t@FXML\n\tprivate Node contentPane;\n\t@FXML\n\tprivate ProgressIndicator progressIndicator;\n\n\tpublic BattleBatchResultToken() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/BattleBatchResultToken.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\t\tcontentPane.setOpacity(0.25);\n\t\twinrate1Bar.setVisible(false);\n\t\twinrate1Label.setVisible(false);\n\t\twinrate2Bar.setVisible(false);\n\t\twinrate2Label.setVisible(false);\n\t}\n\n\tpublic void displayBatchResult(BattleBatchResult result) {\n\t\tif (!result.isCompleted()) {\n\t\t\tprogressIndicator.setProgress(result.getProgress());\n\t\t\tTooltip.install(this, new Tooltip(\"In progress\\n\\n\" + result.getDeck1().getName() + \"\\nVS.\\n\" + result.getDeck2().getName()));\n\t\t} else if (contentPane.getOpacity() < 1) {\n\t\t\tcontentPane.setOpacity(1);\n\t\t\tprogressIndicator.setVisible(false);\n\t\t\twinrate1Bar.setVisible(true);\n\t\t\twinrate1Label.setVisible(true);\n\t\t\twinrate2Bar.setVisible(true);\n\t\t\twinrate2Label.setVisible(true);\n\t\t\tTooltip.install(this, null);\n\t\t}\n\t\tdeck1Label.setText(result.getDeck1().getName());\n\t\tdeck1Icon.setImage(IconFactory.getClassIcon(result.getDeck1().getHeroClass()));\n\t\tdeck2Label.setText(result.getDeck2().getName());\n\t\tdeck2Icon.setImage(IconFactory.getClassIcon(result.getDeck2().getHeroClass()));\n\n\t\twinrate1Bar.setProgress(result.getDeck1Winrate());\n\t\twinrate1Label.setText(String.format(\"%.2f\", result.getDeck1Winrate() * 100) + \"%\");\n\t\twinrate2Bar.setProgress(result.getDeck2Winrate());\n\t\twinrate2Label.setText(String.format(\"%.2f\", result.getDeck2Winrate() * 100) + \"%\");\n\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/battleofdecks/BattleConfig.java",
    "content": "package net.demilich.metastone.gui.battleofdecks;\n\nimport java.util.Collection;\n\nimport net.demilich.metastone.game.behaviour.IBehaviour;\nimport net.demilich.metastone.game.decks.Deck;\n\npublic class BattleConfig {\n\n\tprivate final int numberOfGames;\n\tprivate final IBehaviour behaviour;\n\tprivate final Collection<Deck> decks;\n\n\tpublic BattleConfig(int numberOfGames, IBehaviour behaviour, Collection<Deck> decks) {\n\t\tthis.numberOfGames = numberOfGames;\n\t\tthis.behaviour = behaviour;\n\t\tthis.decks = decks;\n\t}\n\n\tpublic IBehaviour getBehaviour() {\n\t\treturn behaviour;\n\t}\n\n\tpublic Collection<Deck> getDecks() {\n\t\treturn decks;\n\t}\n\n\tpublic int getNumberOfGames() {\n\t\treturn numberOfGames;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/battleofdecks/BattleDeckResult.java",
    "content": "package net.demilich.metastone.gui.battleofdecks;\n\nimport javafx.beans.property.DoubleProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleDoubleProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport net.demilich.metastone.game.statistics.GameStatistics;\nimport net.demilich.metastone.game.statistics.Statistic;\n\npublic class BattleDeckResult {\n\n\tprivate final StringProperty deckName = new SimpleStringProperty();\n\tprivate final ObjectProperty<GameStatistics> deckStatistics = new SimpleObjectProperty<>();\n\tprivate final DoubleProperty winRate = new SimpleDoubleProperty();\n\n\tpublic BattleDeckResult(String deckName, GameStatistics deckStatistics) {\n\t\tsetDeckName(deckName);\n\t\tsetDeckStatistics(deckStatistics);\n\t\tsetWinRate(deckStatistics.getDouble(Statistic.WIN_RATE));\n\t}\n\n\tpublic final StringProperty deckNameProperty() {\n\t\treturn this.deckName;\n\t}\n\n\tpublic final ObjectProperty<GameStatistics> deckStatisticsProperty() {\n\t\treturn this.deckStatistics;\n\t}\n\n\tpublic final String getDeckName() {\n\t\treturn this.deckNameProperty().get();\n\t}\n\n\tpublic final GameStatistics getDeckStatistics() {\n\t\treturn this.deckStatisticsProperty().get();\n\t}\n\n\tpublic final double getWinRate() {\n\t\treturn this.winRateProperty().get();\n\t}\n\n\tpublic final void setDeckName(final String deckName) {\n\t\tthis.deckNameProperty().set(deckName);\n\t}\n\n\tpublic final void setDeckStatistics(final GameStatistics deckStatistics) {\n\t\tthis.deckStatisticsProperty().set(deckStatistics);\n\t}\n\n\tpublic final void setWinRate(final double winRate) {\n\t\tthis.winRateProperty().set(winRate);\n\t}\n\n\tpublic final DoubleProperty winRateProperty() {\n\t\treturn this.winRate;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/battleofdecks/BattleOfDecksConfigView.java",
    "content": "package net.demilich.metastone.gui.battleofdecks;\n\nimport java.io.IOException;\nimport java.util.Collection;\nimport java.util.List;\n\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.event.ActionEvent;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.ListView;\nimport javafx.scene.control.SelectionMode;\nimport javafx.scene.control.cell.TextFieldListCell;\nimport javafx.scene.layout.BorderPane;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.behaviour.IBehaviour;\nimport net.demilich.metastone.game.behaviour.PlayRandomBehaviour;\nimport net.demilich.metastone.game.behaviour.threat.GameStateValueBehaviour;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.gui.common.BehaviourStringConverter;\nimport net.demilich.metastone.gui.common.DeckStringConverter;\n\npublic class BattleOfDecksConfigView extends BorderPane {\n\n\t@FXML\n\tprivate ComboBox<Integer> numberOfGamesBox;\n\t@FXML\n\tprivate ComboBox<IBehaviour> behaviourBox;\n\n\t@FXML\n\tprivate ListView<Deck> selectedDecksListView;\n\t@FXML\n\tprivate ListView<Deck> availableDecksListView;\n\n\t@FXML\n\tprivate Button addButton;\n\t@FXML\n\tprivate Button removeButton;\n\t@FXML\n\tprivate Button startButton;\n\t@FXML\n\tprivate Button backButton;\n\n\tpublic BattleOfDecksConfigView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/BattleOfDecksConfigView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tsetupBehaviourBox();\n\t\tsetupNumberOfGamesBox();\n\n\t\tselectedDecksListView.setCellFactory(TextFieldListCell.forListView(new DeckStringConverter()));\n\t\tselectedDecksListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);\n\t\tavailableDecksListView.setCellFactory(TextFieldListCell.forListView(new DeckStringConverter()));\n\t\tavailableDecksListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);\n\n\t\taddButton.setOnAction(this::handleAddButton);\n\t\tremoveButton.setOnAction(this::handleRemoveButton);\n\n\t\tbackButton.setOnAction(event -> NotificationProxy.sendNotification(GameNotification.MAIN_MENU));\n\t\tstartButton.setOnAction(this::handleStartButton);\n\t}\n\n\tprivate void handleAddButton(ActionEvent event) {\n\t\tCollection<Deck> selectedDecks = availableDecksListView.getSelectionModel().getSelectedItems();\n\t\tselectedDecksListView.getItems().addAll(selectedDecks);\n\t\tavailableDecksListView.getItems().removeAll(selectedDecks);\n\t}\n\n\tprivate void handleRemoveButton(ActionEvent event) {\n\t\tCollection<Deck> selectedDecks = selectedDecksListView.getSelectionModel().getSelectedItems();\n\t\tavailableDecksListView.getItems().addAll(selectedDecks);\n\t\tselectedDecksListView.getItems().removeAll(selectedDecks);\n\t}\n\n\tprivate void handleStartButton(ActionEvent event) {\n\t\tint numberOfGames = numberOfGamesBox.getSelectionModel().getSelectedItem();\n\t\tIBehaviour behaviour = behaviourBox.getSelectionModel().getSelectedItem();\n\t\tCollection<Deck> decks = selectedDecksListView.getItems();\n\t\tBattleConfig battleConfig = new BattleConfig(numberOfGames, behaviour, decks);\n\t\tNotificationProxy.sendNotification(GameNotification.COMMIT_BATTLE_OF_DECKS_CONFIG, battleConfig);\n\t}\n\n\tpublic void injectDecks(List<Deck> decks) {\n\t\tselectedDecksListView.getItems().clear();\n\t\tObservableList<Deck> validDecks = FXCollections.observableArrayList();\n\t\tfor (Deck deck : decks) {\n\t\t\tif (deck.getHeroClass() == HeroClass.MAGE) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tavailableDecksListView.getItems().setAll(validDecks);\n\t}\n\n\tprivate void setupBehaviourBox() {\n\t\tbehaviourBox.setConverter(new BehaviourStringConverter());\n\t\tbehaviourBox.getItems().setAll(new GameStateValueBehaviour(), new PlayRandomBehaviour());\n\t\tbehaviourBox.getSelectionModel().selectFirst();\n\t}\n\n\tprivate void setupNumberOfGamesBox() {\n\t\tObservableList<Integer> numberOfGamesEntries = FXCollections.observableArrayList();\n\t\tnumberOfGamesEntries.add(1);\n\t\tnumberOfGamesEntries.add(10);\n\t\tnumberOfGamesEntries.add(100);\n\t\tnumberOfGamesEntries.add(1000);\n\t\tnumberOfGamesBox.setItems(numberOfGamesEntries);\n\t\tnumberOfGamesBox.getSelectionModel().select(2);\n\t}\n\n\tpublic void injectDeckFormats(List<DeckFormat> deckFormats) {\n//\t\tselectedDeckFormatsListView.getItems().clear();\n//\t\tObservableList<DeckFormat> validDeckFormats = FXCollections.observableArrayList();\n//\t\tavailableDeckFormatsListView.getItems().setAll(validDeckFormats);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/battleofdecks/BattleOfDecksMediator.java",
    "content": "package net.demilich.metastone.gui.battleofdecks;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.nittygrittymvc.Mediator;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport javafx.application.Platform;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\n\npublic class BattleOfDecksMediator extends Mediator<GameNotification> {\n\n\tpublic final static String NAME = \"BattleOfDecksMediator\";\n\n\tprivate final BattleOfDecksConfigView configView;\n\tprivate final BattleOfDecksResultView resultView;\n\n\tpublic BattleOfDecksMediator() {\n\t\tsuper(NAME);\n\t\tconfigView = new BattleOfDecksConfigView();\n\t\tresultView = new BattleOfDecksResultView();\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Override\n\tpublic void handleNotification(final INotification<GameNotification> notification) {\n\t\tswitch (notification.getId()) {\n\t\tcase REPLY_DECKS:\n\t\t\tconfigView.injectDecks((List<Deck>) notification.getBody());\n\t\t\tbreak;\n\t\tcase REPLY_DECK_FORMATS:\n\t\t\tconfigView.injectDeckFormats((List<DeckFormat>) notification.getBody());\n\t\t\tbreak;\n\t\tcase BATTLE_OF_DECKS_PROGRESS_UPDATE:\n\t\t\tfinal BattleResult result = (BattleResult) notification.getBody();\n\t\t\tPlatform.runLater(() -> resultView.updateResults(result));\n\t\t\tbreak;\n\t\tcase COMMIT_BATTLE_OF_DECKS_CONFIG:\n\t\t\tsendNotification(GameNotification.SHOW_VIEW, resultView);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t@Override\n\tpublic List<GameNotification> listNotificationInterests() {\n\t\tList<GameNotification> notificationInterests = new ArrayList<GameNotification>();\n\t\tnotificationInterests.add(GameNotification.REPLY_DECKS);\n\t\tnotificationInterests.add(GameNotification.REPLY_DECK_FORMATS);\n\t\tnotificationInterests.add(GameNotification.BATTLE_OF_DECKS_PROGRESS_UPDATE);\n\t\tnotificationInterests.add(GameNotification.COMMIT_BATTLE_OF_DECKS_CONFIG);\n\t\treturn notificationInterests;\n\t}\n\n\t@Override\n\tpublic void onRegister() {\n\t\tsendNotification(GameNotification.SHOW_VIEW, configView);\n\t\tsendNotification(GameNotification.REQUEST_DECKS);\n\t\tsendNotification(GameNotification.REQUEST_DECK_FORMATS);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/battleofdecks/BattleOfDecksResultView.java",
    "content": "package net.demilich.metastone.gui.battleofdecks;\n\nimport java.io.IOException;\nimport java.util.HashMap;\n\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ProgressBar;\nimport javafx.scene.control.TableCell;\nimport javafx.scene.control.TableColumn;\nimport javafx.scene.control.TableColumn.SortType;\nimport javafx.scene.control.TableView;\nimport javafx.scene.control.cell.PropertyValueFactory;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.FlowPane;\nimport javafx.scene.layout.StackPane;\nimport javafx.util.Callback;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\n\npublic class BattleOfDecksResultView extends BorderPane {\n\n\t@FXML\n\tprivate FlowPane batchResultPane;\n\n\t@FXML\n\tprivate TableView<BattleDeckResult> rankingTable;\n\n\t@FXML\n\tprivate Button backButton;\n\n\tprivate final HashMap<BattleBatchResult, BattleBatchResultToken> tokenMap = new HashMap<>();\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic BattleOfDecksResultView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/BattleOfDecksResultView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tTableColumn<BattleDeckResult, String> nameColumn = new TableColumn<>(\"Deck name\");\n\t\tnameColumn.setPrefWidth(200);\n\t\tTableColumn<BattleDeckResult, Double> winRateColumn = new TableColumn<>(\"Win rate\");\n\t\twinRateColumn.setPrefWidth(150);\n\n\t\tnameColumn.setCellValueFactory(new PropertyValueFactory<BattleDeckResult, String>(\"deckName\"));\n\t\twinRateColumn.setCellValueFactory(new PropertyValueFactory<BattleDeckResult, Double>(\"winRate\"));\n\n\t\twinRateColumn.setCellFactory(new Callback<TableColumn<BattleDeckResult, Double>, TableCell<BattleDeckResult, Double>>() {\n\t\t\tpublic TableCell<BattleDeckResult, Double> call(TableColumn<BattleDeckResult, Double> p) {\n\t\t\t\tTableCell<BattleDeckResult, Double> cell = new TableCell<BattleDeckResult, Double>() {\n\t\t\t\t\tprivate final Label label = new Label();\n\t\t\t\t\tprivate final ProgressBar progressBar = new ProgressBar();\n\t\t\t\t\tprivate final StackPane stackPane = new StackPane();\n\n\t\t\t\t\t{\n\t\t\t\t\t\tlabel.getStyleClass().setAll(\"progress-text\");\n\t\t\t\t\t\tstackPane.setAlignment(Pos.CENTER);\n\t\t\t\t\t\tstackPane.getChildren().setAll(progressBar, label);\n\t\t\t\t\t\tsetGraphic(stackPane);\n\t\t\t\t\t}\n\n\t\t\t\t\t@Override\n\t\t\t\t\tprotected void updateItem(Double winrate, boolean empty) {\n\t\t\t\t\t\tsuper.updateItem(winrate, empty);\n\t\t\t\t\t\tif (winrate == null || empty) {\n\t\t\t\t\t\t\tsetGraphic(null);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprogressBar.setProgress(winrate);\n\t\t\t\t\t\tlabel.setText(String.format(\"%.2f\", winrate * 100) + \"%\");\n\t\t\t\t\t\tsetGraphic(stackPane);\n\t\t\t\t\t}\n\n\t\t\t\t};\n\t\t\t\treturn cell;\n\t\t\t}\n\t\t});\n\n\t\trankingTable.getColumns().setAll(nameColumn, winRateColumn);\n\t\trankingTable.getColumns().get(1).setSortType(SortType.DESCENDING);\n\n\t\tbackButton.setOnAction(event -> NotificationProxy.sendNotification(GameNotification.MAIN_MENU));\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic void updateResults(BattleResult result) {\n\n\t\tfor (BattleBatchResult batchResult : result.getBatchResults()) {\n\t\t\tif (!tokenMap.containsKey(batchResult)) {\n\t\t\t\tBattleBatchResultToken token = new BattleBatchResultToken();\n\t\t\t\ttokenMap.put(batchResult, token);\n\t\t\t\tbatchResultPane.getChildren().add(token);\n\t\t\t}\n\t\t\tBattleBatchResultToken batchResultToken = tokenMap.get(batchResult);\n\t\t\tbatchResultToken.displayBatchResult(batchResult);\n\t\t}\n\n\t\trankingTable.getItems().setAll(result.getDeckResults());\n\t\trankingTable.getSortOrder().setAll(rankingTable.getColumns().get(1));\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/battleofdecks/BattleResult.java",
    "content": "package net.demilich.metastone.gui.battleofdecks;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.statistics.GameStatistics;\n\npublic class BattleResult {\n\n\tprivate final int numberOfGames;\n\tprivate final HashMap<String, GameStatistics> deckResults = new HashMap<String, GameStatistics>();\n\tprivate final List<BattleBatchResult> batchResults = new ArrayList<BattleBatchResult>();\n\n\tpublic BattleResult(int numberOfGames) {\n\t\tthis.numberOfGames = numberOfGames;\n\t}\n\n\tpublic void addBatchResult(BattleBatchResult batchResult) {\n\t\tsynchronized (batchResults) {\n\t\t\tbatchResults.add(batchResult);\n\t\t}\n\t}\n\n\tpublic List<BattleBatchResult> getBatchResults() {\n\t\tsynchronized (batchResults) {\n\t\t\treturn new ArrayList<BattleBatchResult>(batchResults);\n\t\t}\n\t}\n\n\tpublic List<BattleDeckResult> getDeckResults() {\n\t\tList<BattleDeckResult> resultList = new ArrayList<BattleDeckResult>();\n\t\tsynchronized (deckResults) {\n\t\t\tfor (String deckName : deckResults.keySet()) {\n\t\t\t\tBattleDeckResult deckResult = new BattleDeckResult(deckName, deckResults.get(deckName));\n\t\t\t\tresultList.add(deckResult);\n\t\t\t}\n\t\t}\n\t\treturn resultList;\n\t}\n\n\tpublic int getNumberOfGames() {\n\t\treturn numberOfGames;\n\t}\n\n\tpublic void onGameEnded(GameContext result) {\n\t\tfor (Player player : result.getPlayers()) {\n\t\t\tupdateStats(player);\n\t\t}\n\t}\n\n\tprivate void updateStats(Player player) {\n\t\tString deckName = player.getDeckName();\n\t\tsynchronized (deckResults) {\n\t\t\tif (!deckResults.containsKey(deckName)) {\n\t\t\t\tdeckResults.put(deckName, new GameStatistics());\n\t\t\t}\n\t\t\tGameStatistics stats = deckResults.get(deckName);\n\t\t\tstats.merge(player.getStatistics());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/battleofdecks/StartBattleOfDecksCommand.java",
    "content": "package net.demilich.metastone.gui.battleofdecks;\n\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\n\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.behaviour.IBehaviour;\nimport net.demilich.metastone.game.cards.CardSet;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.game.gameconfig.PlayerConfig;\n\npublic class StartBattleOfDecksCommand extends SimpleCommand<GameNotification> {\n\n\tprivate class PlayGameTask implements Callable<Void> {\n\n\t\tprivate final PlayerConfig player1Config;\n\t\tprivate final PlayerConfig player2Config;\n\t\tprivate final BattleBatchResult batchResult;\n\n\t\tpublic PlayGameTask(Deck deck1, Deck deck2, IBehaviour behaviour, BattleBatchResult batchResult) {\n\t\t\tthis.player1Config = new PlayerConfig(deck1, behaviour);\n\t\t\tplayer1Config.setName(\"Player 1\");\n\t\t\tthis.player2Config = new PlayerConfig(deck2, behaviour);\n\t\t\tplayer2Config.setName(\"Player 2\");\n\t\t\tthis.batchResult = batchResult;\n\t\t}\n\n\t\t@Override\n\t\tpublic Void call() throws Exception {\n\t\t\tPlayer player1 = new Player(player1Config);\n\t\t\tPlayer player2 = new Player(player2Config);\n\t\n\t\t\tDeckFormat deckFormat = new DeckFormat();\n\t\t\tfor (CardSet set : CardSet.values()) {\n\t\t\t\tdeckFormat.addSet(set);\n\t\t\t}\n\n\t\t\tGameContext newGame = new GameContext(player1, player2, new GameLogic(), deckFormat);\n\t\t\tnewGame.play();\n\n\t\t\tbatchResult.onGameEnded(newGame);\n\t\t\tresult.onGameEnded(newGame);\n\n\t\t\tperiodicUpdate();\n\t\t\tnewGame.dispose();\n\n\t\t\treturn null;\n\t\t}\n\n\t}\n\n\tprivate static Logger logger = LoggerFactory.getLogger(StartBattleOfDecksCommand.class);\n\tprivate BattleResult result;\n\n\tprivate long lastUpdate;\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tBattleConfig battleConfig = (BattleConfig) notification.getBody();\n\t\tresult = new BattleResult(battleConfig.getNumberOfGames());\n\n\t\tThread t = new Thread(new Runnable() {\n\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tlogger.info(\"Battle of Decks started\");\n\t\t\t\tExecutorService executor = Executors.newWorkStealingPool();\n\n\t\t\t\tList<Future<Void>> futures = new ArrayList<Future<Void>>();\n\t\t\t\tHashSet<Deck> processedDecks = new HashSet<>();\n\t\t\t\tfor (Deck deck1 : battleConfig.getDecks()) {\n\t\t\t\t\tprocessedDecks.add(deck1);\n\t\t\t\t\tfor (Deck deck2 : battleConfig.getDecks()) {\n\t\t\t\t\t\tif (processedDecks.contains(deck2)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tBattleBatchResult batchResult = new BattleBatchResult(deck1, deck2, battleConfig.getNumberOfGames());\n\t\t\t\t\t\tresult.addBatchResult(batchResult);\n\n\t\t\t\t\t\tfor (int i = 0; i < battleConfig.getNumberOfGames(); i++) {\n\t\t\t\t\t\t\tPlayGameTask task = new PlayGameTask(deck1, deck2, battleConfig.getBehaviour(), batchResult);\n\t\t\t\t\t\t\tFuture<Void> future = executor.submit(task);\n\t\t\t\t\t\t\tfutures.add(future);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\texecutor.shutdown();\n\t\t\t\tboolean completed = false;\n\t\t\t\twhile (!completed) {\n\t\t\t\t\tcompleted = true;\n\t\t\t\t\tfor (Future<Void> future : futures) {\n\t\t\t\t\t\tif (!future.isDone()) {\n\t\t\t\t\t\t\tcompleted = false;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tfuture.get();\n\t\t\t\t\t\t} catch (InterruptedException | ExecutionException e) {\n\t\t\t\t\t\t\tlogger.error(ExceptionUtils.getStackTrace(e));\n\t\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t\t\t\tSystem.exit(-1);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfutures.removeIf(future -> future.isDone());\n\t\t\t\t\ttry {\n\t\t\t\t\t\tThread.sleep(100);\n\t\t\t\t\t} catch (InterruptedException e) {\n\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// getFacade().sendNotification(GameNotification.SIMULATION_RESULT,\n\t\t\t\t// result);\n\t\t\t\tlogger.info(\"Battle of Decks finished\");\n\n\t\t\t}\n\t\t});\n\t\tt.setDaemon(true);\n\t\tt.start();\n\t}\n\n\tprivate void periodicUpdate() {\n\t\tif (System.currentTimeMillis() - lastUpdate > 1000) {\n\t\t\tsendNotification(GameNotification.BATTLE_OF_DECKS_PROGRESS_UPDATE, result);\n\t\t\tlastUpdate = System.currentTimeMillis();\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/cards/CardProxy.java",
    "content": "package net.demilich.metastone.gui.cards;\r\n\r\nimport net.demilich.metastone.GameNotification;\r\nimport net.demilich.metastone.game.cards.CardCatalogue;\r\nimport net.demilich.metastone.game.cards.CardParseException;\r\nimport net.demilich.nittygrittymvc.Proxy;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport java.io.IOException;\r\nimport java.net.URISyntaxException;\r\nimport java.nio.file.Files;\r\nimport java.nio.file.Paths;\r\n\r\npublic class CardProxy extends Proxy<GameNotification> {\r\n\r\n\tpublic final static String NAME = \"CardProxy\";\r\n\tprivate static Logger logger = LoggerFactory.getLogger(CardProxy.class);\r\n\r\n\tpublic CardProxy() {\r\n\t\tsuper(NAME);\r\n\t\ttry {\r\n\t\t\t// ensure user's personal cards dir exists\r\n\t\t\tFiles.createDirectories(Paths.get(CardCatalogue.CARDS_FOLDER_PATH));\r\n\t\t\t// ensure cards have been copied to ~/metastone/cards\r\n\t\t\tCardCatalogue.copyCardsFromResources();\r\n\t\t\tCardCatalogue.loadCards();\r\n\t\t} catch (URISyntaxException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t} catch (IOException e) {\r\n\t\t\tlogger.error(\"Trouble creating \" +  Paths.get(CardCatalogue.CARDS_FOLDER_PATH));\r\n\t\t\te.printStackTrace();\r\n\t\t} catch (CardParseException cpe) {\r\n\t\t\tgetFacade().sendNotification(GameNotification.CARD_PARSE_ERROR, cpe.getMessage());\r\n\t\t}\r\n\t}\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/cards/CardToken.java",
    "content": "package net.demilich.metastone.gui.cards;\r\n\r\nimport java.io.IOException;\r\n\r\nimport javafx.fxml.FXML;\r\nimport javafx.fxml.FXMLLoader;\r\nimport javafx.scene.Group;\r\nimport javafx.scene.control.Label;\r\nimport javafx.scene.image.ImageView;\r\nimport javafx.scene.layout.BorderPane;\r\nimport javafx.scene.paint.Color;\r\nimport javafx.scene.shape.Circle;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.CardType;\r\nimport net.demilich.metastone.game.cards.MinionCard;\r\nimport net.demilich.metastone.game.cards.Rarity;\r\nimport net.demilich.metastone.game.cards.WeaponCard;\r\nimport net.demilich.metastone.gui.DigitFactory;\r\nimport net.demilich.metastone.gui.IconFactory;\r\n\r\npublic class CardToken extends BorderPane {\r\n\r\n\t@FXML\r\n\tprotected Group manaCostAnchor;\r\n\t@FXML\r\n\tprotected Label nameLabel;\r\n\t@FXML\r\n\tprotected Label descriptionLabel;\r\n\r\n\t@FXML\r\n\tprotected Group attackAnchor;\r\n\t@FXML\r\n\tprotected Group hpAnchor;\r\n\r\n\t@FXML\r\n\tprotected ImageView attackIcon;\r\n\t@FXML\r\n\tprotected ImageView hpIcon;\r\n\r\n\t@FXML\r\n\tprotected Circle rarityGem;\r\n\r\n\tprivate double baseRarityGemSize;\r\n\r\n\tprotected Card card;\r\n\r\n\tprotected CardToken(String fxml) {\r\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/\" + fxml));\r\n\t\tfxmlLoader.setRoot(this);\r\n\t\tfxmlLoader.setController(this);\r\n\r\n\t\ttry {\r\n\t\t\tfxmlLoader.load();\r\n\t\t} catch (IOException exception) {\r\n\t\t\tthrow new RuntimeException(exception);\r\n\t\t}\r\n\r\n\t\tbaseRarityGemSize = rarityGem.getRadius();\r\n\t}\r\n\r\n\tpublic Card getCard() {\r\n\t\treturn card;\r\n\t}\r\n\r\n\tpublic void setCard(Card card) {\r\n\t\tsetCard(null, card, null);\r\n\t}\r\n\r\n\tpublic void setCard(GameContext context, Card card, Player player) {\r\n\t\tthis.card = card;\r\n\t\tnameLabel.setText(card.getName());\r\n\t\tsetRarity(card.getRarity());\r\n\t\tif (context != null || player != null) {\r\n\t\t\tint modifiedManaCost = context.getLogic().getModifiedManaCost(player, card);\r\n\t\t\tsetScoreValueLowerIsBetter(manaCostAnchor, modifiedManaCost, card.getBaseManaCost());\r\n\t\t} else {\r\n\t\t\tsetScoreValue(manaCostAnchor, card.getBaseManaCost());\r\n\t\t}\r\n\r\n\t\tboolean isMinionOrWeaponCard = card.getCardType().isCardType(CardType.MINION) || card.getCardType().isCardType(CardType.WEAPON);\r\n\t\tattackAnchor.setVisible(isMinionOrWeaponCard);\r\n\t\thpAnchor.setVisible(isMinionOrWeaponCard);\r\n\t\tattackIcon.setVisible(isMinionOrWeaponCard);\r\n\t\thpIcon.setVisible(isMinionOrWeaponCard);\r\n\t\tif (card.getCardType().isCardType(CardType.MINION)) {\r\n\t\t\tMinionCard minionCard = (MinionCard) card;\r\n\t\t\tsetScoreValue(attackAnchor, minionCard.getAttack() + minionCard.getBonusAttack(), minionCard.getBaseAttack());\r\n\t\t\tsetScoreValue(hpAnchor, minionCard.getHp() + minionCard.getBonusHp(), minionCard.getBaseHp());\r\n\t\t} else if (card.getCardType().isCardType(CardType.WEAPON)) {\r\n\t\t\tWeaponCard weaponCard = (WeaponCard) card;\r\n\t\t\tsetScoreValue(attackAnchor, weaponCard.getDamage() + weaponCard.getBonusDamage(), weaponCard.getBaseDamage());\r\n\t\t\tsetScoreValue(hpAnchor, weaponCard.getDurability() + weaponCard.getBonusDurability(), weaponCard.getBaseDurability());\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void setNonCard(String name, String description) {\r\n\t\tnameLabel.setText(name);\r\n\t\tdescriptionLabel.setText(description);\r\n\t\tsetRarity(Rarity.FREE);\r\n\t\tmanaCostAnchor.setVisible(false);\r\n\t\tattackAnchor.setVisible(false);\r\n\t\thpAnchor.setVisible(false);\r\n\t\tattackIcon.setVisible(false);\r\n\t\thpIcon.setVisible(false);\r\n\t}\r\n\r\n\tprivate void setRarity(Rarity rarity) {\r\n\t\trarityGem.setFill(IconFactory.getRarityColor(rarity));\r\n\t\trarityGem.setVisible(rarity != Rarity.FREE);\r\n\t\trarityGem.setRadius(rarity == Rarity.LEGENDARY ? baseRarityGemSize * 1.5 : baseRarityGemSize);\r\n\t}\r\n\r\n\tprotected void setScoreValue(Group group, int value) {\r\n\t\tsetScoreValue(group, value, value);\r\n\t}\r\n\r\n\tprotected void setScoreValue(Group group, int value, int baseValue) {\r\n\t\tColor color = Color.WHITE;\r\n\t\tif (value > baseValue) {\r\n\t\t\tcolor = Color.GREEN;\r\n\t\t}\r\n\t\tDigitFactory.showPreRenderedDigits(group, value, color);\r\n\t}\r\n\t\r\n\tprotected void setScoreValue(Group group, int value, int baseValue, int maxValue) {\r\n\t\tColor color = Color.WHITE;\r\n\t\tif (value < maxValue) {\r\n\t\t\tcolor = Color.RED;\r\n\t\t} else if (value > baseValue) {\r\n\t\t\tcolor = Color.GREEN;\r\n\t\t}\r\n\t\tDigitFactory.showPreRenderedDigits(group, value, color);\r\n\t}\r\n\r\n\tprivate void setScoreValueLowerIsBetter(Group group, int value, int baseValue) {\r\n\t\tColor color = Color.WHITE;\r\n\t\tif (value < baseValue) {\r\n\t\t\tcolor = Color.GREEN;\r\n\t\t} else if (value > baseValue) {\r\n\t\t\tcolor = Color.RED;\r\n\t\t}\r\n\t\tDigitFactory.showPreRenderedDigits(group, value, color);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/cards/CardTokenFactory.java",
    "content": "package net.demilich.metastone.gui.cards;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\n\npublic class CardTokenFactory {\n\n\tprivate static final int HAND_CARDS = 10;\n\n\tprivate List<HandCard> cachedHandCards = new ArrayList<HandCard>(HAND_CARDS);\n\n\tpublic CardTokenFactory() {\n\t\tfor (int i = 0; i < HAND_CARDS; i++) {\n\t\t\tcachedHandCards.add(new HandCard());\n\t\t}\n\t}\n\n\tpublic CardToken createHandCard(GameContext context, Card card, Player player) {\n\t\tHandCard handCard = getHandCard();\n\t\thandCard.setCard(context, card, player);\n\t\treturn handCard;\n\t}\n\n\tprivate HandCard getHandCard() {\n\t\tfor (HandCard handCard : cachedHandCards) {\n\t\t\tif (handCard.getParent() == null) {\n\t\t\t\treturn handCard;\n\t\t\t}\n\t\t}\n\t\treturn new HandCard();\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/cards/CardTooltip.java",
    "content": "package net.demilich.metastone.gui.cards;\n\nimport javafx.fxml.FXML;\nimport javafx.scene.control.Label;\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.minions.Race;\n\npublic class CardTooltip extends CardToken {\n\n\t@FXML\n\tprivate Label raceLabel;\n\n\tpublic CardTooltip() {\n\t\tsuper(\"CardTooltip.fxml\");\n\t}\n\n\t@Override\n\tpublic void setCard(GameContext context, Card card, Player player) {\n\t\tsuper.setCard(context, card, player);\n\t\tdescriptionLabel.setText(card.getDescription());\n\t\tif (!card.hasAttribute(Attribute.RACE) || card.getAttribute(Attribute.RACE) == Race.NONE) {\n\t\t\traceLabel.setVisible(false);\n\t\t} else {\n\t\t\traceLabel.setText(card.getAttribute(Attribute.RACE).toString());\n\t\t\traceLabel.setVisible(true);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/cards/HandCard.java",
    "content": "package net.demilich.metastone.gui.cards;\n\nimport javafx.fxml.FXML;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.layout.Background;\nimport javafx.scene.layout.BackgroundImage;\nimport javafx.scene.layout.BackgroundPosition;\nimport javafx.scene.layout.BackgroundRepeat;\nimport javafx.scene.layout.BackgroundSize;\nimport javafx.scene.layout.Pane;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.gui.IconFactory;\n\npublic class HandCard extends CardToken {\n\n\t@FXML\n\tprivate Pane topPane;\n\t@FXML\n\tprivate Pane centerPane;\n\t@FXML\n\tprivate Pane bottomPane;\n\n\tprivate CardTooltip tooltipContent;\n\tprivate Tooltip tooltip;\n\n\tpublic HandCard() {\n\t\tsuper(\"HandCard.fxml\");\n\t\thideCard(true);\n\t}\n\n\tprivate void hideCard(boolean hide) {\n\t\ttopPane.setVisible(!hide);\n\t\tcenterPane.setVisible(!hide);\n\t\tbottomPane.setVisible(!hide);\n\t\tif (hide) {\n\t\t\tBackgroundSize size = new BackgroundSize(getWidth(), getHeight(), false, false, true, false);\n\t\t\tBackgroundImage image = new BackgroundImage(IconFactory.getDefaultCardBack(), BackgroundRepeat.NO_REPEAT,\n\t\t\t\t\tBackgroundRepeat.NO_REPEAT, BackgroundPosition.CENTER, size);\n\t\t\tBackground background = new Background(image);\n\t\t\tsetBackground(background);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void setCard(GameContext context, Card card, Player player) {\n\t\tsuper.setCard(context, card, player);\n\t\tif (tooltipContent == null) {\n\t\t\ttooltip = new Tooltip();\n\t\t\ttooltipContent = new CardTooltip();\n\t\t\ttooltipContent.setCard(context, card, player);\n\t\t\ttooltip.setGraphic(tooltipContent);\n\t\t\tTooltip.install(this, tooltip);\n\t\t} else {\n\t\t\ttooltipContent.setCard(context, card, player);\n\t\t}\n\n\t\thideCard(player.hideCards());\n\n\t\tif (player.hideCards()) {\n\t\t\tTooltip.uninstall(this, tooltip);\n\t\t\ttooltipContent = null;\n\t\t\ttooltip = null;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/common/BehaviourStringConverter.java",
    "content": "package net.demilich.metastone.gui.common;\n\nimport javafx.util.StringConverter;\nimport net.demilich.metastone.game.behaviour.IBehaviour;\n\npublic class BehaviourStringConverter extends StringConverter<IBehaviour> {\n\n\t@Override\n\tpublic IBehaviour fromString(String string) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic String toString(IBehaviour behaviour) {\n\t\treturn behaviour.getName();\n\t}\n\n}"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/common/CardSetStringConverter.java",
    "content": "package net.demilich.metastone.gui.common;\r\n\r\nimport javafx.util.StringConverter;\r\nimport net.demilich.metastone.game.cards.CardSet;\r\n\r\npublic class CardSetStringConverter extends StringConverter<CardSet> {\r\n\r\n\t@Override\r\n\tpublic CardSet fromString(String string) {\r\n\t\treturn null;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String toString(CardSet object) {\r\n\t\treturn object.toString();\r\n\t}\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/common/ComboBoxKeyHandler.java",
    "content": "package net.demilich.metastone.gui.common;\r\n\r\nimport com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;\r\n\r\nimport javafx.event.EventHandler;\r\nimport javafx.scene.control.ComboBox;\r\nimport javafx.scene.control.ListView;\r\nimport javafx.scene.input.KeyCode;\r\nimport javafx.scene.input.KeyEvent;\r\n\r\npublic class ComboBoxKeyHandler<T> implements EventHandler<KeyEvent> {\r\n\r\n\tprivate static final long WORD_DELAY = 2000;\r\n\r\n\tprivate String s;\r\n\tprivate final ComboBox<T> box;\r\n\tprivate long lastKeyPress;\r\n\r\n\tpublic ComboBoxKeyHandler(ComboBox<T> box) {\r\n\t\tthis.box = box;\r\n\t\ts = \"\";\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void handle(KeyEvent event) {\r\n\t\tif (System.currentTimeMillis() - WORD_DELAY > lastKeyPress) {\r\n\t\t\ts = \"\";\r\n\t\t}\r\n\t\t// handle non alphanumeric keys like backspace, delete etc\r\n\t\tif (event.getCode() == KeyCode.BACK_SPACE && s.length() > 0)\r\n\t\t\ts = s.substring(0, s.length() - 1);\r\n\t\telse\r\n\t\t\ts += event.getText();\r\n\r\n\t\tlastKeyPress = System.currentTimeMillis();\r\n\r\n\t\tif (s.length() == 0) {\r\n\t\t\tselect(0);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tfor (T item : box.getItems()) {\r\n\r\n\t\t\tString name = box.getConverter().toString(item).toLowerCase();\r\n\t\t\tif (name.startsWith(s)) {\r\n\t\t\t\tselect(item);\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t}\r\n\t\t// nothing found, reset search string\r\n\t\ts = \"\";\r\n\t}\r\n\r\n\tprivate void select(int index) {\r\n\t\tselect(box.getItems().get(index));\r\n\t}\r\n\r\n\t@SuppressWarnings(\"rawtypes\")\r\n\tprivate void select(T item) {\r\n\t\tbox.getSelectionModel().select(item);\r\n\t\tListView lv = ((ComboBoxListViewSkin) box.getSkin()).getListView();\r\n\t\tlv.scrollTo(lv.getSelectionModel().getSelectedIndex());\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/common/DeckFormatStringConverter.java",
    "content": "package net.demilich.metastone.gui.common;\n\nimport javafx.util.StringConverter;\nimport net.demilich.metastone.game.decks.DeckFormat;\n\npublic class DeckFormatStringConverter extends StringConverter<DeckFormat> {\n\n\t@Override\n\tpublic DeckFormat fromString(String arg0) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic String toString(DeckFormat format) {\n\t\treturn format.getName();\n\t}\n\n}"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/common/DeckStringConverter.java",
    "content": "package net.demilich.metastone.gui.common;\n\nimport javafx.util.StringConverter;\nimport net.demilich.metastone.game.decks.Deck;\n\npublic class DeckStringConverter extends StringConverter<Deck> {\n\n\t@Override\n\tpublic Deck fromString(String arg0) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic String toString(Deck deck) {\n\t\treturn deck.getName();\n\t}\n\n}"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/common/HeroStringConverter.java",
    "content": "package net.demilich.metastone.gui.common;\n\nimport javafx.util.StringConverter;\nimport net.demilich.metastone.game.cards.HeroCard;\n\npublic class HeroStringConverter extends StringConverter<HeroCard> {\n\n\t@Override\n\tpublic HeroCard fromString(String arg0) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic String toString(HeroCard hero) {\n\t\treturn hero.getHeroClass().toString();\n\t}\n\n}"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/common/IntegerTextField.java",
    "content": "package net.demilich.metastone.gui.common;\n\nimport javafx.beans.property.IntegerProperty;\nimport javafx.beans.property.SimpleIntegerProperty;\n\npublic class IntegerTextField extends RestrictedTextField {\n\n\tprivate final IntegerProperty valueProperty = new SimpleIntegerProperty();\n\n\tpublic IntegerTextField(int maxLength) {\n\t\tsetRestrict(\"\\\\d*\");\n\t\tsetMaxLength(maxLength);\n\t}\n\n\tpublic int getIntValue() {\n\t\treturn valueProperty().get();\n\t}\n\n\tpublic void setIntValue(int value) {\n\t\tsetText(String.valueOf(value));\n\t}\n\n\t@Override\n\tprotected void validInput(String validInput) {\n\t\tvalueProperty().set(validInput.length() > 0 ? Integer.parseInt(validInput) : 0);\n\t\tif (validInput.length() == 0) {\n\t\t\tsetIntValue(0);\n\t\t}\n\t}\n\n\tpublic IntegerProperty valueProperty() {\n\t\treturn valueProperty;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/common/RestrictedTextField.java",
    "content": "package net.demilich.metastone.gui.common;\n\nimport javafx.beans.property.IntegerProperty;\nimport javafx.beans.property.SimpleIntegerProperty;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.control.TextField;\n\n/**\n * A text field, which restricts the user's input.\n *\n * @author Christian Schudt\n */\npublic class RestrictedTextField extends TextField {\n\n\tprivate StringProperty restrict = new SimpleStringProperty();\n\n\tprivate IntegerProperty maxLength = new SimpleIntegerProperty(-1);\n\n\tpublic RestrictedTextField() {\n\n\t\ttextProperty().addListener(new ChangeListener<String>() {\n\n\t\t\tprivate boolean ignore;\n\n\t\t\t@Override\n\t\t\tpublic void changed(ObservableValue<? extends String> observableValue, String s, String s1) {\n\t\t\t\tif (ignore)\n\t\t\t\t\treturn;\n\t\t\t\tif (maxLength.get() > -1 && s1.length() > maxLength.get()) {\n\t\t\t\t\tignore = true;\n\t\t\t\t\tsetText(s1.substring(0, maxLength.get()));\n\t\t\t\t\tvalidInput(getText());\n\t\t\t\t\tignore = false;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (restrict.get() != null && !restrict.get().equals(\"\") && !s1.matches(restrict.get())) {\n\t\t\t\t\tignore = true;\n\t\t\t\t\tsetText(s);\n\t\t\t\t\tignore = false;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tvalidInput(getText());\n\t\t\t}\n\t\t});\n\t}\n\n\tpublic int getMaxLength() {\n\t\treturn maxLength.get();\n\t}\n\n\tpublic String getRestrict() {\n\t\treturn restrict.get();\n\t}\n\n\tpublic IntegerProperty maxLengthProperty() {\n\t\treturn maxLength;\n\t}\n\n\tpublic StringProperty restrictProperty() {\n\t\treturn restrict;\n\t}\n\n\t/**\n\t * Sets the max length of the text field.\n\t *\n\t * @param maxLength\n\t *            The max length.\n\t */\n\tpublic void setMaxLength(int maxLength) {\n\t\tthis.maxLength.set(maxLength);\n\t}\n\n\t/**\n\t * Sets a regular expression character class which restricts the user input.\n\t * <br/>\n\t * E.g. [0-9] only allows numeric values.\n\t *\n\t * @param restrict\n\t *            The regular expression.\n\t */\n\tpublic void setRestrict(String restrict) {\n\t\tthis.restrict.set(restrict);\n\t}\n\n\tprotected void validInput(String validInput) {\n\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/AddCardToDeckCommand.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.cards.Card;\n\npublic class AddCardToDeckCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tDeckProxy deckProxy = (DeckProxy) getFacade().retrieveProxy(DeckProxy.NAME);\n\n\t\tCard card = (Card) notification.getBody();\n\t\tif (deckProxy.addCardToDeck(card)) {\n\t\t\tgetFacade().sendNotification(GameNotification.ACTIVE_DECK_CHANGED, deckProxy.getActiveDeck());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/CardEntry.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.io.IOException;\n\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.text.Text;\nimport net.demilich.metastone.game.cards.Card;\n\npublic class CardEntry extends HBox {\n\n\t@FXML\n\tprivate Label cardNameLabel;\n\n\t@FXML\n\tprivate Text manaCostText;\n\n\t@FXML\n\tprivate Text countText;\n\n\tprivate int stack;\n\n\tprivate Card card;\n\n\tpublic CardEntry() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/CardEntry.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tsetCache(true);\n\t}\n\n\tpublic void addCard(Card card) {\n\t\tthis.card = card;\n\t\tcardNameLabel.setText(card.getName());\n\t\tmanaCostText.setText(String.valueOf(card.getBaseManaCost()));\n\t\tstack++;\n\t\tcountText.setText(String.valueOf(stack));\n\t\tcountText.setVisible(stack > 1);\n\t}\n\n\tpublic Card getCard() {\n\t\treturn card;\n\t}\n\n\tpublic void resetStackCount() {\n\t\tstack = 0;\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/CardEntryFactory.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.metastone.game.cards.Card;\n\npublic class CardEntryFactory {\n\n\tprivate static final int CARD_ENTRIES = 10;\n\n\tprivate List<CardEntry> cachedCardEntries = new ArrayList<CardEntry>(CARD_ENTRIES);\n\n\tpublic CardEntryFactory() {\n\t\tfor (int i = 0; i < CARD_ENTRIES; i++) {\n\t\t\tcachedCardEntries.add(new CardEntry());\n\t\t}\n\t}\n\n\tpublic CardEntry createCardEntry(Card card) {\n\t\tCardEntry cardEntry = getCardEntry();\n\t\tcardEntry.resetStackCount();\n\t\tcardEntry.addCard(card);\n\t\treturn cardEntry;\n\t}\n\n\tprivate CardEntry getCardEntry() {\n\t\tfor (CardEntry handCard : cachedCardEntries) {\n\t\t\tif (handCard.getParent() == null) {\n\t\t\t\treturn handCard;\n\t\t\t}\n\t\t}\n\t\treturn new CardEntry();\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/CardFilter.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\r\n\r\nimport net.demilich.metastone.game.cards.CardSet;\r\nimport net.demilich.metastone.game.decks.DeckFormat;\r\n\r\npublic class CardFilter {\r\n\r\n\tprivate final String text;\r\n\tprivate final CardSet set;\r\n\tprivate final DeckFormat format;\r\n\r\n\tpublic CardFilter(String text, CardSet set, DeckFormat format) {\r\n\t\tthis.text = text;\r\n\t\tthis.set = set;\r\n\t\tthis.format = format;\r\n\t}\r\n\r\n\tpublic DeckFormat getFormat() {\r\n\t\treturn format;\r\n\t}\r\n\r\n\tpublic CardSet getSet() {\r\n\t\treturn set;\r\n\t}\r\n\r\n\tpublic String getText() {\r\n\t\treturn text;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/CardFilterView.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport javafx.beans.value.ObservableValue;\nimport javafx.collections.FXCollections;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.TextField;\nimport javafx.scene.layout.HBox;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.cards.CardSet;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.gui.common.CardSetStringConverter;\nimport net.demilich.metastone.gui.common.DeckFormatStringConverter;\n\npublic class CardFilterView extends HBox {\n\n\t@FXML\n\tprivate TextField searchField;\n\n\t@FXML\n\tprivate ComboBox<CardSet> cardSetBox;\n\n\t@FXML\n\tprivate ComboBox<DeckFormat> deckFormatBox;\n\n\tprivate List<DeckFormat> deckFormats = new ArrayList<DeckFormat>();\n\n\tpublic CardFilterView(List<DeckFormat> deckFormats) {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/CardFilterView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tsearchField.textProperty().addListener(this::textChanged);\n\n\t\tdeckFormatBox.setConverter(new DeckFormatStringConverter());\n\t\tDeckFormat deckFormat = new DeckFormat();\n\t\tdeckFormat.setName(\"DECK FORMAT\");\n\t\tdeckFormats.add(0, deckFormat);\n\t\tdeckFormatBox.setItems(FXCollections.observableArrayList(deckFormats));\n\t\tdeckFormatBox.getSelectionModel().selectFirst();\n\t\tdeckFormatBox.valueProperty().addListener(this::formatChanged);\n\n\t\tcardSetBox.setConverter(new CardSetStringConverter());\n\t\tcardSetBox.setItems(FXCollections.observableArrayList(CardSet.values()));\n\t\tcardSetBox.getSelectionModel().selectFirst();\n\t\tcardSetBox.valueProperty().addListener(this::setChanged);\n\t}\n\n\tprivate void filterChanged() {\n\t\tDeckFormat deckFormat = null;\n\t\tif (!deckFormatBox.getSelectionModel().isSelected(0)) {\n\t\t\tdeckFormat = deckFormatBox.getSelectionModel().getSelectedItem();\n\t\t}\n\t\tNotificationProxy.sendNotification(GameNotification.FILTER_CARDS,\n\t\t\t\tnew CardFilter(searchField.getText(), cardSetBox.getSelectionModel().getSelectedItem(), deckFormat));\n\t}\n\n\tprivate void formatChanged(ObservableValue<? extends DeckFormat> observable, DeckFormat oldValue, DeckFormat newValue) {\n\t\tCardSet set = cardSetBox.getSelectionModel().getSelectedItem();\n\t\tif (deckFormatBox.getSelectionModel().isSelected(0)) {\n\t\t\tcardSetBox.setItems(FXCollections.observableArrayList(CardSet.values()));\n\t\t} else {\n\t\t\tList<CardSet> sets = newValue.getCardSets();\n\t\t\tsets.add(0, CardSet.ANY);\n\t\t\tcardSetBox.setItems(FXCollections.observableArrayList(sets));\n\t\t}\n\t\tif (!deckFormatBox.getSelectionModel().isSelected(0) && !set.equals(CardSet.ANY) && !newValue.isInFormat(set)) {\n\t\t\tcardSetBox.getSelectionModel().selectFirst();\n\t\t} else {\n\t\t\tcardSetBox.getSelectionModel().select(set);\n\t\t}\n\t\tfilterChanged();\n\t}\n\n\tpublic void injectDeckFormats(List<DeckFormat> deckFormats) {\n\t\tthis.deckFormats.addAll(deckFormats);\n\t}\n\n\tprivate void setChanged(ObservableValue<? extends CardSet> observable, CardSet oldValue, CardSet newValue) {\n\t\tfilterChanged();\n\t}\n\n\tprivate void textChanged(ObservableValue<? extends String> observable, String oldValue, String newValue) {\n\t\tfilterChanged();\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/CardListView.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.util.HashMap;\n\nimport javafx.event.EventHandler;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.VBox;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.decks.Deck;\n\npublic class CardListView extends VBox implements EventHandler<MouseEvent> {\n\n\tprivate final HashMap<String, CardEntry> existingCardEntries = new HashMap<String, CardEntry>();\n\tprivate final CardEntryFactory cardEntryFactory = new CardEntryFactory();\n\n\tpublic CardListView() {\n\t\tsuper(2);\n\t\tthis.setAlignment(Pos.TOP_LEFT);\n\t\tthis.setPrefSize(240, USE_COMPUTED_SIZE);\n\t}\n\n\tprivate void clearChildren() {\n\t\tfor (Node child : getChildren()) {\n\t\t\tchild.removeEventHandler(MouseEvent.MOUSE_CLICKED, this);\n\t\t}\n\t\tgetChildren().clear();\n\t}\n\n\tpublic void displayDeck(Deck deck) {\n\t\texistingCardEntries.clear();\n\t\tclearChildren();\n\t\tfor (Card card : deck.getCards()) {\n\t\t\tString cardId = card.getCardId();\n\t\t\tCardEntry cardEntry = null;\n\t\t\tif (existingCardEntries.containsKey(cardId)) {\n\t\t\t\tcardEntry = existingCardEntries.get(cardId);\n\t\t\t\tcardEntry.addCard(card);\n\t\t\t} else {\n\t\t\t\tcardEntry = cardEntryFactory.createCardEntry(card);\n\t\t\t\tcardEntry.addEventHandler(MouseEvent.MOUSE_CLICKED, this);\n\t\t\t\tgetChildren().add(cardEntry);\n\t\t\t\texistingCardEntries.put(cardId, cardEntry);\n\t\t\t}\n\n\t\t}\n\t}\n\n\t@Override\n\tpublic void handle(MouseEvent event) {\n\t\tCard card = null;\n\t\tfor (CardEntry cardEntry : existingCardEntries.values()) {\n\t\t\tif (event.getSource() == cardEntry) {\n\t\t\t\tcard = cardEntry.getCard();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t}\n\t\tif (card != null) {\n\t\t\tNotificationProxy.sendNotification(GameNotification.REMOVE_CARD_FROM_DECK, card);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/CardView.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport javafx.event.EventHandler;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.Pane;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.gui.cards.CardTooltip;\n\npublic class CardView extends BorderPane implements EventHandler<MouseEvent> {\n\n\t@FXML\n\tprivate Pane contentPane;\n\n\t@FXML\n\tprivate Button previousButton;\n\n\t@FXML\n\tprivate Button nextButton;\n\n\t@FXML\n\tprivate Label pageLabel;\n\n\tprivate int offset;\n\tprivate final int rows = 4;\n\tprivate final int columns = 2;\n\tprivate final int cardDisplayCount = rows * columns;\n\n\tprivate List<Card> cards;\n\tprivate final List<CardTooltip> cardWidgets = new ArrayList<CardTooltip>(cardDisplayCount);\n\n\tpublic CardView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/CardView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tsetupCardWidgets();\n\n\t\tpreviousButton.setOnAction(actionEvent -> changeOffset(-cardDisplayCount));\n\t\tnextButton.setOnAction(actionEvent -> changeOffset(+cardDisplayCount));\n\n\t\tsetCache(true);\n\t}\n\n\tprivate void changeOffset(int delta) {\n\t\tint newOffset = offset + delta;\n\t\tif (newOffset < 0 || newOffset >= cards.size()) {\n\t\t\treturn;\n\t\t}\n\t\toffset += delta;\n\t\tdisplayCurrentPage();\n\t}\n\n\tpublic void displayCards(List<Card> cards) {\n\t\tthis.cards = cards;\n\t\toffset = 0;\n\t\tdisplayCurrentPage();\n\t}\n\n\tprivate void displayCurrentPage() {\n\t\tint lastIndex = Math.min(cards.size(), offset + cardDisplayCount);\n\t\tupdatePageLabel();\n\t\tfor (CardTooltip CardTooltip : cardWidgets) {\n\t\t\tCardTooltip.setVisible(false);\n\t\t}\n\t\tint widgetIndex = 0;\n\t\tfor (int i = offset; i < lastIndex; i++) {\n\t\t\tCard card = cards.get(i);\n\t\t\tCardTooltip cardWidget = cardWidgets.get(widgetIndex++);\n\t\t\tcardWidget.setCard(card);\n\t\t\tcardWidget.setVisible(true);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void handle(MouseEvent event) {\n\t\tCardTooltip source = (CardTooltip) event.getSource();\n\t\tCard card = source.getCard();\n\t\tNotificationProxy.sendNotification(GameNotification.ADD_CARD_TO_DECK, card);\n\t}\n\n\tprivate void setupCardWidgets() {\n\t\tfor (int i = 0; i < cardDisplayCount; i++) {\n\t\t\tCardTooltip cardWidget = new CardTooltip();\n\n\t\t\tcardWidget.addEventHandler(MouseEvent.MOUSE_CLICKED, this);\n\t\t\tcardWidget.setScaleX(0.95);\n\t\t\tcardWidget.setScaleY(0.95);\n\t\t\tcardWidget.setScaleZ(0.95);\n\n\t\t\tcontentPane.getChildren().add(cardWidget);\n\t\t\tcardWidgets.add(cardWidget);\n\t\t}\n\t}\n\n\tprivate void updatePageLabel() {\n\t\tint totalPages = (int) Math.ceil(cards.size() / (double) cardDisplayCount);\n\t\tint currentPage = (int) Math.ceil(offset / (double) cardDisplayCount) + 1;\n\t\tpageLabel.setText(currentPage + \"/\" + totalPages);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/ChangeDeckNameCommand.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\n\npublic class ChangeDeckNameCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tDeckProxy deckProxy = (DeckProxy) getFacade().retrieveProxy(DeckProxy.NAME);\n\n\t\tString newDeckName = (String) notification.getBody();\n\t\tdeckProxy.getActiveDeck().setName(newDeckName);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/ChooseClassView.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.io.IOException;\n\nimport javafx.beans.value.ObservableValue;\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.CheckBox;\nimport javafx.scene.layout.BorderPane;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.MetaDeck;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\n\npublic class ChooseClassView extends BorderPane implements EventHandler<ActionEvent> {\n\t@FXML\n\tprivate Button warriorButton;\n\n\t@FXML\n\tprivate Button paladinButton;\n\n\t@FXML\n\tprivate Button druidButton;\n\n\t@FXML\n\tprivate Button rogueButton;\n\n\t@FXML\n\tprivate Button warlockButton;\n\n\t@FXML\n\tprivate Button hunterButton;\n\n\t@FXML\n\tprivate Button shamanButton;\n\n\t@FXML\n\tprivate Button mageButton;\n\n\t@FXML\n\tprivate Button priestButton;\n\n\t@FXML\n\tprivate Button collectionButton;\n\t\n\t@FXML\n\tprivate CheckBox arbitraryCheckBox;\n\t\n\tprivate boolean arbitrary;\n\n\tpublic ChooseClassView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/ChooseClassView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\t\tarbitrary = false;\n\t\t\n\t\tsetupArbitraryBox();\n\n\t\twarriorButton.setOnAction(this);\n\t\tpaladinButton.setOnAction(this);\n\t\tdruidButton.setOnAction(this);\n\n\t\trogueButton.setOnAction(this);\n\t\twarlockButton.setOnAction(this);\n\t\thunterButton.setOnAction(this);\n\n\t\tshamanButton.setOnAction(this);\n\t\tmageButton.setOnAction(this);\n\t\tpriestButton.setOnAction(this);\n\n\t\tcollectionButton.setOnAction(this);\n\t}\n\n\t@Override\n\tpublic void handle(ActionEvent event) {\n\t\tDeck newDeck = null;\n\t\tif (event.getSource() == warriorButton) {\n\t\t\tnewDeck = new Deck(HeroClass.WARRIOR, arbitrary);\n\t\t} else if (event.getSource() == paladinButton) {\n\t\t\tnewDeck = new Deck(HeroClass.PALADIN, arbitrary);\n\t\t} else if (event.getSource() == druidButton) {\n\t\t\tnewDeck = new Deck(HeroClass.DRUID, arbitrary);\n\t\t} else if (event.getSource() == rogueButton) {\n\t\t\tnewDeck = new Deck(HeroClass.ROGUE, arbitrary);\n\t\t} else if (event.getSource() == warlockButton) {\n\t\t\tnewDeck = new Deck(HeroClass.WARLOCK, arbitrary);\n\t\t} else if (event.getSource() == hunterButton) {\n\t\t\tnewDeck = new Deck(HeroClass.HUNTER, arbitrary);\n\t\t} else if (event.getSource() == shamanButton) {\n\t\t\tnewDeck = new Deck(HeroClass.SHAMAN, arbitrary);\n\t\t} else if (event.getSource() == mageButton) {\n\t\t\tnewDeck = new Deck(HeroClass.MAGE, arbitrary);\n\t\t} else if (event.getSource() == priestButton) {\n\t\t\tnewDeck = new Deck(HeroClass.PRIEST, arbitrary);\n\t\t} else if (event.getSource() == collectionButton) {\n\t\t\tnewDeck = new MetaDeck();\n\t\t}\n\t\tNotificationProxy.sendNotification(GameNotification.SET_ACTIVE_DECK, newDeck);\n\t}\n\n\tprivate void onArbitraryBoxChanged(ObservableValue<? extends Boolean> ov, Boolean oldValue, Boolean newValue) {\n\t\tarbitrary = newValue;\n//\t\tdeckProxy = (DeckProxy) getFacade().retrieveProxy(DeckProxy.NAME);\n//\t\tdeckProxy.setActiveDeckValidator(new ArbitraryDeckValidator());\n\t}\n\n\tprivate void setupArbitraryBox() {\n\t\tarbitraryCheckBox.selectedProperty().addListener(this::onArbitraryBoxChanged);\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/DeckBuilderMediator.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.nittygrittymvc.Mediator;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.decks.validation.DefaultDeckValidator;\nimport net.demilich.metastone.gui.dialog.DialogNotification;\nimport net.demilich.metastone.gui.dialog.DialogType;\n\npublic class DeckBuilderMediator extends Mediator<GameNotification> {\n\n\tpublic static final String NAME = \"DeckBuilderMediator\";\n\n\tprivate final DeckBuilderView view;\n\n\tpublic DeckBuilderMediator() {\n\t\tsuper(NAME);\n\t\tview = new DeckBuilderView();\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Override\n\tpublic void handleNotification(final INotification<GameNotification> notification) {\n\t\tswitch (notification.getId()) {\n\t\tcase CREATE_NEW_DECK:\n\t\t\tDeckProxy deckProxy = (DeckProxy) getFacade().retrieveProxy(DeckProxy.NAME);\n\t\t\tdeckProxy.setActiveDeckValidator(new DefaultDeckValidator());\n\t\t\tview.createNewDeck();\n\t\t\tbreak;\n\t\tcase EDIT_DECK:\n\t\t\tview.editDeck((Deck) notification.getBody());\n\t\t\tbreak;\n\t\tcase ACTIVE_DECK_CHANGED:\n\t\t\tview.activeDeckChanged((Deck) notification.getBody());\n\t\t\tbreak;\n\t\tcase FILTERED_CARDS:\n\t\t\tview.filteredCards((List<Card>) notification.getBody());\n\t\t\tbreak;\n\t\tcase DECKS_LOADED:\n\t\t\tview.displayDecks((List<Deck>) notification.getBody());\n\t\t\tbreak;\n\t\tcase INVALID_DECK_NAME:\n\t\t\tDialogNotification dialogNotification = new DialogNotification(\"Name your deck\", \"Please enter a valid name for this deck.\",\n\t\t\t\t\tDialogType.WARNING);\n\t\t\tgetFacade().notifyObservers(dialogNotification);\n\t\t\tbreak;\n\t\tcase DECK_FORMATS_LOADED:\n\t\t\tList<DeckFormat> deckFormats = (List<DeckFormat>) notification.getBody();\n\t\t\tview.injectDeckFormats(deckFormats);\n\t\t\tbreak;\n\t\tcase DUPLICATE_DECK_NAME:\n\t\t\tgetFacade().notifyObservers(new DialogNotification(\"Duplicate deck name\",\n\t\t\t\t\t\"This deck name was already used for another deck. Please choose another name\", DialogType.WARNING));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t@Override\n\tpublic List<GameNotification> listNotificationInterests() {\n\t\tList<GameNotification> notificationInterests = new ArrayList<GameNotification>();\n\t\tnotificationInterests.add(GameNotification.CREATE_NEW_DECK);\n\t\tnotificationInterests.add(GameNotification.EDIT_DECK);\n\t\tnotificationInterests.add(GameNotification.FILTERED_CARDS);\n\t\tnotificationInterests.add(GameNotification.ACTIVE_DECK_CHANGED);\n\t\tnotificationInterests.add(GameNotification.DECKS_LOADED);\n\t\tnotificationInterests.add(GameNotification.DECK_FORMATS_LOADED);\n\t\tnotificationInterests.add(GameNotification.INVALID_DECK_NAME);\n\t\tnotificationInterests.add(GameNotification.DUPLICATE_DECK_NAME);\n\t\treturn notificationInterests;\n\t}\n\n\t@Override\n\tpublic void onRegister() {\n\t\tgetFacade().sendNotification(GameNotification.SHOW_VIEW, view);\n\t\tgetFacade().sendNotification(GameNotification.LOAD_DECKS);\n\t\tgetFacade().sendNotification(GameNotification.LOAD_DECK_FORMATS);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/DeckBuilderView.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.control.TextField;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.Pane;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.decks.MetaDeck;\nimport net.demilich.metastone.gui.deckbuilder.metadeck.MetaDeckListView;\nimport net.demilich.metastone.gui.deckbuilder.metadeck.MetaDeckView;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class DeckBuilderView extends BorderPane implements EventHandler<ActionEvent> {\n\n\t@FXML\n\tprivate ScrollPane scrollPane;\n\n\t@FXML\n\tprivate Pane lowerInfoArea;\n\n\t@FXML\n\tprivate Pane upperInfoArea;\n\n\t@FXML\n\tprivate TextField importField;\n\n\t@FXML\n\tprivate Button importButton;\n\n\t@FXML\n\tprivate Button backButton;\n\n\tprivate final CardView cardView;\n\tprivate final CardListView cardListView;\n\tprivate final DeckInfoView deckInfoView;\n\tprivate final DeckListView deckListView;\n\tprivate final DeckNameView deckNameView;\n\tprivate final MetaDeckView metaDeckView;\n\tprivate final MetaDeckListView metaDeckListView;\n\n\tprivate List<DeckFormat> deckFormats = new ArrayList<DeckFormat>();\n\n\tpublic DeckBuilderView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/DeckBuilderView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\timportButton.setOnAction(this);\n\t\tbackButton.setOnAction(this);\n\n\t\tcardView = new CardView();\n\t\tcardListView = new CardListView();\n\t\tdeckInfoView = new DeckInfoView();\n\t\tdeckListView = new DeckListView();\n\t\tdeckNameView = new DeckNameView();\n\n\t\tmetaDeckView = new MetaDeckView();\n\t\tmetaDeckListView = new MetaDeckListView();\n\t\tshowSidebar(deckListView);\n\t}\n\n\tpublic void activeDeckChanged(Deck activeDeck) {\n\t\tif (activeDeck.isMetaDeck()) {\n\t\t\tMetaDeck metaDeck = (MetaDeck) activeDeck;\n\t\t\tmetaDeckListView.displayDecks(metaDeck.getDecks());\n\t\t\tmetaDeckView.deckChanged(metaDeck);\n\t\t} else {\n\t\t\tactiveDeck.getCards().sortByManaCost();\n\t\t\tcardListView.displayDeck(activeDeck);\n\n\t\t}\n\t\tdeckInfoView.updateDeck(activeDeck);\n\t\tdeckNameView.updateDeck(activeDeck);\n\n\t}\n\n\tpublic void createNewDeck() {\n\t\tshowMainArea(new ChooseClassView());\n\t\tshowSidebar(null);\n\t}\n\n\tpublic void displayDecks(List<Deck> decks) {\n\t\tdeckListView.displayDecks(decks);\n\t\tmetaDeckView.displayDecks(decks);\n\t}\n\n\tpublic void editDeck(Deck deck) {\n\t\tif (deck.isMetaDeck()) {\n\t\t\tshowMainArea(metaDeckView);\n\t\t\tshowSidebar(metaDeckListView);\n\t\t} else {\n\t\t\tshowMainArea(cardView);\n\t\t\tshowSidebar(cardListView);\n\t\t\tshowBottomBar(new CardFilterView(deckFormats));\n\t\t}\n\t\tshowLowerInfoArea(deckInfoView);\n\t\tshowUpperInfoArea(deckNameView);\n\n\t}\n\n\tpublic void filteredCards(List<Card> filteredCards) {\n\t\tcardView.displayCards(filteredCards);\n\t}\n\n\t@Override\n\tpublic void handle(ActionEvent event) {\n\t\tif (event.getSource() == importButton) {\n\t\t\tNotificationProxy.sendNotification(GameNotification.IMPORT_DECK_FROM_URL, importField.getText());\n\t\t} else if (event.getSource() == backButton) {\n\t\t\tNotificationProxy.sendNotification(GameNotification.MAIN_MENU);\n\t\t}\n\t}\n\n\tpublic void injectDeckFormats(List<DeckFormat> deckFormats) {\n\t\tthis.deckFormats.addAll(deckFormats);\n\t}\n\n\tprivate void showBottomBar(Node content) {\n\t\tBorderPane.setAlignment(content, Pos.CENTER);\n\t\tsetBottom(content);\n\t}\n\n\tprivate void showLowerInfoArea(Node content) {\n\t\tlowerInfoArea.getChildren().clear();\n\t\tlowerInfoArea.getChildren().add(content);\n\t}\n\n\tprivate void showMainArea(Node content) {\n\t\tsetCenter(content);\n\t}\n\n\tprivate void showSidebar(Node content) {\n\t\tscrollPane.setVisible(content != null);\n\t\tscrollPane.setContent(content);\n\t}\n\n\tprivate void showUpperInfoArea(Node content) {\n\t\tupperInfoArea.getChildren().clear();\n\t\tupperInfoArea.getChildren().add(content);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/DeckEntry.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.io.IOException;\n\nimport javafx.event.ActionEvent;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.layout.HBox;\nimport net.demilich.metastone.ApplicationFacade;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.gui.IconFactory;\nimport net.demilich.metastone.gui.dialog.DialogNotification;\nimport net.demilich.metastone.gui.dialog.DialogResult;\nimport net.demilich.metastone.gui.dialog.DialogType;\n\npublic class DeckEntry extends HBox {\n\n\t@FXML\n\tprivate Label deckNameLabel;\n\n\t@FXML\n\tprivate ImageView classIcon;\n\n\t@FXML\n\tprivate Button deleteDeckButton;\n\n\tprivate Deck deck;\n\n\tpublic DeckEntry() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/DeckEntry.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tdeleteDeckButton.setOnAction(this::handleDeleteDeck);\n\t}\n\n\tpublic Deck getDeck() {\n\t\treturn deck;\n\t}\n\n\tpublic void setDeck(Deck deck) {\n\t\tthis.deck = deck;\n\t\tdeckNameLabel.setText(deck.getName());\n\t\tclassIcon.setImage(IconFactory.getClassIcon(deck.getHeroClass()));\n\t}\n\n\tprivate void handleDeleteDeck(ActionEvent event) {\n\t\tDialogNotification dialogNotification = new DialogNotification(\"Delete deck\",\n\t\t\t\t\"Do you really want to delete the deck '\" + deck.getName() + \"'? This cannot be undone.\", DialogType.WARNING);\n\t\tdialogNotification.setHandler(this::onDeleteDeckDialog);\n\t\tApplicationFacade.getInstance().notifyObservers(dialogNotification);\n\t}\n\n\tprivate void onDeleteDeckDialog(DialogResult result) {\n\t\tif (result == DialogResult.OK) {\n\t\t\tNotificationProxy.sendNotification(GameNotification.DELETE_DECK, deck);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/DeckFormatProxy.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.net.URISyntaxException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport net.demilich.metastone.utils.ResourceInputStream;\nimport net.demilich.metastone.utils.ResourceLoader;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.reflect.TypeToken;\n\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.cards.CardSet;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.nittygrittymvc.Proxy;\n\npublic class DeckFormatProxy extends Proxy<GameNotification> {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(DeckFormatProxy.class);\n\n\tpublic static final String NAME = \"DeckFormatProxy\";\n\t\n\tprivate static final String DECK_FORMATS_FOLDER = \"formats\";\n\n\tprivate final List<DeckFormat> deckFormats = new ArrayList<DeckFormat>();\n\n\tpublic DeckFormatProxy() {\n\t\tsuper(NAME);\n\t}\n\n\tpublic DeckFormat getDeckFormatByName(String deckName) {\n\t\tfor (DeckFormat deckFormat : deckFormats) {\n\t\t\tif (deckFormat.getName().equals(deckName)) {\n\t\t\t\treturn deckFormat;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic List<DeckFormat> getDeckFormats() {\n\t\treturn deckFormats;\n\t}\n\n\tpublic void loadDeckFormats() throws IOException, URISyntaxException {\n\t\tdeckFormats.clear();\n\n\t\t// load the deck formats from the resources in cards.jar file on the classpath\n\t\tCollection<ResourceInputStream> inputStreams = ResourceLoader.loadJsonInputStreams(DECK_FORMATS_FOLDER, false);\n\n\t\tGson gson = new GsonBuilder().setPrettyPrinting().create();\n\t\tloadDeckFormats(inputStreams, gson);\n\t}\n\n\tprivate void loadDeckFormats(Collection<ResourceInputStream> inputStreams, Gson gson) throws FileNotFoundException {\n\t\tfor (ResourceInputStream resourceInputStream : inputStreams) {\n\t\t\tReader reader = new InputStreamReader(resourceInputStream.inputStream);\n\t\t\tHashMap<String, Object> map = gson.fromJson(reader, new TypeToken<HashMap<String, Object>>() {}.getType());\n\n\t\t\tif (!map.containsKey(\"sets\")) {\n\t\t\t\tlogger.error(\"Deck {} does not specify a value for 'sets' and is therefore not valid\", resourceInputStream.fileName);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tString deckName = (String) map.get(\"name\");\n\t\t\tDeckFormat deckFormat = null;\n\t\t\t// this one is a meta deck; we need to parse those after all other\n\t\t\t// decks are done\n\t\t\tdeckFormat = parseStandardDeckFormat(map);\n\t\t\tdeckFormat.setName(deckName);\n\t\t\tdeckFormat.setFilename(resourceInputStream.fileName);\n\t\t\tdeckFormats.add(deckFormat);\n\t\t}\n\t}\n\n\tprivate DeckFormat parseStandardDeckFormat(Map<String, Object> map) {\n\t\tDeckFormat deckFormat = new DeckFormat();\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<String> setIds = (List<String>) map.get(\"sets\");\n\t\tfor (String setId : setIds) {\n\t\t\tfor (CardSet set : CardSet.values()) {\n\t\t\t\tif (set.toString().equalsIgnoreCase(setId)) {\n\t\t\t\t\tdeckFormat.addSet(set);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn deckFormat;\n\t\t\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/DeckInfoView.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.io.IOException;\n\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.paint.Color;\nimport net.demilich.metastone.ApplicationFacade;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.MetaDeck;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.gui.dialog.DialogNotification;\nimport net.demilich.metastone.gui.dialog.DialogResult;\nimport net.demilich.metastone.gui.dialog.DialogType;\nimport net.demilich.metastone.gui.dialog.IDialogListener;\n\npublic class DeckInfoView extends HBox implements EventHandler<ActionEvent>, IDialogListener {\n\n\t@FXML\n\tprivate Button doneButton;\n\n\t@FXML\n\tprivate Label typeLabel;\n\n\t@FXML\n\tprivate Label countLabel;\n\n\tprivate Deck activeDeck;\n\n\tpublic DeckInfoView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/DeckInfoView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\t\tdoneButton.setOnAction(this);\n\t}\n\n\t@Override\n\tpublic void handle(ActionEvent event) {\n\t\tif (activeDeck.isMetaDeck() && !activeDeck.isComplete()) {\n\t\t\tDialogNotification dialogNotification = new DialogNotification(\"Warning\",\n\t\t\t\t\t\"Your deck collection is not complete yet. Each deck collection has to contain at least 2 (or more) decks. \",\n\t\t\t\t\tDialogType.WARNING);\n\t\t\tApplicationFacade.getInstance().notifyObservers(dialogNotification);\n\t\t} else if (!activeDeck.isMetaDeck() && !activeDeck.isComplete() && !activeDeck.isTooBig() && !activeDeck.isArbitrary()) {\n\t\t\tDialogNotification dialogNotification = new DialogNotification(\"Add random cards\",\n\t\t\t\t\t\"Your deck is not complete yet. If you proceed, all open slots will be filled with random cards.\", DialogType.CONFIRM);\n\t\t\tdialogNotification.setHandler(this);\n\t\t\tApplicationFacade.getInstance().notifyObservers(dialogNotification);\n\t\t} else if (!activeDeck.isMetaDeck() && !activeDeck.isComplete() && activeDeck.isTooBig() && !activeDeck.isArbitrary()) { \n\t\t\tDialogNotification dialogNotification = new DialogNotification(\"Remove random cards\",\n\t\t\t\t\t\"Your deck has too many cards. If you proceed, some cards will be removed at random.\", DialogType.CONFIRM);\n\t\t\tdialogNotification.setHandler(this);\n\t\t\tApplicationFacade.getInstance().notifyObservers(dialogNotification);\n\t\t} else {\n\t\t\tNotificationProxy.sendNotification(GameNotification.SAVE_ACTIVE_DECK);\n\t\t}\n\n\t}\n\n\t@Override\n\tpublic void onDialogClosed(DialogResult result) {\n\t\tif (result == DialogResult.OK) {\n\t\t\tNotificationProxy.sendNotification(GameNotification.FILL_DECK_WITH_RANDOM_CARDS);\n\t\t\tNotificationProxy.sendNotification(GameNotification.SAVE_ACTIVE_DECK);\n\t\t}\n\t}\n\n\tpublic void updateDeck(Deck deck) {\n\t\tthis.activeDeck = deck;\n\t\tif (deck.isMetaDeck()) {\n\t\t\tMetaDeck metaDeck = (MetaDeck) deck;\n\t\t\ttypeLabel.setText(\"Decks\");\n\t\t\tcountLabel.setText(metaDeck.getDecks().size() + \"\");\n\t\t} else {\n\t\t\ttypeLabel.setText(\"Cards\");\n\t\t\tif (deck.isTooBig()) {\n\t\t\t\tcountLabel.setText(deck.getCards().getCount() + \"!/\" + GameLogic.DECK_SIZE);\n\t\t\t\tcountLabel.setTextFill(Color.RED);\n\t\t\t} else {\n\t\t\t\tcountLabel.setText(deck.getCards().getCount() + \"/\" + GameLogic.DECK_SIZE);\n\t\t\t\tcountLabel.setTextFill(Color.BLACK);\n\t\t\t}\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/DeckListView.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport javafx.event.EventHandler;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.VBox;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.decks.Deck;\n\npublic class DeckListView extends VBox implements EventHandler<MouseEvent> {\n\n\t@FXML\n\tprivate Button newDeckButton;\n\n\tpublic DeckListView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/DeckListView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tnewDeckButton.setOnAction(actionEvent -> NotificationProxy.sendNotification(GameNotification.CREATE_NEW_DECK));\n\t\tsetCache(true);\n\t}\n\n\tprivate void clearChildren() {\n\t\tfor (Node child : getChildren()) {\n\t\t\tchild.removeEventHandler(MouseEvent.MOUSE_CLICKED, this);\n\t\t}\n\t\tgetChildren().clear();\n\t}\n\n\tpublic void displayDecks(List<Deck> decks) {\n\t\tclearChildren();\n\t\tgetChildren().add(newDeckButton);\n\t\tfor (Deck deck : decks) {\n\t\t\tDeckEntry deckEntry = new DeckEntry();\n\t\t\tdeckEntry.setDeck(deck);\n\t\t\tdeckEntry.addEventHandler(MouseEvent.MOUSE_CLICKED, this);\n\t\t\tgetChildren().add(deckEntry);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void handle(MouseEvent event) {\n\t\tDeckEntry deckEntry = (DeckEntry) event.getSource();\n\t\tNotificationProxy.sendNotification(GameNotification.SET_ACTIVE_DECK, deckEntry.getDeck());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/DeckNameView.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.io.IOException;\n\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.TextField;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.layout.HBox;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.gui.IconFactory;\n\npublic class DeckNameView extends HBox implements ChangeListener<String> {\n\n\t@FXML\n\tprivate ImageView classIcon;\n\n\t@FXML\n\tprivate TextField nameField;\n\n\tpublic DeckNameView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/DeckNameView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tnameField.textProperty().addListener(this);\n\t}\n\n\t@Override\n\tpublic void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {\n\t\tNotificationProxy.sendNotification(GameNotification.CHANGE_DECK_NAME, newValue);\n\t}\n\n\tpublic void updateDeck(Deck deck) {\n\t\tclassIcon.setImage(IconFactory.getClassIcon(deck.getHeroClass()));\n\t\tnameField.setText(deck.getName());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/DeckProxy.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.NoSuchFileException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport net.demilich.metastone.utils.MetastoneProperties;\nimport net.demilich.metastone.utils.ResourceInputStream;\nimport net.demilich.metastone.utils.ResourceLoader;\nimport net.demilich.metastone.utils.UserHomeMetastone;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.reflect.TypeToken;\n\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.cards.CardSet;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.decks.MetaDeck;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.decks.validation.DefaultDeckValidator;\nimport net.demilich.metastone.game.decks.validation.IDeckValidator;\nimport net.demilich.nittygrittymvc.Proxy;\n\npublic class DeckProxy extends Proxy<GameNotification> {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(DeckProxy.class);\n\n\tpublic static final String NAME = \"DeckProxy\";\n\tprivate static final String DECKS_FOLDER = \"decks\";\n\tprivate static final String DECKS_FOLDER_PATH = UserHomeMetastone.getPath() + File.separator + DECKS_FOLDER;\n\tprivate static final String DECKS_COPIED_PROPERTY = \"decks.copied\";\n\n\tprivate final List<Deck> decks = new ArrayList<Deck>();\n\tprivate IDeckValidator activeDeckValidator = new DefaultDeckValidator();\n\tprivate Deck activeDeck;\n\n\tpublic DeckProxy() {\n\t\tsuper(NAME);\n\t\ttry {\n\t\t\t// ensure user's personal deck dir exists\n\t\t\tFiles.createDirectories(Paths.get(DECKS_FOLDER_PATH));\n\t\t\t// ensure decks have been copied to ~/metastone/decks\n\t\t\tcopyDecksFromResources();\n\t\t} catch (IOException e) {\n\t\t\tlogger.error(\"Trouble creating \" + Paths.get(DECKS_FOLDER_PATH));\n\t\t\te.printStackTrace();\n\t\t} catch (URISyntaxException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n\n\tpublic boolean addCardToDeck(Card card) {\n\t\tboolean result = activeDeckValidator.canAddCardToDeck(card, activeDeck);\n\t\tif (result) {\n\t\t\tactiveDeck.getCards().add(card);\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic Deck getActiveDeck() {\n\t\treturn activeDeck;\n\t}\n\n\tpublic List<Card> getCards(HeroClass heroClass) {\n\t\tDeckFormat deckFormat = new DeckFormat();\n\t\tfor (CardSet cardSet : CardSet.values()) {\n\t\t\tdeckFormat.addSet(cardSet);\n\t\t}\n\t\tCardCollection cardCollection;\n\t\tif (activeDeck.isArbitrary()) {\n\t\t\tcardCollection = CardCatalogue.query(deckFormat);\n\t\t} else {\n\t\t\tcardCollection = CardCatalogue.query(deckFormat, heroClass);\n\t\t\t// add neutral cards\n\t\t\tcardCollection.addAll(CardCatalogue.query(deckFormat, HeroClass.ANY));\n\t\t}\n\t\tcardCollection.sortByName();\n\t\tcardCollection.sortByManaCost();\n\t\treturn cardCollection.toList();\n\t}\n\n\tpublic Deck getDeckByName(String deckName) {\n\t\tfor (Deck deck : decks) {\n\t\t\tif (deck.getName().equals(deckName)) {\n\t\t\t\treturn deck;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic List<Deck> getDecks() {\n\t\treturn decks;\n\t}\n\n\tpublic void deleteDeck(Deck deck) {\n\t\tdecks.remove(deck);\n\t\tlogger.debug(\"Trying to delete deck '{}' contained in file '{}'...\", deck.getName(), deck.getFilename());\n\t\tPath path = Paths.get(DECKS_FOLDER_PATH + File.separator + deck.getFilename());\n\t\ttry {\n\t\t\tFiles.delete(path);\n\t\t} catch (NoSuchFileException x) {\n\t\t\tlogger.error(\"Could not delete deck '{}' as the filename '{}' does not exist\", deck.getName(), path);\n\t\t\treturn;\n\t\t} catch (IOException e) {\n\t\t\tlogger.error(e.getMessage());\n\t\t\tlogger.error(\"Could not delete file '{}'\", path);\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.info(\"Deck '{}' contained in file '{}' has been successfully deleted\", deck.getName(), path.getFileName().toString());\n\t\tgetFacade().sendNotification(GameNotification.DECKS_LOADED, decks);\n\t}\n\n\tpublic void loadDecks() throws IOException, URISyntaxException {\n\t\tdecks.clear();\n\n\t\t// load decks from ~/metastone/decks on the filesystem\n\t\tloadStandardDecks(ResourceLoader.loadJsonInputStreams(DECKS_FOLDER_PATH, true), new GsonBuilder().setPrettyPrinting().create());\n\n\t\tloadMetaDecks(ResourceLoader.loadJsonInputStreams(DECKS_FOLDER_PATH, true), new GsonBuilder().setPrettyPrinting().create());\n\t}\n\n\tprivate void copyDecksFromResources() throws IOException, URISyntaxException {\n\t\t// if we have not copied decks to the USER_HOME_METASTONE decks folder,\n\t\t// then do so now\n\t\tif (!MetastoneProperties.getBoolean(DECKS_COPIED_PROPERTY)) {\n\t\t\tResourceLoader.copyFromResources(DECKS_FOLDER, DECKS_FOLDER_PATH);\n\n\t\t\t// set a property to indicate that we have copied decks\n\t\t\tMetastoneProperties.setBoolean(DECKS_COPIED_PROPERTY, true);\n\t\t}\n\t}\n\n\tprivate void loadMetaDecks(Collection<ResourceInputStream> inputStreams, Gson gson) throws IOException {\n\t\tfor (ResourceInputStream resourceInputStream : inputStreams) {\n\t\t\tReader reader = new InputStreamReader(resourceInputStream.inputStream);\n\t\t\tHashMap<String, Object> map = gson.fromJson(reader, new TypeToken<HashMap<String, Object>>() {\n\t\t\t}.getType());\n\t\t\tif (!map.containsKey(\"heroClass\")) {\n\t\t\t\tlogger.error(\"Deck {} does not specify a value for 'heroClass' and is therefor not valid\", resourceInputStream.fileName);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tString deckName = (String) map.get(\"name\");\n\t\t\tDeck deck = null;\n\t\t\tif (!map.containsKey(\"decks\")) {\n\t\t\t\tcontinue;\n\t\t\t} else {\n\t\t\t\tdeck = parseMetaDeck(map);\n\t\t\t}\n\t\t\tdeck.setName(deckName);\n\t\t\tdeck.setFilename(resourceInputStream.fileName);\n\t\t\tdecks.add(deck);\n\t\t}\n\t}\n\n\tprivate void loadStandardDecks(Collection<ResourceInputStream> inputStreams, Gson gson) throws FileNotFoundException {\n\t\tfor (ResourceInputStream resourceInputStream : inputStreams) {\n\n\t\t\tReader reader = new InputStreamReader(resourceInputStream.inputStream);\n\t\t\tHashMap<String, Object> map = gson.fromJson(reader, new TypeToken<HashMap<String, Object>>() {\n\t\t\t}.getType());\n\t\t\tif (!map.containsKey(\"heroClass\")) {\n\t\t\t\tlogger.error(\"Deck {} does not speficy a value for 'heroClass' and is therefor not valid\", resourceInputStream.fileName);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tHeroClass heroClass = HeroClass.valueOf((String) map.get(\"heroClass\"));\n\t\t\tString deckName = (String) map.get(\"name\");\n\t\t\tDeck deck = null;\n\t\t\t// this one is a meta deck; we need to parse those after all other\n\t\t\t// decks are done\n\t\t\tif (map.containsKey(\"decks\")) {\n\t\t\t\tcontinue;\n\t\t\t} else {\n\t\t\t\tdeck = parseStandardDeck(deckName, heroClass, map);\n\t\t\t}\n\t\t\tdeck.setName(deckName);\n\t\t\tdeck.setFilename(resourceInputStream.fileName);\n\t\t\tdecks.add(deck);\n\t\t}\n\t}\n\n\tpublic boolean nameAvailable(Deck deck) {\n\t\tfor (Deck existingDeck : decks) {\n\t\t\tif (existingDeck != deck && existingDeck.getName().equals(deck.getName())) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\tprivate Deck parseMetaDeck(Map<String, Object> map) {\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<String> referencedDecks = (List<String>) map.get(\"decks\");\n\t\tList<Deck> decksInMetaDeck = new ArrayList<>();\n\t\tfor (String deckName : referencedDecks) {\n\t\t\tDeck deck = getDeckByName(deckName);\n\t\t\tif (deck == null) {\n\t\t\t\tlogger.error(\"Metadeck {} contains invalid reference to deck {}\", map.get(\"name\"), deckName);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tdecksInMetaDeck.add(deck);\n\t\t}\n\t\treturn new MetaDeck(decksInMetaDeck);\n\t}\n\n\tprivate Deck parseStandardDeck(String deckName, HeroClass heroClass, Map<String, Object> map) {\n\t\tboolean arbitrary = false;\n\t\tif (map.containsKey(\"arbitrary\")) {\n\t\t\tarbitrary = (boolean) map.get(\"arbitrary\");\n\t\t}\n\t\tDeck deck = new Deck(heroClass, arbitrary);\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<String> cardIds = (List<String>) map.get(\"cards\");\n\t\tfor (String cardId : cardIds) {\n\t\t\tCard card = CardCatalogue.getCardById(cardId);\n\t\t\tif (card == null) {\n\t\t\t\tlogger.error(\"Deck {} contains invalid cardId '{}'\", deckName, cardId);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tdeck.getCards().add(card);\n\t\t}\n\t\treturn deck;\n\t}\n\n\tpublic void removeCardFromDeck(Card card) {\n\t\tactiveDeck.getCards().remove(card);\n\t}\n\n\tpublic void saveActiveDeck() {\n\t\tdecks.add(activeDeck);\n\t\tsaveToJson(activeDeck);\n\t\tactiveDeck = null;\n\t}\n\n\tprivate void saveToJson(Deck deck) {\n\t\tGson gson = new GsonBuilder().setPrettyPrinting().create();\n\t\tHashMap<String, Object> saveData = new HashMap<String, Object>();\n\t\tsaveData.put(\"name\", deck.getName());\n\t\tsaveData.put(\"description\", deck.getDescription());\n\t\tsaveData.put(\"arbitrary\", deck.isArbitrary());\n\t\tsaveData.put(\"heroClass\", deck.getHeroClass());\n\t\tif (deck.isMetaDeck()) {\n\t\t\tMetaDeck metaDeck = (MetaDeck) deck;\n\t\t\tList<String> referencedDecks = new ArrayList<>();\n\t\t\tfor (Deck referencedDeck : metaDeck.getDecks()) {\n\t\t\t\treferencedDecks.add(referencedDeck.getName());\n\t\t\t}\n\t\t\tsaveData.put(\"decks\", referencedDecks);\n\t\t} else {\n\t\t\tList<String> cardIds = new ArrayList<String>();\n\t\t\tfor (Card card : deck.getCards()) {\n\t\t\t\tcardIds.add(card.getCardId());\n\t\t\t}\n\t\t\tsaveData.put(\"cards\", cardIds);\n\t\t}\n\n\t\tString jsonData = gson.toJson(saveData);\n\t\ttry {\n\t\t\tString filename = deck.getName().toLowerCase();\n\t\t\tfilename = filename.replaceAll(\" \", \"_\");\n\t\t\tfilename = filename.replaceAll(\"\\\\W+\", \"\");\n\t\t\tfilename = DECKS_FOLDER_PATH + File.separator + filename + \".json\";\n\t\t\tPath path = Paths.get(filename);\n\t\t\tFiles.write(path, jsonData.getBytes());\n\t\t\tdeck.setFilename(path.getFileName().toString());\n\t\t} catch (IOException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n\n\tpublic void setActiveDeck(Deck activeDeck) {\n\t\tthis.activeDeck = activeDeck;\n\t}\n\n\tpublic void setActiveDeckValidator(IDeckValidator deckValidator) {\n\t\tthis.activeDeckValidator = deckValidator;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/DeleteDeckCommand.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\r\n\r\nimport net.demilich.metastone.GameNotification;\r\nimport net.demilich.metastone.game.decks.Deck;\r\nimport net.demilich.nittygrittymvc.SimpleCommand;\r\nimport net.demilich.nittygrittymvc.interfaces.INotification;\r\n\r\npublic class DeleteDeckCommand extends SimpleCommand<GameNotification> {\r\n\r\n\t@Override\r\n\tpublic void execute(INotification<GameNotification> notification) {\r\n\t\tDeck deck = (Deck) notification.getBody();\r\n\t\t\r\n\t\tDeckProxy deckProxy = (DeckProxy) getFacade().retrieveProxy(DeckProxy.NAME);\r\n\t\tdeckProxy.deleteDeck(deck);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/FillDeckWithRandomCardsCommand.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.util.List;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.decks.Deck;\n\npublic class FillDeckWithRandomCardsCommand extends SimpleCommand<GameNotification> {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(FillDeckWithRandomCardsCommand.class);\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tDeckProxy deckProxy = (DeckProxy) getFacade().retrieveProxy(DeckProxy.NAME);\n\n\t\tDeck activeDeck = deckProxy.getActiveDeck();\n\t\tList<Card> cards = deckProxy.getCards(activeDeck.getHeroClass());\n\t\tif (activeDeck.isTooBig()) {\n\t\t\twhile (!activeDeck.isComplete()) {\n\t\t\t\tCard randomCard = activeDeck.getCards().getRandom();\n\t\t\t\tdeckProxy.removeCardFromDeck(randomCard);\n\t\t\t\tlogger.debug(\"Removing card {} to deck.\", randomCard);\n\t\t\t}\n\t\t} else {\n\t\t\twhile (!activeDeck.isComplete()) {\n\t\t\t\tCard randomCard = cards.get(ThreadLocalRandom.current().nextInt(cards.size()));\n\t\t\t\tif (deckProxy.addCardToDeck(randomCard)) {\n\t\t\t\t\tlogger.debug(\"Adding card {} to deck.\", randomCard);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tgetFacade().sendNotification(GameNotification.ACTIVE_DECK_CHANGED, activeDeck);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/FilterCardsCommand.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.util.List;\n\nimport org.apache.commons.lang3.StringUtils;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardSet;\nimport net.demilich.metastone.game.decks.DeckFormat;\n\npublic class FilterCardsCommand extends SimpleCommand<GameNotification> {\n\n\tprivate static List<Card> filterByFormat(List<Card> collection, DeckFormat format) {\n\t\tif (format == null) {\n\t\t\treturn collection;\n\t\t}\n\n\t\tcollection.removeIf(card -> !format.isInFormat(card));\n\t\treturn collection;\n\t}\n\n\tprivate static List<Card> filterBySet(List<Card> collection, CardSet set) {\n\t\tif (set == CardSet.ANY) {\n\t\t\treturn collection;\n\t\t}\n\t\tcollection.removeIf(card -> card.getCardSet() != set);\n\t\treturn collection;\n\t}\n\n\tprivate static List<Card> filterByText(List<Card> collection, String text) {\n\t\tif (StringUtils.isBlank(text)) {\n\t\t\treturn collection;\n\t\t}\n\n\t\tString filterText = text.toLowerCase();\n\t\tcollection.removeIf(card -> !card.matchesFilter(filterText));\n\t\treturn collection;\n\t}\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tCardFilter filter = (CardFilter) notification.getBody();\n\n\t\tDeckProxy deckProxy = (DeckProxy) getFacade().retrieveProxy(DeckProxy.NAME);\n\n\t\tList<Card> cards = deckProxy.getCards(deckProxy.getActiveDeck().getHeroClass());\n\t\tcards = filterByFormat(cards, filter.getFormat());\n\t\tcards = filterBySet(cards, filter.getSet());\n\t\tcards = filterByText(cards, filter.getText());\n\n\t\tgetFacade().sendNotification(GameNotification.FILTERED_CARDS, cards);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/ImportDeckCommand.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport net.demilich.metastone.gui.deckbuilder.importer.ImporterFactory;\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.gui.deckbuilder.importer.IDeckImporter;\nimport net.demilich.metastone.gui.dialog.DialogNotification;\nimport net.demilich.metastone.gui.dialog.DialogType;\n\npublic class ImportDeckCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tString url = (String) notification.getBody();\n\n\t\tImporterFactory factory = new ImporterFactory();\n\t\tIDeckImporter importer = factory.createDeckImporter(url);\n\t\tDeck importedDeck = null;\n\t\tif(importer != null)\n\t\t\timportedDeck = importer.importFrom(url);\n\n\t\tif (importedDeck == null) {\n\t\t\tDialogNotification dialogNotification = new DialogNotification(\"Error\",\n\t\t\t\t\t\"Import of deck failed. Please make sure to provide a valid URL. At the moment, only hearthpwn.com, tempostorm.com, icy-veins.com, and heartheed.com are supported for deck import.\",\n\t\t\t\t\tDialogType.ERROR);\n\t\t\tnotifyObservers(dialogNotification);\n\t\t\treturn;\n\t\t}\n\t\tgetFacade().sendNotification(GameNotification.SET_ACTIVE_DECK, importedDeck);\n\t\tgetFacade().sendNotification(GameNotification.SAVE_ACTIVE_DECK);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/LoadDeckFormatsCommand.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\n\npublic class LoadDeckFormatsCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tDeckFormatProxy deckFormatProxy = (DeckFormatProxy) getFacade().retrieveProxy(DeckFormatProxy.NAME);\n\n\t\ttry {\n\t\t\tdeckFormatProxy.loadDeckFormats();\n\t\t} catch (FileNotFoundException e) {\n\t\t\te.printStackTrace();\n\t\t} catch (URISyntaxException e) {\n\t\t\te.printStackTrace();\n\t\t} catch (IOException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\n\t\tgetFacade().sendNotification(GameNotification.DECK_FORMATS_LOADED, deckFormatProxy.getDeckFormats());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/LoadDecksCommand.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\n\npublic class LoadDecksCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tDeckProxy deckProxy = (DeckProxy) getFacade().retrieveProxy(DeckProxy.NAME);\n\n\t\ttry {\n\t\t\tdeckProxy.loadDecks();\n\t\t} catch (FileNotFoundException e) {\n\t\t\te.printStackTrace();\n\t\t} catch (URISyntaxException e) {\n\t\t\te.printStackTrace();\n\t\t} catch (IOException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\n\t\tgetFacade().sendNotification(GameNotification.DECKS_LOADED, deckProxy.getDecks());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/RemoveCardFromDeckCommand.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.cards.Card;\n\npublic class RemoveCardFromDeckCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tDeckProxy deckProxy = (DeckProxy) getFacade().retrieveProxy(DeckProxy.NAME);\n\t\tCard card = (Card) notification.getBody();\n\t\tdeckProxy.getActiveDeck().getCards().remove(card);\n\n\t\tgetFacade().sendNotification(GameNotification.ACTIVE_DECK_CHANGED, deckProxy.getActiveDeck());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/SaveDeckCommand.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\n\npublic class SaveDeckCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tDeckProxy deckProxy = (DeckProxy) getFacade().retrieveProxy(DeckProxy.NAME);\n\t\tString deckName = deckProxy.getActiveDeck().getName().trim();\n\t\tif (deckName == null || deckName.equals(\"\")) {\n\t\t\tgetFacade().sendNotification(GameNotification.INVALID_DECK_NAME);\n\t\t\treturn;\n\t\t} else if (!deckProxy.nameAvailable(deckProxy.getActiveDeck())) {\n\t\t\tgetFacade().sendNotification(GameNotification.DUPLICATE_DECK_NAME);\n\t\t\treturn;\n\t\t}\n\t\tdeckProxy.saveActiveDeck();\n\n\t\tgetFacade().removeMediator(DeckBuilderMediator.NAME);\n\t\tgetFacade().sendNotification(GameNotification.MAIN_MENU);\n\t\tgetFacade().sendNotification(GameNotification.DECK_BUILDER_SELECTED);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/SetActiveDeckCommand.java",
    "content": "package net.demilich.metastone.gui.deckbuilder;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.validation.ArbitraryDeckValidator;\nimport net.demilich.metastone.game.decks.validation.DefaultDeckValidator;\n\npublic class SetActiveDeckCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tDeckProxy deckProxy = (DeckProxy) getFacade().retrieveProxy(DeckProxy.NAME);\n\t\tDeck activeDeck = (Deck) notification.getBody();\n\t\tif (activeDeck.isArbitrary()) {\n\t\t\tdeckProxy.setActiveDeckValidator(new ArbitraryDeckValidator());\n\t\t} else {\n\t\t\tdeckProxy.setActiveDeckValidator(new DefaultDeckValidator());\n\t\t}\n\t\tdeckProxy.setActiveDeck(activeDeck);\n\n\t\tgetFacade().sendNotification(GameNotification.EDIT_DECK, activeDeck);\n\t\tgetFacade().sendNotification(GameNotification.FILTERED_CARDS, deckProxy.getCards(activeDeck.getHeroClass()));\n\t\tgetFacade().sendNotification(GameNotification.ACTIVE_DECK_CHANGED, activeDeck);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/importer/HearthHeadImporter.java",
    "content": "package net.demilich.metastone.gui.deckbuilder.importer;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.jsoup.Connection.Response;\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.select.Elements;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\n\npublic class HearthHeadImporter implements IDeckImporter {\n\tprivate static Logger logger = LoggerFactory.getLogger(IcyVeinsImporter.class);\n\n\t@Override\n\tpublic Deck importFrom(String url) {\n\t\tString exportUrl = url;\n\t\ttry {\n\t\t\treturn parse(exportUrl);\n\t\t} catch (IOException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\treturn null;\n\t}\n\t\n\tprivate List<String> getCardIds(Document doc){\n\t\tElements metas = doc.getElementsByTag(\"meta\"); \n\t\tString title = \"\";\n\t\tString cards = \"\";\n\t\tfor (int i = 0; i < metas.size(); i++) { \n\t\t\tif (metas.get(i).attr(\"property\").equals(\"x-hearthstone:deck:cards\")) { \n\t\t\t\tcards = metas.get(i).attr(\"content\"); \n\t\t\t} \n\t\t\tif (metas.get(i).attr(\"property\").equals(\"x-hearthstone:deck\")) { \n\t\t\t\ttitle = metas.get(i).attr(\"content\"); \n\t\t\t} \n\t\t}\n\t\tList<String> cs = new ArrayList<String>();\n\t\tcs.add(0, title);\n\t\tList<String> cids = Arrays.asList(cards.split(\",\"));\n\t\tfor (String c: cids){\n\t\t\tcs.add(c);\n\t\t}\n\t\treturn cs;\n\t}\n\n\tprivate Deck parse(String url) throws IOException {\n\t\tList<Card> cards = new ArrayList<Card>();\n\t\tHeroClass heroClass = HeroClass.ANY;\n\t\t\n\t\tResponse response= Jsoup.connect(url)\n\t\t           .ignoreContentType(true)\n\t\t           .userAgent(\"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0\")  \n\t\t           .referrer(\"http://www.google.com\")   \n\t\t           .timeout(12000) \n\t\t           .followRedirects(true)\n\t\t           .execute();\n\t\tDocument doc = response.parse();\n\t\tfor (String cid: getCardIds(doc).subList(1, getCardIds(doc).size())) {\n\t\t\tCard card = CardCatalogue.getCardByBlizzardId(cid);\n\t\t\tif (card != null) {\n\t\t\t\tcards.add(card);\n\t\t\t\tif (card.getHeroClass() != HeroClass.ANY) {\n\t\t\t\t\theroClass = card.getHeroClass();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlogger.error(\"Card with id {} could not be found\", cid);\n\t\t\t}\n\t\t}\n\t\tDeck deck = new Deck(heroClass);\n\t\tdeck.setName(getCardIds(doc).get(0));\n\t\tfor (Card card : cards) {\n\t\t\tdeck.getCards().add(card);\n\t\t}\n\t\tif (!deck.isComplete()) {\n\t\t\treturn null;\n\t\t}\n\t\treturn deck;\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/importer/HearthPwnImporter.java",
    "content": "package net.demilich.metastone.gui.deckbuilder.importer;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.apache.commons.lang3.StringEscapeUtils;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.client.config.RequestConfig;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.apache.http.util.EntityUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\n\npublic class HearthPwnImporter implements IDeckImporter {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(HearthPwnImporter.class);\n\n\tprivate String extractId(String url) {\n\t\tString result = \"\";\n\t\tboolean digitEncountered = false;\n\t\tfor (int i = 0; i < url.length(); i++) {\n\t\t\tchar c = url.charAt(i);\n\t\t\tif (Character.isDigit(c)) {\n\t\t\t\tresult += c;\n\t\t\t\tdigitEncountered = true;\n\t\t\t} else if (digitEncountered) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate String getExportUrl(String url) {\n\t\tString idString = extractId(url);\n\t\tString result = \"http://www.hearthpwn.com/decks/{}/export/2\".replace(\"{}\", idString);\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic Deck importFrom(String url) {\n\t\ttry {\n\t\t\tRequestConfig globalConfig = RequestConfig.custom().setCircularRedirectsAllowed(true).build();\n\t\t\tCloseableHttpClient httpclient = HttpClientBuilder.create().build();\n\t\t\tString exportUrl = getExportUrl(url);\n\t\t\tlogger.debug(\"Requesting: \" + exportUrl);\n\n\t\t\tHttpGet httpGet = new HttpGet(exportUrl);\n\t\t\thttpGet.setConfig(globalConfig);\n\n\t\t\tCloseableHttpResponse response = httpclient.execute(httpGet);\n\n\t\t\ttry {\n\n\t\t\t\tHttpEntity entity = response.getEntity();\n\t\t\t\tString htmlContent = EntityUtils.toString(entity);\n\t\t\t\tEntityUtils.consume(entity);\n\t\t\t\treturn parse(htmlContent);\n\t\t\t} finally {\n\t\t\t\tresponse.close();\n\t\t\t}\n\n\t\t} catch (IOException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tprivate Deck parse(String htmlContent) {\n\t\tList<Card> cards = new ArrayList<Card>();\n\t\tHeroClass heroClass = HeroClass.ANY;\n\t\t// remove html tags\n\t\thtmlContent = htmlContent.replaceAll(\"\\\\<.+?\\\\>\", \"\");\n\t\t// remove BBCode tags\n\t\thtmlContent = htmlContent.replaceAll(\"\\\\[.+?\\\\]\", \"\");\n\t\t// remove empty lines\n\t\thtmlContent = htmlContent.replaceAll(\"(?m)^\\\\s+\", \"\");\n\t\t// unescape\n\t\thtmlContent = StringEscapeUtils.unescapeHtml4(htmlContent);\n\t\tString lines[] = htmlContent.split(\"\\\\r?\\\\n\");\n\t\tString deckName = lines[0];\n\t\tfor (String line : lines) {\n\t\t\tif (!line.startsWith(\"1\") && !line.startsWith(\"2\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tint count = Integer.parseInt(String.valueOf(line.charAt(0)));\n\t\t\tString cardName = line.substring(4);\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tCard card = CardCatalogue.getCardByName(cardName);\n\t\t\t\tif (card != null) {\n\t\t\t\t\tcards.add(card);\n\t\t\t\t\tif (card.getHeroClass() != HeroClass.ANY) {\n\t\t\t\t\t\theroClass = card.getHeroClass();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tlogger.error(\"Card with name {} could not be found\", cardName);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tDeck deck = new Deck(heroClass);\n\t\tdeck.setName(deckName);\n\t\tfor (Card card : cards) {\n\t\t\tdeck.getCards().add(card);\n\t\t}\n\t\tif (!deck.isComplete()) {\n\t\t\treturn null;\n\t\t}\n\t\treturn deck;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/importer/IDeckImporter.java",
    "content": "package net.demilich.metastone.gui.deckbuilder.importer;\n\nimport net.demilich.metastone.game.decks.Deck;\n\npublic interface IDeckImporter {\n\n\tDeck importFrom(String uri);\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/importer/IcyVeinsImporter.java",
    "content": "package net.demilich.metastone.gui.deckbuilder.importer;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.apache.http.HttpEntity;\nimport org.apache.http.client.config.RequestConfig;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.apache.http.util.EntityUtils;\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.select.Elements;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\n\npublic class IcyVeinsImporter implements IDeckImporter {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(IcyVeinsImporter.class);\n\n\t@Override\n\tpublic Deck importFrom(String url) {\n\t\ttry {\n\t\t\tRequestConfig globalConfig = RequestConfig.custom().setCircularRedirectsAllowed(true).build();\n\t\t\tCloseableHttpClient httpclient = HttpClientBuilder.create().build();\n\t\t\tString exportUrl = url;\n\t\t\tlogger.debug(\"Requesting: \" + exportUrl);\n\n\t\t\tHttpGet httpGet = new HttpGet(exportUrl);\n\t\t\thttpGet.setConfig(globalConfig);\n\n\t\t\tCloseableHttpResponse response = httpclient.execute(httpGet);\n\n\t\t\ttry {\n\n\t\t\t\tHttpEntity entity = response.getEntity();\n\t\t\t\tString htmlContent = EntityUtils.toString(entity);\n\t\t\t\tEntityUtils.consume(entity);\n\t\t\t\treturn parse(htmlContent);\n\t\t\t} finally {\n\t\t\t\tresponse.close();\n\t\t\t}\n\n\t\t} catch (IOException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tprivate Deck parse(String htmlContent) {\n\t\tList<Card> cards = new ArrayList<Card>();\n\t\tHeroClass heroClass = HeroClass.ANY;\n\t\t\n\t\tDocument doc = Jsoup.parse(htmlContent);\n\t\t\n\t\tString deckName = doc.getElementsByClass(\"page_breadcrumbs_item\").last().text();\n\t\tElements cardLines = doc.getElementsByClass(\"deck_card_list\").get(0).getElementsByTag(\"li\");\n\t\t\n\t\tfor (Element e: cardLines){\n\t\t\tif (!e.text().startsWith(\"1\") && !e.text().startsWith(\"2\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tint count = Integer.parseInt(String.valueOf(e.text().charAt(0)));\n\t\t\tString cardName = e.getElementsByTag(\"a\").get(0).text();\n\t\t\tfor (int i = 0; i < count; i++){\n\t\t\t\tCard card = CardCatalogue.getCardByName(cardName);\n\t\t\t\tif (card != null) {\n\t\t\t\t\tcards.add(card);\n\t\t\t\t\tif (card.getHeroClass() != HeroClass.ANY) {\n\t\t\t\t\t\theroClass = card.getHeroClass();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tlogger.error(\"Card with name {} could not be found\", cardName);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tDeck deck = new Deck(heroClass);\n\t\tdeck.setName(deckName);\n\t\tfor (Card card : cards) {\n\t\t\tdeck.getCards().add(card);\n\t\t\tlogger.debug(\"Card added - {}\", card.getName());\n\t\t}\n\t\tif (!deck.isComplete()) {\n\t\t\tlogger.error(\"Deck with name only has {}.\", deck.getCards().toList().size());\n\t\t\treturn null;\n\t\t}\n\t\treturn deck;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/importer/ImporterFactory.java",
    "content": "package net.demilich.metastone.gui.deckbuilder.importer;\n\npublic class ImporterFactory {\n\n    public IDeckImporter createDeckImporter(String url)\n    {\n        if(url == null)\n            return null;\n\n        if(url.contains(\"hearthpwn.com\"))\n            return new HearthPwnImporter();\n\n        if(url.contains(\"tempostorm.com\"))\n           return new TempostormImporter();\n        \n        if(url.contains(\"icy-veins.com\"))\n            return new IcyVeinsImporter();\n        \n        if(url.contains(\"hearthhead.com\"))\n            return new HearthHeadImporter();\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/importer/TempostormImporter.java",
    "content": "package net.demilich.metastone.gui.deckbuilder.importer;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonParser;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class TempostormImporter implements IDeckImporter{\n\n    private static Logger logger = LoggerFactory.getLogger(TempostormImporter.class);\n\n    Deck parse(JsonObject root)\n    {\n        try {\n            List<Card> cards = new ArrayList<Card>();\n            String deckName = root.get(\"name\").getAsString();\n            String hero = root.get(\"playerClass\").getAsString();\n            HeroClass heroClass = HeroClass.valueOf(hero.toUpperCase());\n            JsonElement cardsEl = root.get(\"cards\");\n            JsonArray cardsArray = cardsEl.getAsJsonArray();\n\n            for (JsonElement cardTypeElem : cardsArray) {\n                JsonObject cardTypeObj = cardTypeElem.getAsJsonObject();\n                int cardCount = cardTypeObj.get(\"cardQuantity\").getAsInt();\n                JsonObject cardObj = cardTypeObj.get(\"card\").getAsJsonObject();\n                String cardName = cardObj.get(\"name\").getAsString();\n\n                Card card = CardCatalogue.getCardByName(cardName);\n\n                if (card != null) {\n                    if(cardCount > 0)\n                        cards.add(card);\n\n                    for (int i = 1; i < cardCount; i++)\n                        cards.add(card.clone());\n                } else {\n                    logger.error(\"Card with name {} could not be found\", cardName);\n                    return null;\n                }\n            }\n\n            Deck deck = new Deck(heroClass);\n            deck.setName(deckName);\n\n            for (Card card : cards)\n                deck.getCards().add(card);\n\n            if (!deck.isComplete())\n                return null;\n\n            return deck;\n        }\n        catch(Exception e)\n        {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    String convertUrl(String url)\n    {\n        Pattern pattern = Pattern.compile(\".*/decks/([^/]+)$\");\n        Matcher matcher = pattern.matcher(url);\n\n        if(!matcher.matches())\n            return null;\n\n        String identifier = matcher.group(1);\n        String filter = \"{\\\"where\\\":{\\\"slug\\\":\\\"\" + identifier\n                + \"\\\"},\\\"fields\\\":{},\\\"include\\\":[{\\\"relation\\\":\\\"cards\\\",\\\"scope\\\":{\\\"include\\\":[\\\"card\\\"]}}]}\";\n        return \"https://tempostorm.com/api/decks/findOne?filter=\" + filter;\n    }\n    @Override\n    public Deck importFrom(String requestedUrl) {\n\n        String apiUrl = convertUrl(requestedUrl);\n        logger.debug(\"Requesting: \" + apiUrl);\n\n        URL url;\n        try {\n            url = new URL(apiUrl);\n        } catch (MalformedURLException e) {\n            e.printStackTrace();\n            return null;\n        }\n\n        HttpURLConnection request;\n\n        try {\n            request = (HttpURLConnection) url.openConnection();\n            request.connect();\n\n            JsonParser jp = new JsonParser();\n            JsonElement root = jp.parse(new InputStreamReader((InputStream) request.getContent()));\n            JsonObject jobj = root.getAsJsonObject();\n            return parse(jobj);\n\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/metadeck/AddDeckToMetaDeckCommand.java",
    "content": "package net.demilich.metastone.gui.deckbuilder.metadeck;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.MetaDeck;\nimport net.demilich.metastone.gui.deckbuilder.DeckProxy;\n\npublic class AddDeckToMetaDeckCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tDeckProxy deckProxy = (DeckProxy) getFacade().retrieveProxy(DeckProxy.NAME);\n\t\tMetaDeck metaDeck = (MetaDeck) deckProxy.getActiveDeck();\n\n\t\tDeck deck = (Deck) notification.getBody();\n\t\tif (metaDeck.getDecks().contains(deck)) {\n\t\t\treturn;\n\t\t}\n\n\t\tmetaDeck.getDecks().add(deck);\n\t\tgetFacade().sendNotification(GameNotification.ACTIVE_DECK_CHANGED, deckProxy.getActiveDeck());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/metadeck/MetaDeckListView.java",
    "content": "package net.demilich.metastone.gui.deckbuilder.metadeck;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport javafx.event.EventHandler;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.VBox;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.gui.deckbuilder.DeckEntry;\n\npublic class MetaDeckListView extends VBox implements EventHandler<MouseEvent> {\n\n\t@FXML\n\tprivate Button newDeckButton;\n\n\tpublic MetaDeckListView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/MetaDeckListView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tsetCache(true);\n\t}\n\n\tpublic void displayDecks(List<Deck> decks) {\n\t\tgetChildren().clear();\n\t\tfor (Deck deck : decks) {\n\t\t\tDeckEntry deckEntry = new DeckEntry();\n\t\t\tdeckEntry.setDeck(deck);\n\t\t\tdeckEntry.addEventHandler(MouseEvent.MOUSE_CLICKED, this);\n\t\t\tgetChildren().add(deckEntry);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void handle(MouseEvent event) {\n\t\tDeckEntry deckEntry = (DeckEntry) event.getSource();\n\t\tNotificationProxy.sendNotification(GameNotification.REMOVE_DECK_FROM_META_DECK, deckEntry.getDeck());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/metadeck/MetaDeckView.java",
    "content": "package net.demilich.metastone.gui.deckbuilder.metadeck;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ContentDisplay;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.Pane;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.MetaDeck;\nimport net.demilich.metastone.gui.IconFactory;\n\npublic class MetaDeckView extends BorderPane {\n\n\t@FXML\n\tprivate Pane contentPane;\n\n\tpublic MetaDeckView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/MetaDeckView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tsetCache(true);\n\t}\n\n\tpublic void deckChanged(MetaDeck metaDeck) {\n\t\tfor (Node node : contentPane.getChildren()) {\n\t\t\tDeck deck = (Deck) node.getUserData();\n\t\t\tnode.setDisable(metaDeck.getDecks().contains(deck));\n\t\t}\n\t}\n\n\tpublic void displayDecks(List<Deck> decks) {\n\t\tcontentPane.getChildren().clear();\n\t\tfor (Deck deck : decks) {\n\t\t\tif (deck.isMetaDeck()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tImageView graphic = new ImageView(IconFactory.getClassIcon(deck.getHeroClass()));\n\t\t\tgraphic.setFitWidth(48);\n\t\t\tgraphic.setFitHeight(48);\n\t\t\tButton deckButton = new Button(deck.getName(), graphic);\n\t\t\tdeckButton.setMaxWidth(160);\n\t\t\tdeckButton.setMinWidth(160);\n\t\t\tdeckButton.setMaxHeight(120);\n\t\t\tdeckButton.setMinHeight(120);\n\t\t\tdeckButton.setWrapText(true);\n\t\t\tdeckButton.setContentDisplay(ContentDisplay.LEFT);\n\t\t\tdeckButton.setOnAction(event -> NotificationProxy.sendNotification(GameNotification.ADD_DECK_TO_META_DECK, deck));\n\t\t\tdeckButton.setUserData(deck);\n\t\t\tcontentPane.getChildren().add(deckButton);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/deckbuilder/metadeck/RemoveDeckFromMetaDeckCommand.java",
    "content": "package net.demilich.metastone.gui.deckbuilder.metadeck;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.MetaDeck;\nimport net.demilich.metastone.gui.deckbuilder.DeckProxy;\n\npublic class RemoveDeckFromMetaDeckCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tDeckProxy deckProxy = (DeckProxy) getFacade().retrieveProxy(DeckProxy.NAME);\n\t\tMetaDeck metaDeck = (MetaDeck) deckProxy.getActiveDeck();\n\n\t\tDeck deck = (Deck) notification.getBody();\n\t\tif (!metaDeck.getDecks().contains(deck)) {\n\t\t\treturn;\n\t\t}\n\n\t\tmetaDeck.getDecks().remove(deck);\n\t\tgetFacade().sendNotification(GameNotification.ACTIVE_DECK_CHANGED, deckProxy.getActiveDeck());\n\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/dialog/DialogMediator.java",
    "content": "package net.demilich.metastone.gui.dialog;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.nittygrittymvc.Mediator;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport javafx.scene.Node;\nimport javafx.scene.control.Alert;\nimport javafx.scene.control.Alert.AlertType;\nimport javafx.scene.layout.Pane;\nimport javafx.stage.Window;\nimport net.demilich.metastone.GameNotification;\n\npublic class DialogMediator extends Mediator<GameNotification> {\n\n\tpublic static final String NAME = \"DialogMediator\";\n\n\tprivate static Logger logger = LoggerFactory.getLogger(DialogMediator.class);\n\n\tprivate Window root;\n\n\tpublic DialogMediator() {\n\t\tsuper(NAME);\n\t}\n\n\t@Override\n\tpublic void handleNotification(final INotification<GameNotification> notification) {\n\t\tswitch (notification.getId()) {\n\t\tcase CANVAS_CREATED:\n\t\t\tPane canvas = (Pane) notification.getBody();\n\t\t\troot = canvas.getScene().getWindow();\n\t\t\tbreak;\n\t\tcase SHOW_USER_DIALOG:\n\t\t\tshowUserDialog((DialogNotification) notification);\n\t\t\tbreak;\n\t\tcase SHOW_MODAL_DIALOG:\n\t\t\tshowModalDialog((Node) notification.getBody());\n\t\t\tbreak;\n\t\tcase CARD_PARSE_ERROR:\n\t\t\tdisplayErrorMessage(\"Something is wrong with your card files\", (String) notification.getBody());\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tlogger.warn(\"Unhandled notification {} in {}\", notification, getClass().getSimpleName());\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t@Override\n\tpublic List<GameNotification> listNotificationInterests() {\n\t\tList<GameNotification> notificationInterests = new ArrayList<GameNotification>();\n\t\tnotificationInterests.add(GameNotification.CANVAS_CREATED);\n\t\tnotificationInterests.add(GameNotification.SHOW_MODAL_DIALOG);\n\t\tnotificationInterests.add(GameNotification.SHOW_USER_DIALOG);\n\t\tnotificationInterests.add(GameNotification.CARD_PARSE_ERROR);\n\t\treturn notificationInterests;\n\t}\n\t\n\tprivate void displayErrorMessage(String header, String message) {\n\t\tAlert alert = new Alert(AlertType.ERROR);\n\t\talert.setTitle(\"Error\");\n\t\talert.setHeaderText(header);\n\t\talert.setContentText(message);\n\t\talert.showAndWait();\n\t}\n\n\tprivate void showModalDialog(Node content) {\n\t\tnew ModalDialog(root, content);\n\t}\n\n\tprivate void showUserDialog(DialogNotification notification) {\n\t\tUserDialog userDialog = new UserDialog(notification.getTitle(), notification.getMessage(), notification.getDialogType());\n\t\tuserDialog.setDialogHandler(notification.getHandler());\n\t\tshowModalDialog(userDialog);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/dialog/DialogNotification.java",
    "content": "package net.demilich.metastone.gui.dialog;\n\nimport net.demilich.nittygrittymvc.Notification;\nimport net.demilich.metastone.GameNotification;\n\npublic class DialogNotification extends Notification<GameNotification> {\n\n\tprivate final String title;\n\tprivate final String message;\n\tprivate final DialogType dialogType;\n\tprivate IDialogListener handler;\n\n\tpublic DialogNotification(String title, String message, DialogType dialogType) {\n\t\tsuper(GameNotification.SHOW_USER_DIALOG);\n\t\tthis.title = title;\n\t\tthis.message = message;\n\t\tthis.dialogType = dialogType;\n\t}\n\n\tpublic DialogType getDialogType() {\n\t\treturn dialogType;\n\t}\n\n\tpublic IDialogListener getHandler() {\n\t\treturn handler;\n\t}\n\n\tpublic String getMessage() {\n\t\treturn message;\n\t}\n\n\tpublic String getTitle() {\n\t\treturn title;\n\t}\n\n\tpublic void setHandler(IDialogListener handler) {\n\t\tthis.handler = handler;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/dialog/DialogResult.java",
    "content": "package net.demilich.metastone.gui.dialog;\n\npublic enum DialogResult {\n\n\tOK, CANCEL\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/dialog/DialogType.java",
    "content": "package net.demilich.metastone.gui.dialog;\n\npublic enum DialogType {\n\tCONFIRM, INFO, WARNING, ERROR,\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/dialog/IDialogListener.java",
    "content": "package net.demilich.metastone.gui.dialog;\n\npublic interface IDialogListener {\n\n\tvoid onDialogClosed(DialogResult result);\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/dialog/ModalDialog.java",
    "content": "package net.demilich.metastone.gui.dialog;\n\nimport javafx.scene.Node;\nimport javafx.scene.Scene;\nimport javafx.scene.layout.StackPane;\nimport javafx.stage.Modality;\nimport javafx.stage.Stage;\nimport javafx.stage.StageStyle;\nimport javafx.stage.Window;\n\npublic class ModalDialog extends StackPane {\n\n\tpublic ModalDialog(Window parent, Node content) {\n\n\t\tStage stage = new Stage();\n\t\tScene scene = new Scene(this);\n\t\tscene.setFill(null);\n\t\tstage.setScene(scene);\n\t\tstage.initModality(Modality.WINDOW_MODAL);\n\t\tstage.initStyle(StageStyle.TRANSPARENT);\n\t\tstage.initOwner(parent);\n\t\tstage.setX(parent.getX());\n\t\tstage.setY(parent.getY());\n\n\t\tsetPrefSize(parent.getWidth(), parent.getHeight());\n\t\tsetStyle(\"-fx-background-color: rgba(0, 0, 0, 0.5);\");\n\t\tgetChildren().add(content);\n\n\t\tstage.show();\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/dialog/UserDialog.java",
    "content": "package net.demilich.metastone.gui.dialog;\n\nimport java.io.IOException;\n\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.layout.BorderPane;\nimport net.demilich.metastone.gui.IconFactory;\n\npublic class UserDialog extends BorderPane implements EventHandler<ActionEvent> {\n\n\t@FXML\n\tprivate Label headerLabel;\n\n\t@FXML\n\tprivate Label textLabel;\n\n\t@FXML\n\tprivate ImageView icon;\n\n\t@FXML\n\tprivate Button positiveButton;\n\n\t@FXML\n\tprivate Button negativeButton;\n\n\tprivate IDialogListener dialogHandler;\n\n\tpublic UserDialog(String title, String message, DialogType dialogType) {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/UserDialog.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\ticon.setImage(IconFactory.getDialogIcon(dialogType));\n\t\theaderLabel.setText(title);\n\t\ttextLabel.setText(message);\n\n\t\tpositiveButton.setOnAction(this);\n\t\tnegativeButton.setOnAction(this);\n\t}\n\n\t@Override\n\tpublic void handle(ActionEvent event) {\n\t\tif (event.getSource() == positiveButton) {\n\t\t\tsetDialogResult(DialogResult.OK);\n\t\t} else if (event.getSource() == negativeButton) {\n\t\t\tsetDialogResult(DialogResult.CANCEL);\n\t\t}\n\t}\n\n\tpublic void setDialogHandler(IDialogListener dialogHandler) {\n\t\tthis.dialogHandler = dialogHandler;\n\t}\n\n\tprivate void setDialogResult(DialogResult result) {\n\t\tif (dialogHandler != null) {\n\t\t\tdialogHandler.onDialogClosed(result);\n\t\t}\n\t\tthis.getScene().getWindow().hide();\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/gameconfig/PlayerConfigView.java",
    "content": "package net.demilich.metastone.gui.gameconfig;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.CheckBox;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.Label;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.layout.VBox;\nimport net.demilich.metastone.game.behaviour.GreedyOptimizeMove;\nimport net.demilich.metastone.game.behaviour.IBehaviour;\nimport net.demilich.metastone.game.behaviour.NoAggressionBehaviour;\nimport net.demilich.metastone.game.behaviour.PlayRandomBehaviour;\nimport net.demilich.metastone.game.behaviour.heuristic.WeightedHeuristic;\nimport net.demilich.metastone.game.behaviour.human.HumanBehaviour;\nimport net.demilich.metastone.game.behaviour.threat.GameStateValueBehaviour;\nimport net.demilich.metastone.game.behaviour.FlatMonteCarlo;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.HeroCard;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFactory;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.heroes.MetaHero;\nimport net.demilich.metastone.game.gameconfig.PlayerConfig;\nimport net.demilich.metastone.gui.IconFactory;\nimport net.demilich.metastone.gui.common.BehaviourStringConverter;\nimport net.demilich.metastone.gui.common.DeckStringConverter;\nimport net.demilich.metastone.gui.common.HeroStringConverter;\nimport net.demilich.metastone.gui.playmode.config.PlayerConfigType;\n\npublic class PlayerConfigView extends VBox {\n\n\t@FXML\n\tprotected Label heroNameLabel;\n\n\t@FXML\n\tprotected ImageView heroIcon;\n\n\t@FXML\n\tprotected ComboBox<IBehaviour> behaviourBox;\n\n\t@FXML\n\tprotected ComboBox<HeroCard> heroBox;\n\n\t@FXML\n\tprotected ComboBox<Deck> deckBox;\n\n\t@FXML\n\tprotected CheckBox hideCardsCheckBox;\n\n\tprivate final PlayerConfig playerConfig = new PlayerConfig();\n\n\tprivate List<Deck> decks = new ArrayList<Deck>();\n\n\tprivate PlayerConfigType selectionHint;\n\n\tprivate DeckFormat deckFormat;\n\n\tpublic PlayerConfigView(PlayerConfigType selectionHint) {\n\t\tthis.selectionHint = selectionHint;\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/PlayerConfigView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\theroBox.setConverter(new HeroStringConverter());\n\t\tdeckBox.setConverter(new DeckStringConverter());\n\t\tbehaviourBox.setConverter(new BehaviourStringConverter());\n\n\t\tsetupHideCardsBox(selectionHint);\n\t\tsetupHeroes();\n\t\tsetupBehaviours();\n\t\tdeckBox.valueProperty().addListener((ChangeListener<Deck>) (observableProperty, oldDeck, newDeck) -> {\n\t\t\tgetPlayerConfig().setDeck(newDeck);\n\t\t});\n\t}\n\n\tprivate void filterDecks() {\n\t\tHeroClass heroClass = getPlayerConfig().getHeroCard().getHeroClass();\n\t\tObservableList<Deck> deckList = FXCollections.observableArrayList();\n\n\t\tif (heroClass == HeroClass.DECK_COLLECTION) {\n\t\t\tfor (Deck deck : decks) {\n\t\t\t\tif (deck.getHeroClass() != HeroClass.DECK_COLLECTION) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (deckFormat != null && deckFormat.isInFormat(deck)) {\n\t\t\t\t\tdeckList.add(deck);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tDeck randomDeck = DeckFactory.getRandomDeck(heroClass, deckFormat);\n\t\t\tdeckList.add(randomDeck);\n\t\t\tfor (Deck deck : decks) {\n\t\t\t\tif (deck.getHeroClass() == HeroClass.DECK_COLLECTION) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (deck.getHeroClass() == heroClass || deck.getHeroClass() == HeroClass.ANY) {\n\t\t\t\t\tif (deckFormat != null && deckFormat.isInFormat(deck)) {\n\t\t\t\t\t\tdeckList.add(deck);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tdeckBox.setItems(deckList);\n\t\tdeckBox.getSelectionModel().selectFirst();\n\t}\n\n\tpublic PlayerConfig getPlayerConfig() {\n\t\treturn playerConfig;\n\t}\n\n\tpublic void injectDecks(List<Deck> decks) {\n\t\tthis.decks = decks;\n\t\theroBox.getSelectionModel().selectFirst();\n\t\tbehaviourBox.getSelectionModel().selectFirst();\n\t}\n\n\tprivate void onBehaviourChanged(ObservableValue<? extends IBehaviour> ov, IBehaviour oldBehaviour, IBehaviour newBehaviour) {\n\t\tgetPlayerConfig().setBehaviour(newBehaviour);\n\t\tboolean humanBehaviourSelected = newBehaviour instanceof HumanBehaviour;\n\t\thideCardsCheckBox.setDisable(humanBehaviourSelected);\n\t\tif (humanBehaviourSelected) {\n\t\t\thideCardsCheckBox.setSelected(false);\n\t\t}\n\t}\n\n\tprivate void onHideCardBoxChanged(ObservableValue<? extends Boolean> ov, Boolean oldValue, Boolean newValue) {\n\t\tplayerConfig.setHideCards(newValue);\n\t}\n\n\tprivate void selectHero(HeroCard heroCard) {\n\t\tImage heroPortrait = new Image(IconFactory.getHeroIconUrl(heroCard.getHeroClass()));\n\t\theroIcon.setImage(heroPortrait);\n\t\theroNameLabel.setText(heroCard.getName());\n\t\tgetPlayerConfig().setHeroCard(heroCard);\n\t\tfilterDecks();\n\t}\n\n\tpublic void setupBehaviours() {\n\t\tObservableList<IBehaviour> behaviourList = FXCollections.observableArrayList();\n\t\tif (selectionHint == PlayerConfigType.HUMAN || selectionHint == PlayerConfigType.SANDBOX) {\n\t\t\tbehaviourList.add(new HumanBehaviour());\n\t\t}\n\n\t\tbehaviourList.add(new GameStateValueBehaviour());\n\n\t\tif (selectionHint == PlayerConfigType.OPPONENT) {\n\t\t\tbehaviourList.add(new HumanBehaviour());\n\t\t}\n\n\t\tbehaviourList.add(new PlayRandomBehaviour());\n\n\t\tbehaviourList.add(new GreedyOptimizeMove(new WeightedHeuristic()));\n\t\tbehaviourList.add(new NoAggressionBehaviour());\n\t\tbehaviourList.add(new FlatMonteCarlo(100));\n\n\t\tbehaviourBox.setItems(behaviourList);\n\t\tbehaviourBox.valueProperty().addListener(this::onBehaviourChanged);\n\t}\n\n\tpublic void setupHeroes() {\n\t\tObservableList<HeroCard> heroList = FXCollections.observableArrayList();\n\t\tfor (Card card : CardCatalogue.getHeroes()) {\n\t\t\theroList.add((HeroCard) card);\n\t\t}\n\n\t\theroList.add(new MetaHero());\n\n\t\theroBox.setItems(heroList);\n\t\theroBox.valueProperty().addListener((ChangeListener<HeroCard>) (observableValue, oldHero, newHero) -> {\n\t\t\tselectHero(newHero);\n\t\t});\n\t}\n\n\tprivate void setupHideCardsBox(PlayerConfigType configType) {\n\t\thideCardsCheckBox.selectedProperty().addListener(this::onHideCardBoxChanged);\n\t\thideCardsCheckBox.setSelected(selectionHint == PlayerConfigType.OPPONENT);\n\t\tif (configType == PlayerConfigType.SIMULATION || configType == PlayerConfigType.SANDBOX) {\n\t\t\thideCardsCheckBox.setVisible(false);\n\t\t}\n\t}\n\n\tpublic void setDeckFormat(DeckFormat newDeckFormat) {\n\t\tdeckFormat = newDeckFormat;\n\t\tfilterDecks();\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/main/ApplicationMediator.java",
    "content": "package net.demilich.metastone.gui.main;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport javafx.scene.Node;\nimport javafx.scene.layout.Pane;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.gui.battleofdecks.BattleOfDecksMediator;\nimport net.demilich.metastone.gui.deckbuilder.DeckBuilderMediator;\nimport net.demilich.metastone.gui.mainmenu.MainMenuMediator;\nimport net.demilich.metastone.gui.playmode.PlayModeMediator;\nimport net.demilich.metastone.gui.playmode.config.PlayModeConfigMediator;\nimport net.demilich.metastone.gui.sandboxmode.SandboxModeMediator;\nimport net.demilich.metastone.gui.simulationmode.SimulationMediator;\nimport net.demilich.metastone.gui.trainingmode.TrainingModeMediator;\nimport net.demilich.nittygrittymvc.Mediator;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\n\npublic class ApplicationMediator extends Mediator<GameNotification> {\n\n\tpublic static final String NAME = \"ApplicationMediator\";\n\n\tprivate Pane root;\n\n\tpublic ApplicationMediator() {\n\t\tsuper(NAME);\n\t}\n\n\t@Override\n\tpublic void handleNotification(final INotification<GameNotification> notification) {\n\t\tswitch (notification.getId()) {\n\t\tcase CANVAS_CREATED:\n\t\t\troot = (Pane) notification.getBody();\n\t\t\tbreak;\n\t\tcase SHOW_VIEW:\n\t\t\tfinal Node view = (Node) notification.getBody();\n\t\t\troot.getChildren().clear();\n\t\t\troot.getChildren().add(view);\n\t\t\tbreak;\n\t\tcase MAIN_MENU:\n\t\t\tremoveOtherViews();\n\t\t\tgetFacade().registerMediator(new MainMenuMediator());\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t@Override\n\tpublic List<GameNotification> listNotificationInterests() {\n\t\tList<GameNotification> notificationInterests = new ArrayList<GameNotification>();\n\t\tnotificationInterests.add(GameNotification.CANVAS_CREATED);\n\t\tnotificationInterests.add(GameNotification.SHOW_VIEW);\n\t\tnotificationInterests.add(GameNotification.MAIN_MENU);\n\t\tnotificationInterests.add(GameNotification.CARD_PARSE_ERROR);\n\t\treturn notificationInterests;\n\t}\n\t\n\tprivate void removeOtherViews() {\n\t\tgetFacade().removeMediator(PlayModeMediator.NAME);\n\t\tgetFacade().removeMediator(PlayModeConfigMediator.NAME);\n\t\tgetFacade().removeMediator(DeckBuilderMediator.NAME);\n\t\tgetFacade().removeMediator(SimulationMediator.NAME);\n\t\tgetFacade().removeMediator(TrainingModeMediator.NAME);\n\t\tgetFacade().removeMediator(SandboxModeMediator.NAME);\n\t\tgetFacade().removeMediator(BattleOfDecksMediator.NAME);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/mainmenu/MainMenuMediator.java",
    "content": "package net.demilich.metastone.gui.mainmenu;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.nittygrittymvc.Mediator;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.gui.battleofdecks.BattleOfDecksMediator;\nimport net.demilich.metastone.gui.deckbuilder.DeckBuilderMediator;\nimport net.demilich.metastone.gui.playmode.config.PlayModeConfigMediator;\nimport net.demilich.metastone.gui.sandboxmode.SandboxModeMediator;\nimport net.demilich.metastone.gui.simulationmode.SimulationMediator;\nimport net.demilich.metastone.gui.trainingmode.TrainingModeMediator;\n\npublic class MainMenuMediator extends Mediator<GameNotification> {\n\n\tpublic static final String NAME = \"MainMenuMediator\";\n\n\tprivate final MainMenuView view;\n\n\tpublic MainMenuMediator() {\n\t\tsuper(NAME);\n\t\tview = new MainMenuView();\n\t}\n\n\t@Override\n\tpublic void handleNotification(final INotification<GameNotification> notification) {\n\t\tswitch (notification.getId()) {\n\t\tcase DECK_BUILDER_SELECTED:\n\t\t\tgetFacade().registerMediator(new DeckBuilderMediator());\n\t\t\tbreak;\n\t\tcase PLAY_MODE_SELECTED:\n\t\t\tgetFacade().registerMediator(new PlayModeConfigMediator());\n\t\t\tbreak;\n\t\tcase SIMULATION_MODE_SELECTED:\n\t\t\tgetFacade().registerMediator(new SimulationMediator());\n\t\t\tbreak;\n\t\tcase SANDBOX_MODE_SELECTED:\n\t\t\tgetFacade().registerMediator(new SandboxModeMediator());\n\t\t\tbreak;\n\t\tcase TRAINING_MODE_SELECTED:\n\t\t\tgetFacade().registerMediator(new TrainingModeMediator());\n\t\t\tbreak;\n\t\tcase BATTLE_OF_DECKS_SELECTED:\n\t\t\tgetFacade().registerMediator(new BattleOfDecksMediator());\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t\tgetFacade().removeMediator(MainMenuMediator.NAME);\n\t}\n\n\t@Override\n\tpublic List<GameNotification> listNotificationInterests() {\n\t\tList<GameNotification> notificationInterests = new ArrayList<GameNotification>();\n\t\tnotificationInterests.add(GameNotification.DECK_BUILDER_SELECTED);\n\t\tnotificationInterests.add(GameNotification.PLAY_MODE_SELECTED);\n\t\tnotificationInterests.add(GameNotification.SIMULATION_MODE_SELECTED);\n\t\tnotificationInterests.add(GameNotification.SANDBOX_MODE_SELECTED);\n\t\tnotificationInterests.add(GameNotification.TRAINING_MODE_SELECTED);\n\t\tnotificationInterests.add(GameNotification.BATTLE_OF_DECKS_SELECTED);\n\t\treturn notificationInterests;\n\t}\n\n\t@Override\n\tpublic void onRegister() {\n\t\tgetFacade().sendNotification(GameNotification.SHOW_VIEW, view);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/mainmenu/MainMenuView.java",
    "content": "package net.demilich.metastone.gui.mainmenu;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\nimport javafx.event.ActionEvent;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.BorderPane;\nimport net.demilich.metastone.BuildConfig;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\n\npublic class MainMenuView extends BorderPane {\n\n\t@FXML\n\tprivate Button deckBuilderButton;\n\n\t@FXML\n\tprivate Button playModeButton;\n\n\t@FXML\n\tprivate Button simulationModeButton;\n\n\t@FXML\n\tprivate Button sandboxModeButton;\n\n\t@FXML\n\tprivate Button trainingModeButton;\n\n\t@FXML\n\tprivate Button battleOfDecksButton;\n\n\t@FXML\n\tprivate Label versionLabel;\n\n\t@FXML\n\tprivate Button donationButton;\n\n\tpublic MainMenuView() {\n\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/MainMenuView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tdeckBuilderButton.setOnAction(event -> NotificationProxy.sendNotification(GameNotification.DECK_BUILDER_SELECTED));\n\n\t\tplayModeButton.setOnAction(event -> NotificationProxy.sendNotification(GameNotification.PLAY_MODE_SELECTED));\n\n\t\tsimulationModeButton\n\t\t\t\t.setOnAction(event -> NotificationProxy.sendNotification(GameNotification.SIMULATION_MODE_SELECTED));\n\n\t\tsandboxModeButton.setOnAction(event -> NotificationProxy.sendNotification(GameNotification.SANDBOX_MODE_SELECTED));\n\n\t\ttrainingModeButton.setOnAction(event -> NotificationProxy.sendNotification(GameNotification.TRAINING_MODE_SELECTED));\n\n\t\tbattleOfDecksButton\n\t\t\t\t.setOnAction(event -> NotificationProxy.sendNotification(GameNotification.BATTLE_OF_DECKS_SELECTED));\n\n\t\tif (!BuildConfig.DEV_BUILD) {\n\t\t\ttrainingModeButton.setVisible(false);\n\t\t\ttrainingModeButton.setManaged(false);\n\t\t\tbattleOfDecksButton.setVisible(false);\n\t\t\tbattleOfDecksButton.setManaged(false);\n\t\t}\n\n\t\tversionLabel.setText(BuildConfig.VERSION + (BuildConfig.DEV_BUILD ? \" (Dev build)\" : \"\"));\n\n\t\tdonationButton.setOnAction(this::openDonation);\n\t}\n\n\tprivate void openDonation(ActionEvent event) {\n\t\ttry {\n\t\t\tjava.awt.Desktop.getDesktop()\n\t\t\t\t\t.browse(new URI(\"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=92DYWPZUVDMEY\"));\n\t\t} catch (IOException e) {\n\t\t\te.printStackTrace();\n\t\t} catch (URISyntaxException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/GameBoardView.java",
    "content": "package net.demilich.metastone.gui.playmode;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.List;\n\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.ActionType;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.behaviour.human.HumanTargetOptions;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Summon;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.gui.IconFactory;\nimport net.demilich.metastone.gui.cards.HandCard;\nimport net.demilich.metastone.gui.playmode.animation.EventVisualizerDispatcher;\n\npublic class GameBoardView extends BorderPane {\n\n\t@FXML\n\tprivate HBox p1CardPane;\n\t@FXML\n\tprivate HBox p2CardPane;\n\n\t@FXML\n\tprivate HBox p1MinionPane;\n\t@FXML\n\tprivate HBox p2MinionPane;\n\n\t@FXML\n\tprivate VBox p1HeroAnchor;\n\t@FXML\n\tprivate VBox p2HeroAnchor;\n\n\t@FXML\n\tprivate HBox centerMessageArea;\n\n\tprivate HeroToken p1Hero;\n\tprivate HeroToken p2Hero;\n\tprivate HandCard[] p1Cards = new HandCard[GameLogic.MAX_HAND_CARDS];\n\tprivate HandCard[] p2Cards = new HandCard[GameLogic.MAX_HAND_CARDS];\n\tprivate SummonToken[] p1Minions = new SummonToken[GameLogic.MAX_MINIONS];\n\tprivate SummonToken[] p2Minions = new SummonToken[GameLogic.MAX_MINIONS];\n\n\tprivate final HashMap<GameToken, Button> summonHelperMap1 = new HashMap<GameToken, Button>();\n\tprivate final HashMap<GameToken, Button> summonHelperMap2 = new HashMap<GameToken, Button>();\n\tprivate final HashMap<Actor, GameToken> entityTokenMap = new HashMap<Actor, GameToken>();\n\n\tprivate final EventVisualizerDispatcher gameEventVisualizer = new EventVisualizerDispatcher();\n\n\t@FXML\n\tprivate Label centerMessageLabel;\n\n\tpublic GameBoardView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/GameBoardView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\t// initialize card ui elements\n\t\tfor (int i = 0; i < p1Cards.length; i++) {\n\t\t\tp1Cards[i] = new HandCard();\n\t\t\tp1Cards[i].setVisible(false);\n\t\t\tp2Cards[i] = new HandCard();\n\t\t\tp2Cards[i].setVisible(false);\n\t\t}\n\t\tp1CardPane.getChildren().addAll(p1Cards);\n\t\tp2CardPane.getChildren().addAll(p2Cards);\n\n\t\t// initialize minion tokens elements\n\t\tfor (int i = 0; i < p1Minions.length; i++) {\n\t\t\tButton summonHelper = createSummonHelper();\n\t\t\tp1MinionPane.getChildren().add(summonHelper);\n\t\t\tp1Minions[i] = new SummonToken();\n\t\t\tp1MinionPane.getChildren().add(p1Minions[i]);\n\t\t\tsummonHelperMap1.put(p1Minions[i], summonHelper);\n\n\t\t\tsummonHelper = createSummonHelper();\n\t\t\tp2MinionPane.getChildren().add(summonHelper);\n\t\t\tp2Minions[i] = new SummonToken();\n\t\t\tp2MinionPane.getChildren().add(p2Minions[i]);\n\t\t\tsummonHelperMap2.put(p2Minions[i], summonHelper);\n\t\t}\n\t\t// create one additional summon helper (for each player)\n\t\tButton summonHelper = createSummonHelper();\n\t\tp1MinionPane.getChildren().add(summonHelper);\n\t\tsummonHelperMap1.put(null, summonHelper);\n\n\t\tsummonHelper = createSummonHelper();\n\t\tp2MinionPane.getChildren().add(summonHelper);\n\t\tsummonHelperMap2.put(null, summonHelper);\n\n\t\tp1Hero = new HeroToken();\n\t\tp2Hero = new HeroToken();\n\n\t\tp1HeroAnchor.getChildren().add(p1Hero);\n\t\tp2HeroAnchor.getChildren().add(p2Hero);\n\t}\n\n\tprivate void checkForWinner(GameContext context) {\n\t\tif (context.gameDecided()) {\n\t\t\tif (context.getWinningPlayerId() == -1) {\n\t\t\t\tcenterMessageLabel.setStyle(\"-fx-text-fill: red;\");\n\t\t\t\tsetCenterMessage(\"Game has ended in a draw.\");\n\t\t\t} else {\n\t\t\t\tcenterMessageLabel.setStyle(\"-fx-text-fill: green;\");\n\t\t\t\tPlayer winner = context.getPlayer(context.getWinningPlayerId());\n\t\t\t\tsetCenterMessage(\"Player \" + winner.getName() + \" has won the game.\");\n\t\t\t}\n\t\t}\n\n\t}\n\n\tprivate Button createSummonHelper() {\n\t\tImageView icon = new ImageView(IconFactory.getSummonHelper());\n\t\ticon.setFitWidth(32);\n\t\ticon.setFitHeight(32);\n\t\tButton helper = new Button(\"\", icon);\n\t\thelper.setStyle(\"-fx-padding: 2 2 2 2;\");\n\t\thelper.setVisible(false);\n\t\thelper.setManaged(false);\n\t\treturn helper;\n\t}\n\n\tpublic void disableTargetSelection() {\n\t\tfor (GameToken token : entityTokenMap.values()) {\n\t\t\ttoken.hideTargetMarker();\n\t\t}\n\t\tfor (Button summonHelper : summonHelperMap1.values()) {\n\t\t\tsummonHelper.setVisible(false);\n\t\t\tsummonHelper.setManaged(false);\n\t\t}\n\t\tfor (Button summonHelper : summonHelperMap2.values()) {\n\t\t\tsummonHelper.setVisible(false);\n\t\t\tsummonHelper.setManaged(false);\n\t\t}\n\t\thideCenterMessage();\n\t}\n\n\tprivate void enableSpellTargets(final HumanTargetOptions targetOptions) {\n\t\tGameContext context = targetOptions.getContext();\n\n\t\tfor (final GameAction action : targetOptions.getActionGroup().getActionsInGroup()) {\n\t\t\tEntity target = context.resolveSingleTarget(action.getTargetKey());\n\t\t\tGameToken token = getToken(target);\n\n\t\t\tEventHandler<MouseEvent> clickedHander = new EventHandler<MouseEvent>() {\n\n\t\t\t\t@Override\n\t\t\t\tpublic void handle(MouseEvent event) {\n\t\t\t\t\tdisableTargetSelection();\n\t\t\t\t\ttargetOptions.getActionSelectionListener().onActionSelected(action);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\ttoken.showTargetMarker(clickedHander);\n\t\t}\n\t}\n\n\tprivate void enableSummonTargets(final HumanTargetOptions targetOptions) {\n\t\tint playerId = targetOptions.getPlayerId();\n\t\tGameContext context = targetOptions.getContext();\n\t\tfor (final GameAction action : targetOptions.getActionGroup().getActionsInGroup()) {\n\t\t\tEntity target = context.resolveSingleTarget(action.getTargetKey());\n\t\t\tGameToken token = getToken(target);\n\t\t\tButton summonHelper = playerId == 0 ? summonHelperMap1.get(token) : summonHelperMap2.get(token);\n\t\t\tsummonHelper.setVisible(true);\n\t\t\tsummonHelper.setManaged(true);\n\t\t\tEventHandler<ActionEvent> clickedHander = new EventHandler<ActionEvent>() {\n\n\t\t\t\t@Override\n\t\t\t\tpublic void handle(ActionEvent event) {\n\t\t\t\t\tdisableTargetSelection();\n\t\t\t\t\ttargetOptions.getActionSelectionListener().onActionSelected(action);\n\t\t\t\t}\n\t\t\t};\n\t\t\tsummonHelper.setOnAction(clickedHander);\n\t\t}\n\t}\n\n\tpublic void enableTargetSelection(final HumanTargetOptions targetOptions) {\n\t\tGameAction action = targetOptions.getActionGroup().getPrototype();\n\t\tif (action.getActionType() == ActionType.SUMMON) {\n\t\t\tenableSummonTargets(targetOptions);\n\t\t} else {\n\t\t\tenableSpellTargets(targetOptions);\n\t\t}\n\t\tsetCenterMessage(\"Select target for \" + action.getPromptText() + \" - ESC to cancel\");\n\t}\n\n\tpublic GameToken getToken(Entity entity) {\n\t\treturn entityTokenMap.get(entity);\n\t}\n\n\tprivate void hideCenterMessage() {\n\t\tcenterMessageLabel.setVisible(false);\n\t}\n\n\tprivate void setCenterMessage(String message) {\n\t\tcenterMessageLabel.setText(message);\n\t\tcenterMessageLabel.setVisible(true);\n\t}\n\n\tpublic void showAnimations(GameContext context) {\n\t\tgameEventVisualizer.visualize((GameContextVisualizable) context, this);\n\t}\n\n\tpublic void updateGameState(GameContext context) {\n\t\tentityTokenMap.clear();\n\t\tp1Hero.setHero(context.getPlayer1());\n\t\tp1Hero.updateHeroPowerCost(context, context.getPlayer1());\n\t\tp1Hero.highlight(context.getActivePlayer() == context.getPlayer1());\n\t\tentityTokenMap.put(context.getPlayer1().getHero(), p1Hero);\n\t\tp2Hero.setHero(context.getPlayer2());\n\t\tp2Hero.updateHeroPowerCost(context, context.getPlayer2());\n\t\tp2Hero.highlight(context.getActivePlayer() == context.getPlayer2());\n\t\tentityTokenMap.put(context.getPlayer2().getHero(), p2Hero);\n\n\t\tupdateHandCards(context, context.getPlayer1(), p1Cards);\n\t\tupdateHandCards(context, context.getPlayer2(), p2Cards);\n\n\t\tupdateSummonTokens(context.getPlayer1(), p1Minions);\n\t\tupdateSummonTokens(context.getPlayer2(), p2Minions);\n\n\t\tcheckForWinner(context);\n\t}\n\n\tprivate void updateHandCards(GameContext context, Player player, HandCard[] handCards) {\n\t\tCardCollection hand = player.getHand();\n\t\tfor (int i = 0; i < handCards.length; i++) {\n\t\t\tif (i < hand.getCount()) {\n\t\t\t\thandCards[i].setManaged(true);\n\t\t\t\thandCards[i].setVisible(true);\n\t\t\t\thandCards[i].setCard(context, hand.get(i), player);\n\n\t\t\t} else {\n\t\t\t\thandCards[i].setManaged(false);\n\t\t\t\thandCards[i].setVisible(false);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void updateSummonTokens(Player player, SummonToken[] summonTokens) {\n\t\tList<Summon> summons = player.getSummons();\n\t\tfor (int i = 0; i < summonTokens.length; i++) {\n\t\t\tif (i < summons.size()) {\n\t\t\t\tSummon summon = summons.get(i);\n\t\t\t\tsummonTokens[i].setSummon(summon);\n\t\t\t\tsummonTokens[i].setManaged(true);\n\t\t\t\tsummonTokens[i].setVisible(true);\n\t\t\t\tentityTokenMap.put(summon, summonTokens[i]);\n\t\t\t} else {\n\t\t\t\tsummonTokens[i].setManaged(false);\n\t\t\t\tsummonTokens[i].setVisible(false);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/GameContextVisualizable.java",
    "content": "package net.demilich.metastone.gui.playmode;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.metastone.BuildConfig;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.logic.GameLogic;\n\npublic class GameContextVisualizable extends GameContext {\n\n\tprivate final List<GameEvent> gameEvents = new ArrayList<>();\n\n\tprivate boolean blockedByAnimation;\n\n\tpublic GameContextVisualizable(Player player1, Player player2, GameLogic logic, DeckFormat deckFormat) {\n\t\tsuper(player1, player2, logic, deckFormat);\n\t}\n\n\tprotected boolean acceptAction(GameAction nextAction) {\n\t\tif (!ignoreEvents()) {\n\t\t\treturn true;\n\t\t}\n\t\twhile (ignoreEvents()) {\n\t\t\ttry {\n\t\t\t\tThread.sleep(BuildConfig.DEFAULT_SLEEP_DELAY);\n\t\t\t} catch (InterruptedException e) {\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void fireGameEvent(GameEvent gameEvent) {\n\t\tif (ignoreEvents()) {\n\t\t\treturn;\n\t\t}\n\t\tsuper.fireGameEvent(gameEvent);\n\t\tgetGameEvents().add(gameEvent);\n\t}\n\n\tpublic synchronized List<GameEvent> getGameEvents() {\n\t\treturn gameEvents;\n\t}\n\n\tpublic boolean isBlockedByAnimation() {\n\t\treturn blockedByAnimation;\n\t}\n\n\t@Override\n\tprotected void onGameStateChanged() {\n\t\tif (ignoreEvents()) {\n\t\t\treturn;\n\t\t}\n\n\t\tsetBlockedByAnimation(true);\n\t\tNotificationProxy.sendNotification(GameNotification.GAME_STATE_UPDATE, this);\n\n\t\twhile (blockedByAnimation) {\n\t\t\ttry {\n\t\t\t\tThread.sleep(BuildConfig.DEFAULT_SLEEP_DELAY);\n\t\t\t} catch (InterruptedException e) {\n\t\t\t}\n\t\t}\n\t\tNotificationProxy.sendNotification(GameNotification.GAME_STATE_LATE_UPDATE, this);\n\t}\n\n\tpublic void setBlockedByAnimation(boolean blockedByAnimation) {\n\t\tthis.blockedByAnimation = blockedByAnimation;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/GameToken.java",
    "content": "package net.demilich.metastone.gui.playmode;\n\nimport java.io.IOException;\n\nimport javafx.beans.binding.Bindings;\nimport javafx.event.EventHandler;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.Group;\nimport javafx.scene.effect.Blend;\nimport javafx.scene.effect.BlendMode;\nimport javafx.scene.effect.ColorAdjust;\nimport javafx.scene.effect.ColorInput;\nimport javafx.scene.effect.Effect;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.paint.Color;\nimport net.demilich.metastone.gui.DigitFactory;\nimport net.demilich.metastone.gui.IconFactory;\n\npublic class GameToken extends BorderPane {\n\n\tprotected StackPane target;\n\tprivate ImageView targetButton;\n\tprivate EventHandler<MouseEvent> existingEventHandler;\n\n\tpublic GameToken(String fxml) {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/\" + fxml));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tcreateTargetButton();\n\t}\n\n\tprivate void createTargetButton() {\n\t\ttarget = (StackPane) lookup(\"#targetAnchor\");\n\t\tImage image = IconFactory.getTargetIcon();\n\t\tImageView targetIcon = new ImageView(image);\n\t\ttargetIcon.setClip(new ImageView(image));\n\t\tColorAdjust monochrome = new ColorAdjust();\n\t\tmonochrome.setSaturation(-1.0);\n\n\t\tBlend red = new Blend(BlendMode.MULTIPLY, monochrome,\n\t\t\t\tnew ColorInput(0, 0, targetIcon.getImage().getWidth(), targetIcon.getImage().getHeight(), Color.RED));\n\n\t\tBlend green = new Blend(BlendMode.MULTIPLY, monochrome,\n\t\t\t\tnew ColorInput(0, 0, targetIcon.getImage().getWidth(), targetIcon.getImage().getHeight(), new Color(0, 1, 0, 0.5)));\n\n\t\ttargetButton = targetIcon;\n\n\t\ttargetIcon.effectProperty().bind(Bindings.when(targetButton.hoverProperty()).then((Effect) green).otherwise((Effect) red));\n\t\ttargetButton.setId(\"target_button\");\n\t\thideTargetMarker();\n\t\ttarget.getChildren().add(targetButton);\n\t}\n\n\tpublic StackPane getAnchor() {\n\t\treturn target;\n\t}\n\n\tpublic void hideTargetMarker() {\n\t\ttargetButton.setVisible(false);\n\t}\n\n\tprotected void setScoreValue(Group group, int value) {\n\t\tsetScoreValue(group, value, value);\n\t}\n\n\tprotected void setScoreValue(Group group, int value, int baseValue) {\n\t\tColor color = Color.WHITE;\n\t\tif (value > baseValue) {\n\t\t\tcolor = Color.GREEN;\n\t\t}\n\t\tDigitFactory.showPreRenderedDigits(group, value, color);\n\t}\n\t\n\tprotected void setScoreValue(Group group, int value, int baseValue, int maxValue) {\n\t\tColor color = Color.WHITE;\n\t\tif (value < maxValue) {\n\t\t\tcolor = Color.RED;\n\t\t} else if (value > baseValue) {\n\t\t\tcolor = Color.GREEN;\n\t\t}\n\t\tDigitFactory.showPreRenderedDigits(group, value, color);\n\t}\n\n\tprotected void setScoreValueLowerIsBetter(Group group, int value, int baseValue) {\n\t\tColor color = Color.WHITE;\n\t\tif (value < baseValue) {\n\t\t\tcolor = Color.GREEN;\n\t\t} else if (value > baseValue) {\n\t\t\tcolor = Color.RED;\n\t\t}\n\t\tDigitFactory.showPreRenderedDigits(group, value, color);\n\t}\n\n\tpublic void showTargetMarker(EventHandler<MouseEvent> clickedHander) {\n\t\tif (existingEventHandler != null) {\n\t\t\ttargetButton.removeEventHandler(MouseEvent.MOUSE_CLICKED, existingEventHandler);\n\t\t}\n\t\ttargetButton.addEventHandler(MouseEvent.MOUSE_CLICKED, clickedHander);\n\t\ttargetButton.setVisible(true);\n\t\texistingEventHandler = clickedHander;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/HeroToken.java",
    "content": "package net.demilich.metastone.gui.playmode;\n\nimport java.util.HashSet;\n\nimport javafx.fxml.FXML;\nimport javafx.scene.Group;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.shape.Shape;\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.QuestCard;\nimport net.demilich.metastone.game.entities.heroes.Hero;\nimport net.demilich.metastone.game.entities.weapons.Weapon;\nimport net.demilich.metastone.gui.IconFactory;\nimport net.demilich.metastone.gui.cards.CardTooltip;\n\npublic class HeroToken extends GameToken {\n\n\t@FXML\n\tprivate Group attackAnchor;\n\t@FXML\n\tprivate Group hpAnchor;\n\t@FXML\n\tprivate Label cardsLabel;\n\t@FXML\n\tprivate Label manaLabel;\n\n\t@FXML\n\tprivate Group armorAnchor;\n\t@FXML\n\tprivate ImageView armorIcon;\n\n\t@FXML\n\tprivate Pane weaponPane;\n\t@FXML\n\tprivate Label weaponNameLabel;\n\t@FXML\n\tprivate Group weaponAttackAnchor;\n\t@FXML\n\tprivate Group weaponDurabilityAnchor;\n\n\t@FXML\n\tprivate ImageView portrait;\n\n\t@FXML\n\tprivate Group heroPowerAnchor;\n\t@FXML\n\tprivate ImageView heroPowerIcon;\n\n\t@FXML\n\tprivate Pane secretsAnchor;\n\n\t@FXML\n\tprivate Shape frozen;\n\n\tpublic HeroToken() {\n\t\tsuper(\"HeroToken.fxml\");\n\t\tfrozen.getStrokeDashArray().add(16.0);\n\t}\n\n\tpublic void highlight(boolean highlight) {\n\t\tString cssBorder = null;\n\t\tif (highlight) {\n\t\t\tcssBorder = \"-fx-border-color:seagreen; \\n\" + \"-fx-border-radius:7;\\n\" + \"-fx-border-width:5.0;\";\n\t\t} else {\n\t\t\tcssBorder = \"-fx-border-color:transparent; \\n\" + \"-fx-border-radius:7;\\n\" + \"-fx-border-width:5.0;\";\n\t\t}\n\n\t\ttarget.setStyle(cssBorder);\n\t}\n\n\tpublic void setHero(Player player) {\n\t\tHero hero = player.getHero();\n\t\tsetScoreValue(attackAnchor, hero.getAttack());\n\t\tImage portraitImage = new Image(IconFactory.getHeroIconUrl(hero.getHeroClass()));\n\t\tportrait.setImage(portraitImage);\n\t\tsetScoreValue(hpAnchor, hero.getHp(), hero.getAttributeValue(Attribute.BASE_HP), hero.getMaxHp());\n\t\tif (!player.getDeck().isEmpty()) {\n\t\t\tcardsLabel.setText(\"Cards in deck: \" + player.getDeck().getCount());\n\t\t} else {\n\t\t\tcardsLabel.setText(\"Fatigue: \" + player.getAttributeValue(Attribute.FATIGUE));\n\t\t}\n\t\tif (player.getAttributeValue(Attribute.OVERLOAD) > 0) {\n\t\t\tmanaLabel.setText(\"Mana: \" + player.getMana() + \"/\" + player.getMaxMana() + \"\\nOver: \" + player.getAttributeValue(Attribute.OVERLOAD));\n\t\t} else {\n\t\t\tmanaLabel.setText(\"Mana: \" + player.getMana() + \"/\" + player.getMaxMana());\n\t\t}\n\t\tupdateArmor(hero.getArmor());\n\t\tupdateHeroPower(hero);\n\t\tupdateWeapon(hero.getWeapon());\n\t\tupdateSecrets(player);\n\t\tupdateStatus(hero);\n\t}\n\n\tprivate void updateArmor(int armor) {\n\t\tsetScoreValue(armorAnchor, armor);\n\t\tboolean visible = armor > 0;\n\t\tarmorIcon.setVisible(visible);\n\t\tarmorAnchor.setVisible(visible);\n\t}\n\n\tprivate void updateHeroPower(Hero hero) {\n\t\tImage heroPowerImage = new Image(IconFactory.getHeroPowerIconUrl(hero.getHeroPower()));\n\t\theroPowerIcon.setImage(heroPowerImage);\n\t\tCard card = CardCatalogue.getCardById(hero.getHeroPower().getCardId());\n\t\tTooltip tooltip = new Tooltip();\n\t\tCardTooltip tooltipContent = new CardTooltip();\n\t\ttooltipContent.setCard(card);\n\t\ttooltip.setGraphic(tooltipContent);\n\t\tTooltip.install(heroPowerIcon, tooltip);\n\t}\n\n\tpublic void updateHeroPowerCost(GameContext context, Player player) {\n\t\tsetScoreValueLowerIsBetter(heroPowerAnchor, context.getLogic().getModifiedManaCost(player, player.getHero().getHeroPower()), player.getHero().getHeroPower().getBaseManaCost());\n\t}\n\n\tprivate void updateSecrets(Player player) {\n\t\tsecretsAnchor.getChildren().clear();\n\t\tHashSet<String> secretsCopy = new HashSet<String>(player.getSecrets());\n\t\tfor (String secretId : secretsCopy) {\n\t\t\tCard card = CardCatalogue.getCardById(secretId);\n\t\t\tImageView secretIcon = null;\n\t\t\tif (card instanceof QuestCard) {\n\t\t\t\tsecretIcon = new ImageView(IconFactory.getImageUrl(\"common/quest.png\"));\n\t\t\t} else {\n\t\t\t\tsecretIcon = new ImageView(IconFactory.getImageUrl(\"common/secret.png\"));\n\t\t\t}\n\t\t\tsecretsAnchor.getChildren().add(secretIcon);\n\n\t\t\tif (!player.hideCards() || card instanceof QuestCard) {\n\t\t\t\tTooltip tooltip = new Tooltip();\n\t\t\t\tCardTooltip tooltipContent = new CardTooltip();\n\t\t\t\ttooltipContent.setCard(card);\n\t\t\t\ttooltip.setGraphic(tooltipContent);\n\t\t\t\tTooltip.install(secretIcon, tooltip);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void updateStatus(Hero hero) {\n\t\tfrozen.setVisible(hero.hasAttribute(Attribute.FROZEN));\n\t}\n\n\tprivate void updateWeapon(Weapon weapon) {\n\t\tboolean hasWeapon = weapon != null;\n\t\tweaponPane.setVisible(hasWeapon);\n\t\tif (hasWeapon) {\n\t\t\tweaponNameLabel.setText(weapon.getName());\n\t\t\tsetScoreValue(weaponAttackAnchor, weapon.getWeaponDamage(), weapon.getBaseAttack());\n\t\t\tsetScoreValue(weaponDurabilityAnchor, weapon.getDurability(), weapon.getBaseDurability(), weapon.getMaxDurability());\n\t\t\tTooltip tooltip = new Tooltip();\n\t\t\tCardTooltip tooltipContent = new CardTooltip();\n\t\t\ttooltipContent.setCard(weapon.getSourceCard());\n\t\t\ttooltip.setGraphic(tooltipContent);\n\t\t\tTooltip.install(weaponPane, tooltip);\n\t\t}\n\t}\n\n}"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/HumanActionPromptView.java",
    "content": "package net.demilich.metastone.gui.playmode;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.layout.Region;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.text.TextAlignment;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.actions.ActionType;\nimport net.demilich.metastone.game.actions.BattlecryAction;\nimport net.demilich.metastone.game.actions.DiscoverAction;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.actions.HeroPowerAction;\nimport net.demilich.metastone.game.actions.PhysicalAttackAction;\nimport net.demilich.metastone.game.actions.PlayCardAction;\nimport net.demilich.metastone.game.behaviour.human.ActionGroup;\nimport net.demilich.metastone.game.behaviour.human.HumanActionOptions;\nimport net.demilich.metastone.game.behaviour.human.HumanTargetOptions;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.targeting.TargetSelection;\nimport net.demilich.metastone.gui.cards.CardTooltip;\n\npublic class HumanActionPromptView extends VBox {\n\n\tprivate static String getActionString(GameContext context, GameAction action) {\n\t\tPlayCardAction playCardAction = null;\n\t\tCard card = null;\n\t\tString actionString = \"\";\n\t\tswitch (action.getActionType()) {\n\t\tcase HERO_POWER:\n\t\t\tHeroPowerAction heroPowerAction = (HeroPowerAction) action;\n\t\t\tcard = context.resolveCardReference(heroPowerAction.getCardReference());\n\t\t\tactionString = \"HERO POWER: \" + card.getName();\n\t\t\tbreak;\n\t\tcase BATTLECRY:\n\t\t\tBattlecryAction battlecry = (BattlecryAction) action;\n\t\t\tactionString = \"BATTLECRY \" + battlecry.getSpell().getSpellClass().getSimpleName();\n\t\t\tbreak;\n\t\tcase PHYSICAL_ATTACK:\n\t\t\tPhysicalAttackAction physicalAttackAction = (PhysicalAttackAction) action;\n\t\t\tEntity attacker = context.resolveSingleTarget(physicalAttackAction.getAttackerReference());\n\t\t\tactionString = \"ATTACK with \" + attacker.getName();\n\t\t\tbreak;\n\t\tcase SPELL:\n\t\t\tplayCardAction = (PlayCardAction) action;\n\t\t\tcard = context.resolveCardReference(playCardAction.getCardReference());\n\t\t\tactionString = \"CAST SPELL: \" + card.getName();\n\t\t\tbreak;\n\t\tcase SUMMON:\n\t\t\tplayCardAction = (PlayCardAction) action;\n\t\t\tcard = context.resolveCardReference(playCardAction.getCardReference());\n\t\t\tactionString = \"SUMMON: \" + card.getName();\n\t\t\tbreak;\n\t\tcase EQUIP_WEAPON:\n\t\t\tplayCardAction = (PlayCardAction) action;\n\t\t\tcard = context.resolveCardReference(playCardAction.getCardReference());\n\t\t\tactionString = \"WEAPON: \" + card.getName();\n\t\t\tbreak;\n\t\tcase END_TURN:\n\t\t\tactionString = \"END TURN\";\n\t\t\tbreak;\n\t\tcase DISCOVER:\n\t\t\tDiscoverAction discover = (DiscoverAction) action;\n\t\t\tactionString = \"DISCOVER \" + discover.getSpell().getSpellClass().getSimpleName();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn \"<unknown action \" + action.getActionType() + \">\";\n\t\t}\n\n\t\tif (action.getActionSuffix() != null) {\n\t\t\tactionString += \" (\" + action.getActionSuffix() + \")\";\n\t\t}\n\n\t\treturn actionString;\n\t}\n\n\tprivate final List<Node> existingButtons = new ArrayList<Node>();\n\n\tpublic HumanActionPromptView() {\n\t\tLabel headerLabel = new Label(\"Choose action:\");\n\t\theaderLabel.setStyle(\"-fx-font-family: Arial;-fx-font-weight: bold; -fx-font-size: 16pt;\");\n\n\t\tsetPrefWidth(Region.USE_COMPUTED_SIZE);\n\t\tsetSpacing(2);\n\t\tsetPadding(new Insets(8));\n\t\tsetAlignment(Pos.CENTER);\n\t\tgetChildren().add(headerLabel);\n\t}\n\n\tprivate Node createActionButton(final ActionGroup actionGroup, HumanActionOptions options) {\n\t\tGameContext context = options.getContext();\n\t\tButton button = new Button(getActionString(context, actionGroup.getPrototype()));\n\t\tbutton.setStyle(\"-fx-font-size: 12px; -fx-padding: 4 8 4 8;\");\n\t\tbutton.setWrapText(true);\n\t\tbutton.setPrefWidth(200);\n\t\tbutton.setTextAlignment(TextAlignment.CENTER);\n\t\tswitch (actionGroup.getPrototype().getActionType()) {\n\t\tcase BATTLECRY:\n\t\t\tbreak;\n\t\tcase DISCOVER:\n\t\t\tCardTooltip tooltipContent = new CardTooltip();\n\t\t\tDiscoverAction discover = (DiscoverAction) actionGroup.getPrototype();\n\t\t\tCard card = discover.getCard();\n\t\t\tif (card != null) {\n\t\t\t\ttooltipContent.setCard(card);\n\t\t\t\tTooltip tooltip = new Tooltip();\n\t\t\t\ttooltip.setGraphic(tooltipContent);\n\t\t\t\tTooltip.install(button, tooltip);\n\t\t\t} else {\n\t\t\t\ttooltipContent.setNonCard(discover.getName(), discover.getDescription());\n\t\t\t\tTooltip tooltip = new Tooltip();\n\t\t\t\ttooltip.setGraphic(tooltipContent);\n\t\t\t\tTooltip.install(button, tooltip);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase END_TURN:\n\t\t\tbreak;\n\t\tcase EQUIP_WEAPON:\n\t\t\tbreak;\n\t\tcase HERO_POWER:\n\t\t\tbreak;\n\t\tcase PHYSICAL_ATTACK:\n\t\t\tbreak;\n\t\tcase SPELL:\n\t\t\tbreak;\n\t\tcase SUMMON:\n\t\t\tbreak;\n\t\tcase SYSTEM:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t\n\t\t}\n\t\t// only one action with no target selection or summon with no other\n\t\t// minion on board\n\t\tif (actionGroup.getActionsInGroup().size() == 1 && (actionGroup.getPrototype().getTargetRequirement() == TargetSelection.NONE\n\t\t\t\t|| actionGroup.getPrototype().getActionType() == ActionType.SUMMON)) {\n\t\t\tbutton.setOnAction(event -> {\n\t\t\t\toptions.getBehaviour().onActionSelected(actionGroup.getPrototype());\n\t\t\t\tsetVisible(false);\n\n\t\t\t});\n\t\t\treturn button;\n\t\t}\n\t\tHumanTargetOptions humanTargetOptions = new HumanTargetOptions(options.getBehaviour(), context, options.getPlayer().getId(),\n\t\t\t\tactionGroup);\n\t\tbutton.setOnAction(event -> {\n\t\t\tNotificationProxy.sendNotification(GameNotification.HUMAN_PROMPT_FOR_TARGET, humanTargetOptions);\n\t\t\tsetVisible(false);\n\t\t});\n\n\t\treturn button;\n\t}\n\n\tprivate Collection<Node> createActionButtons(HumanActionOptions options) {\n\t\tCollection<Node> buttons = new ArrayList<Node>();\n\t\tCollection<ActionGroup> actionGrups = groupActions(options);\n\t\tfor (ActionGroup actionGroup : actionGrups) {\n\t\t\tbuttons.add(createActionButton(actionGroup, options));\n\t\t}\n\t\treturn buttons;\n\t}\n\n\tprivate Collection<ActionGroup> groupActions(HumanActionOptions options) {\n\t\tCollection<ActionGroup> actionGroups = new ArrayList<>();\n\t\tfor (GameAction action : options.getValidActions()) {\n\t\t\tif (!matchesExistingGroup(action, actionGroups)) {\n\t\t\t\tActionGroup newActionGroup = new ActionGroup(action);\n\t\t\t\tactionGroups.add(newActionGroup);\n\t\t\t}\n\t\t}\n\t\treturn actionGroups;\n\t}\n\n\tprivate boolean matchesExistingGroup(GameAction action, Collection<ActionGroup> existingActionGroups) {\n\t\tfor (ActionGroup actionGroup : existingActionGroups) {\n\t\t\tif (actionGroup.getPrototype().isSameActionGroup(action)) {\n\t\t\t\tactionGroup.add(action);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic void setActions(HumanActionOptions options) {\n\t\tgetChildren().removeAll(existingButtons);\n\t\texistingButtons.clear();\n\n\t\tCollection<Node> buttons = createActionButtons(options);\n\t\texistingButtons.addAll(buttons);\n\t\tgetChildren().addAll(buttons);\n\t\tsetVisible(true);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/HumanMulliganView.java",
    "content": "package net.demilich.metastone.gui.playmode;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\n\nimport javafx.event.EventHandler;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.behaviour.human.HumanMulliganOptions;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.gui.IconFactory;\nimport net.demilich.metastone.gui.cards.CardTooltip;\n\npublic class HumanMulliganView extends BorderPane implements EventHandler<MouseEvent> {\n\n\tprivate class MulliganEntry {\n\n\t\tpublic boolean mulligan;\n\n\t\tpublic ImageView discardIcon;\n\n\t\tpublic MulliganEntry(ImageView icon) {\n\t\t\tthis.discardIcon = icon;\n\t\t}\n\t}\n\n\t@FXML\n\tprivate HBox contentArea;\n\n\t@FXML\n\tprivate Button doneButton;\n\n\tprivate final HashMap<Card, MulliganEntry> mulliganState = new HashMap<Card, MulliganEntry>();\n\n\tpublic HumanMulliganView(HumanMulliganOptions options) {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/HumanMulliganView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tdisplayCards(options);\n\n\t\tNotificationProxy.sendNotification(GameNotification.SHOW_MODAL_DIALOG, this);\n\t}\n\n\tprivate void displayCards(final HumanMulliganOptions options) {\n\t\tcontentArea.getChildren().clear();\n\t\tfor (Card card : options.getOfferedCards()) {\n\t\t\tStackPane stackPane = new StackPane();\n\n\t\t\tCardTooltip cardWidget = new CardTooltip();\n\t\t\tcardWidget.setCard(card);\n\t\t\tcardWidget.addEventHandler(MouseEvent.MOUSE_CLICKED, this);\n\t\t\tstackPane.getChildren().add(cardWidget);\n\n\t\t\tImageView mulliganIcon = new ImageView(IconFactory.getImageUrl(\"common/mulligan.png\"));\n\t\t\tmulliganIcon.setMouseTransparent(true);\n\t\t\tmulliganIcon.setVisible(false);\n\t\t\tstackPane.getChildren().add(mulliganIcon);\n\n\t\t\tcontentArea.getChildren().add(stackPane);\n\n\t\t\tmulliganState.put(card, new MulliganEntry(mulliganIcon));\n\t\t}\n\n\t\tdoneButton.setOnAction(event -> {\n\t\t\tList<Card> discardedCards = new ArrayList<>();\n\t\t\tfor (Card card : mulliganState.keySet()) {\n\t\t\t\tMulliganEntry entry = mulliganState.get(card);\n\t\t\t\tif (entry.mulligan) {\n\t\t\t\t\tdiscardedCards.add(card);\n\t\t\t\t}\n\t\t\t}\n\t\t\toptions.getBehaviour().setMulliganCards(discardedCards);\n\t\t\tthis.getScene().getWindow().hide();\n\t\t});\n\t}\n\n\t@Override\n\tpublic void handle(MouseEvent mouseEvent) {\n\t\tCardTooltip cardWidget = (CardTooltip) mouseEvent.getSource();\n\t\tCard card = cardWidget.getCard();\n\t\tMulliganEntry entry = mulliganState.get(card);\n\t\tentry.mulligan = !entry.mulligan;\n\t\tentry.discardIcon.setVisible(entry.mulligan);\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/LoadingBoardView.java",
    "content": "package net.demilich.metastone.gui.playmode;\n\nimport java.io.IOException;\n\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.ProgressIndicator;\nimport javafx.scene.layout.BorderPane;\n\npublic class LoadingBoardView extends BorderPane {\n\n\t@FXML\n\tprivate ProgressIndicator loadingIndicator;\n\n\tpublic LoadingBoardView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/LoadingBoardView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tloadingIndicator.setProgress(-1);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/PlayModeMediator.java",
    "content": "package net.demilich.metastone.gui.playmode;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.nittygrittymvc.Mediator;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport javafx.application.Platform;\nimport javafx.event.EventHandler;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.behaviour.human.HumanActionOptions;\nimport net.demilich.metastone.game.behaviour.human.HumanMulliganOptions;\nimport net.demilich.metastone.game.behaviour.human.HumanTargetOptions;\n\npublic class PlayModeMediator extends Mediator<GameNotification>implements EventHandler<KeyEvent> {\n\n\tpublic static final String NAME = \"PlayModeMediator\";\n\n\tprivate final PlayModeView view;\n\tprivate final HumanActionPromptView actionPromptView;\n\n\tpublic PlayModeMediator() {\n\t\tsuper(NAME);\n\t\tview = new PlayModeView();\n\t\tactionPromptView = view.getActionPromptView();\n\t}\n\n\t@Override\n\tpublic void handle(KeyEvent keyEvent) {\n\t\tif (keyEvent.getCode() != KeyCode.ESCAPE) {\n\t\t\treturn;\n\t\t}\n\n\t\tview.disableTargetSelection();\n\t}\n\n\t@Override\n\tpublic void handleNotification(final INotification<GameNotification> notification) {\n\t\tswitch (notification.getId()) {\n\t\tcase GAME_STATE_UPDATE:\n\t\t\tGameContext context = (GameContext) notification.getBody();\n\t\t\tPlatform.runLater(() -> view.showAnimations(context));\n\t\t\tbreak;\n\t\tcase GAME_STATE_LATE_UPDATE:\n\t\t\tGameContext context2 = (GameContext) notification.getBody();\n\t\t\tPlatform.runLater(() -> view.updateGameState(context2));\n\t\t\tbreak;\n\t\tcase HUMAN_PROMPT_FOR_ACTION:\n\t\t\tHumanActionOptions actionOptions = (HumanActionOptions) notification.getBody();\n\t\t\tPlatform.runLater(() -> actionPromptView.setActions(actionOptions));\n\t\t\tbreak;\n\t\tcase HUMAN_PROMPT_FOR_TARGET:\n\t\t\tHumanTargetOptions options = (HumanTargetOptions) notification.getBody();\n\t\t\tPlatform.runLater(() -> view.enableTargetSelection(options));\n\t\t\tbreak;\n\t\tcase HUMAN_PROMPT_FOR_MULLIGAN:\n\t\t\tHumanMulliganOptions mulliganOptions = (HumanMulliganOptions) notification.getBody();\n\t\t\tPlatform.runLater(() -> new HumanMulliganView(mulliganOptions));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t@Override\n\tpublic List<GameNotification> listNotificationInterests() {\n\t\tList<GameNotification> notificationInterests = new ArrayList<GameNotification>();\n\t\tnotificationInterests.add(GameNotification.GAME_STATE_UPDATE);\n\t\tnotificationInterests.add(GameNotification.GAME_STATE_LATE_UPDATE);\n\t\tnotificationInterests.add(GameNotification.HUMAN_PROMPT_FOR_ACTION);\n\t\tnotificationInterests.add(GameNotification.HUMAN_PROMPT_FOR_TARGET);\n\t\tnotificationInterests.add(GameNotification.HUMAN_PROMPT_FOR_MULLIGAN);\n\t\tnotificationInterests.add(GameNotification.REPLY_DECKS);\n\t\tnotificationInterests.add(GameNotification.REPLY_DECK_FORMATS);\n\t\treturn notificationInterests;\n\t}\n\n\t@Override\n\tpublic void onRegister() {\n\t\tgetFacade().sendNotification(GameNotification.SHOW_VIEW, view);\n\t\tview.getScene().setOnKeyPressed(this);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/PlayModeView.java",
    "content": "package net.demilich.metastone.gui.playmode;\n\nimport java.io.IOException;\n\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.layout.VBox;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.behaviour.human.HumanTargetOptions;\n\npublic class PlayModeView extends BorderPane {\n\n\t@FXML\n\tprivate Button backButton;\n\n\t@FXML\n\tprivate VBox sidePane;\n\n\t@FXML\n\tprivate Pane navigationPane;\n\n\tprivate final GameBoardView boardView;\n\tprivate final HumanActionPromptView actionPromptView;\n\n\tprivate final LoadingBoardView loadingView;\n\n\tprivate boolean firstUpdate = true;\n\n\tpublic PlayModeView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/PlayModeView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tboardView = new GameBoardView();\n\t\t// setCenter(boardView);\n\t\tloadingView = new LoadingBoardView();\n\t\tsetCenter(loadingView);\n\n\t\tactionPromptView = new HumanActionPromptView();\n\t\t//sidePane.getChildren().add(actionPromptView);\n\n\t\tbackButton.setOnAction(actionEvent -> NotificationProxy.sendNotification(GameNotification.MAIN_MENU));\n\n\t\tsidePane.getChildren().setAll(actionPromptView, navigationPane);\n\t}\n\n\tpublic void disableTargetSelection() {\n\t\tboardView.disableTargetSelection();\n\t\tactionPromptView.setVisible(true);\n\t}\n\n\tpublic void enableTargetSelection(HumanTargetOptions targetOptions) {\n\t\tboardView.enableTargetSelection(targetOptions);\n\t}\n\n\tpublic HumanActionPromptView getActionPromptView() {\n\t\treturn actionPromptView;\n\t}\n\n\tpublic void showAnimations(GameContext context) {\n\t\tboardView.showAnimations(context);\n\t}\n\n\tpublic void updateGameState(GameContext context) {\n\t\tif (firstUpdate) {\n\t\t\tsetCenter(boardView);\n\t\t\tfirstUpdate = false;\n\t\t}\n\t\tboardView.updateGameState(context);\n\t\tif (context.gameDecided()) {\n\t\t\tsidePane.getChildren().clear();\n\t\t\tsidePane.getChildren().add(backButton);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/StartGameCommand.java",
    "content": "package net.demilich.metastone.gui.playmode;\n\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.game.gameconfig.GameConfig;\nimport net.demilich.metastone.game.gameconfig.PlayerConfig;\n\npublic class StartGameCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tGameConfig gameConfig = (GameConfig) notification.getBody();\n\n\t\tPlayerConfig playerConfig1 = gameConfig.getPlayerConfig1();\n\t\tPlayerConfig playerConfig2 = gameConfig.getPlayerConfig2();\n\n\t\tPlayer player1 = new Player(playerConfig1);\n\t\tPlayer player2 = new Player(playerConfig2);\n\t\t\n\t\tDeckFormat deckFormat = gameConfig.getDeckFormat();\n\n\t\tGameContext newGame = new GameContextVisualizable(player1, player2, new GameLogic(), deckFormat);\n\t\tThread t = new Thread(new Runnable() {\n\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tNotificationProxy.sendNotification(GameNotification.PLAY_GAME, newGame);\n\t\t\t}\n\t\t});\n\t\tt.setDaemon(true);\n\t\tt.start();\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/SummonToken.java",
    "content": "package net.demilich.metastone.gui.playmode;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javafx.fxml.FXML;\nimport javafx.scene.Group;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.shape.Shape;\nimport javafx.scene.text.Text;\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.entities.minions.Permanent;\nimport net.demilich.metastone.game.entities.minions.Summon;\nimport net.demilich.metastone.gui.cards.CardTooltip;\n\npublic class SummonToken extends GameToken {\n\t@FXML\n\tprivate Label name;\n\t@FXML\n\tprivate Group attackAnchor;\n\t@FXML\n\tprivate Group hpAnchor;\n\n\t@FXML\n\tprivate Node defaultToken;\n\t@FXML\n\tprivate Node divineShield;\n\t@FXML\n\tprivate Node taunt;\n\t@FXML\n\tprivate Text windfury;\n\t@FXML\n\tprivate Node deathrattle;\n\n\t@FXML\n\tprivate Shape frozen;\n\n\tprivate CardTooltip cardTooltip;\n\t\n\tLogger logger = LoggerFactory.getLogger(SummonToken.class);\n\n\tpublic SummonToken() {\n\t\tsuper(\"SummonToken.fxml\");\n\t\tTooltip tooltip = new Tooltip();\n\t\tcardTooltip = new CardTooltip();\n\t\ttooltip.setGraphic(cardTooltip);\n\t\tTooltip.install(this, tooltip);\n\t\tfrozen.getStrokeDashArray().add(16.0);\n\t}\n\n\tpublic void setSummon(Summon summon) {\n\t\tname.setText(summon.getName());\n\t\tif (summon instanceof Minion) {\n\t\t\tattackAnchor.setVisible(true);\n\t\t\thpAnchor.setVisible(true);\n\t\t\tsetScoreValue(attackAnchor, summon.getAttack(), summon.getAttributeValue(Attribute.BASE_ATTACK));\n\t\t\tsetScoreValue(hpAnchor, summon.getHp(), summon.getBaseHp(), summon.getMaxHp());\n\t\t} else if (summon instanceof Permanent) {\n\t\t\tattackAnchor.setVisible(false);\n\t\t\thpAnchor.setVisible(false);\n\t\t}\n\t\tvisualizeStatus(summon);\n\t\tcardTooltip.setCard(summon.getSourceCard());\n\t}\n\n\tprivate void visualizeStatus(Summon summon) {\n\t\ttaunt.setVisible(summon.hasAttribute(Attribute.TAUNT));\n\t\tdefaultToken.setVisible(!summon.hasAttribute(Attribute.TAUNT));\n\t\tdivineShield.setVisible(summon.hasAttribute(Attribute.DIVINE_SHIELD));\n\t\twindfury.setVisible(summon.hasAttribute(Attribute.WINDFURY) || summon.hasAttribute(Attribute.MEGA_WINDFURY));\n\t\tif(summon.hasAttribute(Attribute.MEGA_WINDFURY)) {\n\t\t\twindfury.setText(\"x4\");\n\t\t} else {\n\t\t\twindfury.setText(\"x2\");\n\t\t}\n\t\tdeathrattle.setVisible(summon.hasAttribute(Attribute.DEATHRATTLES));\n\t\tfrozen.setVisible(summon.hasAttribute(Attribute.FROZEN));\n\t\tvisualizeStealth(summon);\n\t}\n\n\tprivate void visualizeStealth(Summon summon) {\n\t\tNode token = summon.hasAttribute(Attribute.TAUNT) ? taunt : defaultToken;\n\t\ttoken.setOpacity(summon.hasAttribute(Attribute.STEALTH) ? 0.5 : 1);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/AnimationCompletedCommand.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\n\npublic class AnimationCompletedCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tAnimationProxy animationProxy = (AnimationProxy) getFacade().retrieveProxy(AnimationProxy.NAME);\n\t\tanimationProxy.animationCompleted();\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/AnimationLockCommand.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.gui.playmode.GameContextVisualizable;\n\npublic class AnimationLockCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tAnimationProxy animationProxy = (AnimationProxy) getFacade().retrieveProxy(AnimationProxy.NAME);\n\n\t\tGameContextVisualizable contextVisualizable = (GameContextVisualizable) notification.getBody();\n\t\tanimationProxy.setContext(contextVisualizable);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/AnimationProxy.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\nimport net.demilich.nittygrittymvc.Proxy;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.gui.playmode.GameContextVisualizable;\n\npublic class AnimationProxy extends Proxy<GameNotification> {\n\n\tpublic static final String NAME = \"AnimationProxy\";\n\n\tprivate GameContextVisualizable context;\n\tprivate int animationsRunning;\n\n\tpublic AnimationProxy() {\n\t\tsuper(NAME);\n\t}\n\n\tpublic void animationCompleted() {\n\t\tif (--animationsRunning == 0) {\n\t\t\tcontext.setBlockedByAnimation(false);\n\t\t}\n\t}\n\n\tpublic void animationStarted() {\n\t\tanimationsRunning++;\n\t}\n\n\tpublic GameContextVisualizable getContext() {\n\t\treturn context;\n\t}\n\n\tpublic void setContext(GameContextVisualizable context) {\n\t\tthis.context = context;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/AnimationStartedCommand.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\n\npublic class AnimationStartedCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tAnimationProxy animationProxy = (AnimationProxy) getFacade().retrieveProxy(AnimationProxy.NAME);\n\t\tanimationProxy.animationStarted();\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/CardPlayedToken.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\nimport javafx.animation.FadeTransition;\nimport javafx.event.ActionEvent;\nimport javafx.stage.Popup;\nimport javafx.stage.Window;\nimport javafx.util.Duration;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.gui.cards.CardTooltip;\nimport net.demilich.metastone.gui.playmode.GameBoardView;\n\npublic class CardPlayedToken {\n\n\tprivate final Popup popup;\n\tprivate final CardTooltip cardToken;\n\n\tpublic CardPlayedToken(GameBoardView boardView, Card card) {\n\t\tWindow parent = boardView.getScene().getWindow();\n\t\tthis.cardToken = new CardTooltip();\n\n\t\tpopup = new Popup();\n\t\tpopup.getContent().setAll(cardToken);\n\t\tpopup.setX(parent.getX() + 40);\n\t\tpopup.show(parent);\n\t\tpopup.setY(parent.getY() + parent.getHeight() * 0.5 - cardToken.getHeight() * 0.5);\n\n\t\tcardToken.setCard(card);\n\n\t\tNotificationProxy.sendNotification(GameNotification.ANIMATION_STARTED);\n\t\tFadeTransition animation = new FadeTransition(Duration.seconds(1.2), cardToken);\n\t\tanimation.setDelay(Duration.seconds(0.6f));\n\t\tanimation.setOnFinished(this::onComplete);\n\t\tanimation.setFromValue(1);\n\t\tanimation.setToValue(0);\n\t\tanimation.play();\n\t}\n\n\tprivate void onComplete(ActionEvent event) {\n\t\tpopup.hide();\n\t\tNotificationProxy.sendNotification(GameNotification.ANIMATION_COMPLETED);\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/CardRevealedToken.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\nimport javafx.animation.FadeTransition;\nimport javafx.event.ActionEvent;\nimport javafx.stage.Popup;\nimport javafx.stage.Window;\nimport javafx.util.Duration;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.gui.cards.CardTooltip;\nimport net.demilich.metastone.gui.playmode.GameBoardView;\n\npublic class CardRevealedToken {\n\n\tprivate final Popup popup;\n\tprivate final CardTooltip cardToken;\n\n\tpublic CardRevealedToken(GameBoardView boardView, Card card, double delay) {\n\t\tWindow parent = boardView.getScene().getWindow();\n\t\tthis.cardToken = new CardTooltip();\n\n\t\tpopup = new Popup();\n\t\tpopup.getContent().setAll(cardToken);\n\t\tpopup.setX(parent.getX() + 40);\n\t\tpopup.show(parent);\n\t\tpopup.setY(parent.getY() + parent.getHeight() * 0.5 - cardToken.getHeight() * 0.5);\n\n\t\tcardToken.setCard(card);\n\t\tNotificationProxy.sendNotification(GameNotification.ANIMATION_STARTED);\n\t\tFadeTransition animation = new FadeTransition(Duration.seconds(delay), cardToken);\n\t\tanimation.setOnFinished(this::secondTransition);\n\t\tanimation.setFromValue(0);\n\t\tanimation.setToValue(0);\n\t\tanimation.play();\n\t}\n\n\tprivate void secondTransition(ActionEvent event) {\n\t\tFadeTransition animation = new FadeTransition(Duration.seconds(.6), cardToken);\n\t\tanimation.setOnFinished(this::nextTransition);\n\t\tanimation.setFromValue(0);\n\t\tanimation.setToValue(1);\n\t\tanimation.play();\n\t}\n\n\tprivate void nextTransition(ActionEvent event) {\n\t\tFadeTransition animation = new FadeTransition(Duration.seconds(.6), cardToken);\n\t\tanimation.setOnFinished(this::onComplete);\n\t\tanimation.setFromValue(1);\n\t\tanimation.setToValue(0);\n\t\tanimation.play();\n\t}\n\n\tprivate void onComplete(ActionEvent event) {\n\t\tpopup.hide();\n\t\tNotificationProxy.sendNotification(GameNotification.ANIMATION_COMPLETED);\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/DamageEventVisualizer.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\nimport java.util.HashMap;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.events.DamageEvent;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.gui.playmode.GameBoardView;\nimport net.demilich.metastone.gui.playmode.GameToken;\n\npublic class DamageEventVisualizer implements IGameEventVisualizer {\n\n\tprivate HashMap<Integer, HitInfo> recentHits = new HashMap<>();\n\n\t@Override\n\tpublic void visualizeEvent(GameContext gameContext, GameEvent event, GameBoardView boardView) {\n\t\tDamageEvent damageEvent = (DamageEvent) event;\n\t\tGameToken targetToken = boardView.getToken(damageEvent.getVictim());\n\t\tif (targetToken == null) {\n\t\t\treturn;\n\t\t}\n\n\t\tInteger victimId = damageEvent.getVictim().getId();\n\t\tif (!recentHits.containsKey(victimId)) {\n\t\t\trecentHits.put(victimId, new HitInfo());\n\t\t}\n\t\t// when the last displayed hit was on the same target and only a small\n\t\t// amount of time passed, offset\n\t\t// the damage numbers so that all are actually visible\n\t\tHitInfo hitInfo = recentHits.get(victimId);\n\t\tif (System.currentTimeMillis() - hitInfo.lastHitTime < 1000) {\n\t\t\thitInfo.successiveHits++;\n\t\t} else {\n\t\t\thitInfo.successiveHits = 0;\n\t\t}\n\n\t\tnew DamageNumber(\"-\" + damageEvent.getDamage(), targetToken, hitInfo.successiveHits);\n\t\thitInfo.lastHitTime = System.currentTimeMillis();\n\t}\n\n\tprivate class HitInfo {\n\t\tpublic long lastHitTime;\n\t\tpublic int successiveHits;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/DamageNumber.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\nimport javafx.animation.PauseTransition;\nimport javafx.event.ActionEvent;\nimport javafx.geometry.Pos;\nimport javafx.scene.CacheHint;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.paint.Color;\nimport javafx.scene.text.Text;\nimport javafx.util.Duration;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.gui.IconFactory;\nimport net.demilich.metastone.gui.playmode.GameToken;\n\npublic class DamageNumber extends StackPane {\n\n\tprivate final GameToken parent;\n\n\tpublic DamageNumber(String text, GameToken parent, int successiveHits) {\n\t\tthis.parent = parent;\n\t\tthis.setAlignment(Pos.CENTER);\n\n\t\tImageView image = new ImageView(IconFactory.getImageUrl(\"common/splash.png\"));\n\t\timage.setFitWidth(96);\n\t\timage.setFitHeight(96);\n\t\t\n\t\tif (successiveHits > 0) {\n\t\t\tdouble xOffset = -48 * successiveHits;\n\t\t\tsetTranslateX(xOffset);\n\t\t}\n\n\t\tText textShape = new Text(text);\n\t\ttextShape.setFill(Color.WHITE);\n\t\ttextShape.setStyle(\"-fx-font-size: 22pt; -fx-font-family: \\\"System\\\";-fx-font-weight: 900;-fx-stroke: black;-fx-stroke-width: 2;\");\n\n\t\tsetCache(true);\n\t\tsetCacheHint(CacheHint.SPEED);\n\n\t\tgetChildren().add(image);\n\t\tgetChildren().add(textShape);\n\t\tparent.getAnchor().getChildren().add(this);\n\n\t\tNotificationProxy.sendNotification(GameNotification.ANIMATION_STARTED);\n\n\t\tPauseTransition animation = new PauseTransition(Duration.seconds(1.2));\n\t\tanimation.setOnFinished(this::onComplete);\n\t\tanimation.play();\n\t}\n\n\tprivate void onComplete(ActionEvent event) {\n\t\tparent.getAnchor().getChildren().remove(this);\n\t\tNotificationProxy.sendNotification(GameNotification.ANIMATION_COMPLETED);\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/EventVisualizerDispatcher.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\nimport java.util.HashMap;\n\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.gui.playmode.GameBoardView;\nimport net.demilich.metastone.gui.playmode.GameContextVisualizable;\n\npublic class EventVisualizerDispatcher {\n\n\tprivate static final HashMap<GameEventType, IGameEventVisualizer> visualizers = new HashMap<GameEventType, IGameEventVisualizer>();\n\n\tstatic {\n\t\tvisualizers.put(GameEventType.DAMAGE, new DamageEventVisualizer());\n\t\tvisualizers.put(GameEventType.HEAL, new HealEventVisualizer());\n\t\tvisualizers.put(GameEventType.PLAY_CARD, new PlayCardVisualizer());\n\t\tvisualizers.put(GameEventType.JOUST, new JoustVisualizer());\n\t\tvisualizers.put(GameEventType.REVEAL_CARD, new RevealCardVisualizer());\n\t}\n\n\tpublic void visualize(GameContextVisualizable gameContext, GameBoardView boardView) {\n\t\tNotificationProxy.sendNotification(GameNotification.ANIMATION_STARTED);\n\t\tfor (GameEvent event : gameContext.getGameEvents()) {\n\t\t\tIGameEventVisualizer gameEventVisualizer = visualizers.get(event.getEventType());\n\t\t\tif (gameEventVisualizer == null) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tgameEventVisualizer.visualizeEvent(gameContext, event, boardView);\n\t\t}\n\t\tgameContext.getGameEvents().clear();\n\t\tNotificationProxy.sendNotification(GameNotification.ANIMATION_COMPLETED);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/HealEventVisualizer.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.HealEvent;\nimport net.demilich.metastone.gui.playmode.GameBoardView;\nimport net.demilich.metastone.gui.playmode.GameToken;\n\npublic class HealEventVisualizer implements IGameEventVisualizer {\n\n\t@Override\n\tpublic void visualizeEvent(GameContext gameContext, GameEvent event, GameBoardView boardView) {\n\t\tHealEvent healEvent = (HealEvent) event;\n\t\tGameToken targetToken = boardView.getToken(healEvent.getEventTarget());\n\t\tif (targetToken == null) {\n\t\t\treturn;\n\t\t}\n\t\tnew HealingNumber(\"+\" + healEvent.getHealing(), targetToken);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/HealingNumber.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\nimport javafx.animation.TranslateTransition;\nimport javafx.event.ActionEvent;\nimport javafx.scene.CacheHint;\nimport javafx.scene.paint.Color;\nimport javafx.scene.text.Text;\nimport javafx.util.Duration;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.gui.playmode.GameToken;\n\npublic class HealingNumber extends Text {\n\n\tprivate final GameToken parent;\n\n\tpublic HealingNumber(String text, GameToken parent) {\n\t\tthis.parent = parent;\n\n\t\tsetText(text);\n\t\tsetFill(Color.GREEN);\n\t\tsetStyle(\"-fx-font-size: 28pt; -fx-font-family: \\\"System\\\";-fx-font-weight: bolder;-fx-stroke: black;-fx-stroke-width: 2;\");\n\n\t\tsetCache(true);\n\t\tsetCacheHint(CacheHint.SPEED);\n\n\t\tparent.getAnchor().getChildren().add(this);\n\n\t\tNotificationProxy.sendNotification(GameNotification.ANIMATION_STARTED);\n\t\tTranslateTransition animation = new TranslateTransition(Duration.seconds(0.5), this);\n\t\tanimation.setToY(-30);\n\t\tanimation.setOnFinished(this::onComplete);\n\t\tanimation.play();\n\t}\n\n\tprivate void onComplete(ActionEvent event) {\n\t\tparent.getAnchor().getChildren().remove(this);\n\t\tNotificationProxy.sendNotification(GameNotification.ANIMATION_COMPLETED);\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/IAnimationListener.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\npublic interface IAnimationListener {\n\n\tpublic void animationCompleted();\n\n\tpublic void animationStarted();\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/IGameEventVisualizer.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.gui.playmode.GameBoardView;\n\npublic interface IGameEventVisualizer {\n\n\tvoid visualizeEvent(GameContext gameContext, GameEvent event, GameBoardView boardView);\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/JoustToken.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\r\n\r\nimport javafx.animation.FadeTransition;\r\nimport javafx.animation.ScaleTransition;\r\nimport javafx.event.ActionEvent;\r\nimport javafx.stage.Popup;\r\nimport javafx.stage.Window;\r\nimport javafx.util.Duration;\r\nimport net.demilich.metastone.GameNotification;\r\nimport net.demilich.metastone.NotificationProxy;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.gui.cards.CardTooltip;\r\nimport net.demilich.metastone.gui.playmode.GameBoardView;\r\n\r\npublic class JoustToken {\r\n\t\r\n\tprivate final Popup popup;\r\n\tprivate final CardTooltip cardToken;\r\n\r\n\tpublic JoustToken(GameBoardView boardView, Card card, boolean up, boolean won) {\r\n\t\tWindow parent = boardView.getScene().getWindow();\r\n\t\tthis.cardToken = new CardTooltip();\r\n\r\n\t\tpopup = new Popup();\r\n\t\tpopup.getContent().setAll(cardToken);\r\n\t\tpopup.setX(parent.getX() + 600);\r\n\t\tpopup.show(parent);\r\n\t\tint offsetY = up ? -200 : 100;\r\n\t\tpopup.setY(parent.getY() + parent.getHeight() * 0.5 - cardToken.getHeight() * 0.5 + offsetY);\r\n\r\n\t\tcardToken.setCard(card);\r\n\r\n\t\tNotificationProxy.sendNotification(GameNotification.ANIMATION_STARTED);\r\n\t\tFadeTransition animation = new FadeTransition(Duration.seconds(1.0), cardToken);\r\n\t\tanimation.setDelay(Duration.seconds(1f));\r\n\t\tanimation.setOnFinished(this::onComplete);\r\n\t\tanimation.setFromValue(1);\r\n\t\tanimation.setToValue(0);\r\n\t\tanimation.play();\r\n\t\t\r\n\t\tif (won) {\r\n\t\t\tScaleTransition scaleAnimation = new ScaleTransition(Duration.seconds(0.5f), cardToken);\r\n\t\t\tscaleAnimation.setByX(0.1);\r\n\t\t\tscaleAnimation.setByY(0.1);\r\n\t\t\tscaleAnimation.setCycleCount(2);\r\n\t\t\tscaleAnimation.setAutoReverse(true);\r\n\t\t\tscaleAnimation.play();\t\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void onComplete(ActionEvent event) {\r\n\t\tpopup.hide();\r\n\t\tNotificationProxy.sendNotification(GameNotification.ANIMATION_COMPLETED);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/JoustVisualizer.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.events.GameEvent;\r\nimport net.demilich.metastone.game.events.JoustEvent;\r\nimport net.demilich.metastone.gui.playmode.GameBoardView;\r\n\r\npublic class JoustVisualizer implements IGameEventVisualizer {\r\n\r\n\t@Override\r\n\tpublic void visualizeEvent(GameContext gameContext, GameEvent event, GameBoardView boardView) {\r\n\t\tJoustEvent joustEvent = (JoustEvent) event;\r\n\r\n\t\tCard downCard = null;\r\n\t\tCard upCard = null;\r\n\t\tboolean upWon = false;\r\n\t\tif (joustEvent.getTargetPlayerId() == GameContext.PLAYER_1) {\r\n\t\t\tdownCard = joustEvent.getOwnCard();\r\n\t\t\tupCard = joustEvent.getOpponentCard();\r\n\t\t\tupWon = !joustEvent.isWon();\r\n\t\t} else {\r\n\t\t\tdownCard = joustEvent.getOpponentCard();\r\n\t\t\tupCard = joustEvent.getOwnCard();\r\n\t\t\tupWon = joustEvent.isWon();\r\n\t\t}\r\n\r\n\t\tif (upCard != null) {\r\n\t\t\tnew JoustToken(boardView, upCard, true, upWon);\r\n\t\t}\r\n\r\n\t\tif (downCard != null) {\r\n\t\t\tnew JoustToken(boardView, downCard, false, !upWon);\r\n\t\t}\r\n\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/PlayCardVisualizer.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.events.CardPlayedEvent;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.gui.playmode.GameBoardView;\n\npublic class PlayCardVisualizer implements IGameEventVisualizer {\n\n\t@Override\n\tpublic void visualizeEvent(GameContext gameContext, GameEvent event, GameBoardView boardView) {\n\t\tCardPlayedEvent cardPlayedEvent = (CardPlayedEvent) event;\n\n\t\tif (cardPlayedEvent.getCard().hasAttribute(Attribute.SECRET)) {\n\t\t\treturn;\n\t\t}\n\n\t\tnew CardPlayedToken(boardView, cardPlayedEvent.getCard());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/animation/RevealCardVisualizer.java",
    "content": "package net.demilich.metastone.gui.playmode.animation;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.events.CardRevealedEvent;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.gui.playmode.GameBoardView;\n\npublic class RevealCardVisualizer implements IGameEventVisualizer {\n\n\t@Override\n\tpublic void visualizeEvent(GameContext gameContext, GameEvent event, GameBoardView boardView) {\n\t\tCardRevealedEvent cardRevealedEvent = (CardRevealedEvent) event;\n\n\t\tif (cardRevealedEvent.getCard().hasAttribute(Attribute.SECRET)) {\n\t\t\treturn;\n\t\t}\n\n\t\tnew CardRevealedToken(boardView, cardRevealedEvent.getCard(), cardRevealedEvent.getDelay());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/config/PlayModeConfigMediator.java",
    "content": "package net.demilich.metastone.gui.playmode.config;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.nittygrittymvc.Mediator;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.gameconfig.GameConfig;\nimport net.demilich.metastone.gui.playmode.PlayModeMediator;\n\npublic class PlayModeConfigMediator extends Mediator<GameNotification> {\n\n\tpublic static final String NAME = \"PlayModeConfigMediator\";\n\n\tprivate final PlayModeConfigView view;\n\n\tpublic PlayModeConfigMediator() {\n\t\tsuper(NAME);\n\t\tview = new PlayModeConfigView();\n\t}\n\n\t@Override\n\tpublic void handleNotification(final INotification<GameNotification> notification) {\n\t\tswitch (notification.getId()) {\n\t\tcase REPLY_DECKS:\n\t\t\t@SuppressWarnings(\"unchecked\")\n\t\t\tList<Deck> decks = (List<Deck>) notification.getBody();\n\t\t\tview.injectDecks(decks);\n\t\t\tbreak;\n\t\tcase REPLY_DECK_FORMATS:\n\t\t\t@SuppressWarnings(\"unchecked\")\n\t\t\tList<DeckFormat> deckFormats = (List<DeckFormat>) notification.getBody();\n\t\t\tview.injectDeckFormats(deckFormats);\n\t\t\tbreak;\n\t\tcase COMMIT_PLAYMODE_CONFIG:\n\t\t\tgetFacade().registerMediator(new PlayModeMediator());\n\t\t\tnew Thread(new Runnable() {\n\n\t\t\t\t@Override\n\t\t\t\tpublic void run() {\n\t\t\t\t\tGameConfig gameConfig = (GameConfig) notification.getBody();\n\t\t\t\t\tgetFacade().sendNotification(GameNotification.START_GAME, gameConfig);\n\t\t\t\t}\n\t\t\t}).start();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t@Override\n\tpublic List<GameNotification> listNotificationInterests() {\n\t\tList<GameNotification> notificationInterests = new ArrayList<GameNotification>();\n\t\tnotificationInterests.add(GameNotification.REPLY_DECKS);\n\t\tnotificationInterests.add(GameNotification.REPLY_DECK_FORMATS);\n\t\tnotificationInterests.add(GameNotification.COMMIT_PLAYMODE_CONFIG);\n\t\treturn notificationInterests;\n\t}\n\n\t@Override\n\tpublic void onRegister() {\n\t\tgetFacade().sendNotification(GameNotification.SHOW_VIEW, view);\n\t\tgetFacade().sendNotification(GameNotification.REQUEST_DECKS);\n\t\tgetFacade().sendNotification(GameNotification.REQUEST_DECK_FORMATS);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/config/PlayModeConfigView.java",
    "content": "package net.demilich.metastone.gui.playmode.config;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport javafx.beans.value.ChangeListener;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.gui.common.DeckFormatStringConverter;\nimport net.demilich.metastone.game.gameconfig.GameConfig;\nimport net.demilich.metastone.gui.gameconfig.PlayerConfigView;\n\npublic class PlayModeConfigView extends BorderPane implements EventHandler<ActionEvent> {\n\n\t@FXML\n\tprotected ComboBox<DeckFormat> formatBox;\n\n\t@FXML\n\tprotected HBox playerArea;\n\n\t@FXML\n\tprotected Button startButton;\n\n\t@FXML\n\tprotected Button backButton;\n\n\tprotected PlayerConfigView player1Config;\n\tprotected PlayerConfigView player2Config;\n\n\tprivate List<DeckFormat> deckFormats;\n\n\tpublic PlayModeConfigView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/PlayModeConfigView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tformatBox.setConverter(new DeckFormatStringConverter());\n\n\t\tplayer1Config = new PlayerConfigView(PlayerConfigType.HUMAN);\n\t\tplayer2Config = new PlayerConfigView(PlayerConfigType.OPPONENT);\n\n\t\tplayerArea.getChildren().add(player1Config);\n\t\tplayerArea.getChildren().add(player2Config);\n\n\t\tstartButton.setOnAction(this);\n\t\tbackButton.setOnAction(this);\n\n\t\tformatBox.valueProperty().addListener((ChangeListener<DeckFormat>) (observableProperty, oldDeckFormat, newDeckFormat) -> {\n\t\t\tsetDeckFormats(newDeckFormat);\n\t\t});\n\t}\n\n\tprivate void setupDeckFormats() {\n\t\tObservableList<DeckFormat> deckFormatList = FXCollections.observableArrayList();\n\n\t\tfor (DeckFormat deckFormat : deckFormats) {\n\t\t\tdeckFormatList.add(deckFormat);\n\t\t}\n\n\t\tformatBox.setItems(deckFormatList);\n\t\tformatBox.getSelectionModel().selectFirst();\n\t}\n\n\tprivate void setDeckFormats(DeckFormat newDeckFormat) {\n\t\tplayer1Config.setDeckFormat(newDeckFormat);\n\t\tplayer2Config.setDeckFormat(newDeckFormat);\n\t}\n\n\t@Override\n\tpublic void handle(ActionEvent actionEvent) {\n\t\tif (actionEvent.getSource() == startButton) {\n\t\t\tGameConfig gameConfig = new GameConfig();\n\t\t\tgameConfig.setNumberOfGames(1);\n\t\t\tgameConfig.setPlayerConfig1(player1Config.getPlayerConfig());\n\t\t\tgameConfig.setPlayerConfig2(player2Config.getPlayerConfig());\n\t\t\tgameConfig.setDeckFormat(formatBox.getValue());\n\t\t\tNotificationProxy.sendNotification(GameNotification.COMMIT_PLAYMODE_CONFIG, gameConfig);\n\t\t} else if (actionEvent.getSource() == backButton) {\n\t\t\tNotificationProxy.sendNotification(GameNotification.MAIN_MENU);\n\t\t}\n\t}\n\n\tpublic void injectDecks(List<Deck> decks) {\n\t\tplayer1Config.injectDecks(decks);\n\t\tplayer2Config.injectDecks(decks);\n\t}\n\n\tpublic void injectDeckFormats(List<DeckFormat> deckFormats) {\n\t\tthis.deckFormats = deckFormats;\n\t\tsetupDeckFormats();\n\t\tplayer1Config.setDeckFormat(formatBox.getValue());\n\t\tplayer2Config.setDeckFormat(formatBox.getValue());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/config/PlayerConfigType.java",
    "content": "package net.demilich.metastone.gui.playmode.config;\n\npublic enum PlayerConfigType {\n\n\tHUMAN, OPPONENT, SIMULATION, SANDBOX\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/config/RequestDeckFormatsCommand.java",
    "content": "package net.demilich.metastone.gui.playmode.config;\n\nimport java.util.List;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.gui.deckbuilder.DeckFormatProxy;\n\npublic class RequestDeckFormatsCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tDeckFormatProxy deckFormatProxy = (DeckFormatProxy) getFacade().retrieveProxy(DeckFormatProxy.NAME);\n\n\t\tgetFacade().sendNotification(GameNotification.LOAD_DECK_FORMATS);\n\t\t\n\t\tList<DeckFormat> deckFormats = deckFormatProxy.getDeckFormats();\n\t\tgetFacade().sendNotification(GameNotification.REPLY_DECK_FORMATS, deckFormats);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/playmode/config/RequestDecksCommand.java",
    "content": "package net.demilich.metastone.gui.playmode.config;\n\nimport java.util.List;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.gui.deckbuilder.DeckProxy;\n\npublic class RequestDecksCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tDeckProxy deckProxy = (DeckProxy) getFacade().retrieveProxy(DeckProxy.NAME);\n\n\t\tgetFacade().sendNotification(GameNotification.LOAD_DECKS);\n\n\t\tList<Deck> decks = deckProxy.getDecks();\n\t\tgetFacade().sendNotification(GameNotification.REPLY_DECKS, decks);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/CardCollectionEditor.java",
    "content": "package net.demilich.metastone.gui.sandboxmode;\n\nimport javafx.beans.value.ObservableValue;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ListChangeListener.Change;\nimport javafx.collections.ObservableList;\nimport javafx.event.ActionEvent;\nimport javafx.fxml.FXML;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ListView;\nimport javafx.scene.control.TextField;\nimport javafx.scene.control.cell.TextFieldListCell;\nimport javafx.util.StringConverter;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.cards.CardType;\n\npublic class CardCollectionEditor extends SandboxEditor {\n\n\tprivate class CardStringConverter extends StringConverter<Card> {\n\n\t\t@Override\n\t\tpublic Card fromString(String arg0) {\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString(Card card) {\n\t\t\tString result = card.getName();\n\t\t\tresult += \" [\" + card.getCardType() + \"] \";\n\t\t\tresult += \"Mana: \" + card.getBaseManaCost();\n\t\t\treturn result;\n\t\t}\n\n\t}\n\n\t@FXML\n\tprivate Label cardCountLabel;\n\t@FXML\n\tprivate ListView<Card> editableListView;\n\n\t@FXML\n\tprivate ListView<Card> catalogueListView;\n\t@FXML\n\tprivate TextField filterTextfield;\n\n\t@FXML\n\tprivate Button clearFilterButton;\n\t@FXML\n\tprivate Button addCardButton;\n\n\t@FXML\n\tprivate Button removeCardButton;\n\n\tprivate ICardCollectionEditingListener listener;\n\n\tprivate int cardLimit;\n\n\tpublic CardCollectionEditor(String title, CardCollection cardCollection, ICardCollectionEditingListener listener, int cardLimit) {\n\t\tsuper(\"CardCollectionEditor.fxml\");\n\t\tthis.listener = listener;\n\t\tthis.cardLimit = cardLimit;\n\n\t\tsetTitle(title);\n\n\t\teditableListView.setCellFactory(TextFieldListCell.forListView(new CardStringConverter()));\n\t\tpopulateEditableView(cardCollection);\n\n\t\tcatalogueListView.setCellFactory(TextFieldListCell.forListView(new CardStringConverter()));\n\t\tpopulateCatalogueView(null);\n\n\t\tfilterTextfield.textProperty().addListener(this::onFilterTextChanged);\n\t\tclearFilterButton.setOnAction(actionEvent -> filterTextfield.clear());\n\n\t\tokButton.setOnAction(this::handleOkButton);\n\t\tcancelButton.setOnAction(this::handleCancelButton);\n\n\t\taddCardButton.setOnAction(this::handleAddCardButton);\n\t\tremoveCardButton.setOnAction(this::handleRemoveCardButton);\n\t}\n\n\tprivate void handleAddCardButton(ActionEvent actionEvent) {\n\t\tfor (Card selectedCard : catalogueListView.getSelectionModel().getSelectedItems()) {\n\t\t\teditableListView.getItems().add(selectedCard.clone());\n\t\t}\n\t}\n\n\tprivate void handleCancelButton(ActionEvent actionEvent) {\n\t\tthis.getScene().getWindow().hide();\n\t}\n\n\tprivate void handleEditableCardListChanged(Change<? extends Card> change) {\n\t\tint count = editableListView.getItems().size();\n\t\tcardCountLabel.setText(\"Cards in collection: \" + count + \"/\" + cardLimit);\n\t\taddCardButton.setDisable(count >= cardLimit);\n\t}\n\n\tprivate void handleOkButton(ActionEvent actionEvent) {\n\t\tCardCollection changedCollection = new CardCollection();\n\t\tfor (Card card : editableListView.getItems()) {\n\t\t\tchangedCollection.add(card);\n\t\t}\n\t\tlistener.onFinishedEditing(changedCollection);\n\t\tthis.getScene().getWindow().hide();\n\t}\n\n\tprivate void handleRemoveCardButton(ActionEvent actionEvent) {\n\t\teditableListView.getItems().remove(editableListView.getSelectionModel().getSelectedItem());\n\t}\n\n\tprivate void onFilterTextChanged(ObservableValue<? extends String> observable, String oldValue, String newValue) {\n\t\tpopulateCatalogueView(newValue);\n\t}\n\n\tprivate void populateCatalogueView(String filter) {\n\t\tObservableList<Card> data = FXCollections.observableArrayList();\n\t\tfor (Card card : CardCatalogue.getAll()) {\n\t\t\tif (card.getCardType().isCardType(CardType.HERO)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (filter == null || card.matchesFilter(filter)) {\n\t\t\t\tdata.add(card);\n\t\t\t}\n\t\t}\n\t\tcatalogueListView.setItems(data);\n\t}\n\n\tprivate void populateEditableView(CardCollection cardCollection) {\n\t\tObservableList<Card> data = FXCollections.observableArrayList();\n\t\tfor (Card card : cardCollection) {\n\t\t\tdata.add(card);\n\t\t}\n\t\teditableListView.setItems(data);\n\t\tdata.addListener(this::handleEditableCardListChanged);\n\t\thandleEditableCardListChanged(null);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/CardPanel.java",
    "content": "package net.demilich.metastone.gui.sandboxmode;\n\nimport java.io.IOException;\n\nimport javafx.event.ActionEvent;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.layout.VBox;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.logic.GameLogic;\n\npublic class CardPanel extends VBox {\n\n\t@FXML\n\tprivate Button editHandButton;\n\n\t@FXML\n\tprivate Button editDeckButton;\n\n\tprivate Player selectedPlayer;\n\n\tpublic CardPanel() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/CardPanel.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\teditHandButton.setOnAction(this::handleEditHandButton);\n\t\teditDeckButton.setOnAction(this::handleEditDeckButton);\n\t}\n\n\tprivate void handleEditDeckButton(ActionEvent actionEvent) {\n\t\tCardCollection deck = selectedPlayer.getDeck();\n\t\tCardCollectionEditor cardCollectionEditor = new CardCollectionEditor(\"Edit deck\", deck, this::onDeckFinishedEditing,\n\t\t\t\tGameLogic.MAX_DECK_SIZE);\n\t\tNotificationProxy.sendNotification(GameNotification.SHOW_MODAL_DIALOG, cardCollectionEditor);\n\t}\n\n\tprivate void handleEditHandButton(ActionEvent actionEvent) {\n\t\tCardCollection hand = selectedPlayer.getHand();\n\t\tCardCollectionEditor cardCollectionEditor = new CardCollectionEditor(\"Edit hand\", hand, this::onHandFinishedEditing,\n\t\t\t\tGameLogic.MAX_HAND_CARDS);\n\t\tNotificationProxy.sendNotification(GameNotification.SHOW_MODAL_DIALOG, cardCollectionEditor);\n\t}\n\n\tprivate void onDeckFinishedEditing(CardCollection cardCollection) {\n\t\tNotificationProxy.sendNotification(GameNotification.MODIFY_PLAYER_DECK, cardCollection);\n\t}\n\n\tprivate void onHandFinishedEditing(CardCollection cardCollection) {\n\t\tNotificationProxy.sendNotification(GameNotification.MODIFY_PLAYER_HAND, cardCollection);\n\t}\n\n\tpublic void onPlayerSelectionChanged(Player selectedPlayer) {\n\t\tthis.selectedPlayer = selectedPlayer;\n\t\teditHandButton.setDisable(selectedPlayer == null);\n\t\teditDeckButton.setDisable(selectedPlayer == null);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/EntityEditor.java",
    "content": "package net.demilich.metastone.gui.sandboxmode;\n\nimport java.util.EnumMap;\nimport java.util.Map;\n\nimport javafx.beans.property.ReadOnlyObjectWrapper;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.event.ActionEvent;\nimport javafx.fxml.FXML;\nimport javafx.scene.control.CheckBox;\nimport javafx.scene.control.TableCell;\nimport javafx.scene.control.TableColumn;\nimport javafx.scene.control.TableView;\nimport javafx.util.Callback;\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.utils.TagValueType;\nimport net.demilich.metastone.gui.common.IntegerTextField;\nimport net.demilich.metastone.utils.ICallback;\n\npublic class EntityEditor extends SandboxEditor {\n\n\tprivate class PairKeyFactory implements Callback<TableColumn.CellDataFeatures<GameTagEntry, String>, ObservableValue<String>> {\n\t\t@Override\n\t\tpublic ObservableValue<String> call(TableColumn.CellDataFeatures<GameTagEntry, String> data) {\n\t\t\treturn new ReadOnlyObjectWrapper<>(data.getValue().getName());\n\t\t}\n\t}\n\n\tprivate class PairValueCell extends TableCell<GameTagEntry, Object> {\n\t\t@Override\n\t\tprotected void updateItem(Object item, boolean empty) {\n\t\t\tsuper.updateItem(item, empty);\n\n\t\t\tif (item == null) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tGameTagEntry entry = (GameTagEntry) item;\n\t\t\tTagValueType tagValueType = entry.getValueType();\n\t\t\tAttribute tag = entry.getTag();\n\t\t\tif (tagValueType == TagValueType.INTEGER) {\n\t\t\t\tIntegerTextField numericTextfield = getNumericTextField();\n\t\t\t\tnumericTextfield.setIntValue(entry.getValueInt());\n\t\t\t\tnumericTextfield.valueProperty().addListener(new ChangeListener<Number>() {\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic void changed(ObservableValue<? extends Number> observableValue, Number oldValue, Number newValue) {\n\t\t\t\t\t\tworkingCopy.put(tag, numericTextfield.getIntValue());\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tsetGraphic(numericTextfield);\n\t\t\t\tsetText(null);\n\t\t\t} else if (tagValueType == TagValueType.BOOLEAN) {\n\t\t\t\tCheckBox checkBox = new CheckBox();\n\t\t\t\tcheckBox.setSelected(entry.getValueBool());\n\t\t\t\tcheckBox.selectedProperty().addListener(new ChangeListener<Boolean>() {\n\t\t\t\t\tpublic void changed(ObservableValue<? extends Boolean> observableValue, Boolean oldValue, Boolean newValue) {\n\t\t\t\t\t\tif (checkBox.isSelected()) {\n\t\t\t\t\t\t\tworkingCopy.put(tag, 1);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tworkingCopy.remove(tag);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tsetGraphic(checkBox);\n\t\t\t\tsetText(null);\n\t\t\t} else {\n\t\t\t\tsetGraphic(null);\n\t\t\t\tsetText(entry.getValue().toString());\n\t\t\t}\n\n\t\t}\n\t}\n\n\tprivate class PairValueFactory implements Callback<TableColumn.CellDataFeatures<GameTagEntry, Object>, ObservableValue<Object>> {\n\t\t@Override\n\t\tpublic ObservableValue<Object> call(TableColumn.CellDataFeatures<GameTagEntry, Object> data) {\n\t\t\treturn new ReadOnlyObjectWrapper<>(data.getValue());\n\t\t}\n\t}\n\n\tprivate final Map<Attribute, Object> workingCopy = new EnumMap<Attribute, Object>(Attribute.class);\n\tprivate final Entity entity;\n\n\t@FXML\n\tprivate TableView<GameTagEntry> propertiesTable;\n\n\t@FXML\n\tprivate TableColumn<GameTagEntry, String> nameColumn;\n\n\t@FXML\n\tprivate TableColumn<GameTagEntry, Object> valueColumn;\n\n\tprivate final ICallback callback;\n\n\tpublic EntityEditor(Entity entity, ICallback callback) {\n\t\tsuper(\"EntityEditor.fxml\");\n\t\tthis.entity = entity;\n\t\tthis.callback = callback;\n\t\tsetTitle(\"Edit \" + entity.getName());\n\n\t\tokButton.setOnAction(this::handleOkButton);\n\t\tcancelButton.setOnAction(this::handleCancelButton);\n\n\t\tnameColumn.setCellValueFactory(new PairKeyFactory());\n\t\tvalueColumn.setCellValueFactory(new PairValueFactory());\n\n\t\tvalueColumn.setCellFactory(new Callback<TableColumn<GameTagEntry, Object>, TableCell<GameTagEntry, Object>>() {\n\t\t\t@Override\n\t\t\tpublic TableCell<GameTagEntry, Object> call(TableColumn<GameTagEntry, Object> column) {\n\t\t\t\treturn new PairValueCell();\n\t\t\t}\n\t\t});\n\n\t\taddTagsIfMissing(entity);\n\t\tpopulateTable(entity);\n\t}\n\n\tprivate void addTagIfMissing(Entity entity, Attribute tag, Object defaultValue) {\n\t\tif (entity.hasAttribute(tag)) {\n\t\t\treturn;\n\t\t}\n\t\tentity.setAttribute(tag, defaultValue);\n\t}\n\n\tprivate void addTagsIfMissing(Entity entity) {\n\t\tswitch (entity.getEntityType()) {\n\t\tcase CARD:\n\t\t\tbreak;\n\t\tcase HERO:\n\t\t\taddTagIfMissing(entity, Attribute.ARMOR, 0);\n\t\t\tbreak;\n\t\tcase MINION:\n\t\t\taddTagIfMissing(entity, Attribute.DIVINE_SHIELD, 0);\n\t\t\taddTagIfMissing(entity, Attribute.WINDFURY, 0);\n\t\t\taddTagIfMissing(entity, Attribute.FROZEN, 0);\n\t\t\taddTagIfMissing(entity, Attribute.TEMPORARY_ATTACK_BONUS, 0);\n\t\t\taddTagIfMissing(entity, Attribute.HP_BONUS, 0);\n\t\t\taddTagIfMissing(entity, Attribute.ATTACK_BONUS, 0);\n\t\t\taddTagIfMissing(entity, Attribute.CHARGE, 0);\n\t\t\taddTagIfMissing(entity, Attribute.STEALTH, 0);\n\t\t\taddTagIfMissing(entity, Attribute.TAUNT, 0);\n\t\t\tbreak;\n\t\tcase WEAPON:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\n\t\t}\n\t}\n\n\tprivate IntegerTextField getNumericTextField() {\n\t\tIntegerTextField textField = new IntegerTextField(3);\n\t\ttextField.setMaxWidth(100);\n\t\treturn textField;\n\t}\n\n\tprivate void handleCancelButton(ActionEvent actionEvent) {\n\t\tthis.getScene().getWindow().hide();\n\t}\n\n\tprivate void handleOkButton(ActionEvent actionEvent) {\n\t\tentity.getAttributes().clear();\n\t\tfor (Attribute tag : workingCopy.keySet()) {\n\t\t\tentity.setAttribute(tag, workingCopy.get(tag));\n\t\t}\n\t\tthis.getScene().getWindow().hide();\n\t\tif (callback != null) {\n\t\t\tcallback.call(entity);\n\t\t}\n\t}\n\n\tprivate void populateTable(Entity entity) {\n\t\tMap<Attribute, Object> tags = entity.getAttributes();\n\t\tObservableList<GameTagEntry> data = FXCollections.observableArrayList();\n\n\t\tfor (Attribute tag : tags.keySet()) {\n\t\t\tObject value = tags.get(tag);\n\t\t\tworkingCopy.put(tag, value);\n\n\t\t\tGameTagEntry entry = new GameTagEntry(tag, value);\n\t\t\tdata.add(entry);\n\n\t\t}\n\t\tpropertiesTable.getItems().setAll(data);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/GameTagEntry.java",
    "content": "package net.demilich.metastone.gui.sandboxmode;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.utils.GameTagUtils;\nimport net.demilich.metastone.game.utils.TagValueType;\n\npublic class GameTagEntry {\n\n\tprivate final Attribute tag;\n\tprivate final TagValueType valueType;\n\tprivate final Object value;\n\n\tpublic GameTagEntry(Attribute tag, Object value) {\n\t\tthis.tag = tag;\n\t\tthis.value = value;\n\t\tthis.valueType = GameTagUtils.getTagValueType(tag);\n\t}\n\n\tpublic String getName() {\n\t\treturn GameTagUtils.getTagName(getTag());\n\t}\n\n\tpublic Attribute getTag() {\n\t\treturn tag;\n\t}\n\n\tpublic Object getValue() {\n\t\treturn value;\n\t}\n\n\tpublic boolean getValueBool() {\n\t\treturn (int) value >= 1;\n\t}\n\n\tpublic int getValueInt() {\n\t\treturn (int) value;\n\t}\n\n\tpublic TagValueType getValueType() {\n\t\treturn valueType;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/ICardCollectionEditingListener.java",
    "content": "package net.demilich.metastone.gui.sandboxmode;\n\nimport net.demilich.metastone.game.cards.CardCollection;\n\npublic interface ICardCollectionEditingListener {\n\n\tvoid onFinishedEditing(CardCollection cardCollection);\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/MinionPanel.java",
    "content": "package net.demilich.metastone.gui.sandboxmode;\n\nimport java.io.IOException;\n\nimport javafx.beans.value.ObservableValue;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.event.ActionEvent;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.TextField;\nimport javafx.scene.layout.VBox;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.gui.sandboxmode.actions.KillAction;\nimport net.demilich.metastone.gui.sandboxmode.actions.SilenceAction;\n\npublic class MinionPanel extends VBox {\n\n\t@FXML\n\tprivate ComboBox<MinionCard> minionComboBox;\n\t@FXML\n\tprivate TextField filterMinionsTextField;\n\t@FXML\n\tprivate Button spawnMinionButton;\n\t@FXML\n\tprivate Button killMinionButton;\n\t@FXML\n\tprivate Button silenceButton;\n\n\tpublic MinionPanel() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/MinionPanel.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tpopulateMinions(null);\n\t\tfilterMinionsTextField.textProperty().addListener(this::onMinionFilterChanged);\n\t\tspawnMinionButton.setOnAction(this::handleSpawnMinionButton);\n\t\tkillMinionButton.setOnAction(this::handleKillMinionButton);\n\t\tsilenceButton.setOnAction(this::handleSilenceButton);\n\t}\n\n\tprivate void handleKillMinionButton(ActionEvent actionEvent) {\n\t\tKillAction killAction = new KillAction();\n\t\tNotificationProxy.sendNotification(GameNotification.PERFORM_ACTION, killAction);\n\t}\n\n\tprivate void handleSilenceButton(ActionEvent actionEvent) {\n\t\tSilenceAction silenceAction = new SilenceAction();\n\t\tNotificationProxy.sendNotification(GameNotification.PERFORM_ACTION, silenceAction);\n\t}\n\n\tprivate void handleSpawnMinionButton(ActionEvent actionEvent) {\n\t\tMinionCard selectedMinion = minionComboBox.getSelectionModel().getSelectedItem();\n\t\tNotificationProxy.sendNotification(GameNotification.SPAWN_MINION, selectedMinion);\n\t}\n\n\tprivate void onMinionFilterChanged(ObservableValue<? extends String> observable, String oldValue, String newValue) {\n\t\tpopulateMinions(newValue);\n\t}\n\n\tprivate void populateMinions(String filter) {\n\t\tObservableList<MinionCard> data = FXCollections.observableArrayList();\n\t\tfor (Card card : CardCatalogue.getAll()) {\n\t\t\tif (!card.getCardType().isCardType(CardType.MINION)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!card.matchesFilter(filter)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tMinionCard minionCard = (MinionCard) card;\n\t\t\tdata.add(minionCard);\n\t\t}\n\n\t\tminionComboBox.setItems(data);\n\t\tminionComboBox.getSelectionModel().selectFirst();\n\n\t\tspawnMinionButton.setDisable(minionComboBox.getSelectionModel().getSelectedItem() == null);\n\t}\n\n\tpublic void setContext(GameContext context) {\n\t\tkillMinionButton.setDisable(true);\n\t\tfor (Player player : context.getPlayers()) {\n\t\t\tif (context.getSummonCount(player) > 0) {\n\t\t\t\tkillMinionButton.setDisable(false);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tsilenceButton.setDisable(killMinionButton.isDisabled());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/PlayerPanel.java",
    "content": "package net.demilich.metastone.gui.sandboxmode;\n\nimport java.io.IOException;\n\nimport javafx.beans.value.ObservableValue;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.event.ActionEvent;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.layout.VBox;\nimport javafx.util.StringConverter;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.gui.sandboxmode.actions.EditEntityAction;\nimport net.demilich.metastone.gui.sandboxmode.actions.SetManaAction;\nimport net.demilich.metastone.gui.sandboxmode.actions.SetMaxManaAction;\n\npublic class PlayerPanel extends VBox {\n\n\tprivate class PlayerStringConverter extends StringConverter<Player> {\n\n\t\t@Override\n\t\tpublic Player fromString(String arg0) {\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString(Player player) {\n\t\t\treturn player.getName();\n\t\t}\n\n\t}\n\n\t@FXML\n\tprivate ComboBox<Player> playerComboBox;\n\n\t@FXML\n\tprivate Button editEntityButton;\n\t@FXML\n\tprivate ComboBox<Integer> currentManaBox;\n\n\t@FXML\n\tprivate ComboBox<Integer> maxManaBox;\n\n\tprivate boolean ignoreManaChange;\n\n\tprivate Player selectedPlayer;\n\n\tpublic PlayerPanel() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/PlayerPanel.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tplayerComboBox.setConverter(new PlayerStringConverter());\n\t\tplayerComboBox.getSelectionModel().selectedItemProperty().addListener(this::handlePlayerChanged);\n\n\t\tcurrentManaBox.getSelectionModel().selectedItemProperty().addListener(this::handleCurrentManaChanged);\n\t\tmaxManaBox.getSelectionModel().selectedItemProperty().addListener(this::handleMaxManaChanged);\n\n\t\teditEntityButton.setOnAction(this::handleEditEntityButton);\n\t}\n\n\tprivate void handleCurrentManaChanged(ObservableValue<? extends Number> ov, Number oldIndex, Number newIndex) {\n\t\tif (selectedPlayer == null || ignoreManaChange) {\n\t\t\treturn;\n\t\t}\n\t\tInteger newValue = currentManaBox.getSelectionModel().getSelectedItem();\n\t\tSetManaAction setManaAction = new SetManaAction(selectedPlayer.getId(), newValue);\n\t\tNotificationProxy.sendNotification(GameNotification.PERFORM_ACTION, setManaAction);\n\t}\n\n\tprivate void handleEditEntityButton(ActionEvent actionEvent) {\n\t\tEditEntityAction editAction = new EditEntityAction();\n\t\tNotificationProxy.sendNotification(GameNotification.PERFORM_ACTION, editAction);\n\t}\n\n\tprivate void handleMaxManaChanged(ObservableValue<? extends Number> ov, Number oldIndex, Number newIndex) {\n\t\tif (selectedPlayer == null || ignoreManaChange) {\n\t\t\treturn;\n\t\t}\n\t\tInteger newValue = maxManaBox.getSelectionModel().getSelectedItem();\n\t\tSetMaxManaAction setMaxManaAction = new SetMaxManaAction(selectedPlayer.getId(), newValue);\n\t\tNotificationProxy.sendNotification(GameNotification.PERFORM_ACTION, setMaxManaAction);\n\t}\n\n\tprivate void handlePlayerChanged(ObservableValue<? extends Player> ov, Player oldSelected, Player newSelected) {\n\t\tselectedPlayer = newSelected;\n\t\tNotificationProxy.sendNotification(GameNotification.SELECT_PLAYER, selectedPlayer);\n\t\tpopulateManaBoxes();\n\t}\n\n\tprivate void populateManaBoxes() {\n\t\tignoreManaChange = true;\n\t\tcurrentManaBox.getItems().clear();\n\t\tfor (int i = 0; i <= GameLogic.MAX_MANA; i++) {\n\t\t\tcurrentManaBox.getItems().add(i);\n\t\t}\n\t\tcurrentManaBox.autosize();\n\t\tmaxManaBox.getItems().clear();\n\t\tfor (int i = 0; i <= GameLogic.MAX_MANA; i++) {\n\t\t\tmaxManaBox.getItems().add(i);\n\t\t}\n\t\tcurrentManaBox.getSelectionModel().select(selectedPlayer.getMana());\n\t\tmaxManaBox.getSelectionModel().select(selectedPlayer.getMaxMana());\n\t\tignoreManaChange = false;\n\t}\n\n\tpublic void setContext(GameContext context) {\n\t\tif (playerComboBox.getSelectionModel().isEmpty()) {\n\t\t\tObservableList<Player> players = FXCollections.observableArrayList();\n\t\t\tplayers.addAll(context.getPlayers());\n\t\t\tplayerComboBox.setItems(players);\n\t\t\tplayerComboBox.getSelectionModel().selectFirst();\n\t\t}\n\t\tpopulateManaBoxes();\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/SandboxEditor.java",
    "content": "package net.demilich.metastone.gui.sandboxmode;\n\nimport java.io.IOException;\n\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.BorderPane;\n\npublic class SandboxEditor extends BorderPane {\n\n\t@FXML\n\tprotected Label headerLabel;\n\n\t@FXML\n\tprotected Button okButton;\n\n\t@FXML\n\tprotected Button cancelButton;\n\n\tpublic SandboxEditor(String fxmlFile) {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/\" + fxmlFile));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\t}\n\n\tprotected void setTitle(String title) {\n\t\theaderLabel.setText(title);\n\t}\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/SandboxModeConfigView.java",
    "content": "package net.demilich.metastone.gui.sandboxmode;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport javafx.beans.value.ChangeListener;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.event.ActionEvent;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.gui.common.DeckFormatStringConverter;\nimport net.demilich.metastone.game.gameconfig.GameConfig;\nimport net.demilich.metastone.gui.gameconfig.PlayerConfigView;\nimport net.demilich.metastone.gui.playmode.config.PlayerConfigType;\n\npublic class SandboxModeConfigView extends BorderPane {\n\n\t@FXML\n\tprotected ComboBox<DeckFormat> formatBox;\n\n\t@FXML\n\tprotected HBox playerArea;\n\n\t@FXML\n\tprotected Button startButton;\n\n\t@FXML\n\tprotected Button backButton;\n\n\tprotected PlayerConfigView player1Config;\n\tprotected PlayerConfigView player2Config;\n\n\tprivate List<DeckFormat> deckFormats;\n\n\tpublic SandboxModeConfigView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/SandboxModeConfigView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tformatBox.setConverter(new DeckFormatStringConverter());\n\n\t\tplayer1Config = new PlayerConfigView(PlayerConfigType.SANDBOX);\n\t\tplayer2Config = new PlayerConfigView(PlayerConfigType.SANDBOX);\n\n\t\tplayerArea.getChildren().add(player1Config);\n\t\tplayerArea.getChildren().add(player2Config);\n\n\t\tstartButton.setOnAction(this::handleStartButton);\n\t\tbackButton.setOnAction(this::handleBackButton);\n\n\t\tformatBox.valueProperty().addListener((ChangeListener<DeckFormat>) (observableProperty, oldDeckFormat, newDeckFormat) -> {\n\t\t\tsetDeckFormats(newDeckFormat);\n\t\t});\n\t}\n\n\tprivate void setupDeckFormats() {\n\t\tObservableList<DeckFormat> deckFormatList = FXCollections.observableArrayList();\n\n\t\tfor (DeckFormat deckFormat : deckFormats) {\n\t\t\tdeckFormatList.add(deckFormat);\n\t\t}\n\n\t\tformatBox.setItems(deckFormatList);\n\t\tformatBox.getSelectionModel().selectFirst();\n\t}\n\n\tprivate void setDeckFormats(DeckFormat newDeckFormat) {\n\t\tplayer1Config.setDeckFormat(newDeckFormat);\n\t\tplayer2Config.setDeckFormat(newDeckFormat);\n\t}\n\n\tprivate void handleBackButton(ActionEvent event) {\n\t\tNotificationProxy.sendNotification(GameNotification.MAIN_MENU);\n\t}\n\n\tprivate void handleStartButton(ActionEvent event) {\n\t\tGameConfig gameConfig = new GameConfig();\n\t\tgameConfig.setNumberOfGames(1);\n\t\tgameConfig.setPlayerConfig1(player1Config.getPlayerConfig());\n\t\tgameConfig.setPlayerConfig2(player2Config.getPlayerConfig());\n\t\tgameConfig.setDeckFormat(formatBox.getValue());\n\t\tNotificationProxy.sendNotification(GameNotification.COMMIT_SANDBOXMODE_CONFIG, gameConfig);\n\t}\n\n\tpublic void injectDecks(List<Deck> decks) {\n\t\tplayer1Config.injectDecks(decks);\n\t\tplayer2Config.injectDecks(decks);\n\t}\n\n\tpublic void injectDeckFormats(List<DeckFormat> deckFormats) {\n\t\tthis.deckFormats = deckFormats;\n\t\tsetupDeckFormats();\n\t\tplayer1Config.setDeckFormat(formatBox.getValue());\n\t\tplayer2Config.setDeckFormat(formatBox.getValue());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/SandboxModeMediator.java",
    "content": "package net.demilich.metastone.gui.sandboxmode;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.nittygrittymvc.Mediator;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport javafx.application.Platform;\nimport javafx.event.EventHandler;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.behaviour.human.HumanActionOptions;\nimport net.demilich.metastone.game.behaviour.human.HumanTargetOptions;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\n\npublic class SandboxModeMediator extends Mediator<GameNotification>implements EventHandler<KeyEvent> {\n\n\tpublic static final String NAME = \"SandboxModeMediator\";\n\n\tprivate final SandboxModeConfigView configView;\n\tprivate final SandboxModeView view;\n\n\tpublic SandboxModeMediator() {\n\t\tsuper(NAME);\n\t\tconfigView = new SandboxModeConfigView();\n\t\tview = new SandboxModeView();\n\t}\n\n\t@Override\n\tpublic void handle(KeyEvent keyEvent) {\n\t\tif (keyEvent.getCode() != KeyCode.ESCAPE) {\n\t\t\treturn;\n\t\t}\n\n\t\tview.disableTargetSelection();\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Override\n\tpublic void handleNotification(final INotification<GameNotification> notification) {\n\n\t\tswitch (notification.getId()) {\n\t\tcase GAME_STATE_LATE_UPDATE:\n\t\tcase UPDATE_SANDBOX_STATE:\n\t\t\tGameContext context = (GameContext) notification.getBody();\n\t\t\tPlatform.runLater(() -> view.updateSandbox(context));\n\t\t\tbreak;\n\t\tcase GAME_STATE_UPDATE:\n\t\t\tGameContext context2 = (GameContext) notification.getBody();\n\t\t\tPlatform.runLater(() -> view.showAnimations(context2));\n\t\t\tbreak;\n\t\tcase SELECT_TARGET:\n\t\t\tHumanTargetOptions targetOptions = (HumanTargetOptions) notification.getBody();\n\t\t\tPlatform.runLater(() -> view.getBoardView().enableTargetSelection(targetOptions));\n\t\t\tbreak;\n\t\tcase HUMAN_PROMPT_FOR_ACTION:\n\t\t\tHumanActionOptions actionOptions = (HumanActionOptions) notification.getBody();\n\t\t\tPlatform.runLater(() -> view.getActionPromptView().setActions(actionOptions));\n\t\t\tbreak;\n\t\tcase HUMAN_PROMPT_FOR_TARGET:\n\t\t\tHumanTargetOptions options = (HumanTargetOptions) notification.getBody();\n\t\t\tPlatform.runLater(() -> view.enableTargetSelection(options));\n\t\t\tbreak;\n\t\tcase SELECT_PLAYER:\n\t\t\tview.onPlayerSelectionChanged((Player) notification.getBody());\n\t\t\tbreak;\n\t\tcase COMMIT_SANDBOXMODE_CONFIG:\n\t\t\tgetFacade().sendNotification(GameNotification.SHOW_VIEW, view);\n\t\t\tview.setOnKeyPressed(this);\n\t\t\tgetFacade().sendNotification(GameNotification.CREATE_NEW_SANDBOX, notification.getBody());\n\t\t\tbreak;\n\t\tcase REPLY_DECKS:\n\t\t\tconfigView.injectDecks((List<Deck>) notification.getBody());\n\t\t\tbreak;\n\t\tcase REPLY_DECK_FORMATS:\n\t\t\tconfigView.injectDeckFormats((List<DeckFormat>) notification.getBody());\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t@Override\n\tpublic List<GameNotification> listNotificationInterests() {\n\t\tList<GameNotification> notificationInterests = new ArrayList<GameNotification>();\n\t\tnotificationInterests.add(GameNotification.UPDATE_SANDBOX_STATE);\n\t\tnotificationInterests.add(GameNotification.SELECT_TARGET);\n\t\tnotificationInterests.add(GameNotification.HUMAN_PROMPT_FOR_ACTION);\n\t\tnotificationInterests.add(GameNotification.HUMAN_PROMPT_FOR_TARGET);\n\t\tnotificationInterests.add(GameNotification.GAME_STATE_UPDATE);\n\t\tnotificationInterests.add(GameNotification.GAME_STATE_LATE_UPDATE);\n\t\tnotificationInterests.add(GameNotification.SELECT_PLAYER);\n\t\tnotificationInterests.add(GameNotification.COMMIT_SANDBOXMODE_CONFIG);\n\t\tnotificationInterests.add(GameNotification.REPLY_DECK_FORMATS);\n\t\tnotificationInterests.add(GameNotification.REPLY_DECKS);\n\t\treturn notificationInterests;\n\t}\n\n\t@Override\n\tpublic void onRegister() {\n\t\tgetFacade().sendNotification(GameNotification.SHOW_VIEW, configView);\n\t\tgetFacade().sendNotification(GameNotification.REQUEST_DECKS);\n\t\tgetFacade().sendNotification(GameNotification.REQUEST_DECK_FORMATS);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/SandboxModeView.java",
    "content": "package net.demilich.metastone.gui.sandboxmode;\n\nimport java.io.IOException;\n\nimport javafx.event.ActionEvent;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.layout.VBox;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.behaviour.human.HumanTargetOptions;\nimport net.demilich.metastone.gui.IconFactory;\nimport net.demilich.metastone.gui.playmode.GameBoardView;\nimport net.demilich.metastone.gui.playmode.HumanActionPromptView;\nimport net.demilich.metastone.gui.playmode.LoadingBoardView;\n\npublic class SandboxModeView extends BorderPane {\n\n\t@FXML\n\tprivate Button backButton;\n\t@FXML\n\tprivate Button playButton;\n\t@FXML\n\tprivate VBox sidebar;\n\n\t@FXML\n\tprivate Pane navigationPane;\n\n\tprivate final GameBoardView boardView;\n\tprivate final ToolboxView toolboxView;\n\tprivate final HumanActionPromptView actionPromptView;\n\tprivate final LoadingBoardView loadingBoardView;\n\tprivate boolean firstUpdate = true;\n\n\tpublic SandboxModeView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/SandboxModeView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tboardView = new GameBoardView();\n\n\t\tloadingBoardView = new LoadingBoardView();\n\t\tloadingBoardView.setScaleX(0.9);\n\t\tloadingBoardView.setScaleY(0.9);\n\t\tloadingBoardView.setScaleZ(0.9);\n\t\tsetCenter(loadingBoardView);\n\n\t\ttoolboxView = new ToolboxView();\n\t\tactionPromptView = new HumanActionPromptView();\n\n\t\tbackButton.setOnAction(actionEvent -> NotificationProxy.sendNotification(GameNotification.MAIN_MENU));\n\t\tplayButton.setOnAction(this::startPlayMode);\n\n\t\tsidebar.getChildren().setAll(toolboxView, navigationPane);\n\t}\n\n\tpublic void disableTargetSelection() {\n\t\tboardView.disableTargetSelection();\n\t\tactionPromptView.setVisible(true);\n\t}\n\n\tpublic void enableTargetSelection(HumanTargetOptions targetOptions) {\n\t\tboardView.enableTargetSelection(targetOptions);\n\t}\n\n\tpublic HumanActionPromptView getActionPromptView() {\n\t\treturn actionPromptView;\n\t}\n\n\tpublic GameBoardView getBoardView() {\n\t\treturn boardView;\n\t}\n\n\tpublic void onPlayerSelectionChanged(Player selectedPlayer) {\n\t\ttoolboxView.onPlayerSelectionChanged(selectedPlayer);\n\t}\n\n\tpublic void showAnimations(GameContext context) {\n\t\tgetBoardView().showAnimations(context);\n\t}\n\n\tprivate void startPlayMode(ActionEvent actionEvent) {\n\t\tsidebar.getChildren().setAll(getActionPromptView(), navigationPane);\n\t\tbackButton.setVisible(false);\n\t\tplayButton.setText(\"Stop\");\n\t\tImageView buttonGraphic = (ImageView) playButton.getGraphic();\n\t\tbuttonGraphic.setImage(new Image(IconFactory.getImageUrl(\"ui/pause_icon.png\")));\n\t\tplayButton.setOnAction(this::stopPlayMode);\n\t\tNotificationProxy.sendNotification(GameNotification.START_PLAY_SANDBOX);\n\t}\n\n\tprivate void stopPlayMode(ActionEvent actionEvent) {\n\t\tsidebar.getChildren().setAll(toolboxView, navigationPane);\n\t\tbackButton.setVisible(true);\n\t\tplayButton.setText(\"Play\");\n\t\tImageView buttonGraphic = (ImageView) playButton.getGraphic();\n\t\tbuttonGraphic.setImage(new Image(IconFactory.getImageUrl(\"ui/play_icon.png\")));\n\t\tplayButton.setOnAction(this::startPlayMode);\n\t\tNotificationProxy.sendNotification(GameNotification.STOP_PLAY_SANDBOX);\n\t}\n\n\tpublic void updateSandbox(GameContext context) {\n\t\tif (firstUpdate) {\n\t\t\tsetCenter(getBoardView());\n\t\t\tfirstUpdate = false;\n\t\t}\n\n\t\tgetBoardView().updateGameState(context);\n\n\t\tif (toolboxView.getParent() != null) {\n\t\t\ttoolboxView.setContext(context);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/SandboxProxy.java",
    "content": "package net.demilich.metastone.gui.sandboxmode;\n\nimport net.demilich.nittygrittymvc.Proxy;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\n\npublic class SandboxProxy extends Proxy<GameNotification> {\n\n\tpublic static final String NAME = \"SandboxProxy\";\n\n\tprivate GameContext sandbox;\n\tprivate Player selectedPlayer;\n\n\tpublic SandboxProxy() {\n\t\tsuper(NAME);\n\t}\n\n\tpublic GameContext getSandbox() {\n\t\treturn sandbox;\n\t}\n\n\tpublic Player getSelectedPlayer() {\n\t\treturn selectedPlayer;\n\t}\n\n\tpublic void setSandbox(GameContext sandbox) {\n\t\tthis.sandbox = sandbox;\n\t}\n\n\tpublic void setSelectedPlayer(Player selectedPlayer) {\n\t\tthis.selectedPlayer = selectedPlayer;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/ToolboxView.java",
    "content": "package net.demilich.metastone.gui.sandboxmode;\n\nimport java.io.IOException;\n\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Separator;\nimport javafx.scene.control.ToolBar;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\n\npublic class ToolboxView extends ToolBar {\n\n\tprivate final PlayerPanel playerPanel;\n\tprivate final CardPanel cardPanel;\n\tprivate final MinionPanel minionPanel;\n\n\tpublic ToolboxView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/ToolboxView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tplayerPanel = new PlayerPanel();\n\t\tgetItems().add(playerPanel);\n\t\tgetItems().add(new Separator());\n\t\tcardPanel = new CardPanel();\n\t\tgetItems().add(cardPanel);\n\t\tgetItems().add(new Separator());\n\t\tminionPanel = new MinionPanel();\n\t\tgetItems().add(minionPanel);\n\t}\n\n\tpublic void onPlayerSelectionChanged(Player selectedPlayer) {\n\t\tcardPanel.onPlayerSelectionChanged(selectedPlayer);\n\t}\n\n\tpublic void setContext(GameContext context) {\n\t\tplayerPanel.setContext(context);\n\t\tminionPanel.setContext(context);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/actions/EditEntityAction.java",
    "content": "package net.demilich.metastone.gui.sandboxmode.actions;\n\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.actions.ActionType;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.targeting.TargetSelection;\nimport net.demilich.metastone.gui.sandboxmode.EntityEditor;\n\npublic class EditEntityAction extends GameAction {\n\n\tpublic EditEntityAction() {\n\t\tsetTargetRequirement(TargetSelection.ANY);\n\t\tsetActionType(ActionType.SYSTEM);\n\t}\n\n\t@Override\n\tpublic void execute(GameContext context, int playerId) {\n\t\tEntity entity = context.resolveSingleTarget(getTargetKey());\n\t\tEntityEditor editor = new EntityEditor(entity,\n\t\t\t\tresult -> NotificationProxy.sendNotification(GameNotification.UPDATE_SANDBOX_STATE, context));\n\t\tNotificationProxy.sendNotification(GameNotification.SHOW_MODAL_DIALOG, editor);\n\t}\n\n\t@Override\n\tpublic String getPromptText() {\n\t\treturn \"[Edit entity]\";\n\t}\n\n\t@Override\n\tpublic boolean isSameActionGroup(GameAction anotherAction) {\n\t\treturn false;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/actions/KillAction.java",
    "content": "package net.demilich.metastone.gui.sandboxmode.actions;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.actions.ActionType;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class KillAction extends GameAction {\n\n\tpublic KillAction() {\n\t\tsetTargetRequirement(TargetSelection.MINIONS);\n\t\tsetActionType(ActionType.SYSTEM);\n\t}\n\n\t@Override\n\tpublic void execute(GameContext context, int playerId) {\n\t\tActor target = (Actor) context.resolveSingleTarget(getTargetKey());\n\t\tcontext.getLogic().markAsDestroyed(target);\n\t}\n\n\t@Override\n\tpublic String getPromptText() {\n\t\treturn \"[Kill]\";\n\t}\n\n\t@Override\n\tpublic boolean isSameActionGroup(GameAction anotherAction) {\n\t\treturn anotherAction instanceof KillAction;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/actions/SetManaAction.java",
    "content": "package net.demilich.metastone.gui.sandboxmode.actions;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class SetManaAction extends GameAction {\n\n\tprivate final int targetPlayerId;\n\tprivate final int mana;\n\n\tpublic SetManaAction(int playerId, int mana) {\n\t\tthis.targetPlayerId = playerId;\n\t\tthis.mana = mana;\n\t\tsetTargetRequirement(TargetSelection.NONE);\n\t}\n\n\t@Override\n\tpublic void execute(GameContext context, int playerId) {\n\t\tPlayer player = context.getPlayer(targetPlayerId);\n\t\tplayer.setMana(mana);\n\t}\n\n\t@Override\n\tpublic String getPromptText() {\n\t\treturn \"[SetMana]\";\n\t}\n\n\t@Override\n\tpublic boolean isSameActionGroup(GameAction anotherAction) {\n\t\treturn false;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/actions/SetMaxManaAction.java",
    "content": "package net.demilich.metastone.gui.sandboxmode.actions;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class SetMaxManaAction extends GameAction {\n\n\tprivate final int targetPlayerId;\n\tprivate final int mana;\n\n\tpublic SetMaxManaAction(int playerId, int mana) {\n\t\tthis.targetPlayerId = playerId;\n\t\tthis.mana = mana;\n\t\tsetTargetRequirement(TargetSelection.NONE);\n\t}\n\n\t@Override\n\tpublic void execute(GameContext context, int playerId) {\n\t\tPlayer player = context.getPlayer(targetPlayerId);\n\t\tplayer.setMaxMana(mana);\n\t}\n\n\t@Override\n\tpublic String getPromptText() {\n\t\treturn \"[SetMana]\";\n\t}\n\n\t@Override\n\tpublic boolean isSameActionGroup(GameAction anotherAction) {\n\t\treturn false;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/actions/SilenceAction.java",
    "content": "package net.demilich.metastone.gui.sandboxmode.actions;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.actions.ActionType;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class SilenceAction extends GameAction {\n\n\tpublic SilenceAction() {\n\t\tsetTargetRequirement(TargetSelection.MINIONS);\n\t\tsetActionType(ActionType.SYSTEM);\n\t}\n\n\t@Override\n\tpublic void execute(GameContext context, int playerId) {\n\t\tMinion target = (Minion) context.resolveSingleTarget(getTargetKey());\n\t\tcontext.getLogic().silence(playerId, target);\n\t}\n\n\t@Override\n\tpublic String getPromptText() {\n\t\treturn \"[Silence]\";\n\t}\n\n\t@Override\n\tpublic boolean isSameActionGroup(GameAction anotherAction) {\n\t\treturn anotherAction instanceof SilenceAction;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/commands/CreateNewSandboxCommand.java",
    "content": "package net.demilich.metastone.gui.sandboxmode.commands;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.behaviour.DoNothingBehaviour;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.game.gameconfig.GameConfig;\nimport net.demilich.metastone.game.gameconfig.PlayerConfig;\nimport net.demilich.metastone.gui.playmode.GameContextVisualizable;\nimport net.demilich.metastone.gui.sandboxmode.SandboxProxy;\n\npublic class CreateNewSandboxCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tThread thread = new Thread(new Runnable() {\n\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tGameConfig gameConfig = (GameConfig) notification.getBody();\n\t\t\t\tSandboxProxy sandboxProxy = (SandboxProxy) getFacade().retrieveProxy(SandboxProxy.NAME);\n\n\t\t\t\tPlayerConfig player1Config = gameConfig.getPlayerConfig1();\n\t\t\t\tplayer1Config.setName(\"Player 1\");\n\t\t\t\tPlayer player1 = new Player(player1Config);\n\t\t\t\tplayer1.setBehaviour(new DoNothingBehaviour());\n\n\t\t\t\tPlayerConfig player2Config = gameConfig.getPlayerConfig2();\n\t\t\t\tplayer2Config.setName(\"Player 2\");\n\t\t\t\tPlayer player2 = new Player(player2Config);\n\t\t\t\tplayer2.setBehaviour(new DoNothingBehaviour());\n\t\t\t\t\n\t\t\t\tDeckFormat deckFormat = gameConfig.getDeckFormat();\n\n\t\t\t\tGameContext sandbox = new GameContextVisualizable(player1, player2, new GameLogic(), deckFormat);\n\t\t\t\tsandboxProxy.setSandbox(sandbox);\n\t\t\t\tsendNotification(GameNotification.UPDATE_SANDBOX_STATE, sandbox);\n\n\t\t\t\tplayer1.setBehaviour(player1Config.getBehaviour());\n\t\t\t\tplayer2.setBehaviour(player2Config.getBehaviour());\n\t\t\t\tsandbox.setIgnoreEvents(true);\n\t\t\t\tsandbox.play();\n\n\t\t\t}\n\t\t});\n\t\tthread.setDaemon(true);\n\t\tthread.setUncaughtExceptionHandler((t, exception) -> exception.printStackTrace());\n\t\tthread.start();\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/commands/ModifyPlayerDeckCommand.java",
    "content": "package net.demilich.metastone.gui.sandboxmode.commands;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.gui.sandboxmode.SandboxProxy;\n\npublic class ModifyPlayerDeckCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tSandboxProxy sandboxProxy = (SandboxProxy) getFacade().retrieveProxy(SandboxProxy.NAME);\n\n\t\tPlayer player = sandboxProxy.getSelectedPlayer();\n\t\tCardCollection modifiedDeck = (CardCollection) notification.getBody();\n\t\tGameContext context = sandboxProxy.getSandbox();\n\n\t\tfor (Card card : player.getDeck().toList()) {\n\t\t\tcontext.getLogic().removeCardFromDeck(player.getId(), card);\n\t\t}\n\t\tfor (Card card : modifiedDeck) {\n\t\t\tcontext.getLogic().shuffleToDeck(player, card);\n\t\t}\n\n\t\tsendNotification(GameNotification.UPDATE_SANDBOX_STATE, sandboxProxy.getSandbox());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/commands/ModifyPlayerHandCommand.java",
    "content": "package net.demilich.metastone.gui.sandboxmode.commands;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.gui.sandboxmode.SandboxProxy;\n\npublic class ModifyPlayerHandCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tSandboxProxy sandboxProxy = (SandboxProxy) getFacade().retrieveProxy(SandboxProxy.NAME);\n\n\t\tPlayer player = sandboxProxy.getSelectedPlayer();\n\t\tCardCollection modifiedHand = (CardCollection) notification.getBody();\n\t\tGameContext context = sandboxProxy.getSandbox();\n\n\t\tfor (Card card : player.getHand().toList()) {\n\t\t\tcontext.getLogic().removeCard(player.getId(), card);\n\t\t}\n\t\tfor (Card card : modifiedHand) {\n\t\t\tcontext.getLogic().receiveCard(player.getId(), card);\n\t\t}\n\n\t\tsendNotification(GameNotification.UPDATE_SANDBOX_STATE, sandboxProxy.getSandbox());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/commands/PerformActionCommand.java",
    "content": "package net.demilich.metastone.gui.sandboxmode.commands;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.behaviour.human.ActionGroup;\nimport net.demilich.metastone.game.behaviour.human.HumanTargetOptions;\nimport net.demilich.metastone.game.logic.ActionLogic;\nimport net.demilich.metastone.game.targeting.TargetSelection;\nimport net.demilich.metastone.gui.sandboxmode.SandboxProxy;\n\npublic class PerformActionCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tSandboxProxy sandboxProxy = (SandboxProxy) getFacade().retrieveProxy(SandboxProxy.NAME);\n\t\tGameAction gameAction = (GameAction) notification.getBody();\n\n\t\tActionLogic actionLogic = new ActionLogic();\n\t\tGameContext context = sandboxProxy.getSandbox();\n\t\tPlayer selectedPlayer = sandboxProxy.getSelectedPlayer();\n\t\tList<GameAction> rolledOutActions = new ArrayList<GameAction>();\n\t\tactionLogic.rollout(gameAction, context, selectedPlayer, rolledOutActions);\n\t\tif (rolledOutActions.isEmpty()) {\n\t\t\treturn;\n\t\t}\n\t\tif (gameAction.getTargetRequirement() != TargetSelection.NONE && gameAction.getTargetRequirement() != TargetSelection.AUTO) {\n\t\t\tActionGroup actionGroup = new ActionGroup(rolledOutActions.get(0));\n\t\t\tfor (GameAction rolledAction : rolledOutActions) {\n\t\t\t\tactionGroup.add(rolledAction);\n\t\t\t}\n\t\t\tHumanTargetOptions targetOptions = new HumanTargetOptions(this::performAction, context, selectedPlayer.getId(), actionGroup);\n\t\t\tsendNotification(GameNotification.SELECT_TARGET, targetOptions);\n\t\t} else {\n\t\t\tperformAction(gameAction);\n\t\t}\n\n\t}\n\n\tprivate void performAction(GameAction action) {\n\t\tSandboxProxy sandboxProxy = (SandboxProxy) getFacade().retrieveProxy(SandboxProxy.NAME);\n\t\tGameContext context = sandboxProxy.getSandbox();\n\t\tPlayer selectedPlayer = sandboxProxy.getSelectedPlayer();\n\t\taction.setSource(selectedPlayer.getReference());\n\n\t\tcontext.getLogic().performGameAction(selectedPlayer.getId(), action);\n\t\tsendNotification(GameNotification.UPDATE_SANDBOX_STATE, context);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/commands/SelectPlayerCommand.java",
    "content": "package net.demilich.metastone.gui.sandboxmode.commands;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.gui.sandboxmode.SandboxProxy;\n\npublic class SelectPlayerCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tPlayer player = (Player) notification.getBody();\n\t\tSandboxProxy sandboxProxy = (SandboxProxy) getFacade().retrieveProxy(SandboxProxy.NAME);\n\n\t\tsandboxProxy.setSelectedPlayer(player);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/commands/SpawnMinionCommand.java",
    "content": "package net.demilich.metastone.gui.sandboxmode.commands;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.behaviour.human.ActionGroup;\nimport net.demilich.metastone.game.behaviour.human.HumanTargetOptions;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.events.BoardChangedEvent;\nimport net.demilich.metastone.game.logic.ActionLogic;\nimport net.demilich.metastone.gui.sandboxmode.SandboxProxy;\n\npublic class SpawnMinionCommand extends SimpleCommand<GameNotification> {\n\n\tprivate MinionCard minionCard;\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tminionCard = (MinionCard) notification.getBody();\n\n\t\tSandboxProxy sandboxProxy = (SandboxProxy) getFacade().retrieveProxy(SandboxProxy.NAME);\n\t\tGameAction summonAction = minionCard.play();\n\n\t\tActionLogic actionLogic = new ActionLogic();\n\t\tGameContext context = sandboxProxy.getSandbox();\n\t\tPlayer selectedPlayer = sandboxProxy.getSelectedPlayer();\n\t\tList<GameAction> rolledOutActions = new ArrayList<GameAction>();\n\t\tactionLogic.rollout(summonAction, context, selectedPlayer, rolledOutActions);\n\t\tActionGroup actionGroup = new ActionGroup(summonAction);\n\t\tfor (GameAction gameAction : rolledOutActions) {\n\t\t\tactionGroup.add(gameAction);\n\t\t}\n\n\t\tHumanTargetOptions targetOptions = new HumanTargetOptions(this::spawnMinion, context, selectedPlayer.getId(), actionGroup);\n\t\tsendNotification(GameNotification.SELECT_TARGET, targetOptions);\n\t}\n\n\tprivate void spawnMinion(GameAction action) {\n\t\tSandboxProxy sandboxProxy = (SandboxProxy) getFacade().retrieveProxy(SandboxProxy.NAME);\n\t\tGameContext context = sandboxProxy.getSandbox();\n\t\tPlayer selectedPlayer = sandboxProxy.getSelectedPlayer();\n\n\t\tMinion minion = minionCard.summon();\n\t\tActor nextTo = (Actor) context.resolveSingleTarget(action.getTargetKey());\n\t\tint index = selectedPlayer.getSummons().indexOf(nextTo);\n\t\tcontext.getLogic().summon(selectedPlayer.getId(), minion, minionCard, index, false);\n\t\t\n\t\tif (context.ignoreEvents()) {\n\t\t\tcontext.setIgnoreEvents(false);\n\t\t\tcontext.fireGameEvent(new BoardChangedEvent(context));\n\t\t\tcontext.setIgnoreEvents(true);\n\t\t}\n\n\t\tsendNotification(GameNotification.UPDATE_SANDBOX_STATE, context);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/commands/StartPlaySandboxCommand.java",
    "content": "package net.demilich.metastone.gui.sandboxmode.commands;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.gui.sandboxmode.SandboxProxy;\n\npublic class StartPlaySandboxCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tSandboxProxy sandboxProxy = (SandboxProxy) getFacade().retrieveProxy(SandboxProxy.NAME);\n\n\t\tGameContext context = sandboxProxy.getSandbox();\n\t\tcontext.setIgnoreEvents(false);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/sandboxmode/commands/StopPlaySandboxCommand.java",
    "content": "package net.demilich.metastone.gui.sandboxmode.commands;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.gui.sandboxmode.SandboxProxy;\n\npublic class StopPlaySandboxCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tSandboxProxy sandboxProxy = (SandboxProxy) getFacade().retrieveProxy(SandboxProxy.NAME);\n\n\t\tGameContext context = sandboxProxy.getSandbox();\n\t\tcontext.setIgnoreEvents(true);\n\n\t\tgetFacade().sendNotification(GameNotification.UPDATE_SANDBOX_STATE, context);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/simulationmode/PlayerConfigView.java",
    "content": "package net.demilich.metastone.gui.simulationmode;\n\nimport net.demilich.metastone.gui.playmode.config.PlayerConfigType;\n\npublic class PlayerConfigView extends net.demilich.metastone.gui.gameconfig.PlayerConfigView {\n\n\tpublic PlayerConfigView() {\n\t\tsuper(PlayerConfigType.SIMULATION);\n\n\t\tsetPrefHeight(400);\n\t}\n\n}"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/simulationmode/PlayerInfoView.java",
    "content": "package net.demilich.metastone.gui.simulationmode;\n\nimport java.io.IOException;\n\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Label;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.layout.Pane;\nimport net.demilich.metastone.game.cards.HeroCard;\nimport net.demilich.metastone.gui.IconFactory;\nimport net.demilich.metastone.game.gameconfig.PlayerConfig;\n\npublic class PlayerInfoView extends Pane {\n\n\t@FXML\n\tprivate ImageView classIcon;\n\n\t@FXML\n\tprivate Label heroLabel;\n\n\t@FXML\n\tprivate Label deckLabel;\n\n\t@FXML\n\tprivate Label behaviourLabel;\n\n\tpublic PlayerInfoView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/PlayerInfoView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\t}\n\n\tpublic void setInfo(PlayerConfig playerConfig) {\n\t\tHeroCard heroCard = playerConfig.getHeroCard();\n\t\tclassIcon.setImage(IconFactory.getClassIcon(heroCard.getHeroClass()));\n\t\theroLabel.setText(playerConfig.getName());\n\t\tdeckLabel.setText(playerConfig.getDeck().getName());\n\t\tbehaviourLabel.setText(playerConfig.getBehaviour().getName());\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/simulationmode/SimulateGamesCommand.java",
    "content": "package net.demilich.metastone.gui.simulationmode;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\n\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.nittygrittymvc.Notification;\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.game.gameconfig.GameConfig;\nimport net.demilich.metastone.game.gameconfig.PlayerConfig;\nimport net.demilich.metastone.utils.Tuple;\n\npublic class SimulateGamesCommand extends SimpleCommand<GameNotification> {\n\n\tprivate class PlayGameTask implements Callable<Void> {\n\n\t\tprivate final GameConfig gameConfig;\n\n\t\tpublic PlayGameTask(GameConfig gameConfig) {\n\t\t\tthis.gameConfig = gameConfig;\n\t\t}\n\n\t\t@Override\n\t\tpublic Void call() throws Exception {\n\t\t\tPlayerConfig playerConfig1 = gameConfig.getPlayerConfig1();\n\t\t\tPlayerConfig playerConfig2 = gameConfig.getPlayerConfig2();\n\n\t\t\tPlayer player1 = new Player(playerConfig1);\n\t\t\tPlayer player2 = new Player(playerConfig2);\n\t\t\t\n\t\t\tDeckFormat deckFormat = gameConfig.getDeckFormat();\n\n\t\t\tGameContext newGame = new GameContext(player1, player2, new GameLogic(), deckFormat);\n\t\t\tnewGame.play();\n\n\t\t\tonGameComplete(gameConfig, newGame);\n\t\t\tnewGame.dispose();\n\n\t\t\treturn null;\n\t\t}\n\n\t}\n\n\tprivate static Logger logger = LoggerFactory.getLogger(SimulateGamesCommand.class);\n\tprivate int gamesCompleted;\n\tprivate long lastUpdate;\n\n\tprivate SimulationResult result;\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tfinal GameConfig gameConfig = (GameConfig) notification.getBody();\n\t\tresult = new SimulationResult(gameConfig);\n\n\t\tgamesCompleted = 0;\n\n\t\tThread t = new Thread(new Runnable() {\n\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tint cores = Runtime.getRuntime().availableProcessors();\n\t\t\t\tlogger.info(\"Starting simulation on \" + cores + \" cores\");\n\t\t\t\tExecutorService executor = Executors.newFixedThreadPool(cores);\n\t\t\t\t// ExecutorService executor =\n\t\t\t\t// Executors.newSingleThreadExecutor();\n\n\t\t\t\tList<Future<Void>> futures = new ArrayList<Future<Void>>();\n\t\t\t\t// send initial status update\n\t\t\t\tTuple<Integer, Integer> progress = new Tuple<>(0, gameConfig.getNumberOfGames());\n\t\t\t\tgetFacade().sendNotification(GameNotification.SIMULATION_PROGRESS_UPDATE, progress);\n\n\t\t\t\t// queue up all games as tasks\n\t\t\t\tlastUpdate = System.currentTimeMillis();\n\t\t\t\tfor (int i = 0; i < gameConfig.getNumberOfGames(); i++) {\n\t\t\t\t\tPlayGameTask task = new PlayGameTask(gameConfig);\n\t\t\t\t\tFuture<Void> future = executor.submit(task);\n\t\t\t\t\tfutures.add(future);\n\t\t\t\t}\n\n\t\t\t\texecutor.shutdown();\n\t\t\t\tboolean completed = false;\n\t\t\t\twhile (!completed) {\n\t\t\t\t\tcompleted = true;\n\t\t\t\t\tfor (Future<Void> future : futures) {\n\t\t\t\t\t\tif (!future.isDone()) {\n\t\t\t\t\t\t\tcompleted = false;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tfuture.get();\n\t\t\t\t\t\t} catch (InterruptedException | ExecutionException e) {\n\t\t\t\t\t\t\tlogger.error(ExceptionUtils.getStackTrace(e));\n\t\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t\t\t\tSystem.exit(-1);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfutures.removeIf(future -> future.isDone());\n\t\t\t\t\ttry {\n\t\t\t\t\t\tThread.sleep(50);\n\t\t\t\t\t} catch (InterruptedException e) {\n\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresult.calculateMetaStatistics();\n\t\t\t\tgetFacade().sendNotification(GameNotification.SIMULATION_RESULT, result);\n\t\t\t\tlogger.info(\"Simulation finished\");\n\n\t\t\t}\n\t\t});\n\t\tt.setDaemon(true);\n\t\tt.start();\n\t}\n\n\tprivate void onGameComplete(GameConfig gameConfig, GameContext context) {\n\t\tlong timeStamp = System.currentTimeMillis();\n\t\tgamesCompleted++;\n\t\tif (timeStamp - lastUpdate > 100) {\n\t\t\tlastUpdate = timeStamp;\n\t\t\tTuple<Integer, Integer> progress = new Tuple<>(gamesCompleted, gameConfig.getNumberOfGames());\n\t\t\tNotification<GameNotification> updateNotification = new Notification<>(GameNotification.SIMULATION_PROGRESS_UPDATE, progress);\n\t\t\tgetFacade().notifyObservers(updateNotification);\n\t\t}\n\t\tsynchronized (result) {\n\t\t\tresult.getPlayer1Stats().merge(context.getPlayer1().getStatistics());\n\t\t\tresult.getPlayer2Stats().merge(context.getPlayer2().getStatistics());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/simulationmode/SimulationMediator.java",
    "content": "package net.demilich.metastone.gui.simulationmode;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.nittygrittymvc.Mediator;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport javafx.application.Platform;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.utils.Tuple;\n\npublic class SimulationMediator extends Mediator<GameNotification> {\n\n\tpublic static final String NAME = \"SimulationMediator\";\n\n\tprivate static Logger logger = LoggerFactory.getLogger(SimulationMediator.class);\n\n\tprivate final SimulationModeConfigView view;\n\tprivate final WaitForSimulationView waitView;\n\tprivate final SimulationResultView resultView;\n\n\tpublic SimulationMediator() {\n\t\tsuper(NAME);\n\t\tview = new SimulationModeConfigView();\n\t\twaitView = new WaitForSimulationView();\n\t\tresultView = new SimulationResultView();\n\t}\n\n\t@Override\n\t@SuppressWarnings(\"unchecked\")\n\tpublic void handleNotification(final INotification<GameNotification> notification) {\n\t\tswitch (notification.getId()) {\n\t\tcase REPLY_DECKS:\n\t\t\tList<Deck> decks = (List<Deck>) notification.getBody();\n\t\t\tview.injectDecks(decks);\n\t\t\tbreak;\n\t\tcase REPLY_DECK_FORMATS:\n\t\t\tList<DeckFormat> deckFormats = (List<DeckFormat>) notification.getBody();\n\t\t\tview.injectDeckFormats(deckFormats);\n\t\t\tbreak;\n\t\tcase COMMIT_SIMULATIONMODE_CONFIG:\n\t\t\tgetFacade().sendNotification(GameNotification.SHOW_MODAL_DIALOG, waitView);\n\t\t\tgetFacade().sendNotification(GameNotification.SIMULATE_GAMES, notification.getBody());\n\t\t\tbreak;\n\t\tcase SIMULATION_PROGRESS_UPDATE:\n\t\t\tTuple<Integer, Integer> progress = (Tuple<Integer, Integer>) notification.getBody();\n\t\t\tPlatform.runLater(new Runnable() {\n\t\t\t\t@Override\n\t\t\t\tpublic void run() {\n\t\t\t\t\twaitView.update(progress.getFirst(), progress.getSecond());\n\t\t\t\t}\n\t\t\t});\n\t\t\tbreak;\n\t\tcase SIMULATION_RESULT:\n\t\t\tPlatform.runLater(new Runnable() {\n\t\t\t\t@Override\n\t\t\t\tpublic void run() {\n\t\t\t\t\twaitView.getScene().getWindow().hide();\n\t\t\t\t\tSimulationResult result = (SimulationResult) notification.getBody();\n\t\t\t\t\tresultView.showSimulationResult(result);\n\t\t\t\t\tgetFacade().sendNotification(GameNotification.SHOW_VIEW, resultView);\n\t\t\t\t}\n\t\t\t});\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tlogger.warn(\"Unhandled notification {} in {}\", notification, getClass().getSimpleName());\n\t\t\tbreak;\n\n\t\t}\n\t}\n\n\t@Override\n\tpublic List<GameNotification> listNotificationInterests() {\n\t\tList<GameNotification> notificationInterests = new ArrayList<GameNotification>();\n\t\tnotificationInterests.add(GameNotification.REPLY_DECKS);\n\t\tnotificationInterests.add(GameNotification.REPLY_DECK_FORMATS);\n\t\tnotificationInterests.add(GameNotification.COMMIT_SIMULATIONMODE_CONFIG);\n\t\tnotificationInterests.add(GameNotification.SIMULATION_PROGRESS_UPDATE);\n\t\tnotificationInterests.add(GameNotification.SIMULATION_RESULT);\n\t\treturn notificationInterests;\n\t}\n\n\t@Override\n\tpublic void onRegister() {\n\t\tgetFacade().sendNotification(GameNotification.SHOW_VIEW, view);\n\t\tgetFacade().sendNotification(GameNotification.REQUEST_DECKS);\n\t\tgetFacade().sendNotification(GameNotification.REQUEST_DECK_FORMATS);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/simulationmode/SimulationModeConfigView.java",
    "content": "package net.demilich.metastone.gui.simulationmode;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport javafx.beans.value.ChangeListener;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.gui.common.DeckFormatStringConverter;\nimport net.demilich.metastone.game.gameconfig.GameConfig;\n\npublic class SimulationModeConfigView extends BorderPane implements EventHandler<ActionEvent> {\n\n\t@FXML\n\tprotected ComboBox<DeckFormat> formatBox;\n\n\t@FXML\n\tprotected HBox playerArea;\n\n\t@FXML\n\tprotected Button startButton;\n\n\t@FXML\n\tprotected Button backButton;\n\n\t@FXML\n\tprotected ComboBox<Integer> numberOfGamesBox;\n\n\tprotected PlayerConfigView player1Config;\n\tprotected PlayerConfigView player2Config;\n\n\tprivate List<DeckFormat> deckFormats;\n\n\tpublic SimulationModeConfigView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/SimulationModeConfigView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tformatBox.setConverter(new DeckFormatStringConverter());\n\n\t\tplayer1Config = new PlayerConfigView();\n\t\tplayer2Config = new PlayerConfigView();\n\n\t\tplayerArea.getChildren().add(player1Config);\n\t\tplayerArea.getChildren().add(player2Config);\n\n\t\tstartButton.setOnAction(this);\n\t\tbackButton.setOnAction(this);\n\t\tsetupNumberOfGamesBox();\n\n\t\tformatBox.valueProperty().addListener((ChangeListener<DeckFormat>) (observableProperty, oldDeckFormat, newDeckFormat) -> {\n\t\t\tsetDeckFormats(newDeckFormat);\n\t\t});\n\t}\n\n\tprivate void setupDeckFormats() {\n\t\tObservableList<DeckFormat> deckFormatList = FXCollections.observableArrayList();\n\n\t\tfor (DeckFormat deckFormat : deckFormats) {\n\t\t\tdeckFormatList.add(deckFormat);\n\t\t}\n\n\t\tformatBox.setItems(deckFormatList);\n\t\tformatBox.getSelectionModel().selectFirst();\n\t}\n\n\tprivate void setDeckFormats(DeckFormat newDeckFormat) {\n\t\tplayer1Config.setDeckFormat(newDeckFormat);\n\t\tplayer2Config.setDeckFormat(newDeckFormat);\n\t}\n\n\t@Override\n\tpublic void handle(ActionEvent actionEvent) {\n\t\tif (actionEvent.getSource() == startButton) {\n\t\t\tGameConfig gameConfig = new GameConfig();\n\t\t\tgameConfig.setNumberOfGames(numberOfGamesBox.getSelectionModel().getSelectedItem());\n\t\t\tgameConfig.setPlayerConfig1(player1Config.getPlayerConfig());\n\t\t\tgameConfig.setPlayerConfig2(player2Config.getPlayerConfig());\n\t\t\tgameConfig.setDeckFormat(formatBox.getValue());\n\t\t\tNotificationProxy.sendNotification(GameNotification.COMMIT_SIMULATIONMODE_CONFIG, gameConfig);\n\t\t} else if (actionEvent.getSource() == backButton) {\n\t\t\tNotificationProxy.sendNotification(GameNotification.MAIN_MENU);\n\t\t}\n\t}\n\n\tpublic void injectDecks(List<Deck> decks) {\n\t\tplayer1Config.injectDecks(decks);\n\t\tplayer2Config.injectDecks(decks);\n\t}\n\n\tpublic void injectDeckFormats(List<DeckFormat> deckFormats) {\n\t\tthis.deckFormats = deckFormats;\n\t\tsetupDeckFormats();\n\t\tplayer1Config.setDeckFormat(formatBox.getValue());\n\t\tplayer2Config.setDeckFormat(formatBox.getValue());\n\t}\n\n\tprivate void setupNumberOfGamesBox() {\n\t\tObservableList<Integer> numberOfGamesEntries = FXCollections.observableArrayList();\n\t\tnumberOfGamesEntries.add(1);\n\t\tnumberOfGamesEntries.add(10);\n\t\tnumberOfGamesEntries.add(100);\n\t\tnumberOfGamesEntries.add(1000);\n\t\tnumberOfGamesEntries.add(10000);\n\t\tnumberOfGamesEntries.add(100000);\n\t\tnumberOfGamesBox.setItems(numberOfGamesEntries);\n\t\tnumberOfGamesBox.getSelectionModel().select(2);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/simulationmode/SimulationResult.java",
    "content": "package net.demilich.metastone.gui.simulationmode;\n\nimport net.demilich.metastone.game.statistics.GameStatistics;\nimport net.demilich.metastone.game.statistics.Statistic;\nimport net.demilich.metastone.game.gameconfig.GameConfig;\n\npublic class SimulationResult {\n\n\tprivate final GameStatistics player1Stats = new GameStatistics();\n\tprivate final GameStatistics player2Stats = new GameStatistics();\n\tprivate final long startTimestamp;\n\tprivate long duration;\n\tprivate final GameConfig config;\n\n\tpublic SimulationResult(GameConfig config) {\n\t\tthis.config = config;\n\t\tthis.startTimestamp = System.currentTimeMillis();\n\t}\n\n\tpublic void calculateMetaStatistics() {\n\t\tcalculateMetaStatistics(player1Stats);\n\t\tcalculateMetaStatistics(player2Stats);\n\t}\n\n\tprivate void calculateMetaStatistics(GameStatistics statistics) {\n\t\tdouble gamesPlayed = getNumberOfGames();\n\t\tdouble winRate = statistics.getLong(Statistic.GAMES_WON) / gamesPlayed * 100;\n\t\tString winRateString = String.format(\"%.2f\", winRate) + \"%\";\n\t\tstatistics.set(Statistic.WIN_RATE, winRateString);\n\n\t\tlong endTimestamp = System.currentTimeMillis();\n\t\tduration = endTimestamp - startTimestamp;\n\t}\n\n\tpublic GameConfig getConfig() {\n\t\treturn config;\n\t}\n\n\tpublic long getDuration() {\n\t\treturn this.duration;\n\t}\n\n\tpublic int getNumberOfGames() {\n\t\treturn getConfig().getNumberOfGames();\n\t}\n\n\tpublic GameStatistics getPlayer1Stats() {\n\t\treturn player1Stats;\n\t}\n\n\tpublic GameStatistics getPlayer2Stats() {\n\t\treturn player2Stats;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/simulationmode/SimulationResultView.java",
    "content": "package net.demilich.metastone.gui.simulationmode;\n\nimport java.io.IOException;\nimport java.text.DecimalFormat;\nimport java.text.NumberFormat;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport net.demilich.metastone.NotificationProxy;\nimport org.apache.commons.lang3.time.DurationFormatUtils;\n\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TableView;\nimport javafx.scene.control.cell.PropertyValueFactory;\nimport javafx.scene.layout.BorderPane;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.game.statistics.GameStatistics;\nimport net.demilich.metastone.game.statistics.Statistic;\n\npublic class SimulationResultView extends BorderPane {\n\n\tprivate static String getStatName(Statistic stat) {\n\t\tswitch (stat) {\n\t\tcase ARMOR_GAINED:\n\t\t\treturn \"Armor gained\";\n\t\tcase CARDS_DRAWN:\n\t\t\treturn \"Cards drawn\";\n\t\tcase CARDS_PLAYED:\n\t\t\treturn \"Cards played\";\n\t\tcase DAMAGE_DEALT:\n\t\t\treturn \"Damage dealt\";\n\t\tcase FATIGUE_DAMAGE:\n\t\t\treturn \"Fatigue damage\";\n\t\tcase GAMES_LOST:\n\t\t\treturn \"Games Lost\";\n\t\tcase GAMES_WON:\n\t\t\treturn \"Games won\";\n\t\tcase HEALING_DONE:\n\t\t\treturn \"Healing done\";\n\t\tcase HERO_POWER_USED:\n\t\t\treturn \"Hero power used\";\n\t\tcase MANA_SPENT:\n\t\t\treturn \"Mana spent\";\n\t\tcase MINIONS_PLAYED:\n\t\t\treturn \"Minions played\";\n\t\tcase SPELLS_CAST:\n\t\t\treturn \"Spells cast\";\n\t\tcase TURNS_TAKEN:\n\t\t\treturn \"Turns taken\";\n\t\tcase WEAPONS_EQUIPPED:\n\t\t\treturn \"Weapons equipped\";\n\t\tcase WIN_RATE:\n\t\t\treturn \"Win rate\";\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t\treturn stat.toString();\n\t}\n\n\t@FXML\n\tprivate BorderPane infoArea;\n\n\t@FXML\n\tprivate TableView<StatEntry> absoluteResultTable;\n\n\t@FXML\n\tprivate TableView<StatEntry> averageResultTable;\n\n\t@FXML\n\tprivate Button doneButton;\n\n\t@FXML\n\tprivate Label durationLabel;\n\tprivate PlayerInfoView player1InfoView;\n\n\tprivate PlayerInfoView player2InfoView;\n\n\tprivate final NumberFormat formatter = DecimalFormat.getInstance();\n\n\tpublic SimulationResultView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/SimulationResultView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tdoneButton.setOnAction(event -> NotificationProxy.sendNotification(GameNotification.MAIN_MENU));\n\n\t\tplayer1InfoView = new PlayerInfoView();\n\t\tinfoArea.setLeft(player1InfoView);\n\t\tplayer2InfoView = new PlayerInfoView();\n\t\tinfoArea.setRight(player2InfoView);\n\n\t\tformatter.setMinimumFractionDigits(0);\n\t\tformatter.setMaximumFractionDigits(2);\n\t}\n\n\tprivate String getAverageStatString(Statistic stat, GameStatistics playerStatistics, int numberOfGames) {\n\t\tif (playerStatistics.contains(stat)) {\n\t\t\tObject statValue = playerStatistics.get(stat);\n\t\t\tif (statValue instanceof Number) {\n\t\t\t\tdouble value = ((Number) statValue).doubleValue();\n\t\t\t\treturn formatter.format(value / numberOfGames);\n\t\t\t}\n\n\t\t}\n\t\treturn \"-\";\n\t}\n\n\tprivate String getFavouriteCardName(GameStatistics stats, CardType cardType) {\n\t\tList<Card> cards = new ArrayList<Card>();\n\t\tfor (String cardId : stats.getCardsPlayed().keySet()) {\n\t\t\tif (cardId.startsWith(GameLogic.TEMP_CARD_LABEL)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tCard card = CardCatalogue.getCardById(cardId);\n\t\t\tif (card == null) {\n\t\t\t\tSystem.out.println(\"Invalid card with id: \" + cardId);\n\t\t\t}\n\t\t\tif (card.getCardType() == cardType) {\n\t\t\t\tcards.add(card);\n\t\t\t}\n\t\t}\n\n\t\tif (cards.isEmpty()) {\n\t\t\treturn \"-\";\n\t\t}\n\n\t\tCollections.sort(cards, (c1, c2) -> {\n\t\t\tint c1Count = stats.getCardsPlayedCount(c1.getCardId());\n\t\t\tint c2Count = stats.getCardsPlayedCount(c2.getCardId());\n\t\t\t// sort descending\n\t\t\treturn Integer.compare(c2Count, c1Count);\n\t\t});\n\t\treturn cards.get(0).getName();\n\t}\n\n\tprivate String getStatString(Statistic stat, GameStatistics playerStatistics) {\n\t\tif (playerStatistics.contains(stat)) {\n\t\t\tObject statValue = playerStatistics.get(stat);\n\t\t\tif (statValue instanceof Number) {\n\t\t\t\treturn formatter.format(playerStatistics.get(stat));\n\t\t\t}\n\t\t\treturn statValue.toString();\n\t\t}\n\t\treturn \"-\";\n\t}\n\n\t@SuppressWarnings({ \"rawtypes\", \"unchecked\" })\n\tpublic void showSimulationResult(SimulationResult result) {\n\t\tplayer1InfoView.setInfo(result.getConfig().getPlayerConfig1());\n\t\tplayer2InfoView.setInfo(result.getConfig().getPlayerConfig2());\n\t\tdurationLabel.setText(\"Simulation took \" + DurationFormatUtils.formatDurationHMS(result.getDuration()));\n\n\t\tObservableList<StatEntry> absoluteStatEntries = FXCollections.observableArrayList();\n\t\tObservableList<StatEntry> averageStatEntries = FXCollections.observableArrayList();\n\t\tfor (Statistic stat : Statistic.values()) {\n\t\t\tStatEntry absoluteStatEntry = new StatEntry();\n\t\t\tabsoluteStatEntry.setStatName(getStatName(stat));\n\t\t\tabsoluteStatEntry.setPlayer1Value(getStatString(stat, result.getPlayer1Stats()));\n\t\t\tabsoluteStatEntry.setPlayer2Value(getStatString(stat, result.getPlayer2Stats()));\n\t\t\tabsoluteStatEntries.add(absoluteStatEntry);\n\n\t\t\tStatEntry averageStatEntry = new StatEntry();\n\t\t\taverageStatEntry.setStatName(getStatName(stat));\n\t\t\taverageStatEntry.setPlayer1Value(getAverageStatString(stat, result.getPlayer1Stats(), result.getNumberOfGames()));\n\t\t\taverageStatEntry.setPlayer2Value(getAverageStatString(stat, result.getPlayer2Stats(), result.getNumberOfGames()));\n\t\t\taverageStatEntries.add(averageStatEntry);\n\n\t\t}\n\t\tStatEntry favouriteMinionCard = new StatEntry();\n\t\tfavouriteMinionCard.setStatName(\"Favourite minion card\");\n\t\tfavouriteMinionCard.setPlayer1Value(getFavouriteCardName(result.getPlayer1Stats(), CardType.MINION));\n\t\tfavouriteMinionCard.setPlayer2Value(getFavouriteCardName(result.getPlayer2Stats(), CardType.MINION));\n\t\tabsoluteStatEntries.add(favouriteMinionCard);\n\n\t\tStatEntry favouriteSpellCard = new StatEntry();\n\t\tfavouriteSpellCard.setStatName(\"Favourite spell card\");\n\t\tfavouriteSpellCard.setPlayer1Value(getFavouriteCardName(result.getPlayer1Stats(), CardType.SPELL));\n\t\tfavouriteSpellCard.setPlayer2Value(getFavouriteCardName(result.getPlayer2Stats(), CardType.SPELL));\n\t\tabsoluteStatEntries.add(favouriteSpellCard);\n\n\t\tStatEntry favouriteWeaponCard = new StatEntry();\n\t\tfavouriteWeaponCard.setStatName(\"Favourite weapon card\");\n\t\tfavouriteWeaponCard.setPlayer1Value(getFavouriteCardName(result.getPlayer1Stats(), CardType.WEAPON));\n\t\tfavouriteWeaponCard.setPlayer2Value(getFavouriteCardName(result.getPlayer2Stats(), CardType.WEAPON));\n\t\tabsoluteStatEntries.add(favouriteWeaponCard);\n\n\t\tabsoluteResultTable.setItems(absoluteStatEntries);\n\n\t\tabsoluteResultTable.getColumns().get(0).setCellValueFactory(new PropertyValueFactory(\"statName\"));\n\t\tabsoluteResultTable.getColumns().get(1).setCellValueFactory(new PropertyValueFactory(\"player1Value\"));\n\t\tabsoluteResultTable.getColumns().get(2).setCellValueFactory(new PropertyValueFactory(\"player2Value\"));\n\n\t\taverageResultTable.setItems(averageStatEntries);\n\n\t\taverageResultTable.getColumns().get(0).setCellValueFactory(new PropertyValueFactory(\"statName\"));\n\t\taverageResultTable.getColumns().get(1).setCellValueFactory(new PropertyValueFactory(\"player1Value\"));\n\t\taverageResultTable.getColumns().get(2).setCellValueFactory(new PropertyValueFactory(\"player2Value\"));\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/simulationmode/StatEntry.java",
    "content": "package net.demilich.metastone.gui.simulationmode;\n\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\n\npublic class StatEntry {\n\tprivate final StringProperty statName = new SimpleStringProperty(this, \"statName\");\n\tprivate final StringProperty player1Value = new SimpleStringProperty(this, \"player1Value\");\n\tprivate final StringProperty player2Value = new SimpleStringProperty(this, \"player2Value\");\n\n\tpublic StatEntry() {\n\t}\n\n\tpublic StatEntry(String statName, String player1Value, String player2Value) {\n\t\tsetStatName(statName);\n\t\tsetPlayer1Value(player1Value);\n\t\tsetPlayer2Value(player2Value);\n\t}\n\n\tpublic String getPlayer1Value() {\n\t\treturn player1Value.get();\n\t}\n\n\tpublic String getPlayer2Value() {\n\t\treturn player2Value.get();\n\t}\n\n\tpublic String getStatName() {\n\t\treturn statName.get();\n\t}\n\n\tpublic StringProperty player1ValueProperty() {\n\t\treturn player1Value;\n\t}\n\n\tpublic StringProperty player2ValueProperty() {\n\t\treturn player2Value;\n\t}\n\n\tpublic void setPlayer1Value(String value) {\n\t\tplayer1Value.set(value);\n\t}\n\n\tpublic void setPlayer2Value(String value) {\n\t\tplayer2Value.set(value);\n\t}\n\n\tpublic void setStatName(String value) {\n\t\tstatName.set(value);\n\t}\n\n\tpublic StringProperty statNameProperty() {\n\t\treturn statName;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/simulationmode/WaitForSimulationView.java",
    "content": "package net.demilich.metastone.gui.simulationmode;\n\nimport java.io.IOException;\n\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ProgressBar;\nimport javafx.scene.layout.BorderPane;\n\npublic class WaitForSimulationView extends BorderPane {\n\n\t@FXML\n\tprivate ProgressBar progressBar;\n\n\t@FXML\n\tprivate Label gamesCompletedLabel;\n\n\tpublic WaitForSimulationView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/WaitForSimulationView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t}\n\n\tpublic void update(int gamesCompleted, int gamesTotal) {\n\t\tgamesCompletedLabel.setText(gamesCompleted + \"/\" + gamesTotal + \" games completed\");\n\t\tprogressBar.setProgress(gamesCompleted / (double) gamesTotal);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/trainingmode/PerformTrainingCommand.java",
    "content": "package net.demilich.metastone.gui.trainingmode;\n\nimport net.demilich.metastone.trainingmode.TrainingData;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.nittygrittymvc.Notification;\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.behaviour.threat.FeatureVector;\nimport net.demilich.metastone.game.behaviour.threat.GameStateValueBehaviour;\nimport net.demilich.metastone.game.behaviour.threat.cuckoo.CuckooLearner;\nimport net.demilich.metastone.game.cards.CardSet;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.game.statistics.Statistic;\nimport net.demilich.metastone.game.gameconfig.PlayerConfig;\n\npublic class PerformTrainingCommand extends SimpleCommand<GameNotification> {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(PerformTrainingCommand.class);\n\n\tprivate int gamesCompleted;\n\tprivate int gamesWon;\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tfinal TrainingConfig config = (TrainingConfig) notification.getBody();\n\t\tif (config.getDecks().isEmpty()) {\n\t\t\tlogger.error(\"Deck list is empty!!\");\n\t\t}\n\n\t\tgamesCompleted = 0;\n\t\tgamesWon = 0;\n\n\t\tThread t = new Thread(new Runnable() {\n\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tlogger.info(\"Training started\");\n\n\t\t\t\tCuckooLearner learner = new CuckooLearner(config.getDeckToTrain(), config.getDecks());\n\n\t\t\t\t// send initial status update\n\t\t\t\tTrainingProgressReport progress = new TrainingProgressReport(gamesCompleted, config.getNumberOfGames(), gamesWon);\n\t\t\t\tgetFacade().sendNotification(GameNotification.TRAINING_PROGRESS_UPDATE, progress);\n\t\t\t\tfor (int i = 0; i < config.getNumberOfGames(); i++) {\n\t\t\t\t\tFeatureVector fittest = learner.getFittest();\n\n\t\t\t\t\tPlayerConfig learnerConfig = new PlayerConfig(config.getDeckToTrain(),\n\t\t\t\t\t\t\tnew GameStateValueBehaviour(fittest, \"(fittest)\"));\n\t\t\t\t\tlearnerConfig.setName(\"Learner\");\n\t\t\t\t\tPlayer player1 = new Player(learnerConfig);\n\n\t\t\t\t\tPlayerConfig opponentConfig = new PlayerConfig(config.getRandomDeck(), new GameStateValueBehaviour());\n\t\t\t\t\topponentConfig.setName(\"Opponent\");\n\t\t\t\t\tPlayer player2 = new Player(opponentConfig);\n\t\t\t\t\t\n\t\t\t\t\tDeckFormat deckFormat = new DeckFormat();\n\t\t\t\t\tfor (CardSet set : CardSet.values()) {\n\t\t\t\t\t\tdeckFormat.addSet(set);\n\t\t\t\t\t}\n\n\t\t\t\t\tGameContext newGame = new GameContext(player1, player2, new GameLogic(), deckFormat);\n\t\t\t\t\tnewGame.play();\n\n\t\t\t\t\tonGameComplete(config, newGame);\n\t\t\t\t\tnewGame.dispose();\n\n\t\t\t\t\tif (i % 10 == 0) {\n\t\t\t\t\t\tlearner.evolve();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tgetFacade().sendNotification(GameNotification.TRAINING_PROGRESS_UPDATE,\n\t\t\t\t\t\tnew TrainingProgressReport(gamesCompleted, config.getNumberOfGames(), gamesWon));\n\n\t\t\t\tlogger.info(\"Training ended\");\n\t\t\t\tFeatureVector fittest = learner.getFittest();\n\t\t\t\tlogger.info(\"**************Final weights: {}\", fittest);\n\n\t\t\t\t// save training data\n\t\t\t\tgetFacade().sendNotification(GameNotification.SAVE_TRAINING_DATA,\n\t\t\t\t\t\tnew TrainingData(config.getDeckToTrain().getName(), fittest));\n\t\t\t}\n\t\t});\n\t\tt.setDaemon(true);\n\t\tt.start();\n\t}\n\n\tprivate void onGameComplete(TrainingConfig config, GameContext completedGame) {\n\t\tgamesCompleted++;\n\n\t\tgamesWon += completedGame.getPlayer1().getStatistics().getLong(Statistic.GAMES_WON);\n\t\tTrainingProgressReport progress = new TrainingProgressReport(gamesCompleted, config.getNumberOfGames(), gamesWon);\n\t\tNotification<GameNotification> updateNotification = new Notification<>(GameNotification.TRAINING_PROGRESS_UPDATE, progress);\n\t\tgetFacade().notifyObservers(updateNotification);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/trainingmode/RequestTrainingDataCommand.java",
    "content": "package net.demilich.metastone.gui.trainingmode;\n\nimport net.demilich.metastone.trainingmode.ITrainingDataListener;\nimport net.demilich.metastone.trainingmode.RequestTrainingDataNotification;\nimport net.demilich.metastone.trainingmode.TrainingData;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\n\npublic class RequestTrainingDataCommand extends SimpleCommand<GameNotification> {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(RequestTrainingDataCommand.class);\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tRequestTrainingDataNotification trainingDataNotification = (RequestTrainingDataNotification) notification;\n\t\tTrainingProxy trainingProxy = (TrainingProxy) getFacade().retrieveProxy(TrainingProxy.NAME);\n\t\tTrainingData trainingData = trainingProxy.getTrainingData(trainingDataNotification.getDeckName());\n\t\tif (trainingData == null) {\n\t\t\tlogger.debug(\"No training data found for \" + trainingDataNotification.getDeckName());\n\t\t} else {\n\t\t\tlogger.debug(\"Training data found for \" + trainingDataNotification.getDeckName());\n\t\t}\n\n\t\tITrainingDataListener listener = trainingDataNotification.getListener();\n\t\tlistener.answerTrainingData(trainingData);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/trainingmode/SaveTrainingDataCommand.java",
    "content": "package net.demilich.metastone.gui.trainingmode;\n\nimport net.demilich.metastone.trainingmode.TrainingData;\nimport net.demilich.nittygrittymvc.SimpleCommand;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.metastone.GameNotification;\n\npublic class SaveTrainingDataCommand extends SimpleCommand<GameNotification> {\n\n\t@Override\n\tpublic void execute(INotification<GameNotification> notification) {\n\t\tTrainingData trainingData = (TrainingData) notification.getBody();\n\n\t\tTrainingProxy trainingProxy = (TrainingProxy) getFacade().retrieveProxy(TrainingProxy.NAME);\n\t\ttrainingProxy.saveTrainingData(trainingData);\n\t\t;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/trainingmode/TrainingConfig.java",
    "content": "package net.demilich.metastone.gui.trainingmode;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport net.demilich.metastone.game.decks.Deck;\n\npublic class TrainingConfig {\n\n\tprivate int numberOfGames;\n\tprivate final Deck deckToTrain;\n\tprivate final List<Deck> decks = new ArrayList<Deck>();\n\n\tpublic TrainingConfig(Deck deckToTrain) {\n\t\tthis.deckToTrain = deckToTrain;\n\t}\n\n\tpublic List<Deck> getDecks() {\n\t\treturn decks;\n\t}\n\n\tpublic Deck getDeckToTrain() {\n\t\treturn deckToTrain;\n\t}\n\n\tpublic int getNumberOfGames() {\n\t\treturn numberOfGames;\n\t}\n\n\tpublic Deck getRandomDeck() {\n\t\treturn decks.get(ThreadLocalRandom.current().nextInt(decks.size()));\n\t}\n\n\tpublic void setNumberOfGames(int numberOfGames) {\n\t\tthis.numberOfGames = numberOfGames;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/trainingmode/TrainingConfigView.java",
    "content": "package net.demilich.metastone.gui.trainingmode;\n\nimport java.io.IOException;\nimport java.util.Collection;\nimport java.util.List;\n\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.event.ActionEvent;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.ListView;\nimport javafx.scene.control.SelectionMode;\nimport javafx.scene.control.cell.TextFieldListCell;\nimport javafx.scene.layout.BorderPane;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.gui.common.DeckStringConverter;\n\npublic class TrainingConfigView extends BorderPane {\n\n\t@FXML\n\tprivate ComboBox<Integer> numberOfGamesBox;\n\t@FXML\n\tprivate ComboBox<Deck> deckBox;\n\n\t@FXML\n\tprivate ListView<Deck> selectedDecksListView;\n\t@FXML\n\tprivate ListView<Deck> availableDecksListView;\n\n\t@FXML\n\tprivate Button addButton;\n\t@FXML\n\tprivate Button removeButton;\n\t@FXML\n\tprivate Button startButton;\n\t@FXML\n\tprivate Button backButton;\n\n\tpublic TrainingConfigView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/TrainingConfigView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tsetupDeckBox();\n\t\tsetupNumberOfGamesBox();\n\n\t\tselectedDecksListView.setCellFactory(TextFieldListCell.forListView(new DeckStringConverter()));\n\t\tselectedDecksListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);\n\t\tavailableDecksListView.setCellFactory(TextFieldListCell.forListView(new DeckStringConverter()));\n\t\tavailableDecksListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);\n\n\t\taddButton.setOnAction(this::handleAddButton);\n\t\tremoveButton.setOnAction(this::handleRemoveButton);\n\n\t\tbackButton.setOnAction(event -> NotificationProxy.sendNotification(GameNotification.MAIN_MENU));\n\t\tstartButton.setOnAction(this::handleStartButton);\n\t}\n\n\tprivate void handleAddButton(ActionEvent event) {\n\t\tCollection<Deck> selectedDecks = availableDecksListView.getSelectionModel().getSelectedItems();\n\t\tselectedDecksListView.getItems().addAll(selectedDecks);\n\t\tavailableDecksListView.getItems().removeAll(selectedDecks);\n\t}\n\n\tprivate void handleRemoveButton(ActionEvent event) {\n\t\tCollection<Deck> selectedDecks = selectedDecksListView.getSelectionModel().getSelectedItems();\n\t\tavailableDecksListView.getItems().addAll(selectedDecks);\n\t\tselectedDecksListView.getItems().removeAll(selectedDecks);\n\t}\n\n\tprivate void handleStartButton(ActionEvent event) {\n\t\tint numberOfGames = numberOfGamesBox.getSelectionModel().getSelectedItem();\n\t\tDeck deckToTrain = deckBox.getSelectionModel().getSelectedItem();\n\t\tCollection<Deck> decks = selectedDecksListView.getItems();\n\n\t\tTrainingConfig trainingConfig = new TrainingConfig(deckToTrain);\n\t\ttrainingConfig.setNumberOfGames(numberOfGames);\n\t\ttrainingConfig.getDecks().addAll(decks);\n\t\tNotificationProxy.sendNotification(GameNotification.COMMIT_TRAININGMODE_CONFIG, trainingConfig);\n\t}\n\n\tpublic void injectDecks(List<Deck> decks) {\n\t\tList<Deck> filteredDecks = FXCollections.observableArrayList();\n\t\tfor (Deck deck : decks) {\n\t\t\tif (deck.getHeroClass() != HeroClass.DECK_COLLECTION) {\n\t\t\t\tfilteredDecks.add(deck);\n\t\t\t}\n\t\t}\n\t\tselectedDecksListView.getItems().clear();\n\t\tavailableDecksListView.getItems().setAll(filteredDecks);\n\t\tdeckBox.getItems().setAll(filteredDecks);\n\t\tdeckBox.getSelectionModel().selectFirst();\n\t}\n\n\tprivate void setupDeckBox() {\n\t\tdeckBox.setConverter(new DeckStringConverter());\n\t}\n\n\tprivate void setupNumberOfGamesBox() {\n\t\tObservableList<Integer> numberOfGamesEntries = FXCollections.observableArrayList();\n\t\tnumberOfGamesEntries.add(1);\n\t\tnumberOfGamesEntries.add(10);\n\t\tnumberOfGamesEntries.add(100);\n\t\tnumberOfGamesEntries.add(1000);\n\t\tnumberOfGamesEntries.add(10000);\n\t\tnumberOfGamesBox.setItems(numberOfGamesEntries);\n\t\tnumberOfGamesBox.getSelectionModel().select(2);\n\t}\n\n\tpublic void injectDeckFormats(List<DeckFormat> body) {\n//\t\tthis.deckFormats = deckFormats;\n//\t\tplayer1Config.setDeckFormat(deckFormats.get(0));\n//\t\tplayer2Config.setDeckFormat(deckFormats.get(0));\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/trainingmode/TrainingModeMediator.java",
    "content": "package net.demilich.metastone.gui.trainingmode;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.nittygrittymvc.Mediator;\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport javafx.application.Platform;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\n\npublic class TrainingModeMediator extends Mediator<GameNotification> {\n\n\tpublic static final String NAME = \"TrainingModeMediator\";\n\n\tprivate final TrainingConfigView configView;\n\tprivate final TrainingModeView view;\n\n\tpublic TrainingModeMediator() {\n\t\tsuper(NAME);\n\t\tconfigView = new TrainingConfigView();\n\t\tview = new TrainingModeView();\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Override\n\tpublic void handleNotification(final INotification<GameNotification> notification) {\n\t\tswitch (notification.getId()) {\n\t\tcase TRAINING_PROGRESS_UPDATE:\n\t\t\tTrainingProgressReport progress = (TrainingProgressReport) notification.getBody();\n\t\t\tPlatform.runLater(new Runnable() {\n\t\t\t\t@Override\n\t\t\t\tpublic void run() {\n\t\t\t\t\tview.showProgress(progress);\n\t\t\t\t}\n\t\t\t});\n\t\t\tbreak;\n\t\tcase COMMIT_TRAININGMODE_CONFIG:\n\t\t\tgetFacade().sendNotification(GameNotification.SHOW_VIEW, view);\n\t\t\tTrainingConfig trainingConfig = (TrainingConfig) notification.getBody();\n\t\t\tview.setDeckName(trainingConfig.getDeckToTrain().getName());\n\t\t\tview.startTraining();\n\t\t\tgetFacade().sendNotification(GameNotification.START_TRAINING, trainingConfig);\n\t\t\tbreak;\n\t\tcase REPLY_DECKS:\n\t\t\tconfigView.injectDecks((List<Deck>) notification.getBody());\n\t\t\tbreak;\n\t\tcase REPLY_DECK_FORMATS:\n\t\t\tconfigView.injectDeckFormats((List<DeckFormat>) notification.getBody());\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t@Override\n\tpublic List<GameNotification> listNotificationInterests() {\n\t\tList<GameNotification> notificationInterests = new ArrayList<GameNotification>();\n\t\tnotificationInterests.add(GameNotification.TRAINING_PROGRESS_UPDATE);\n\t\tnotificationInterests.add(GameNotification.COMMIT_TRAININGMODE_CONFIG);\n\t\tnotificationInterests.add(GameNotification.REPLY_DECKS);\n\t\tnotificationInterests.add(GameNotification.REPLY_DECK_FORMATS);\n\t\treturn notificationInterests;\n\t}\n\n\t@Override\n\tpublic void onRegister() {\n\t\tgetFacade().sendNotification(GameNotification.SHOW_VIEW, configView);\n\t\tgetFacade().sendNotification(GameNotification.REQUEST_DECKS);\n\t\tgetFacade().sendNotification(GameNotification.REQUEST_DECK_FORMATS);\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/trainingmode/TrainingModeView.java",
    "content": "package net.demilich.metastone.gui.trainingmode;\n\nimport java.io.IOException;\n\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.fxml.FXML;\nimport javafx.fxml.FXMLLoader;\nimport javafx.scene.chart.LineChart;\nimport javafx.scene.chart.XYChart;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.BorderPane;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\n\npublic class TrainingModeView extends BorderPane implements EventHandler<ActionEvent> {\n\n\t@FXML\n\tprivate Button backButton;\n\n\t@FXML\n\tprivate Label trainingLabel;\n\n\t@FXML\n\tprivate Label progressLabel;\n\n\t@FXML\n\tprivate LineChart<Number, Number> resultChart;\n\n\tprivate XYChart.Series<Number, Number> series;\n\n\tpublic TrainingModeView() {\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/TrainingModeView.fxml\"));\n\t\tfxmlLoader.setRoot(this);\n\t\tfxmlLoader.setController(this);\n\n\t\ttry {\n\t\t\tfxmlLoader.load();\n\t\t} catch (IOException exception) {\n\t\t\tthrow new RuntimeException(exception);\n\t\t}\n\n\t\tbackButton.setOnAction(this);\n\n\t\tresultChart.setVisible(false);\n\t}\n\n\t@Override\n\tpublic void handle(ActionEvent actionEvent) {\n\t\tif (actionEvent.getSource() == backButton) {\n\t\t\tNotificationProxy.sendNotification(GameNotification.MAIN_MENU);\n\t\t}\n\t}\n\n\tpublic void setDeckName(String deckname) {\n\t\ttrainingLabel.setText(\"Training: \" + deckname);\n\t}\n\n\tpublic void showProgress(TrainingProgressReport progress) {\n\t\tprogressLabel.setText(progress.getGamesCompleted() + \" out of \" + progress.getGamesTotal() + \" games completed\");\n\t\tint progressMark = Math.max(progress.getGamesTotal() / 100, 10);\n\t\tif (progress.getGamesCompleted() == 0 || progress.getGamesCompleted() % progressMark != 0) {\n\t\t\treturn;\n\t\t}\n\t\tdouble winRate = progress.getGamesWon() / (double) progress.getGamesCompleted();\n\t\tseries.getData().add(new XYChart.Data<Number, Number>(progress.getGamesCompleted(), winRate));\n\t\tif (progress.getGamesCompleted() == progress.getGamesTotal()) {\n\t\t\tbackButton.setDisable(false);\n\t\t}\n\t}\n\n\tpublic void startTraining() {\n\t\tresultChart.getData().clear();\n\t\tbackButton.setDisable(true);\n\t\tresultChart.setVisible(true);\n\t\tseries = new XYChart.Series<>();\n\t\tseries.setName(\"Win rate\");\n\t\tresultChart.getData().add(series);\n\t\tseries.getData().add(new XYChart.Data<Number, Number>(0, 0));\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/trainingmode/TrainingProgressReport.java",
    "content": "package net.demilich.metastone.gui.trainingmode;\n\npublic class TrainingProgressReport {\n\n\tprivate final int gamesCompleted;\n\tprivate final int gamesTotal;\n\tprivate final int gamesWon;\n\n\tpublic TrainingProgressReport(int gamesCompleted, int gamesTotal, int gamesWon) {\n\t\tthis.gamesCompleted = gamesCompleted;\n\t\tthis.gamesTotal = gamesTotal;\n\t\tthis.gamesWon = gamesWon;\n\t}\n\n\tpublic int getGamesCompleted() {\n\t\treturn gamesCompleted;\n\t}\n\n\tpublic int getGamesTotal() {\n\t\treturn gamesTotal;\n\t}\n\n\tpublic int getGamesWon() {\n\t\treturn gamesWon;\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/gui/trainingmode/TrainingProxy.java",
    "content": "package net.demilich.metastone.gui.trainingmode;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.Collection;\nimport java.util.HashMap;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.reflect.TypeToken;\n\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.game.behaviour.threat.FeatureVector;\nimport net.demilich.metastone.game.behaviour.threat.WeightedFeature;\nimport net.demilich.metastone.trainingmode.TrainingData;\nimport net.demilich.metastone.utils.ResourceInputStream;\nimport net.demilich.metastone.utils.ResourceLoader;\nimport net.demilich.metastone.utils.UserHomeMetastone;\nimport net.demilich.nittygrittymvc.Proxy;\n\npublic class TrainingProxy extends Proxy<GameNotification> {\n\n\tpublic static final String NAME = \"TrainingProxy\";\n\tprivate static final String TRAINING_FOLDER = \"training\";\n\tprivate static final String TRAINING_FOLDER_PATH = UserHomeMetastone.getPath() + File.separator + TRAINING_FOLDER;\n\n\tprivate static Logger logger = LoggerFactory.getLogger(TrainingProxy.class);\n\n\tprivate final HashMap<String, FeatureVector> trainingData = new HashMap<String, FeatureVector>();\n\n\tpublic TrainingProxy() {\n\t\tsuper(NAME);\n\t\tif (new File(TRAINING_FOLDER_PATH).mkdir()) {\n\t\t\tlogger.info(TRAINING_FOLDER_PATH + \" folder created\");\n\t\t}\n\t\ttry {\n\t\t\tloadTrainingData();\n\t\t} catch (FileNotFoundException e) {\n\t\t\te.printStackTrace();\n\t\t} catch (URISyntaxException e) {\n\t\t\te.printStackTrace();\n\t\t} catch (IOException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n\n\tpublic TrainingData getTrainingData(String deckName) {\n\t\treturn trainingData.containsKey(deckName) ? new TrainingData(deckName, trainingData.get(deckName)) : null;\n\t}\n\n\tpublic void loadTrainingData() throws IOException, URISyntaxException {\n\t\ttrainingData.clear();\n\n\t\t// load training from resources jar on the classpath\n\t\tCollection<ResourceInputStream> inputStreams = ResourceLoader.loadJsonInputStreams(TRAINING_FOLDER, false);\n\n\t\t// load cards from ~/metastone/training folder on the filesystem\n\t\tif (Paths.get(TRAINING_FOLDER_PATH).toFile().exists()) {\n\t\t\tinputStreams.addAll((ResourceLoader.loadJsonInputStreams(TRAINING_FOLDER_PATH, true)));\n\t\t}\n\n\t\tGson gson = new GsonBuilder().setPrettyPrinting().create();\n\t\tHashMap<String, Object>  map;\n\t\tReader reader;\n\t\tfor (ResourceInputStream resourceInputStream : inputStreams) {\n\t\t\treader = new InputStreamReader(resourceInputStream.inputStream);\n\t\t\tmap = gson.fromJson(reader, new TypeToken<HashMap<String, Object>>() {}.getType());\n\n\t\t\tfinal String DECK_NAME = \"deck\";\n\t\t\tif (!map.containsKey(DECK_NAME)) {\n\t\t\t\tlogger.error(\"Training data {} does not specify a value for '{}' and is therefor not valid\", resourceInputStream.fileName, DECK_NAME);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tString deckName = (String) map.get(DECK_NAME);\n\t\t\tmap.remove(DECK_NAME);\n\n\t\t\tFeatureVector featureVector = new FeatureVector();\n\t\t\tfor (String element : map.keySet()) {\n\t\t\t\ttry {\n\t\t\t\t\tWeightedFeature feature = WeightedFeature.valueOf(element);\n\t\t\t\t\tdouble value = (double) map.get(element);\n\t\t\t\t\tfeatureVector.set(feature, value);\n\t\t\t\t} catch (IllegalArgumentException ex) {\n\t\t\t\t\tlogger.warn(\"Illegal argument in training data: \" + element + \" Value: \" + map.get(element));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttrainingData.put(deckName, featureVector);\n\t\t}\n\t}\n\n\tpublic void saveTrainingData(TrainingData trainingData) {\n\t\tString deckName = trainingData.getDeckName();\n\t\tFeatureVector featureVector = trainingData.getFeatureVector();\n\t\tthis.trainingData.put(deckName, featureVector);\n\n\t\tGson gson = new GsonBuilder().setPrettyPrinting().create();\n\t\tHashMap<String, Object> saveData = new HashMap<String, Object>();\n\t\tsaveData.put(\"deck\", deckName);\n\t\tfor (WeightedFeature feature : featureVector.getValues().keySet()) {\n\t\t\tdouble value = featureVector.get(feature);\n\t\t\tsaveData.put(feature.toString(), value);\n\t\t}\n\t\tString jsonData = gson.toJson(saveData);\n\t\ttry {\n\t\t\tString filename = deckName.toLowerCase();\n\t\t\tfilename = filename.replaceAll(\" \", \"_\");\n\t\t\tfilename = filename.replaceAll(\"\\\\W+\", \"\");\n\t\t\tfilename = TRAINING_FOLDER_PATH + File.separator + filename + \".json\";\n\t\t\tFiles.write(Paths.get(filename), jsonData.getBytes());\n\t\t} catch (IOException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/tools/CardCreator.java",
    "content": "package net.demilich.metastone.tools;\r\n\r\nimport javafx.application.Application;\r\nimport javafx.scene.Scene;\r\nimport javafx.stage.Stage;\r\nimport javafx.stage.StageStyle;\r\n\r\npublic class CardCreator extends Application {\r\n\r\n\tpublic static void main(String[] args) {\r\n\t\tlaunch(args);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void start(Stage primaryStage) throws Exception {\r\n\t\tprimaryStage.setTitle(\"Card Creator\");\r\n\t\tprimaryStage.initStyle(StageStyle.UNIFIED);\r\n\r\n\t\tScene scene = new Scene(new EditorMainWindow());\r\n\t\t// scene.getStylesheets().add(IconFactory.class.getResource(\"main.css\").toExternalForm());\r\n\t\tprimaryStage.setScene(scene);\r\n\t\tprimaryStage.show();\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/tools/CardEditor.java",
    "content": "package net.demilich.metastone.tools;\r\n\r\nimport java.io.File;\r\nimport java.io.IOException;\r\n\r\nimport org.apache.commons.io.FileUtils;\r\n\r\nimport javafx.collections.FXCollections;\r\nimport javafx.collections.ObservableList;\r\nimport javafx.fxml.FXMLLoader;\r\nimport javafx.scene.Node;\r\nimport javafx.scene.control.ComboBox;\r\nimport javafx.scene.layout.VBox;\r\nimport net.demilich.metastone.game.spells.Spell;\r\nimport net.demilich.metastone.gui.common.ComboBoxKeyHandler;\r\n\r\npublic abstract class CardEditor extends VBox implements ICardEditor {\r\n\r\n\tpublic CardEditor(String fxmlFile) {\r\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/\" + fxmlFile));\r\n\t\tfxmlLoader.setRoot(this);\r\n\t\tfxmlLoader.setController(this);\r\n\r\n\t\ttry {\r\n\t\t\tfxmlLoader.load();\r\n\t\t} catch (IOException exception) {\r\n\t\t\tthrow new RuntimeException(exception);\r\n\t\t}\r\n\t}\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\tprotected void fillWithSpells(ComboBox<Class<? extends Spell>> comboBox) {\r\n\t\tObservableList<Class<? extends Spell>> items = FXCollections.observableArrayList();\r\n\t\tString spellPath = \"./src/main/java/\" + Spell.class.getPackage().getName().replace(\".\", \"/\") + \"/\";\r\n\t\tfor (File file : FileUtils.listFiles(new File(spellPath), new String[] { \"java\" }, false)) {\r\n\t\t\tString fileName = file.getName().replace(\".java\", \"\");\r\n\t\t\tString spellClassName = Spell.class.getPackage().getName() + \".\" + fileName;\r\n\t\t\tClass<? extends Spell> spellClass = null;\r\n\t\t\ttry {\r\n\t\t\t\tspellClass = (Class<? extends Spell>) Class.forName(spellClassName);\r\n\t\t\t} catch (ClassNotFoundException e) {\r\n\t\t\t\te.printStackTrace();\r\n\t\t\t}\r\n\r\n\t\t\titems.add(spellClass);\r\n\t\t}\r\n\t\tcomboBox.setItems(items);\r\n\t\tcomboBox.setOnKeyReleased(new ComboBoxKeyHandler<Class<? extends Spell>>(comboBox));\r\n\r\n\t}\r\n\r\n\t@Override\r\n\tpublic Node getPanel() {\r\n\t\treturn this;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/tools/EditorMainWindow.java",
    "content": "package net.demilich.metastone.tools;\r\n\r\nimport java.awt.Desktop;\r\nimport java.io.File;\r\nimport java.io.IOException;\r\nimport java.nio.file.Files;\r\nimport java.nio.file.Path;\r\nimport java.nio.file.Paths;\r\nimport java.util.ArrayList;\r\nimport java.util.Collections;\r\nimport java.util.EnumMap;\r\nimport java.util.List;\r\n\r\nimport org.apache.commons.lang3.StringUtils;\r\n\r\nimport com.google.gson.Gson;\r\nimport com.google.gson.GsonBuilder;\r\n\r\nimport javafx.beans.value.ObservableValue;\r\nimport javafx.collections.FXCollections;\r\nimport javafx.collections.ObservableList;\r\nimport javafx.event.ActionEvent;\r\nimport javafx.fxml.FXML;\r\nimport javafx.fxml.FXMLLoader;\r\nimport javafx.scene.control.Button;\r\nimport javafx.scene.control.CheckBox;\r\nimport javafx.scene.control.ComboBox;\r\nimport javafx.scene.control.Label;\r\nimport javafx.scene.control.RadioButton;\r\nimport javafx.scene.control.TextField;\r\nimport javafx.scene.control.ToggleGroup;\r\nimport javafx.scene.layout.BorderPane;\r\nimport javafx.scene.layout.Pane;\r\nimport javafx.stage.FileChooser;\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.cards.CardSet;\r\nimport net.demilich.metastone.game.cards.Rarity;\r\nimport net.demilich.metastone.game.cards.desc.CardDesc;\r\nimport net.demilich.metastone.game.cards.desc.ParseUtils;\r\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.gui.common.ComboBoxKeyHandler;\r\n\r\nclass EditorMainWindow extends BorderPane {\r\n\r\n\tprivate static String getCardId(CardDesc card) {\r\n\t\tString result = \"\";\r\n\t\tString prefix = \"\";\r\n\t\tswitch (card.type) {\r\n\t\tcase HERO_POWER:\r\n\t\t\tprefix = \"hero_power_\";\r\n\t\t\tbreak;\r\n\t\tcase MINION:\r\n\t\t\tprefix = card.collectible ? \"minion_\" : \"token_\";\r\n\t\t\tbreak;\r\n\t\tcase SPELL:\r\n\t\tcase CHOOSE_ONE:\r\n\t\t\tprefix = \"spell_\";\r\n\t\t\tbreak;\r\n\t\tcase WEAPON:\r\n\t\t\tprefix = \"weapon_\";\r\n\t\t\tbreak;\r\n\t\tdefault:\r\n\t\t\tbreak;\r\n\r\n\t\t}\r\n\t\tfor (String word : card.name.split(\" \")) {\r\n\t\t\tString cleansedWord = word.replace(\"'\", \"\").replace(\":\", \"\");\r\n\t\t\tresult += prefix + cleansedWord.toLowerCase();\r\n\t\t\tprefix = \"_\";\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n\r\n\t@FXML\r\n\tprivate RadioButton minionRadioButton;\r\n\r\n\t@FXML\r\n\tprivate RadioButton spellRadioButton;\r\n\r\n\t@FXML\r\n\tprivate RadioButton weaponRadioButton;\r\n\r\n\t@FXML\r\n\tprivate TextField nameField;\r\n\r\n\t@FXML\r\n\tprivate Label idLabel;\r\n\r\n\t@FXML\r\n\tprivate TextField descriptionField;\r\n\r\n\t@FXML\r\n\tprivate ComboBox<Rarity> rarityBox;\r\n\r\n\t@FXML\r\n\tprivate ComboBox<HeroClass> heroClassBox;\r\n\r\n\t@FXML\r\n\tprivate ComboBox<CardSet> cardSetBox;\r\n\r\n\t@FXML\r\n\tprivate TextField manaCostField;\r\n\r\n\t@FXML\r\n\tprivate CheckBox collectibleBox;\r\n\r\n\t@FXML\r\n\tprivate Pane contentPanel;\r\n\r\n\t@FXML\r\n\tprivate Button resetButton;\r\n\r\n\t@FXML\r\n\tprivate Button saveButton;\r\n\r\n\tprivate final ToggleGroup cardTypeGroup = new ToggleGroup();\r\n\tprivate List<ComboBox<Attribute>> attributeBoxes;\r\n\r\n\tprivate List<TextField> attributeFields;\r\n\tprivate ICardEditor cardEditor;\r\n\r\n\tprivate CardDesc card;\r\n\r\n\tpublic EditorMainWindow() {\r\n\r\n\t\tFXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(\"/fxml/EditorMainWindow.fxml\"));\r\n\t\tfxmlLoader.setRoot(this);\r\n\t\tfxmlLoader.setController(this);\r\n\r\n\t\ttry {\r\n\t\t\tfxmlLoader.load();\r\n\t\t} catch (IOException exception) {\r\n\t\t\tthrow new RuntimeException(exception);\r\n\t\t}\r\n\r\n\t\tminionRadioButton.setToggleGroup(cardTypeGroup);\r\n\t\tspellRadioButton.setToggleGroup(cardTypeGroup);\r\n\t\tweaponRadioButton.setToggleGroup(cardTypeGroup);\r\n\t\tminionRadioButton.setSelected(true);\r\n\r\n\t\tminionRadioButton.setOnAction(event -> setCardEditor(new MinionCardPanel()));\r\n\t\tspellRadioButton.setOnAction(event -> setCardEditor(new SpellCardPanel()));\r\n\r\n\t\tnameField.textProperty().addListener(this::onNameChanged);\r\n\t\tdescriptionField.textProperty().addListener(this::onDescriptionChanged);\r\n\r\n\t\trarityBox.setItems(FXCollections.observableArrayList(Rarity.values()));\r\n\t\theroClassBox.setItems(FXCollections.observableArrayList(HeroClass.values()));\r\n\t\tcardSetBox.setItems(FXCollections.observableArrayList(CardSet.values()));\r\n\r\n\t\tsetCardEditor(new MinionCardPanel());\r\n\r\n\t\trarityBox.valueProperty().addListener(this::onRarityChanged);\r\n\t\tresetButton.setOnAction(this::reset);\r\n\t\tsaveButton.setOnAction(this::onSaveButton);\r\n\t\theroClassBox.valueProperty().addListener(this::onHeroClassChanged);\r\n\t\tcardSetBox.valueProperty().addListener(this::onCardSetChanged);\r\n\t\tcollectibleBox.setOnAction(this::onCollectibleChanged);\r\n\t\tmanaCostField.textProperty().addListener(new IntegerListener(value -> card.baseManaCost = value));\r\n\r\n\t\tattributeBoxes = new ArrayList<>();\r\n\t\tattributeFields = new ArrayList<>();\r\n\t\tfor (int i = 1; i < 99; i++) {\r\n\t\t\t@SuppressWarnings(\"unchecked\")\r\n\t\t\tComboBox<Attribute> box = (ComboBox<Attribute>) lookup(\"#attributeBox\" + i);\r\n\t\t\tif (box == null) {\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\tTextField field = (TextField) lookup(\"#attributeField\" + i);\r\n\t\t\tattributeBoxes.add(box);\r\n\t\t\tattributeFields.add(field);\r\n\t\t}\r\n\t\tsetupAttributeBoxes();\r\n\t}\r\n\r\n\tprivate Object getAttributeValue(String valueString) {\r\n\t\tObject value = null;\r\n\t\tif (ParseUtils.tryParseInt(valueString)) {\r\n\t\t\tvalue = Integer.parseInt(valueString);\r\n\t\t} else if (ParseUtils.tryParseBool(valueString)) {\r\n\t\t\tvalue = Boolean.parseBoolean(valueString);\r\n\t\t} else {\r\n\t\t\tvalue = valueString;\r\n\t\t}\r\n\t\treturn value;\r\n\t}\r\n\r\n\tprivate void onAttributesChanged() {\r\n\t\tcard.attributes = new EnumMap<Attribute, Object>(Attribute.class);\r\n\t\tfor (int i = 0; i < attributeBoxes.size(); i++) {\r\n\t\t\tComboBox<Attribute> attributeBox = attributeBoxes.get(i);\r\n\t\t\tTextField attributeField = attributeFields.get(i);\r\n\t\t\tif (attributeBox.getSelectionModel().getSelectedItem() == null) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tif (StringUtils.isEmpty(attributeField.getText())) {\r\n\t\t\t\tattributeField.setText(\"true\");\r\n\t\t\t}\r\n\r\n\t\t\tAttribute attribute = attributeBox.getSelectionModel().getSelectedItem();\r\n\t\t\tObject value = getAttributeValue(attributeField.getText());\r\n\t\t\tcard.attributes.put(attribute, value);\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void onCardSetChanged(ObservableValue<? extends CardSet> ov, CardSet oldCardSet, CardSet newCardSet) {\r\n\t\tcard.set = newCardSet;\r\n\t}\r\n\r\n\tprivate void onCollectibleChanged(ActionEvent event) {\r\n\t\tcard.collectible = collectibleBox.isSelected();\r\n\t}\r\n\r\n\tprivate void onDescriptionChanged(ObservableValue<? extends String> ov, String oldValue, String newValue) {\r\n\t\tcard.description = newValue;\r\n\t}\r\n\r\n\tprivate void onHeroClassChanged(ObservableValue<? extends HeroClass> ov, HeroClass oldHeroClass, HeroClass newHeroClass) {\r\n\t\tcard.heroClass = newHeroClass;\r\n\t}\r\n\r\n\tprivate void onNameChanged(ObservableValue<? extends String> ov, String oldValue, String newValue) {\r\n\t\tcard.name = newValue;\r\n\t\tcard.id = getCardId(card);\r\n\t\tidLabel.setText(card.id);\r\n\t}\r\n\r\n\tprivate void onRarityChanged(ObservableValue<? extends Rarity> ov, Rarity oldRarity, Rarity newRarity) {\r\n\t\tcard.rarity = newRarity;\r\n\t}\r\n\r\n\tprivate void onSaveButton(ActionEvent event) {\r\n\t\tsave();\r\n\t}\r\n\r\n\tprivate void reset(ActionEvent event) {\r\n\t\tfor (int i = 0; i < attributeBoxes.size(); i++) {\r\n\t\t\tattributeBoxes.get(i).valueProperty().set(null);\r\n\t\t}\r\n\t\tcard.attributes = null;\r\n\t\tcardEditor.reset();\r\n\t}\r\n\r\n\tprivate void save() {\r\n\t\tFileChooser fileChooser = new FileChooser();\r\n\t\tfileChooser.setTitle(\"Save card\");\r\n\t\tfileChooser.setInitialDirectory(new File(\"./cards/\"));\r\n\t\tfileChooser.setInitialFileName(card.id + \".json\");\r\n\t\tFile file = fileChooser.showSaveDialog(getScene().getWindow());\r\n\t\tif (file == null) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tSystem.out.println(\"Saving to: \" + file.getName());\r\n\t\tGsonBuilder builder = new GsonBuilder().setPrettyPrinting();\r\n\t\tbuilder.disableHtmlEscaping();\r\n\t\tbuilder.registerTypeAdapter(SpellDesc.class, new SpellDescSerializer());\r\n\t\tGson gson = builder.create();\r\n\t\tString json = gson.toJson(card);\r\n\t\ttry {\r\n\t\t\t// FileUtils.writeStringToFile(file, json);\r\n\t\t\tPath path = Paths.get(file.getPath());\r\n\t\t\tFiles.write(path, json.getBytes());\r\n\t\t\tDesktop.getDesktop().open(file);\r\n\t\t} catch (IOException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void setCardEditor(ICardEditor cardEditor) {\r\n\t\tthis.cardEditor = cardEditor;\r\n\t\tCardDesc newCard = cardEditor.getCardDesc();\r\n\t\tif (card != null) {\r\n\t\t\tnewCard.name = card.name;\r\n\t\t\tnewCard.description = card.description;\r\n\t\t\tnewCard.rarity = card.rarity;\r\n\t\t\tnewCard.heroClass = card.heroClass;\r\n\t\t\tnewCard.baseManaCost = card.baseManaCost;\r\n\t\t} else {\r\n\t\t\tnewCard.name = \"\";\r\n\t\t\tnewCard.rarity = Rarity.FREE;\r\n\t\t\tnewCard.heroClass = HeroClass.ANY;\r\n\t\t\tnewCard.set = CardSet.CUSTOM;\r\n\t\t\tnewCard.baseManaCost = 0;\r\n\t\t\tnewCard.collectible = true;\r\n\t\t}\r\n\t\tcard = newCard;\r\n\t\tcard.id = getCardId(card);\r\n\t\tcontentPanel.getChildren().setAll(cardEditor.getPanel());\r\n\r\n\t\t// update ui\r\n\t\tnameField.setText(card.name);\r\n\t\tidLabel.setText(card.id);\r\n\t\tdescriptionField.setText(card.description);\r\n\t\trarityBox.getSelectionModel().select(card.rarity);\r\n\t\theroClassBox.getSelectionModel().select(card.heroClass);\r\n\t\tcardSetBox.getSelectionModel().select(card.set);\r\n\t\tmanaCostField.setText(String.valueOf(card.baseManaCost));\r\n\t\tcollectibleBox.setSelected(card.collectible);\r\n\t}\r\n\r\n\tprivate void setupAttributeBoxes() {\r\n\t\tfor (ComboBox<Attribute> comboBox : attributeBoxes) {\r\n\t\t\tObservableList<Attribute> items = FXCollections.observableArrayList();\r\n\t\t\titems.addAll(Attribute.values());\r\n\t\t\tCollections.sort(items, (obj1, obj2) -> {\r\n\t\t\t\tif (obj1 == obj2) {\r\n\t\t\t\t\treturn 0;\r\n\t\t\t\t}\r\n\t\t\t\tif (obj1 == null) {\r\n\t\t\t\t\treturn -1;\r\n\t\t\t\t}\r\n\t\t\t\tif (obj2 == null) {\r\n\t\t\t\t\treturn 1;\r\n\t\t\t\t}\r\n\t\t\t\treturn obj1.toString().compareTo(obj2.toString());\r\n\t\t\t});\r\n\t\t\tcomboBox.setItems(items);\r\n\t\t\tcomboBox.valueProperty().addListener((ov, oldValue, newValue) -> onAttributesChanged());\r\n\t\t\tcomboBox.setOnKeyReleased(new ComboBoxKeyHandler<Attribute>(comboBox));\r\n\t\t}\r\n\t\tfor (TextField attributeField : attributeFields) {\r\n\t\t\tattributeField.textProperty().addListener((ov, oldValue, newValue) -> onAttributesChanged());\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/tools/ICardEditor.java",
    "content": "package net.demilich.metastone.tools;\r\n\r\nimport javafx.scene.Node;\r\nimport net.demilich.metastone.game.cards.desc.CardDesc;\r\n\r\npublic interface ICardEditor {\r\n\r\n\tpublic CardDesc getCardDesc();\r\n\r\n\tpublic Node getPanel();\r\n\r\n\tpublic void reset();\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/tools/ITextFieldAction.java",
    "content": "package net.demilich.metastone.tools;\r\n\r\ninterface ITextFieldAction {\r\n\r\n\tpublic void onChanged(int value);\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/tools/IntegerListener.java",
    "content": "package net.demilich.metastone.tools;\r\n\r\nimport javafx.beans.value.ChangeListener;\r\nimport javafx.beans.value.ObservableValue;\r\nimport javafx.scene.control.TextField;\r\n\r\npublic class IntegerListener implements ChangeListener<String> {\r\n\r\n\tprivate final ITextFieldAction action;\r\n\r\n\tpublic IntegerListener(ITextFieldAction action) {\r\n\t\tthis.action = action;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void changed(ObservableValue<? extends String> observable, String oldText, String newText) {\r\n\t\tif (newText.matches(\"\\\\d*\")) {\r\n\t\t\tint value = Integer.parseInt(newText);\r\n\t\t\taction.onChanged(value);\r\n\t\t} else {\r\n\t\t\tTextField textField = (TextField) observable;\r\n\t\t\ttextField.setText(oldText);\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/tools/MinionCardPanel.java",
    "content": "package net.demilich.metastone.tools;\r\n\r\nimport javafx.beans.value.ObservableValue;\r\nimport javafx.collections.FXCollections;\r\nimport javafx.fxml.FXML;\r\nimport javafx.scene.control.ComboBox;\r\nimport javafx.scene.control.TextField;\r\nimport net.demilich.metastone.game.cards.CardType;\r\nimport net.demilich.metastone.game.cards.desc.CardDesc;\r\nimport net.demilich.metastone.game.cards.desc.MinionCardDesc;\r\nimport net.demilich.metastone.game.entities.minions.Race;\r\nimport net.demilich.metastone.game.spells.Spell;\r\nimport net.demilich.metastone.game.spells.desc.BattlecryDesc;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.targeting.TargetSelection;\r\n\r\nclass MinionCardPanel extends CardEditor {\r\n\r\n\t@FXML\r\n\tprivate ComboBox<Race> raceBox;\r\n\r\n\t@FXML\r\n\tprivate TextField attackField;\r\n\r\n\t@FXML\r\n\tprivate TextField hpField;\r\n\r\n\t@FXML\r\n\tprivate ComboBox<Class<? extends Spell>> battlecrySpellBox;\r\n\t@FXML\r\n\tprivate ComboBox<TargetSelection> battlecryTargetSelectionBox;\r\n\r\n\t@FXML\r\n\tprivate ComboBox<Class<? extends Spell>> deathrattleSpellBox;\r\n\r\n\tprivate final MinionCardDesc card = new MinionCardDesc();\r\n\r\n\tpublic MinionCardPanel() {\r\n\t\tsuper(\"MinionCardPanel.fxml\");\r\n\r\n\t\traceBox.setItems(FXCollections.observableArrayList(Race.values()));\r\n\t\traceBox.valueProperty().addListener(this::onRaceChanged);\r\n\r\n\t\tbattlecryTargetSelectionBox.setItems(FXCollections.observableArrayList(TargetSelection.values()));\r\n\t\tbattlecryTargetSelectionBox.getSelectionModel().selectFirst();\r\n\t\tbattlecryTargetSelectionBox.valueProperty().addListener(this::onTargetSelectionChanged);\r\n\r\n\t\tbattlecrySpellBox.setConverter(new SpellStringConverter());\r\n\t\tfillWithSpells(battlecrySpellBox);\r\n\t\tbattlecrySpellBox.valueProperty().addListener(this::onBattlecryChanged);\r\n\r\n\t\tdeathrattleSpellBox.setConverter(new SpellStringConverter());\r\n\t\tfillWithSpells(deathrattleSpellBox);\r\n\t\tdeathrattleSpellBox.valueProperty().addListener(this::onDeathrattleChanged);\r\n\r\n\t\tattackField.textProperty().addListener(new IntegerListener(value -> card.baseAttack = value));\r\n\t\thpField.textProperty().addListener(new IntegerListener(value -> card.baseHp = value));\r\n\t}\r\n\r\n\t@Override\r\n\tpublic CardDesc getCardDesc() {\r\n\t\tcard.type = CardType.MINION;\r\n\t\tcard.name = \"\";\r\n\t\treturn card;\r\n\t}\r\n\r\n\tprivate void onBattlecryChanged(ObservableValue<? extends Class<? extends Spell>> ov, Class<? extends Spell> oldSpell,\r\n\t\t\tClass<? extends Spell> newSpell) {\r\n\t\tSpellDesc spell = new SpellDesc(SpellDesc.build(newSpell));\r\n\t\tif (card.battlecry == null) {\r\n\t\t\tcard.battlecry = new BattlecryDesc();\r\n\t\t}\r\n\t\tcard.battlecry.spell = spell;\r\n\t}\r\n\r\n\tprivate void onDeathrattleChanged(ObservableValue<? extends Class<? extends Spell>> ov, Class<? extends Spell> oldSpell,\r\n\t\t\tClass<? extends Spell> newSpell) {\r\n\t\tcard.deathrattle = new SpellDesc(SpellDesc.build(newSpell));\r\n\t}\r\n\r\n\tprivate void onRaceChanged(ObservableValue<? extends Race> ov, Race oldRace, Race newRace) {\r\n\t\tcard.race = newRace != Race.NONE ? newRace : null;\r\n\t}\r\n\r\n\tprivate void onTargetSelectionChanged(ObservableValue<? extends TargetSelection> ov, TargetSelection oldValue,\r\n\t\t\tTargetSelection newValue) {\r\n\t\tif (card.battlecry == null) {\r\n\t\t\tcard.battlecry = new BattlecryDesc();\r\n\t\t}\r\n\t\tcard.battlecry.targetSelection = newValue;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void reset() {\r\n\t\tbattlecrySpellBox.valueProperty().set(null);\r\n\t\tbattlecryTargetSelectionBox.getSelectionModel().select(TargetSelection.NONE);\r\n\t\tcard.battlecry = null;\r\n\t\tdeathrattleSpellBox.valueProperty().set(null);\r\n\t\tcard.deathrattle = null;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/tools/SpellCardPanel.java",
    "content": "package net.demilich.metastone.tools;\r\n\r\nimport javafx.beans.value.ObservableValue;\r\nimport javafx.collections.FXCollections;\r\nimport javafx.fxml.FXML;\r\nimport javafx.scene.control.ComboBox;\r\nimport net.demilich.metastone.game.cards.CardType;\r\nimport net.demilich.metastone.game.cards.desc.CardDesc;\r\nimport net.demilich.metastone.game.cards.desc.SpellCardDesc;\r\nimport net.demilich.metastone.game.spells.Spell;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.targeting.TargetSelection;\r\n\r\npublic class SpellCardPanel extends CardEditor {\r\n\r\n\t@FXML\r\n\tprivate ComboBox<Class<? extends Spell>> spellBox;\r\n\t@FXML\r\n\tprivate ComboBox<TargetSelection> targetSelectionBox;\r\n\r\n\tprivate SpellCardDesc card = new SpellCardDesc();\r\n\r\n\tpublic SpellCardPanel() {\r\n\t\tsuper(\"SpellCardPanel.fxml\");\r\n\r\n\t\ttargetSelectionBox.setItems(FXCollections.observableArrayList(TargetSelection.values()));\r\n\t\ttargetSelectionBox.getSelectionModel().selectFirst();\r\n\t\ttargetSelectionBox.valueProperty().addListener(this::onTargetSelectionChanged);\r\n\r\n\t\tspellBox.setConverter(new SpellStringConverter());\r\n\t\tfillWithSpells(spellBox);\r\n\t\tspellBox.valueProperty().addListener(this::onSpellChanged);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic CardDesc getCardDesc() {\r\n\t\tcard.type = CardType.SPELL;\r\n\t\tcard.targetSelection = TargetSelection.NONE;\r\n\t\tcard.name = \"\";\r\n\t\treturn card;\r\n\t}\r\n\r\n\tprivate void onSpellChanged(ObservableValue<? extends Class<? extends Spell>> ov, Class<? extends Spell> oldSpell,\r\n\t\t\tClass<? extends Spell> newSpell) {\r\n\t\tcard.spell = new SpellDesc(SpellDesc.build(newSpell));\r\n\t}\r\n\r\n\tprivate void onTargetSelectionChanged(ObservableValue<? extends TargetSelection> ov, TargetSelection oldValue,\r\n\t\t\tTargetSelection newValue) {\r\n\t\tcard.targetSelection = newValue;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void reset() {\r\n\t\tspellBox.valueProperty().set(null);\r\n\t\tcard.spell = null;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/tools/SpellDescSerializer.java",
    "content": "package net.demilich.metastone.tools;\r\n\r\nimport java.lang.reflect.Type;\r\n\r\nimport com.google.gson.JsonElement;\r\nimport com.google.gson.JsonObject;\r\nimport com.google.gson.JsonPrimitive;\r\nimport com.google.gson.JsonSerializationContext;\r\nimport com.google.gson.JsonSerializer;\r\n\r\nimport net.demilich.metastone.game.cards.desc.ParseUtils;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class SpellDescSerializer implements JsonSerializer<SpellDesc> {\r\n\r\n\t@Override\r\n\tpublic JsonElement serialize(SpellDesc spell, Type type, JsonSerializationContext context) {\r\n\t\tJsonObject result = new JsonObject();\r\n\t\tresult.add(\"class\", new JsonPrimitive(spell.getSpellClass().getSimpleName()));\r\n\t\tfor (SpellArg spellArg : SpellArg.values()) {\r\n\t\t\tif (spellArg == SpellArg.CLASS) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif (!spell.contains(spellArg)) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tString argName = ParseUtils.toCamelCase(spellArg.toString());\r\n\t\t\tresult.add(argName, new JsonPrimitive(spell.get(spellArg).toString()));\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/tools/SpellStringConverter.java",
    "content": "package net.demilich.metastone.tools;\r\n\r\nimport javafx.util.StringConverter;\r\nimport net.demilich.metastone.game.spells.Spell;\r\n\r\npublic class SpellStringConverter extends StringConverter<Class<? extends Spell>> {\r\n\r\n\t@Override\r\n\tpublic Class<? extends Spell> fromString(String arg0) {\r\n\t\treturn null;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String toString(Class<? extends Spell> spell) {\r\n\t\treturn spell.getSimpleName();\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/net/demilich/metastone/tools/WeaponClassPanel.java",
    "content": "package net.demilich.metastone.tools;\r\n\r\nimport net.demilich.metastone.game.cards.CardType;\r\nimport net.demilich.metastone.game.cards.desc.CardDesc;\r\nimport net.demilich.metastone.game.cards.desc.WeaponCardDesc;\r\n\r\npublic class WeaponClassPanel extends CardEditor {\r\n\r\n\tprivate final WeaponCardDesc card = new WeaponCardDesc();\r\n\r\n\tpublic WeaponClassPanel() {\r\n\t\tsuper(\"WeaponCardPanel.fxml\");\r\n\t}\r\n\r\n\t@Override\r\n\tpublic CardDesc getCardDesc() {\r\n\t\tcard.type = CardType.WEAPON;\r\n\t\tcard.name = \"\";\r\n\t\treturn card;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void reset() {\r\n\t\tcard.battlecry = null;\r\n\t\tcard.deathrattle = null;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/resources/css/deckbuilder.css",
    "content": ".class-button {\n\t-fx-font-size: 20pt;\n    -fx-font-family: \"System\";\n}\n\n.card-entry-name {\n\t-fx-font-size: 10pt;\n    -fx-font-family: \"System\";\n    -fx-fill: black;\n    -fx-font-weight: bold;\n\t-fx-wrap-text: true;\n}\n\n.card-entry {\n\t-fx-border-width: 2;\n\t-fx-border-color: black;\n\t-fx-background-color: tan;\n\t-fx-border-radius: 10;\n}\n\n.card-entry:hover {\n\t-fx-border-width: 3;\n\t-fx-border-color: yellow;\n\t-fx-background-color: silver;\n\t-fx-border-radius: 10;\n}\n\n.delete-button {\n\t-fx-background-color: transparent;\n\t-fx-background-radius: 0;\n    -fx-background-insets: 0;\n\t-fx-padding: 4;\n}\n\n.delete-button:hover {\n\t-fx-background-color: transparent;\n\t-fx-scale-x: 1.2;\n\t-fx-scale-y: 1.2;\n\t-fx-scale-z: 1.2;\n}\n"
  },
  {
    "path": "app/src/main/resources/css/gameboard.css",
    "content": ".board {\n\t-fx-background-color: bisque;\n}\n\n.center-message {\n\t-fx-font-size: 20pt;\n    -fx-font-family: \"System\";\n    -fx-font-weight: bold;\n    -fx-text-fill: gray;\n    -fx-font-weight: bold;\n    -fx-font-style: italic;\n}\n\n#target-button {\n\t-fx-background-color: transparent;\n}"
  },
  {
    "path": "app/src/main/resources/css/main.css",
    "content": ".button {\n\t-fx-background-color:\n        #090a0c,\n        linear-gradient(#38424b 0%, #1f2429 20%, #191d22 100%),\n        linear-gradient(#20262b, #191d22),\n        radial-gradient(center 50% 0%, radius 100%, rgba(114,131,148,0.9), rgba(255,255,255,0));\n    -fx-background-radius: 5,4,3,5;\n    -fx-background-insets: 0,1,2,0;\n    -fx-text-fill: white;\n    -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 5, 0.0 , 0 , 1 );\n    -fx-font-family: \"System\";\n    -fx-text-fill: linear-gradient(white, #d0d0d0);\n    -fx-font-size: 16px;\n    -fx-padding: 8 16 8 16;\n}\n\n.button:hover {\n\t-fx-background-color: \n        #090a0c,\n        linear-gradient(#38424b 0%, #1f2429 20%, #191d22 100%),\n        linear-gradient(#10161b, #090d12),\n        radial-gradient(center 50% 0%, radius 100%, rgba(84,101,118,0.9), rgba(128,128,128,0));\n    -fx-text-fill: darkorange;\n}\n\n.combo-box {\n\t-fx-background-color: \n        #090a0c,\n        linear-gradient(#38424b 0%, #1f2429 20%, #191d22 100%),\n        linear-gradient(#20262b, #191d22),\n        radial-gradient(center 50% 0%, radius 100%, rgba(114,131,148,0.9), rgba(255,255,255,0));\n    -fx-background-radius: 5,4,3,5;\n    -fx-background-insets: 0,1,2,0;\n    -fx-font-family: \"System\";\n    -fx-text-fill: black;\n    -fx-font-size: 12px;\n}\n\n.combo-box .list-cell:selected {\n     -fx-text-fill: white; \n}\n\n.panel {\n\t-fx-background-color: dimgray;\n}\n\n.label {\n\t-fx-font-family: \"System\";\n\t-fx-text-fill: black;\n}\n\n.progress-bar .bar {\n    -fx-background-color: linear-gradient(to bottom, derive(darkorange, -22%), derive(darkorange, 3%), derive(darkorange, -18%), derive(darkorange, -24%) );\n    -fx-background-insets: 3 3 4 3;\n    -fx-background-radius: 2;\n    -fx-padding: 0.75em;\n}\n\n.progress-bar .track {\n    -fx-background-color: \n          -fx-shadow-highlight-color,\n          linear-gradient(to bottom, derive(dimgray, 20%), dimgray),\n          linear-gradient(to bottom, \n            derive(darkgray, 3%),\n            derive(darkgray, 10%),\n            derive(darkgray, 7%),\n            derive(darkgray, 1%)\n          );\n}\n\n.scroll-pane {\n\t-fx-background-color: linen;\n\t-fx-control-inner-background: linen;\n}\n\n.bordered-panel {\n\t-fx-background-color: darkgray;\n\t-fx-background-radius: 10;\n\t-fx-border-width: 2;\n\t-fx-border-color: darkslategrey;\n\t-fx-border-radius: 10;\n\t-fx-border-insets: 4;\n}\n\n.bordered-dialog {\n\t-fx-background-color: linen;\n\t-fx-background-radius: 10;\n\t-fx-border-width: 3;\n\t-fx-border-color: darkslategrey;\n\t-fx-border-radius: 10;\n\t-fx-border-insets: 4;\n}\n\n.dialog-text {\n\t-fx-font-size: 10pt;\n    -fx-font-family: \"System\";\n    -fx-text-fill: black;\n}\n\n.sidebar {\n\t-fx-background: tan;\n\t-fx-border-width: 2;\n\t-fx-border-color: darkslategrey;\n\t-fx-background-color: tan;\n\t-fx-border-radius: 10;\n}\n\n.header {\n\t-fx-font-size: 24pt;\n    -fx-font-family: \"System\";\n    -fx-font-weight: bold;\n    -fx-fill: black;\n}\n\n.card-border {\n\t -fx-background-color: navajowhite;\n\t -fx-border-color: black;\n\t -fx-border-style: solid;\n\t -fx-border-width: 1.0;\n}\n\n.outlined-score {\n \t-fx-font-size: 14pt;\n    -fx-font-family: \"System\";\n    -fx-font-weight: bold;\n    -fx-fill: white;\n    -fx-stroke: black;\n    -fx-stroke-width: 2;\n}\n\n.outlined-score-yellow {\n \t-fx-font-size: 14pt;\n    -fx-font-family: \"System\";\n    -fx-font-weight: bold;\n    -fx-fill: yellow;\n    -fx-stroke: black;\n    -fx-stroke-width: 2;\n}\n\n.tooltip-description {\n\t-fx-font-size: 11pt;\n    -fx-font-family: \"System\";\n    -fx-text-fill: black;\n}\n\n.race-label {\n\t-fx-font-size: 8pt;\n    -fx-font-family: \"System\";\n    -fx-text-fill: black;\n    -fx-font-weight: bold;\n    -fx-font-style : italic;\n}\n\n.name-big {\n\t-fx-font-size: 14pt;\n    -fx-font-family: \"System\";\n    -fx-font-weight: bold;\n    -fx-text-fill: black;\n}\n\n.name-small {\n\t-fx-font-size: 10pt;\n    -fx-font-family: \"System\";\n    -fx-font-weight: bold;\n    -fx-text-fill: black;\n}\n\n.progress-text {\n\t-fx-font-size: 8pt;\n    -fx-font-family: \"System\";\n    -fx-font-weight: bold;\n    -fx-text-fill: black;\n}\n\n.info-small {\n\t-fx-font-size: 10pt;\n    -fx-font-family: \"System\";\n    -fx-text-fill: black;\n}\n\n.label-white {\n\t-fx-font-family: \"System\";\n\t-fx-text-fill: white;\n}\n\n.default-label {\n\t-fx-font-size: 18pt;\n    -fx-font-family: \"System\";\n    -fx-fill: dimgray;\n}\n\n.separator {\n    -fx-background-color: dimgray;\n    -fx-background-radius: 2;\n}\n\n.separator *.line \n{ \n\t-fx-border-style: solid;\n\t-fx-border-width: 0.5px;\n}\n\n.text-field {\n   -fx-background-color: antiquewhite;\n   -fx-border-width: 1;\n   -fx-border-color: black;\n}\n\n.mana-label {\n\t-fx-font-size: 12pt;\n    -fx-font-family: \"System\";\n    -fx-text-fill: blue;\n    -fx-font-weight: bold;\n}\n\n.card-entry {\n\t-fx-border-width: 2;\n\t-fx-border-color: black;\n\t-fx-background-color: tan;\n\t-fx-border-radius: 10;\n\t-fx-background-radius: 10;\n}\n\n"
  },
  {
    "path": "app/src/main/resources/css/mainmenu.css",
    "content": ".title {\n\t-fx-font-size: 32pt;\n    -fx-font-family: \"System\";\n    -fx-font-weight: bold;\n    -fx-effect: dropshadow(one-pass-box, black, 6.0, 0.9, 1.0, 1.0);\n    -fx-fill: white;\n}\n\n.main-button {\n    -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 5, 0.0 , 0 , 1 );\n    -fx-font-family: \"System\";\n    -fx-text-fill: linear-gradient(white, #d0d0d0);\n    -fx-font-size: 20px;\n    -fx-font-weight: bold;\n}\n\n.main-button:hover {\n    -fx-text-fill: darkorange;\n}"
  },
  {
    "path": "app/src/main/resources/fxml/BattleBatchResultToken.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.chart.*?>\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"140.0\" prefWidth=\"200.0\" styleClass=\"card-entry\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <center>\n      <StackPane>\n         <children>\n            <ProgressIndicator fx:id=\"progressIndicator\" progress=\"0.0\" />\n            <VBox fx:id=\"contentPane\" alignment=\"TOP_CENTER\" BorderPane.alignment=\"CENTER\">\n               <children>\n                  <Label fx:id=\"deck1Label\" alignment=\"TOP_LEFT\" minHeight=\"40.0\" styleClass=\"info-small\" text=\"&lt;Deckname deck1&gt;\" wrapText=\"true\">\n                     <graphic>\n                        <ImageView fx:id=\"deck1Icon\" fitHeight=\"32.0\" fitWidth=\"32.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                           <image>\n                              <Image url=\"/img/classes/priest.png\" />\n                           </image>\n                        </ImageView>\n                     </graphic>\n                     <VBox.margin>\n                        <Insets />\n                     </VBox.margin>\n                  </Label>\n                  <Label alignment=\"CENTER\" contentDisplay=\"CENTER\" styleClass=\"name-small\" text=\"VS.\" />\n                  <Label fx:id=\"deck2Label\" alignment=\"TOP_RIGHT\" contentDisplay=\"RIGHT\" minHeight=\"40.0\" styleClass=\"info-small\" text=\"&lt;Deckname deck2&gt;\" textAlignment=\"RIGHT\" wrapText=\"true\">\n                     <graphic>\n                        <ImageView fx:id=\"deck2Icon\" fitHeight=\"32.0\" fitWidth=\"32.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                           <image>\n                              <Image url=\"/img/classes/priest.png\" />\n                           </image>\n                        </ImageView>\n                     </graphic>\n                     <VBox.margin>\n                        <Insets />\n                     </VBox.margin>\n                  </Label>\n                  <HBox prefHeight=\"100.0\" prefWidth=\"200.0\">\n                     <children>\n                        <StackPane>\n                           <children>\n                              <ProgressBar fx:id=\"winrate1Bar\" nodeOrientation=\"RIGHT_TO_LEFT\" prefWidth=\"100.0\" progress=\"1.0\" />\n                              <Label fx:id=\"winrate1Label\" alignment=\"TOP_CENTER\" styleClass=\"progress-text\" text=\"Label\" StackPane.alignment=\"CENTER\" />\n                           </children>\n                        </StackPane>\n                        <StackPane layoutX=\"10.0\" layoutY=\"10.0\">\n                           <children>\n                              <ProgressBar fx:id=\"winrate2Bar\" nodeOrientation=\"LEFT_TO_RIGHT\" prefWidth=\"100.0\" progress=\"1.0\" />\n                              <Label fx:id=\"winrate2Label\" alignment=\"TOP_CENTER\" styleClass=\"progress-text\" text=\"Label\" StackPane.alignment=\"CENTER\" />\n                           </children>\n                        </StackPane>\n                     </children>\n                  </HBox>\n               </children>\n            </VBox>\n         </children>\n      </StackPane>\n   </center>\n   <padding>\n      <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n   </padding>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/BattleOfDecksConfigView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"768.0\" prefWidth=\"1024.0\" styleClass=\"panel\"  type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <top>\n      <Text strokeType=\"OUTSIDE\" strokeWidth=\"0.0\" styleClass=\"header\" text=\"Battle of Decks\" BorderPane.alignment=\"CENTER\" />\n   </top>\n   <padding>\n      <Insets bottom=\"20.0\" left=\"20.0\" right=\"20.0\" top=\"20.0\" />\n   </padding>\n   <center>\n      <VBox alignment=\"TOP_CENTER\" spacing=\"8.0\" BorderPane.alignment=\"TOP_CENTER\">\n         <children>\n            <Label styleClass=\"name-small\" text=\"Deck Format:\" />\n            <ComboBox fx:id=\"formatBox\" prefWidth=\"150.0\" />\n            <HBox fx:id=\"playerArea\" alignment=\"CENTER\" maxHeight=\"400.0\" BorderPane.alignment=\"CENTER\" />\n            <Label styleClass=\"name-small\" text=\"Number of games (per match)\" />\n            <ComboBox fx:id=\"numberOfGamesBox\" prefWidth=\"150.0\" />\n            <Label layoutX=\"407.0\" layoutY=\"310.0\" styleClass=\"name-small\" text=\"Behaviour\" />\n            <ComboBox fx:id=\"behaviourBox\" layoutX=\"427.0\" layoutY=\"324.0\" prefWidth=\"150.0\" />\n            <HBox alignment=\"CENTER\" spacing=\"8.0\">\n               <children>\n                  <VBox>\n                     <children>\n                        <Label styleClass=\"name-small\" text=\"Available Decks\" />\n                        <ScrollPane prefViewportHeight=\"300.0\" prefViewportWidth=\"300.0\">\n                           <content>\n                              <ListView fx:id=\"availableDecksListView\" prefHeight=\"300.0\" prefWidth=\"300.0\" />\n                           </content>\n                        </ScrollPane>\n                     </children>\n                  </VBox>\n                  <VBox alignment=\"CENTER\" spacing=\"8.0\">\n                     <children>\n                        <Button fx:id=\"addButton\" mnemonicParsing=\"false\" text=\"&gt;&gt;\" />\n                        <Button fx:id=\"removeButton\" mnemonicParsing=\"false\" text=\"&lt;&lt;\" />\n                     </children>\n                  </VBox>\n                  <VBox>\n                     <children>\n                        <Label styleClass=\"name-small\" text=\"Selected Decks\" />\n                        <ScrollPane prefViewportHeight=\"300.0\" prefViewportWidth=\"300.0\">\n                           <content>\n                              <ListView fx:id=\"selectedDecksListView\" prefHeight=\"300.0\" prefWidth=\"300.0\" />\n                           </content>\n                        </ScrollPane>\n                     </children>\n                  </VBox>\n               </children>\n            </HBox>\n         </children>\n         <padding>\n            <Insets top=\"20.0\" />\n         </padding>\n      </VBox>\n   </center>\n   <bottom>\n      <AnchorPane>\n         <children>\n            <Button fx:id=\"backButton\" mnemonicParsing=\"false\" text=\"Back\" AnchorPane.leftAnchor=\"0.0\" />\n            <Button fx:id=\"startButton\" mnemonicParsing=\"false\" text=\"Start\" AnchorPane.leftAnchor=\"380.0\" AnchorPane.rightAnchor=\"380.0\" />\n         </children>\n      </AnchorPane>\n   </bottom>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/BattleOfDecksResultView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.chart.*?>\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"768.0\" prefWidth=\"1024.0\" styleClass=\"panel\"  type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n\t<top>\n\t\t<Text strokeType=\"OUTSIDE\" strokeWidth=\"0.0\" styleClass=\"header\" text=\"Battle of Decks\" BorderPane.alignment=\"CENTER\" />\n\t</top>\n\t<padding>\n\t\t<Insets bottom=\"20.0\" left=\"20.0\" right=\"20.0\" top=\"20.0\" />\n\t</padding>\n\t<bottom>\n\t\t<AnchorPane>\n\t\t\t<children>\n\t\t\t\t<Button fx:id=\"backButton\" mnemonicParsing=\"false\" text=\"Back\" AnchorPane.leftAnchor=\"0.0\" />\n\t\t\t</children>\n\t\t</AnchorPane>\n\t</bottom>\n   <center>\n      <HBox>\n         <children>\n            <VBox alignment=\"TOP_CENTER\">\n               <children>\n                  <Label styleClass=\"name-big\" text=\"Result (batches)\" />\n                  <ScrollPane prefViewportHeight=\"600.0\" prefViewportWidth=\"720.0\" prefWidth=\"720.0\">\n                     <content>\n                        <FlowPane fx:id=\"batchResultPane\" hgap=\"4.0\" prefWidth=\"700.0\" prefWrapLength=\"800.0\" vgap=\"4.0\" BorderPane.alignment=\"CENTER\" />\n                     </content>\n                  </ScrollPane>\n               </children>\n            </VBox>\n            <VBox alignment=\"TOP_CENTER\">\n               <children>\n                  <Label styleClass=\"name-big\" text=\"Overall result\" />\n                  <TableView fx:id=\"rankingTable\" prefHeight=\"602.0\" prefWidth=\"400.0\">\n                    <columns>\n                      <TableColumn prefWidth=\"111.0\" text=\"Deck\" />\n                      <TableColumn minWidth=\"0.0\" prefWidth=\"134.0\" text=\"Win rate\" />\n                    </columns>\n                  </TableView>\n               </children>\n            </VBox>\n         </children>\n      </HBox>\n   </center>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/CardCollectionEditor.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.chart.*?>\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"480.0\" prefWidth=\"600.0\" styleClass=\"bordered-dialog\" stylesheets=\"@../css/main.css\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <center>\n      <HBox alignment=\"CENTER\" prefHeight=\"100.0\" prefWidth=\"200.0\" spacing=\"4.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <VBox alignment=\"TOP_CENTER\" spacing=\"4.0\">\n               <children>\n                  <Button fx:id=\"removeCardButton\" mnemonicParsing=\"false\" text=\"Remove\">\n                     <graphic>\n                        <ImageView fitHeight=\"24.0\" fitWidth=\"24.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                           <image>\n                              <Image url=\"/img/ui/delete.png\" />\n                           </image>\n                        </ImageView>\n                     </graphic></Button>\n                  <ListView fx:id=\"editableListView\" prefHeight=\"270.0\" prefWidth=\"240.0\" />\n                  <Label fx:id=\"cardCountLabel\" text=\"&lt;card count&gt;\">\n                     <font>\n                        <Font name=\"Arial\" size=\"16.0\" />\n                     </font>\n                     <VBox.margin>\n                        <Insets />\n                     </VBox.margin>\n                     <padding>\n                        <Insets bottom=\"1.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n                     </padding>\n                  </Label>\n               </children>\n            </VBox>\n            <VBox alignment=\"TOP_CENTER\" spacing=\"4.0\">\n               <children>\n                  <Button fx:id=\"addCardButton\" mnemonicParsing=\"false\" text=\"Add\">\n                     <graphic>\n                        <ImageView fitHeight=\"24.0\" fitWidth=\"24.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                           <image>\n                              <Image url=\"/img/ui/new.png\" />\n                           </image>\n                        </ImageView>\n                     </graphic></Button>\n                  <ListView fx:id=\"catalogueListView\" layoutX=\"10.0\" layoutY=\"10.0\" prefHeight=\"270.0\" prefWidth=\"240.0\" />\n                  <HBox alignment=\"CENTER\" spacing=\"4.0\">\n                     <children>\n                        <TextField fx:id=\"filterTextfield\" promptText=\"&lt;type to filter cards&gt;\" />\n                        <Button fx:id=\"clearFilterButton\" mnemonicParsing=\"false\" text=\"clear\" />\n                     </children>\n                  </HBox>\n               </children>\n            </VBox>\n         </children>\n      </HBox>\n   </center>\n   <bottom>\n      <HBox alignment=\"CENTER\" spacing=\"128.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <Button fx:id=\"okButton\" mnemonicParsing=\"false\" text=\"Ok\" />\n            <Button fx:id=\"cancelButton\" layoutX=\"10.0\" layoutY=\"10.0\" mnemonicParsing=\"false\" text=\"Cancel\" />\n         </children>\n         <BorderPane.margin>\n            <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n         </BorderPane.margin>\n         <padding>\n            <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n         </padding>\n      </HBox>\n   </bottom>\n   <top>\n      <Label fx:id=\"headerLabel\" styleClass=\"header\" text=\"&lt;title&gt;\" BorderPane.alignment=\"CENTER\" />\n   </top>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/CardEntry.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.control.*?>\n<?import java.net.*?>\n<?import javafx.scene.text.*?>\n<?import javafx.scene.image.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root styleClass=\"card-entry\" alignment=\"CENTER_LEFT\" maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"40.0\" prefWidth=\"220.0\" type=\"HBox\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <children>\n      <StackPane prefHeight=\"40.0\" prefWidth=\"40.0\">\n         <children>\n            <ImageView fitHeight=\"32.0\" fitWidth=\"32.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n               <image>\n                  <Image url=\"/img/common/mana_crystal.png\" />\n               </image>\n            </ImageView>\n            <Text styleClass=\"outlined-score\" fx:id=\"manaCostText\" strokeType=\"OUTSIDE\" strokeWidth=\"0.0\" text=\"0\" />\n         </children>\n      </StackPane>\n      <Label styleClass=\"card-entry-name\" fx:id=\"cardNameLabel\" maxWidth=\"160.0\" minWidth=\"160.0\" text=\"&lt;cardname&gt;\" />\n      <Text styleClass=\"outlined-score-yellow\" fx:id=\"countText\" strokeType=\"OUTSIDE\" strokeWidth=\"0.0\" text=\"2\" />\n   </children>\n   <stylesheets>\n      <URL value=\"@/css/deckbuilder.css\" />\n   </stylesheets>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/CardFilterView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import java.net.*?>\n<?import javafx.scene.control.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n\n<fx:root alignment=\"CENTER\" spacing=\"8.0\" type=\"HBox\" xmlns=\"http://javafx.com/javafx/8.0.40\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <children>\n      <HBox alignment=\"CENTER\" maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"50.0\" spacing=\"8.0\" styleClass=\"bordered-panel\">\n         <children>\n            <ImageView fitHeight=\"32.0\" fitWidth=\"32.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n               <image>\n                  <Image url=\"/img/ui/search.png\" />\n               </image>\n            </ImageView>\n            <TextField fx:id=\"searchField\" promptText=\"Search...\" />\n         </children>\n         <stylesheets>\n            <URL value=\"@/css/deckbuilder.css\" />\n         </stylesheets>\n         <padding>\n            <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n         </padding>\n         <opaqueInsets>\n            <Insets />\n         </opaqueInsets>\n      </HBox>\n      <VBox>\n        <ComboBox fx:id=\"cardSetBox\" prefWidth=\"150.0\" />\n        <ComboBox fx:id=\"deckFormatBox\" prefWidth=\"150.0\" />\n      </VBox>\n   </children>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/CardPanel.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.chart.*?>\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root styleClass=\"panel\" spacing=\"4.0\"  type=\"VBox\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <children>\n      <Label styleClass=\"info-small\" text=\"Cards:\" />\n      <Button fx:id=\"editHandButton\" mnemonicParsing=\"false\" text=\"Edit hand\" />\n      <Button fx:id=\"editDeckButton\" layoutX=\"10.0\" layoutY=\"117.0\" mnemonicParsing=\"false\" text=\"Edit deck\" />\n   </children>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/CardTooltip.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.*?>\n<?import javafx.scene.shape.*?>\n<?import java.lang.*?>\n<?import java.net.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.scene.layout.*?>\n<?import javafx.scene.layout.BorderPane?>\n<?import javafx.scene.text.*?>\n\n<fx:root prefHeight=\"240.0\" prefWidth=\"174.0\" styleClass=\"card-border\"  type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n  <bottom>\n    <StackPane prefWidth=\"-1.0\">\n      <children>\n        <ImageView fx:id=\"attackIcon\" fitHeight=\"64.0\" fitWidth=\"32.0\" pickOnBounds=\"true\" preserveRatio=\"true\" translateX=\"-8.0\" translateY=\"8.0\" StackPane.alignment=\"BOTTOM_LEFT\">\n          <image>\n            <Image url=\"/img/common/attack_value_icon.png\" />\n          </image>\n        </ImageView>\n        <ImageView fx:id=\"hpIcon\" fitHeight=\"64.0\" fitWidth=\"32.0\" pickOnBounds=\"true\" preserveRatio=\"true\" translateX=\"8.0\" translateY=\"6.0\" StackPane.alignment=\"BOTTOM_RIGHT\">\n          <image>\n            <Image url=\"/img/common/health_value_icon.png\" />\n          </image>\n        </ImageView>\n            <Group fx:id=\"attackAnchor\" translateX=\"1.0\" translateY=\"2.0\" StackPane.alignment=\"BOTTOM_LEFT\" />\n            <Group fx:id=\"hpAnchor\" translateX=\"-1.0\" translateY=\"2.0\" StackPane.alignment=\"BOTTOM_RIGHT\" />\n        <Label fx:id=\"raceLabel\" styleClass=\"race-label\" text=\"&lt;race&gt;\" translateY=\"8.0\">\n          <font>\n            <Font name=\"Arial Bold Italic\" size=\"11.0\" />\n          </font>\n        </Label>\n            <Circle fx:id=\"rarityGem\" fill=\"DODGERBLUE\" radius=\"8.0\" stroke=\"BLACK\" strokeLineJoin=\"BEVEL\" strokeType=\"INSIDE\" strokeWidth=\"2.0\" translateY=\"-10.0\" />\n      </children>\n    </StackPane>\n  </bottom>\n  <center>\n    <VBox alignment=\"TOP_CENTER\" prefHeight=\"-1.0\" prefWidth=\"-1.0\" spacing=\"16.0\" BorderPane.alignment=\"CENTER\">\n      <children>\n        <Label fx:id=\"nameLabel\" alignment=\"CENTER\" contentDisplay=\"CENTER\" maxWidth=\"150.0\" styleClass=\"name-big\" text=\"&lt;card name&gt;\" textAlignment=\"CENTER\" wrapText=\"true\" />\n        <Label fx:id=\"descriptionLabel\" alignment=\"CENTER\" maxWidth=\"150.0\" styleClass=\"tooltip-description\" text=\"&lt;card description&gt;\" textAlignment=\"CENTER\" wrapText=\"true\">\n          <font>\n            <Font name=\"Arial Bold\" size=\"13.0\" />\n          </font>\n        </Label>\n      </children>\n    </VBox>\n  </center>\n  <top>\n    <StackPane prefHeight=\"-1.0\" prefWidth=\"-1.0\">\n      <children>\n        <ImageView fitHeight=\"32.0\" fitWidth=\"32.0\" pickOnBounds=\"true\" preserveRatio=\"true\" translateX=\"-8.0\" translateY=\"-8.0\" StackPane.alignment=\"TOP_LEFT\">\n          <image>\n            <Image url=\"/img/common/mana_crystal.png\" />\n          </image>\n        </ImageView>\n            <Group fx:id=\"manaCostAnchor\" translateY=\"-4.0\" StackPane.alignment=\"TOP_LEFT\" />\n      </children>\n    </StackPane>\n  </top>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/CardView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import java.net.*?>\n<?import javafx.scene.control.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root styleClass=\"bordered-panel\" maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"630.0\" prefWidth=\"760.0\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <bottom>\n      <HBox alignment=\"CENTER\" prefHeight=\"50.0\" prefWidth=\"400.0\" spacing=\"128.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <Button fx:id=\"previousButton\" contentDisplay=\"CENTER\" mnemonicParsing=\"false\" text=\"&lt;&lt;\" />\n            <Label styleClass=\"default-label\" fx:id=\"pageLabel\" alignment=\"CENTER\" contentDisplay=\"CENTER\" text=\"Label\" />\n            <Button fx:id=\"nextButton\" alignment=\"CENTER_RIGHT\" contentDisplay=\"CENTER\" mnemonicParsing=\"false\" text=\"&gt;&gt;\" />\n         </children>\n      </HBox>\n   </bottom>\n   <stylesheets>\n      <URL value=\"@/css/deckbuilder.css\" />\n   </stylesheets>\n   <padding>\n      <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n   </padding>\n   <center>\n      <FlowPane fx:id=\"contentPane\" alignment=\"CENTER\" columnHalignment=\"CENTER\" hgap=\"8.0\" prefHeight=\"200.0\" prefWidth=\"200.0\" vgap=\"16.0\" BorderPane.alignment=\"CENTER\" />\n   </center>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/ChooseClassView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.effect.*?>\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"640.0\" prefWidth=\"760.0\" styleClass=\"panel\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <padding>\n      <Insets top=\"20.0\" />\n   </padding>\n   <right>\n      <VBox spacing=\"2.0\">\n         <children>\n            <CheckBox styleClass=\"name-small\" fx:id=\"arbitraryCheckBox\" mnemonicParsing=\"false\" text=\"Arbitrary Deck?\" />\n         </children>\n      </VBox>\n   </right>\n   <center>\n      <GridPane alignment=\"TOP_CENTER\" BorderPane.alignment=\"CENTER\">\n        <columnConstraints>\n          <ColumnConstraints halignment=\"CENTER\" hgrow=\"SOMETIMES\" minWidth=\"10.0\" prefWidth=\"80.0\" />\n          <ColumnConstraints halignment=\"CENTER\" hgrow=\"SOMETIMES\" minWidth=\"10.0\" prefWidth=\"80.0\" />\n          <ColumnConstraints halignment=\"CENTER\" hgrow=\"SOMETIMES\" minWidth=\"10.0\" prefWidth=\"80.0\" />\n        </columnConstraints>\n        <rowConstraints>\n          <RowConstraints minHeight=\"10.0\" prefHeight=\"30.0\" vgrow=\"SOMETIMES\" />\n          <RowConstraints minHeight=\"10.0\" prefHeight=\"30.0\" vgrow=\"SOMETIMES\" />\n          <RowConstraints minHeight=\"10.0\" prefHeight=\"30.0\" vgrow=\"SOMETIMES\" />\n        </rowConstraints>\n         <children>\n            <Button fx:id=\"warriorButton\" alignment=\"CENTER\" contentDisplay=\"TOP\" mnemonicParsing=\"false\" prefHeight=\"160.0\" prefWidth=\"160.0\" styleClass=\"class-button\" text=\"Warrior\" textAlignment=\"CENTER\">\n               <graphic>\n                  <ImageView fitHeight=\"96.0\" fitWidth=\"96.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                     <image>\n                        <Image url=\"/img/classes/warrior.png\" />\n                     </image>\n                  </ImageView>\n               </graphic></Button>\n            <Button fx:id=\"paladinButton\" alignment=\"CENTER\" contentDisplay=\"TOP\" layoutX=\"81.0\" layoutY=\"29.0\" mnemonicParsing=\"false\" prefHeight=\"160.0\" prefWidth=\"160.0\" styleClass=\"class-button\" text=\"Paladin\" textAlignment=\"CENTER\" GridPane.columnIndex=\"1\">\n               <graphic>\n                  <ImageView fitHeight=\"96.0\" fitWidth=\"96.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                     <image>\n                        <Image url=\"/img/classes/paladin.png\" />\n                     </image>\n                  </ImageView>\n               </graphic></Button>\n            <Button fx:id=\"druidButton\" alignment=\"CENTER\" contentDisplay=\"TOP\" layoutX=\"423.0\" layoutY=\"29.0\" mnemonicParsing=\"false\" prefHeight=\"160.0\" prefWidth=\"160.0\" styleClass=\"class-button\" text=\"Druid\" textAlignment=\"CENTER\" GridPane.columnIndex=\"2\">\n               <graphic>\n                  <ImageView fitHeight=\"96.0\" fitWidth=\"96.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                     <image>\n                        <Image url=\"/img/classes/druid.png\" />\n                     </image>\n                  </ImageView>\n               </graphic></Button>\n            <Button fx:id=\"rogueButton\" alignment=\"CENTER\" contentDisplay=\"TOP\" layoutX=\"764.0\" layoutY=\"29.0\" mnemonicParsing=\"false\" prefHeight=\"160.0\" prefWidth=\"160.0\" styleClass=\"class-button\" text=\"Rogue\" textAlignment=\"CENTER\" GridPane.rowIndex=\"1\">\n               <graphic>\n                  <ImageView fitHeight=\"96.0\" fitWidth=\"96.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                     <image>\n                        <Image url=\"/img/classes/rogue.png\" />\n                     </image>\n                  </ImageView>\n               </graphic></Button>\n            <Button fx:id=\"warlockButton\" alignment=\"CENTER\" contentDisplay=\"TOP\" layoutX=\"81.0\" layoutY=\"267.0\" mnemonicParsing=\"false\" prefHeight=\"160.0\" prefWidth=\"160.0\" styleClass=\"class-button\" text=\"Warlock\" textAlignment=\"CENTER\" GridPane.columnIndex=\"1\" GridPane.rowIndex=\"1\">\n               <graphic>\n                  <ImageView fitHeight=\"96.0\" fitWidth=\"96.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                     <image>\n                        <Image url=\"/img/classes/warlock.png\" />\n                     </image>\n                  </ImageView>\n               </graphic></Button>\n            <Button fx:id=\"hunterButton\" alignment=\"CENTER\" contentDisplay=\"TOP\" layoutX=\"423.0\" layoutY=\"267.0\" mnemonicParsing=\"false\" prefHeight=\"160.0\" prefWidth=\"160.0\" styleClass=\"class-button\" text=\"Hunter\" textAlignment=\"CENTER\" GridPane.columnIndex=\"2\" GridPane.rowIndex=\"1\">\n               <graphic>\n                  <ImageView fitHeight=\"96.0\" fitWidth=\"96.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                     <image>\n                        <Image url=\"/img/classes/hunter.png\" />\n                     </image>\n                  </ImageView>\n               </graphic></Button>\n            <Button fx:id=\"shamanButton\" alignment=\"CENTER\" contentDisplay=\"TOP\" layoutX=\"764.0\" layoutY=\"267.0\" mnemonicParsing=\"false\" prefHeight=\"160.0\" prefWidth=\"160.0\" styleClass=\"class-button\" text=\"Shaman\" textAlignment=\"CENTER\" GridPane.rowIndex=\"2\">\n               <graphic>\n                  <ImageView fitHeight=\"96.0\" fitWidth=\"96.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                     <image>\n                        <Image url=\"/img/classes/shaman.png\" />\n                     </image>\n                  </ImageView>\n               </graphic></Button>\n            <Button fx:id=\"mageButton\" alignment=\"CENTER\" contentDisplay=\"TOP\" layoutX=\"81.0\" layoutY=\"504.0\" mnemonicParsing=\"false\" prefHeight=\"160.0\" prefWidth=\"160.0\" styleClass=\"class-button\" text=\"Mage\" textAlignment=\"CENTER\" GridPane.columnIndex=\"1\" GridPane.rowIndex=\"2\">\n               <graphic>\n                  <ImageView fitHeight=\"96.0\" fitWidth=\"96.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                     <image>\n                        <Image url=\"/img/classes/mage.png\" />\n                     </image>\n                  </ImageView>\n               </graphic></Button>\n            <Button fx:id=\"priestButton\" alignment=\"CENTER\" contentDisplay=\"TOP\" layoutX=\"423.0\" layoutY=\"504.0\" mnemonicParsing=\"false\" prefHeight=\"160.0\" prefWidth=\"160.0\" styleClass=\"class-button\" text=\"Priest\" textAlignment=\"CENTER\" GridPane.columnIndex=\"2\" GridPane.rowIndex=\"2\">\n               <graphic>\n                  <ImageView fitHeight=\"96.0\" fitWidth=\"96.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                     <image>\n                        <Image url=\"/img/classes/priest.png\" />\n                     </image>\n                  </ImageView>\n               </graphic></Button>\n         </children>\n      </GridPane>\n   </center>\n   <stylesheets>\n      <URL value=\"@/css/deckbuilder.css\" />\n   </stylesheets>\n   <bottom>\n      <Button fx:id=\"collectionButton\" mnemonicParsing=\"false\" styleClass=\"class-button\" text=\"Create deck collection\" BorderPane.alignment=\"CENTER\">\n         <graphic>\n            <ImageView fitHeight=\"56.0\" fitWidth=\"56.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n               <image>\n                  <Image url=\"/img/classes/deck_collection.png\" />\n               </image>\n            </ImageView>\n         </graphic>\n      </Button>\n   </bottom>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/DeckBuilderView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import java.net.URL?>\n<?import javafx.geometry.Insets?>\n<?import javafx.scene.control.Button?>\n<?import javafx.scene.control.Label?>\n<?import javafx.scene.control.ScrollPane?>\n<?import javafx.scene.control.TextField?>\n<?import javafx.scene.layout.AnchorPane?>\n<?import javafx.scene.layout.BorderPane?>\n<?import javafx.scene.layout.HBox?>\n<?import javafx.scene.layout.Pane?>\n<?import javafx.scene.layout.VBox?>\n<?import javafx.scene.text.Text?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"768.0\" prefWidth=\"1024.0\" styleClass=\"panel\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8.0.65\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <top>\n      <Text strokeType=\"OUTSIDE\" strokeWidth=\"0.0\" styleClass=\"header\" text=\"Deck Builder\" BorderPane.alignment=\"CENTER\" />\n   </top>\n   <right>\n      <VBox spacing=\"2.0\">\n         <children>\n            <Pane fx:id=\"upperInfoArea\" prefHeight=\"50.0\" prefWidth=\"240.0\" />\n            <ScrollPane fx:id=\"scrollPane\" fitToHeight=\"true\" fitToWidth=\"true\" hbarPolicy=\"NEVER\" prefHeight=\"580.0\" prefViewportWidth=\"220.0\" prefWidth=\"250.0\" styleClass=\"sidebar\">\n               <padding>\n                  <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n               </padding>\n               <VBox.margin>\n                  <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n               </VBox.margin></ScrollPane>\n            <Pane fx:id=\"lowerInfoArea\" prefHeight=\"50.0\" prefWidth=\"240.0\" />\n         </children>\n      </VBox>\n   </right>\n   <padding>\n      <Insets top=\"20.0\" />\n   </padding>\n   <center>\n      <VBox alignment=\"CENTER\" spacing=\"16.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <Label alignment=\"CENTER\" contentDisplay=\"CENTER\" styleClass=\"default-label\" text=\"Load an existing deck from the deck list to the right or create a new one\" wrapText=\"true\" VBox.vgrow=\"ALWAYS\" />\n            <HBox alignment=\"CENTER\" prefHeight=\"40.0\" spacing=\"8.0\" BorderPane.alignment=\"CENTER\">\n            <children>\n               <Label id=\"#name-small\" text=\"Or import from:\" />\n               <TextField fx:id=\"importField\" promptText=\"enter URL\" />\n               <Button fx:id=\"importButton\" mnemonicParsing=\"false\" text=\"Import\" />\n            </children>\n         </HBox>\n         </children>\n         <padding>\n            <Insets bottom=\"32.0\" left=\"32.0\" right=\"32.0\" top=\"32.0\" />\n         </padding>\n      </VBox>\n   </center>\n   <stylesheets>\n      <URL value=\"@/css/deckbuilder.css\" />\n   </stylesheets>\n   <bottom>\n      <AnchorPane BorderPane.alignment=\"TOP_LEFT\">\n         <children>\n            <Button fx:id=\"backButton\" mnemonicParsing=\"false\" text=\"Back\" AnchorPane.bottomAnchor=\"20.0\" AnchorPane.leftAnchor=\"20.0\" AnchorPane.topAnchor=\"0.0\" BorderPane.alignment=\"CENTER\" />\n         </children>\n      </AnchorPane>\n   </bottom>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/DeckEntry.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import java.net.URL?>\n<?import javafx.scene.control.Button?>\n<?import javafx.scene.control.Label?>\n<?import javafx.scene.image.Image?>\n<?import javafx.scene.image.ImageView?>\n<?import javafx.scene.layout.HBox?>\n\n<fx:root alignment=\"CENTER_LEFT\" fillHeight=\"false\" maxHeight=\"50.0\" maxWidth=\"220.0\" minHeight=\"50.0\" minWidth=\"220.0\" prefHeight=\"50.0\" type=\"HBox\" xmlns=\"http://javafx.com/javafx/8.0.65\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <children>\n      <HBox alignment=\"CENTER_LEFT\" maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" spacing=\"4.0\" styleClass=\"card-entry\">\n         <children>\n            <ImageView fx:id=\"classIcon\" fitHeight=\"36.0\" fitWidth=\"36.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n               <image>\n                  <Image url=\"/img/classes/mage.png\" />\n               </image>\n            </ImageView>\n            <Label fx:id=\"deckNameLabel\" maxHeight=\"44.0\" maxWidth=\"150.0\" minWidth=\"150.0\" prefHeight=\"44.0\" styleClass=\"card-entry-name\" text=\"&lt;deckname&gt;\" wrapText=\"true\" />\n         </children>\n         <stylesheets>\n            <URL value=\"@/css/deckbuilder.css\" />\n         </stylesheets>\n      </HBox>\n      <Button fx:id=\"deleteDeckButton\" mnemonicParsing=\"false\" styleClass=\"delete-button\">\n         <graphic>\n            <ImageView fitHeight=\"20.0\" fitWidth=\"20.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n               <image>\n                  <Image url=\"@../img/ui/delete.png\" />\n               </image>\n            </ImageView>\n         </graphic>\n      </Button>\n   </children>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/DeckInfoView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import java.net.*?>\n<?import javafx.scene.control.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root alignment=\"CENTER\" maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"50.0\" prefWidth=\"230.0\" style=\"-fx-border-color: darkgrey; -fx-border-width: 1; -fx-border-radius: 10; -fx-background-radius: 10;\" styleClass=\"panel\" type=\"HBox\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <children>\n      <VBox alignment=\"CENTER\" prefHeight=\"40.0\" prefWidth=\"80.0\">\n         <children>\n            <Label fx:id=\"countLabel\" styleClass=\"name-big\" text=\"0/30\" />\n            <Label fx:id=\"typeLabel\" styleClass=\"name-small\" text=\"Cards\" textAlignment=\"CENTER\" />\n         </children>\n      </VBox>\n      <Button fx:id=\"doneButton\" alignment=\"BASELINE_CENTER\" contentDisplay=\"CENTER\" mnemonicParsing=\"false\" prefWidth=\"140.0\" text=\"Done\" />\n   </children>\n   <stylesheets>\n      <URL value=\"@/css/deckbuilder.css\" />\n   </stylesheets>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/DeckListView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.geometry.Insets?>\n<?import javafx.scene.control.Button?>\n<?import javafx.scene.image.Image?>\n<?import javafx.scene.image.ImageView?>\n<?import javafx.scene.layout.VBox?>\n<?import javafx.scene.text.Font?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefWidth=\"220.0\" spacing=\"2.0\" style=\"-fx-background-color: tan;\" type=\"VBox\" xmlns=\"http://javafx.com/javafx/8.0.65\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <children>\n      <Button fx:id=\"newDeckButton\" graphicTextGap=\"8.0\" mnemonicParsing=\"false\" prefWidth=\"220.0\" text=\"New deck\">\n         <font>\n            <Font name=\"Arial\" size=\"18.0\" />\n         </font>\n         <graphic>\n            <ImageView fitHeight=\"32.0\" fitWidth=\"32.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n               <image>\n                  <Image url=\"/img/ui/new.png\" />\n               </image>\n            </ImageView>\n         </graphic>\n      </Button>\n   </children>\n   <padding>\n      <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n   </padding>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/DeckNameView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.image.*?>\n<?import java.net.*?>\n<?import javafx.scene.control.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root styleClass=\"panel\" alignment=\"CENTER\" maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"40.0\" prefWidth=\"240.0\" style=\"-fx-border-color: darkgrey; -fx-border-width: 1; -fx-border-radius: 10; -fx-background-radius: 10;\" type=\"HBox\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <stylesheets>\n\t<URL value=\"@/css/deckbuilder.css\" />\n   </stylesheets>\n   <children>\n      <ImageView fx:id=\"classIcon\" fitHeight=\"32.0\" fitWidth=\"32.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n         <image>\n            <Image url=\"/img/classes/mage.png\" />\n         </image>\n      </ImageView>\n      <TextField styleClass=\"name-small\" fx:id=\"nameField\" />\n   </children>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/DigitTemplate.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.paint.*?>\n<?import javafx.scene.shape.*?>\n<?import java.lang.*?>\n<?import java.net.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.scene.layout.*?>\n<?import javafx.scene.text.*?>\n\n\n<fx:root alignment=\"CENTER\" style=\"-fx-background-color: transparent;\" stylesheets=\"@/css/main.css\" type=\"HBox\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <children>\n      <Text fx:id=\"digit\" strokeType=\"OUTSIDE\" strokeWidth=\"0.0\" styleClass=\"outlined-score\" text=\"?\" />\n   </children>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/EditorMainWindow.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.web.*?>\n<?import javafx.scene.image.*?>\n<?import java.net.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" styleClass=\"panel\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8.0.40\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <center>\n      <VBox alignment=\"CENTER\" spacing=\"10.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <HBox spacing=\"8.0\">\n               <children>\n                  <RadioButton fx:id=\"minionRadioButton\" mnemonicParsing=\"false\" text=\"Minion\" />\n                  <RadioButton fx:id=\"spellRadioButton\" layoutX=\"181.0\" layoutY=\"52.0\" mnemonicParsing=\"false\" text=\"Spell\" />\n                  <RadioButton fx:id=\"weaponRadioButton\" layoutX=\"216.0\" layoutY=\"52.0\" mnemonicParsing=\"false\" text=\"Weapon\" />\n               </children>\n               <opaqueInsets>\n                  <Insets />\n               </opaqueInsets>\n               <VBox.margin>\n                  <Insets left=\"16.0\" />\n               </VBox.margin>\n            </HBox>\n            <HBox spacing=\"8.0\">\n               <children>\n                  <Label minWidth=\"84.0\" text=\"Name:\" />\n                  <TextField fx:id=\"nameField\" promptText=\"enter card name\" />\n               </children>\n               <padding>\n                  <Insets left=\"16.0\" />\n               </padding>\n            </HBox>\n            <HBox layoutX=\"10.0\" layoutY=\"37.0\" spacing=\"8.0\">\n               <children>\n                  <Label minWidth=\"84.0\" text=\"Id:\" />\n                  <Label fx:id=\"idLabel\" layoutX=\"26.0\" layoutY=\"10.0\" minWidth=\"84.0\" />\n               </children>\n               <padding>\n                  <Insets left=\"16.0\" />\n               </padding>\n            </HBox>\n            <HBox layoutX=\"10.0\" layoutY=\"301.0\" spacing=\"8.0\">\n               <children>\n                  <Label minWidth=\"84.0\" text=\"Description:\" />\n                  <TextField fx:id=\"descriptionField\" promptText=\"enter card description\" />\n               </children>\n               <padding>\n                  <Insets left=\"16.0\" />\n               </padding>\n            </HBox>\n            <HBox layoutX=\"10.0\" layoutY=\"319.0\" spacing=\"8.0\">\n               <children>\n                  <Label minWidth=\"84.0\" text=\"Rarity:\" />\n                  <ComboBox fx:id=\"rarityBox\" prefWidth=\"150.0\" />\n               </children>\n               <padding>\n                  <Insets left=\"16.0\" />\n               </padding>\n            </HBox>\n            <HBox layoutX=\"10.0\" layoutY=\"336.0\" spacing=\"8.0\">\n               <children>\n                  <Label minWidth=\"84.0\" text=\"Hero Class:\" />\n                  <ComboBox fx:id=\"heroClassBox\" prefWidth=\"150.0\" />\n               </children>\n               <padding>\n                  <Insets left=\"16.0\" />\n               </padding>\n            </HBox>\n            <HBox spacing=\"8.0\">\n               <children>\n                  <Label minWidth=\"84.0\" text=\"Card Set:\" />\n                  <ComboBox fx:id=\"cardSetBox\" prefWidth=\"150.0\" />\n               </children>\n               <padding>\n                  <Insets left=\"16.0\" />\n               </padding>\n            </HBox>\n            <HBox layoutX=\"10.0\" layoutY=\"37.0\" spacing=\"8.0\">\n               <children>\n                  <Label minWidth=\"84.0\" text=\"Mana Cost:\" />\n                  <TextField fx:id=\"manaCostField\" promptText=\"enter mana cost\" text=\"1\" />\n               </children>\n               <padding>\n                  <Insets left=\"16.0\" />\n               </padding>\n            </HBox>\n            <HBox layoutX=\"10.0\" layoutY=\"72.0\" spacing=\"8.0\">\n               <children>\n                  <Label minWidth=\"84.0\" text=\"Collectible:\" />\n                  <CheckBox fx:id=\"collectibleBox\" mnemonicParsing=\"false\" text=\"Usable in DeckBuilder\" />\n               </children>\n               <padding>\n                  <Insets left=\"16.0\" />\n               </padding>\n            </HBox>\n            <HBox layoutX=\"10.0\" layoutY=\"249.0\" spacing=\"8.0\">\n               <children>\n                  <VBox>\n                     <children>\n                        <Label minWidth=\"84.0\" text=\"Attributes:\" />\n                        <HBox>\n                           <children>\n                              <ComboBox fx:id=\"attributeBox1\" prefWidth=\"150.0\" />\n                              <TextField fx:id=\"attributeField1\" promptText=\"enter value\" />\n                           </children>\n                        </HBox>\n                        <HBox layoutX=\"10.0\" layoutY=\"27.0\">\n                           <children>\n                              <ComboBox fx:id=\"attributeBox2\" prefWidth=\"150.0\" />\n                              <TextField fx:id=\"attributeField2\" promptText=\"enter value\" />\n                           </children>\n                        </HBox>\n                        <HBox layoutX=\"10.0\" layoutY=\"52.0\">\n                           <children>\n                              <ComboBox fx:id=\"attributeBox3\" prefWidth=\"150.0\" />\n                              <TextField fx:id=\"attributeField3\" promptText=\"enter value\" />\n                           </children>\n                        </HBox>\n                        <HBox layoutX=\"10.0\" layoutY=\"77.0\">\n                           <children>\n                              <ComboBox fx:id=\"attributeBox4\" prefWidth=\"150.0\" />\n                              <TextField fx:id=\"attributeField4\" promptText=\"enter value\" />\n                           </children>\n                        </HBox>\n                     </children>\n                  </VBox>\n               </children>\n               <padding>\n                  <Insets left=\"16.0\" />\n               </padding>\n            </HBox>\n            <Pane fx:id=\"contentPanel\" />\n            <HBox>\n               <children>\n                  <Button fx:id=\"resetButton\" mnemonicParsing=\"false\" text=\"Reset attributes\" />\n               </children>\n            </HBox>\n         </children></VBox>\n   </center>\n   <padding>\n      <Insets bottom=\"8.0\" left=\"24.0\" right=\"24.0\" top=\"24.0\" />\n   </padding>\n   <bottom>\n      <HBox alignment=\"BOTTOM_CENTER\" BorderPane.alignment=\"BOTTOM_CENTER\">\n         <children>\n            <Button fx:id=\"saveButton\" mnemonicParsing=\"false\" text=\"Save as...\" />\n         </children>\n         <opaqueInsets>\n            <Insets />\n         </opaqueInsets>\n         <BorderPane.margin>\n            <Insets bottom=\"16.0\" left=\"16.0\" right=\"16.0\" top=\"16.0\" />\n         </BorderPane.margin>\n      </HBox>\n   </bottom>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/EntityEditor.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.chart.*?>\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root styleClass=\"bordered-dialog\" maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"400.0\" prefWidth=\"600.0\" stylesheets=\"@../css/main.css\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <bottom>\n      <HBox alignment=\"CENTER\" spacing=\"128.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <Button  fx:id=\"okButton\" mnemonicParsing=\"false\" text=\"Ok\" />\n            <Button  fx:id=\"cancelButton\" layoutX=\"10.0\" layoutY=\"10.0\" mnemonicParsing=\"false\" text=\"Cancel\" />\n         </children>\n         <BorderPane.margin>\n            <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n         </BorderPane.margin>\n      </HBox>\n   </bottom>\n   <top>\n      <Label styleClass=\"header\" fx:id=\"headerLabel\" text=\"&lt;title&gt;\" BorderPane.alignment=\"CENTER\" />\n   </top>\n   <center>\n      <VBox prefHeight=\"200.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <TableView fx:id=\"propertiesTable\" prefHeight=\"300.0\" prefWidth=\"200.0\">\n              <columns>\n                <TableColumn fx:id=\"nameColumn\" maxWidth=\"1000.0\" minWidth=\"20.0\" prefWidth=\"240.0\" text=\"Property\" />\n                <TableColumn fx:id=\"valueColumn\" maxWidth=\"1000.0\" minWidth=\"20.0\" prefWidth=\"240.0\" text=\"Value\" />\n              </columns>\n            </TableView>\n         </children>\n         <BorderPane.margin>\n            <Insets />\n         </BorderPane.margin>\n         <padding>\n            <Insets left=\"20.0\" right=\"20.0\" />\n         </padding></VBox>\n   </center>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/GameBoardView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import java.lang.*?>\n<?import java.net.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root prefHeight=\"748.0\" prefWidth=\"1024.0\" styleClass=\"board\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n\t<stylesheets>\n\t\t<URL value=\"@/css/gameboard.css\" />\n\t</stylesheets>\n\t<bottom>\n\t\t<BorderPane prefHeight=\"-1.0\" prefWidth=\"-1.0\">\n\t\t\t<bottom>\n\t\t\t\t<HBox fx:id=\"p1CardPane\" alignment=\"CENTER\" prefHeight=\"100.0\" prefWidth=\"200.0\" spacing=\"4.0\">\n\t\t\t\t\t<padding>\n\t\t\t\t\t\t<Insets bottom=\"4.0\" top=\"4.0\" fx:id=\"x1\" />\n\t\t\t\t\t</padding>\n\t\t\t\t</HBox>\n\t\t\t</bottom>\n\t\t\t<center>\n\t\t\t\t<VBox fx:id=\"p1HeroAnchor\" alignment=\"CENTER\" fillWidth=\"false\" prefHeight=\"-1.0\" prefWidth=\"-1.0\" />\n\t\t\t</center>\n\t\t</BorderPane>\n\t</bottom>\n\t<center>\n\t\t<BorderPane prefHeight=\"200.0\" prefWidth=\"200.0\">\n\t\t\t<bottom>\n\t\t\t\t<HBox fx:id=\"p1MinionPane\" alignment=\"CENTER\" prefHeight=\"100.0\" prefWidth=\"200.0\" />\n\t\t\t</bottom>\n\t\t\t<center>\n\t\t\t\t<HBox alignment=\"CENTER\" prefHeight=\"-1.0\" prefWidth=\"-1.0\" BorderPane.alignment=\"CENTER\">\n\t\t\t\t\t<children>\n\t\t\t\t\t\t<Label fx:id=\"centerMessageLabel\" styleClass=\"center-message\" text=\"&lt;center message&gt;\" visible=\"false\">\n\t\t\t\t\t\t</Label>\n\t\t\t\t\t</children>\n\t\t\t\t</HBox>\n\t\t\t</center>\n\t\t\t<top>\n\t\t\t\t<HBox fx:id=\"p2MinionPane\" alignment=\"CENTER\" prefHeight=\"100.0\" prefWidth=\"200.0\" />\n\t\t\t</top>\n\t\t</BorderPane>\n\t</center>\n\t<stylesheets>\n\t\t<URL value=\"@/css/gameboard.css\" />\n\t</stylesheets>\n\t<top>\n\t\t<BorderPane prefHeight=\"-1.0\" prefWidth=\"-1.0\">\n\t\t\t<center>\n\t\t\t\t<VBox fx:id=\"p2HeroAnchor\" alignment=\"CENTER\" fillWidth=\"false\" prefHeight=\"-1.0\" prefWidth=\"-1.0\" BorderPane.alignment=\"CENTER\" />\n\t\t\t</center>\n\t\t\t<top>\n\t\t\t\t<HBox fx:id=\"p2CardPane\" alignment=\"CENTER\" padding=\"$x1\" prefHeight=\"100.0\" prefWidth=\"200.0\" spacing=\"4.0\" />\n\t\t\t</top>\n\t\t</BorderPane>\n\t</top>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/HandCard.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.*?>\n<?import javafx.scene.paint.*?>\n<?import javafx.scene.shape.*?>\n<?import java.lang.*?>\n<?import java.net.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.scene.layout.*?>\n<?import javafx.scene.text.*?>\n\n<fx:root prefHeight=\"120.0\" prefWidth=\"80.0\" styleClass=\"card-border\"  type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n  <bottom>\n    <StackPane fx:id=\"bottomPane\" prefHeight=\"0.0\" prefWidth=\"0.0\" translateY=\"8.0\">\n      <children>\n        <ImageView fx:id=\"attackIcon\" fitHeight=\"150.0\" fitWidth=\"27.0\" pickOnBounds=\"true\" preserveRatio=\"true\" translateX=\"-5.0\" translateY=\"-2.0\" StackPane.alignment=\"BOTTOM_LEFT\">\n          <image>\n            <Image url=\"/img/common/attack_value_icon.png\" />\n          </image>\n        </ImageView>\n        <ImageView fx:id=\"hpIcon\" fitHeight=\"150.0\" fitWidth=\"25.0\" pickOnBounds=\"true\" preserveRatio=\"true\" translateX=\"4.0\" translateY=\"-4.0\" StackPane.alignment=\"BOTTOM_RIGHT\">\n          <image>\n            <Image url=\"/img/common/health_value_icon.png\" />\n          </image>\n        </ImageView>\n            <Group fx:id=\"attackAnchor\" translateX=\"2.0\" translateY=\"-4.0\" StackPane.alignment=\"BOTTOM_LEFT\" />\n            <Group fx:id=\"hpAnchor\" translateX=\"-2.0\" translateY=\"-4.0\" StackPane.alignment=\"BOTTOM_RIGHT\" />\n            <Circle fx:id=\"rarityGem\" fill=\"#4989cd\" radius=\"6.0\" stroke=\"BLACK\" strokeLineJoin=\"BEVEL\" strokeType=\"INSIDE\" strokeWidth=\"2.0\" translateY=\"-20.0\" />\n      </children>\n    </StackPane>\n  </bottom>\n  <center>\n      <VBox fx:id=\"centerPane\" alignment=\"TOP_CENTER\" spacing=\"32.0\">\n         <children>\n          <Label fx:id=\"nameLabel\" alignment=\"CENTER\" maxWidth=\"78.0\" text=\"&lt;card name&gt;\" textAlignment=\"CENTER\" wrapText=\"true\" BorderPane.alignment=\"TOP_CENTER\">\n            <font>\n              <Font name=\"Arial Bold\" size=\"12.0\" />\n            </font>\n          </Label>\n         </children>\n      </VBox>\n  </center>\n  <top>\n    <StackPane fx:id=\"topPane\" prefHeight=\"-1.0\" prefWidth=\"-1.0\">\n      <children>\n        <ImageView fitHeight=\"24.0\" fitWidth=\"24.0\" pickOnBounds=\"true\" preserveRatio=\"true\" translateX=\"-8.0\" translateY=\"-4.0\" StackPane.alignment=\"TOP_LEFT\">\n          <image>\n            <Image url=\"/img/common/mana_crystal.png\" />\n          </image>\n        </ImageView>\n            <Group fx:id=\"manaCostAnchor\" translateX=\"-4.0\" translateY=\"-5.0\" StackPane.alignment=\"TOP_LEFT\" />\n      </children>\n    </StackPane>\n  </top>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/HeroToken.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.*?>\n<?import java.lang.*?>\n<?import java.net.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.scene.layout.*?>\n<?import javafx.scene.layout.BorderPane?>\n<?import javafx.scene.shape.*?>\n<?import javafx.scene.text.*?>\n\n<fx:root id=\"\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8.0.40\" xmlns:fx=\"http://javafx.com/fxml/1\">\n  <center>\n    <StackPane fx:id=\"targetAnchor\" prefHeight=\"115.0\" prefWidth=\"108.0\">\n      <children>\n        <ImageView fx:id=\"portrait\" fitHeight=\"150.0\" fitWidth=\"100.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n          <image>\n            <Image url=\"/img/heroes/garrosh.png\" />\n          </image>\n        </ImageView>\n        <ImageView fitHeight=\"100.0\" fitWidth=\"30.0\" pickOnBounds=\"true\" preserveRatio=\"true\" translateX=\"2.0\" translateY=\"2.0\" StackPane.alignment=\"BOTTOM_RIGHT\">\n          <image>\n            <Image url=\"/img/common/health_value_icon.png\" />\n          </image>\n        </ImageView>\n            <Group fx:id=\"hpAnchor\" translateX=\"-5.0\" translateY=\"-2.0\" StackPane.alignment=\"BOTTOM_RIGHT\" />\n        <ImageView fitHeight=\"100.0\" fitWidth=\"32.0\" pickOnBounds=\"true\" preserveRatio=\"true\" translateX=\"-2.0\" translateY=\"5.0\" StackPane.alignment=\"BOTTOM_LEFT\">\n          <image>\n            <Image url=\"/img/common/attack_value_icon.png\" />\n          </image>\n        </ImageView>\n            <Group fx:id=\"attackAnchor\" translateX=\"7.0\" translateY=\"-2.0\" StackPane.alignment=\"BOTTOM_LEFT\" />\n        <ImageView fx:id=\"armorIcon\" fitHeight=\"32.0\" fitWidth=\"32.0\" pickOnBounds=\"true\" preserveRatio=\"true\" translateX=\"4.0\" translateY=\"8.0\" StackPane.alignment=\"CENTER_RIGHT\">\n          <image>\n            <Image url=\"/img/common/armor.png\" />\n          </image>\n        </ImageView>\n            <Group fx:id=\"armorAnchor\" translateX=\"-6.0\" translateY=\"5.0\" StackPane.alignment=\"CENTER_RIGHT\" />\n        <FlowPane fx:id=\"secretsAnchor\" prefHeight=\"-1.0\" prefWidth=\"64.0\" StackPane.alignment=\"TOP_CENTER\" />\n        <Ellipse fx:id=\"frozen\" fill=\"#ffffff00\" radiusX=\"50.0\" radiusY=\"30.0\" smooth=\"false\" stroke=\"#0e90e8\" strokeDashOffset=\"10.0\" strokeType=\"OUTSIDE\" strokeWidth=\"5.0\" visible=\"false\" />\n      </children>\n      <padding>\n        <Insets left=\"8.0\" />\n      </padding>\n    </StackPane>\n  </center>\n  <left>\n    <VBox maxWidth=\"-1.0\" prefHeight=\"-1.0\" prefWidth=\"-1.0\">\n\t  <children>\n\t    <StackPane fx:id=\"weaponPane\" prefHeight=\"-1.0\" prefWidth=\"-1.0\">\n\t      <children>\n\t        <Circle fill=\"#404040\" radius=\"40.0\" stroke=\"BLACK\" strokeType=\"INSIDE\" />\n\t        <Label fx:id=\"weaponNameLabel\" maxWidth=\"70.0\" styleClass=\"label-white\" text=\"&lt;weapon name&gt;\" textAlignment=\"CENTER\" textFill=\"WHITE\" wrapText=\"true\" StackPane.alignment=\"CENTER\">\n\t          <font>\n\t            <Font name=\"Arial Bold\" size=\"10.0\" />\n\t          </font>\n\t        </Label>\n\t            <Group fx:id=\"weaponAttackAnchor\" StackPane.alignment=\"BOTTOM_LEFT\" />\n\t            <Group fx:id=\"weaponDurabilityAnchor\" StackPane.alignment=\"BOTTOM_RIGHT\" />\n\t      </children>\n\t      <padding>\n\t        <Insets bottom=\"4.0\" left=\"4.0\" top=\"4.0\" />\n\t      </padding>\n\t    </StackPane>\n      </children>\n\t</VBox>\n  </left>\n  <padding>\n    <Insets bottom=\"8.0\" top=\"8.0\" />\n  </padding>\n  <right>\n      <HBox alignment=\"CENTER_LEFT\">\n         <children>\n          <VBox maxWidth=\"-1.0\" prefHeight=\"-1.0\" prefWidth=\"-1.0\">\n            <children>\n            <StackPane fx:id=\"heroPowerPane\" prefHeight=\"-1.0\" prefWidth=\"-1.0\">\n\t      \t <children>\n              <ImageView id=\"heropower\" fx:id=\"heroPowerIcon\" fitHeight=\"150.0\" fitWidth=\"64.0\" pickOnBounds=\"true\" preserveRatio=\"true\" translateY=\"0.0\" y=\"0.0\">\n                <image>\n                  <Image url=\"/img/powers/armor_up.png\" />\n                </image>\n               </ImageView>\n              <Group fx:id=\"heroPowerAnchor\" translateX=\"0.0\" translateY=\"-5.0\" StackPane.alignment=\"TOP_CENTER\" />\n             </children>\n            </StackPane>\n              <Label id=\"mana\" fx:id=\"manaLabel\" alignment=\"CENTER\" maxWidth=\"64.0\" styleClass=\"mana-label\" text=\"Mana: 10/10\" textAlignment=\"LEFT\" textFill=\"#0066ff\" translateX=\"8.0\" translateY=\"4.0\" wrapText=\"true\" VBox.vgrow=\"ALWAYS\">\n                <font>\n                  <Font name=\"Arial Bold\" size=\"16.0\" />\n                </font>\n                <VBox.margin>\n                  <Insets left=\"4.0\" />\n                </VBox.margin>\n              </Label>\n            </children>\n          </VBox>\n\t    <Label id=\"cards\" fx:id=\"cardsLabel\" alignment=\"CENTER\" maxWidth=\"60.0\" text=\"Cards in deck: 30\" textAlignment=\"LEFT\" wrapText=\"true\">\n          <font>\n            <Font name=\"Arial Bold\" size=\"12.0\" />\n          </font>\n               <HBox.margin>\n                  <Insets bottom=\"4.0\" left=\"16.0\" right=\"4.0\" top=\"4.0\" />\n               </HBox.margin>\n        </Label>\n         </children>\n         <opaqueInsets>\n            <Insets />\n         </opaqueInsets>\n         <BorderPane.margin>\n            <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n         </BorderPane.margin>\n      </HBox>\n  </right>\n  <stylesheets>\n    <URL value=\"@/css/gameboard.css\" />\n  </stylesheets>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/HumanMulliganView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.shape.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" styleClass=\"bordered-dialog\" stylesheets=\"@../css/main.css\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8.0.40\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <center>\n      <HBox fx:id=\"contentArea\" alignment=\"CENTER\" spacing=\"16.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <Rectangle arcHeight=\"5.0\" arcWidth=\"5.0\" fill=\"DODGERBLUE\" height=\"260.0\" stroke=\"BLACK\" strokeType=\"INSIDE\" width=\"174.0\" />\n            <Rectangle arcHeight=\"5.0\" arcWidth=\"5.0\" fill=\"DODGERBLUE\" height=\"260.0\" layoutX=\"10.0\" layoutY=\"10.0\" stroke=\"BLACK\" strokeType=\"INSIDE\" width=\"174.0\" />\n            <Rectangle arcHeight=\"5.0\" arcWidth=\"5.0\" fill=\"DODGERBLUE\" height=\"260.0\" layoutX=\"184.0\" layoutY=\"10.0\" stroke=\"BLACK\" strokeType=\"INSIDE\" width=\"174.0\" />\n            <Rectangle arcHeight=\"5.0\" arcWidth=\"5.0\" fill=\"DODGERBLUE\" height=\"260.0\" layoutX=\"358.0\" layoutY=\"10.0\" stroke=\"BLACK\" strokeType=\"INSIDE\" width=\"174.0\" />\n         </children>\n         <padding>\n            <Insets bottom=\"16.0\" left=\"16.0\" right=\"16.0\" top=\"16.0\" />\n         </padding>\n      </HBox>\n   </center>\n   <bottom>\n      <Button fx:id=\"doneButton\" alignment=\"CENTER\" mnemonicParsing=\"false\" text=\"Done\" BorderPane.alignment=\"CENTER\">\n         <BorderPane.margin>\n            <Insets bottom=\"8.0\" left=\"8.0\" right=\"8.0\" top=\"8.0\" />\n         </BorderPane.margin>\n      </Button>\n   </bottom>\n   <top>\n      <Label styleClass=\"header\" text=\"Mulligan\" BorderPane.alignment=\"CENTER\" />\n   </top>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/LoadingBoardView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import java.lang.*?>\n<?import java.net.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root id=\"board\" prefHeight=\"768.0\" prefWidth=\"1024.0\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n  <stylesheets>\n    <URL value=\"@/css/gameboard.css\" />\n  </stylesheets>\n   <center>\n      <ProgressIndicator fx:id=\"loadingIndicator\" maxHeight=\"128.0\" maxWidth=\"128.0\" prefHeight=\"128.0\" prefWidth=\"128.0\" progress=\"0.0\" BorderPane.alignment=\"CENTER\" />\n   </center>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/MainMenuView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.web.*?>\n<?import javafx.scene.image.*?>\n<?import java.net.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"768.0\" prefWidth=\"1024.0\" styleClass=\"panel\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8.0.40\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <center>\n      <VBox alignment=\"CENTER\" spacing=\"10.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <Button fx:id=\"playModeButton\" layoutX=\"447.0\" layoutY=\"339.0\" mnemonicParsing=\"false\" prefWidth=\"200.0\" styleClass=\"main-button\" text=\"Play Mode\" textAlignment=\"CENTER\" />\n            <Button fx:id=\"deckBuilderButton\" mnemonicParsing=\"false\" prefWidth=\"200.0\" styleClass=\"main-button\" text=\"Deck Builder\" textAlignment=\"CENTER\" />\n            <Button fx:id=\"simulationModeButton\" layoutX=\"422.0\" layoutY=\"313.0\" mnemonicParsing=\"false\" prefWidth=\"200.0\" styleClass=\"main-button\" text=\"Simulation Mode\" textAlignment=\"CENTER\" />\n            <Button fx:id=\"trainingModeButton\" layoutX=\"422.0\" layoutY=\"391.0\" mnemonicParsing=\"false\" prefWidth=\"200.0\" styleClass=\"main-button\" text=\"Training Mode\" textAlignment=\"CENTER\" />\n            <Button fx:id=\"sandboxModeButton\" layoutX=\"422.0\" layoutY=\"417.0\" mnemonicParsing=\"false\" prefWidth=\"200.0\" styleClass=\"main-button\" text=\"Sandbox Mode\" textAlignment=\"CENTER\" />\n            <Button fx:id=\"battleOfDecksButton\" layoutX=\"422.0\" layoutY=\"388.0\" mnemonicParsing=\"false\" prefWidth=\"200.0\" styleClass=\"main-button\" text=\"Battle of Decks\" textAlignment=\"CENTER\" />\n         </children></VBox>\n   </center>\n   <top>\n      <HBox alignment=\"CENTER\">\n         <children>\n            <ImageView fitHeight=\"128.0\" fitWidth=\"128.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n               <image>\n                  <Image url=\"/img/ui/logo.png\" />\n               </image>\n            </ImageView>\n            <Text cache=\"true\" strokeType=\"OUTSIDE\" strokeWidth=\"0.0\" styleClass=\"title\" text=\"MetaStone\" BorderPane.alignment=\"CENTER\" />\n         </children>\n      </HBox>\n   </top>\n   <stylesheets>\n      <URL value=\"@/css/mainmenu.css\" />\n      <URL value=\"@/css/main.css\" />\n   </stylesheets>\n   <bottom>\n      <AnchorPane>\n         <children>\n            <Label fx:id=\"versionLabel\" styleClass=\"info-small\" text=\"x.x.x\" AnchorPane.bottomAnchor=\"8.0\" AnchorPane.leftAnchor=\"8.0\">\n               <opaqueInsets>\n                  <Insets />\n               </opaqueInsets>\n               <padding>\n                  <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n               </padding>\n            </Label>\n            <Button fx:id=\"donationButton\" mnemonicParsing=\"false\" AnchorPane.bottomAnchor=\"8.0\" AnchorPane.leftAnchor=\"433.0\" AnchorPane.rightAnchor=\"433.0\">\n               <graphic>\n                  <ImageView fitHeight=\"47.0\" fitWidth=\"126.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                     <image>\n                        <Image url=\"/img/ui/paypal_donate_button.gif\" />\n                     </image>\n                  </ImageView>\n               </graphic>\n            </Button>\n         </children>\n      </AnchorPane>\n   </bottom>\n   <padding>\n      <Insets top=\"20.0\" />\n   </padding>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/MetaDeckListView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.geometry.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.scene.text.*?>\n<?import javafx.scene.control.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefWidth=\"230.0\" spacing=\"2.0\" style=\"-fx-background-color: tan;\" type=\"VBox\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <padding>\n      <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n   </padding>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/MetaDeckView (2).fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import java.net.*?>\n<?import javafx.scene.control.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"630.0\" prefWidth=\"760.0\" styleClass=\"bordered-panel\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <bottom>\n      <HBox alignment=\"CENTER\" prefHeight=\"50.0\" prefWidth=\"400.0\" spacing=\"128.0\" BorderPane.alignment=\"CENTER\" />\n   </bottom>\n   <stylesheets>\n      <URL value=\"@/css/deckbuilder.css\" />\n   </stylesheets>\n   <padding>\n      <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n   </padding>\n   <center>\n      <ScrollPane prefViewportHeight=\"560.0\" prefViewportWidth=\"740.0\">\n         <content>\n            <FlowPane fx:id=\"contentPane\" alignment=\"CENTER\" columnHalignment=\"CENTER\" hgap=\"8.0\" prefHeight=\"200.0\" prefWidth=\"200.0\" vgap=\"16.0\" BorderPane.alignment=\"CENTER\" />\n         </content>\n      </ScrollPane>\n   </center>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/MetaDeckView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import java.net.*?>\n<?import javafx.scene.control.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"630.0\" prefWidth=\"760.0\" styleClass=\"bordered-panel\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <padding>\n      <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n   </padding>\n   <center>\n      <ScrollPane prefViewportHeight=\"622.0\" prefViewportWidth=\"752.0\">\n         <content>\n            <FlowPane fx:id=\"contentPane\" alignment=\"CENTER_LEFT\" columnHalignment=\"CENTER\" hgap=\"8.0\" prefWrapLength=\"800.0\" vgap=\"16.0\" BorderPane.alignment=\"CENTER\" />\n         </content>\n      </ScrollPane>\n   </center>\n   <stylesheets>\n      <URL value=\"@/css/deckbuilder.css\" />\n   </stylesheets>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/MinionCardPanel.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.web.*?>\n<?import javafx.scene.image.*?>\n<?import java.net.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root spacing=\"8.0\" type=\"VBox\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <children>\n      <HBox spacing=\"8.0\">\n         <children>\n            <Label minWidth=\"84.0\" text=\"Attack:\" />\n            <TextField fx:id=\"attackField\" promptText=\"enter attack value\" />\n         </children>\n         <padding>\n            <Insets left=\"16.0\" />\n         </padding>\n      </HBox>\n      <HBox layoutX=\"10.0\" layoutY=\"10.0\" spacing=\"8.0\">\n         <children>\n            <Label minWidth=\"84.0\" text=\"Hp:\" />\n            <TextField fx:id=\"hpField\" promptText=\"enter hp value\" />\n         </children>\n         <padding>\n            <Insets left=\"16.0\" />\n         </padding>\n      </HBox>\n      <HBox spacing=\"8.0\">\n         <children>\n            <Label minWidth=\"84.0\" text=\"Race:\" />\n            <ComboBox fx:id=\"raceBox\" prefWidth=\"150.0\" />\n         </children>\n         <padding>\n            <Insets left=\"16.0\" />\n         </padding>\n      </HBox>\n      <HBox fx:id=\"battlecryPanel\" spacing=\"8.0\">\n         <children>\n            <Label alignment=\"TOP_LEFT\" minWidth=\"84.0\" text=\"Battlecry\" />\n            <VBox alignment=\"CENTER_LEFT\" spacing=\"8.0\">\n               <children>\n                  <HBox spacing=\"8.0\">\n                     <children>\n                        <Label minWidth=\"84.0\" text=\"TargetSelection\" />\n                        <ComboBox fx:id=\"battlecryTargetSelectionBox\" prefWidth=\"150.0\" />\n                     </children>\n                  </HBox>\n                  <HBox layoutX=\"10.0\" layoutY=\"10.0\" spacing=\"8.0\">\n                     <children>\n                        <Label minWidth=\"84.0\" text=\"Spell\" />\n                        <ComboBox fx:id=\"battlecrySpellBox\" prefWidth=\"150.0\" />\n                     </children>\n                  </HBox>\n               </children>\n            </VBox>\n         </children>\n         <padding>\n            <Insets left=\"16.0\" />\n         </padding>\n      </HBox>\n      <HBox fx:id=\"deathrattlePanel\" spacing=\"8.0\">\n         <children>\n            <Label minWidth=\"84.0\" text=\"Deathrattle\" />\n            <HBox spacing=\"8.0\">\n               <children>\n                  <Label minWidth=\"84.0\" text=\"Spell\" />\n                  <ComboBox fx:id=\"deathrattleSpellBox\" prefWidth=\"150.0\" />\n               </children>\n            </HBox>\n         </children>\n         <padding>\n            <Insets left=\"16.0\" />\n         </padding>\n      </HBox>\n   </children>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/MinionPanel.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.chart.*?>\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root styleClass=\"panel\" spacing=\"4.0\"  type=\"VBox\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <children>\n      <Label styleClass=\"info-small\" text=\"Minions\" />\n      <ComboBox  fx:id=\"minionComboBox\" prefWidth=\"200.0\" promptText=\"&lt;select a minion&gt;\" />\n      <TextField fx:id=\"filterMinionsTextField\" prefWidth=\"200.0\" promptText=\"&lt;filter minions&gt;\" />\n      <Button fx:id=\"spawnMinionButton\" mnemonicParsing=\"false\" text=\"Spawn\" />\n      <Button fx:id=\"killMinionButton\" mnemonicParsing=\"false\" text=\"Kill...\" />\n      <Button fx:id=\"silenceButton\" layoutX=\"10.0\" layoutY=\"117.0\" mnemonicParsing=\"false\" text=\"Silence...\" />\n   </children>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/PlayModeConfigView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"768.0\" prefWidth=\"1024.0\" styleClass=\"panel\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <top>\n      <Text strokeType=\"OUTSIDE\" strokeWidth=\"0.0\" styleClass=\"header\" text=\"Play Mode\" BorderPane.alignment=\"CENTER\" />\n   </top>\n   <padding>\n      <Insets bottom=\"20.0\" left=\"20.0\" right=\"20.0\" top=\"20.0\" />\n   </padding>\n   <center>\n      <VBox alignment=\"CENTER\" spacing=\"8.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <Label styleClass=\"name-small\" text=\"Deck Format:\" />\n            <ComboBox fx:id=\"formatBox\" prefWidth=\"150.0\" />\n            <HBox fx:id=\"playerArea\" alignment=\"CENTER\" prefHeight=\"100.0\" prefWidth=\"200.0\" spacing=\"0.0\" BorderPane.alignment=\"CENTER\" />\n         </children>\n      </VBox>\n   </center>\n   <bottom>\n      <AnchorPane>\n         <children>\n            <Button fx:id=\"backButton\" mnemonicParsing=\"false\" text=\"Back\" AnchorPane.leftAnchor=\"0.0\" />\n            <Button fx:id=\"startButton\" mnemonicParsing=\"false\" text=\"Start\" AnchorPane.leftAnchor=\"380.0\" AnchorPane.rightAnchor=\"380.0\" />\n         </children>\n      </AnchorPane>\n   </bottom>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/PlayModeView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import java.lang.*?>\n<?import java.net.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root styleClass=\"panel\" maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"768.0\" prefWidth=\"1024.0\"  type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <right>\n      <VBox styleClass=\"bordered-panel\" fx:id=\"sidePane\" alignment=\"BOTTOM_CENTER\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <VBox fx:id=\"navigationPane\" spacing=\"8.0\" styleClass=\"bordered-panel\">\n               <children>\n                  <Button fx:id=\"backButton\" mnemonicParsing=\"false\" prefWidth=\"120.0\" text=\"Back to main menu\" wrapText=\"true\" />\n               </children>\n               <padding>\n                  <Insets bottom=\"8.0\" left=\"8.0\" right=\"8.0\" top=\"8.0\" />\n               </padding>\n            </VBox>\n         </children>\n      </VBox>\n   </right>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/PlayerConfigView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root styleClass=\"bordered-panel\" alignment=\"TOP_CENTER\" maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"600.0\" prefWidth=\"400.0\" spacing=\"4.0\"  type=\"VBox\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <children>\n      <Label styleClass=\"name-big\" fx:id=\"heroNameLabel\" text=\"&lt;heroname&gt;\" />\n      <ImageView fx:id=\"heroIcon\" fitHeight=\"128.0\" fitWidth=\"128.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n         <image>\n            <Image url=\"/img/heroes/garrosh.png\" />\n         </image>\n      </ImageView>\n      <Label styleClass=\"name-small\" text=\"Choose hero:\" />\n      <ComboBox  fx:id=\"heroBox\" prefWidth=\"150.0\" />\n      <Separator prefWidth=\"200.0\" />\n      <Label styleClass=\"name-small\" text=\"Behaviour:\" />\n      <ComboBox  fx:id=\"behaviourBox\" prefWidth=\"150.0\" />\n      <Separator prefWidth=\"200.0\" />\n      <Label styleClass=\"name-small\" text=\"Deck:\" />\n      <ComboBox  fx:id=\"deckBox\" prefWidth=\"150.0\" />\n      <Separator prefWidth=\"200.0\" />\n      <CheckBox styleClass=\"name-small\" fx:id=\"hideCardsCheckBox\" mnemonicParsing=\"false\" text=\"Hide cards\" />\n   </children>\n   <opaqueInsets>\n      <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n   </opaqueInsets>\n   <padding>\n      <Insets bottom=\"8.0\" left=\"8.0\" right=\"8.0\" top=\"8.0\" />\n   </padding>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/PlayerInfoView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.image.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" styleClass=\"panel\"  type=\"Pane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <children>\n      <VBox alignment=\"CENTER\">\n         <children>\n            <ImageView fx:id=\"classIcon\" fitHeight=\"64.0\" fitWidth=\"64.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n               <image>\n                  <Image url=\"/img/classes/mage.png\" />\n               </image>\n            </ImageView>\n            <Label fx:id=\"heroLabel\" styleClass=\"name-small\" text=\"&lt;heroname&gt;\" textAlignment=\"CENTER\" />\n            <GridPane alignment=\"CENTER\">\n               <children>\n                  <Label styleClass=\"name-small\" text=\"Deck:\" />\n                  <Label styleClass=\"name-small\" text=\"Behaviour:\" GridPane.rowIndex=\"1\" />\n                  <Label fx:id=\"deckLabel\" styleClass=\"info-small\" text=\"&lt;deckname&gt;\" GridPane.columnIndex=\"1\" />\n                  <Label fx:id=\"behaviourLabel\" layoutX=\"110.0\" layoutY=\"12.0\" styleClass=\"info-small\" text=\"&lt;behaviour&gt;\" GridPane.columnIndex=\"1\" GridPane.rowIndex=\"1\" />\n               </children>\n               <columnConstraints>\n                  <ColumnConstraints hgrow=\"SOMETIMES\" minWidth=\"10.0\" prefWidth=\"100.0\" />\n                  <ColumnConstraints hgrow=\"SOMETIMES\" minWidth=\"10.0\" prefWidth=\"100.0\" />\n               </columnConstraints>\n               <rowConstraints>\n                  <RowConstraints minHeight=\"10.0\" prefHeight=\"20.0\" vgrow=\"SOMETIMES\" />\n                  <RowConstraints minHeight=\"10.0\" prefHeight=\"20.0\" vgrow=\"SOMETIMES\" />\n               </rowConstraints>\n            </GridPane>\n         </children>\n         <padding>\n            <Insets bottom=\"12.0\" left=\"12.0\" right=\"12.0\" top=\"12.0\" />\n         </padding>\n      </VBox>\n   </children>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/PlayerPanel.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.chart.*?>\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root spacing=\"4.0\" styleClass=\"panel\"  type=\"VBox\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <children>\n      <Label styleClass=\"info-small\" text=\"Player\" />\n      <Label layoutX=\"10.0\" layoutY=\"41.0\" styleClass=\"info-small\" text=\"Select player:\" />\n      <ComboBox fx:id=\"playerComboBox\" prefWidth=\"200.0\" />\n      <HBox alignment=\"CENTER_LEFT\" spacing=\"8.0\">\n         <children>\n            <Label text=\"Mana:\" />\n            <ComboBox fx:id=\"currentManaBox\" prefWidth=\"60.0\" />\n            <Label layoutX=\"10.0\" layoutY=\"10.0\" text=\"/\" />\n            <ComboBox fx:id=\"maxManaBox\" layoutX=\"51.0\" layoutY=\"10.0\" prefWidth=\"60.0\" />\n         </children>\n         <opaqueInsets>\n            <Insets />\n         </opaqueInsets>\n         <VBox.margin>\n            <Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n         </VBox.margin>\n      </HBox>\n      <Button fx:id=\"editEntityButton\" mnemonicParsing=\"false\" text=\"Edit entity...\" />\n   </children>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/SandboxModeConfigView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root styleClass=\"panel\" maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"768.0\" prefWidth=\"1024.0\"  type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <top>\n      <Text styleClass=\"header\" strokeType=\"OUTSIDE\" strokeWidth=\"0.0\" text=\"Sandbox Mode\" BorderPane.alignment=\"CENTER\" />\n   </top>\n   <padding>\n      <Insets bottom=\"20.0\" left=\"20.0\" right=\"20.0\" top=\"20.0\" />\n   </padding>\n   <center>\n      <VBox alignment=\"CENTER\" spacing=\"8.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <Label styleClass=\"name-small\" text=\"Deck Format:\" />\n            <ComboBox fx:id=\"formatBox\" prefWidth=\"150.0\" />\n            <HBox fx:id=\"playerArea\" alignment=\"CENTER\" maxHeight=\"400.0\" BorderPane.alignment=\"CENTER\" />\n         </children>\n      </VBox>\n   </center>\n   <bottom>\n      <AnchorPane>\n         <children>\n            <Button  fx:id=\"backButton\" mnemonicParsing=\"false\" text=\"Back\" AnchorPane.leftAnchor=\"0.0\" />\n            <Button  fx:id=\"startButton\" mnemonicParsing=\"false\" text=\"Start\" AnchorPane.leftAnchor=\"380.0\" AnchorPane.rightAnchor=\"380.0\" />\n         </children>\n      </AnchorPane>\n   </bottom>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/SandboxModeView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.chart.*?>\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"768.0\" prefWidth=\"1024.0\" styleClass=\"panel\"  type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <padding>\n      <Insets bottom=\"10.0\" left=\"10.0\" right=\"10.0\" top=\"10.0\" />\n   </padding>\n   <right>\n      <VBox fx:id=\"sidebar\" alignment=\"BOTTOM_CENTER\" spacing=\"8.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <VBox fx:id=\"navigationPane\" spacing=\"8.0\" styleClass=\"bordered-panel\">\n               <children>\n                  <Button fx:id=\"backButton\" mnemonicParsing=\"false\" prefWidth=\"120.0\" text=\"Back to main menu\" wrapText=\"true\" />\n                  <Button fx:id=\"playButton\" mnemonicParsing=\"false\" text=\"Play\">\n                     <graphic>\n                        <ImageView fitHeight=\"52.0\" fitWidth=\"52.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n                           <image>\n                              <Image url=\"/img/ui/play_icon.png\" />\n                           </image>\n                        </ImageView>\n                     </graphic>\n                  </Button>\n               </children>\n               <padding>\n                  <Insets bottom=\"8.0\" left=\"8.0\" right=\"8.0\" top=\"8.0\" />\n               </padding>\n            </VBox>\n         </children>\n      </VBox>\n   </right>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/SimulationModeConfigView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root styleClass=\"panel\" maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"768.0\" prefWidth=\"1024.0\"  type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <top>\n      <Text styleClass=\"header\" strokeType=\"OUTSIDE\" strokeWidth=\"0.0\" text=\"Simulation Mode\" BorderPane.alignment=\"CENTER\" />\n   </top>\n   <padding>\n      <Insets bottom=\"20.0\" left=\"20.0\" right=\"20.0\" top=\"20.0\" />\n   </padding>\n   <center>\n      <VBox alignment=\"CENTER\" spacing=\"8.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <Label styleClass=\"name-small\" text=\"Deck Format:\" />\n            <ComboBox fx:id=\"formatBox\" prefWidth=\"150.0\" />\n            <HBox fx:id=\"playerArea\" alignment=\"CENTER\" maxHeight=\"400.0\" BorderPane.alignment=\"CENTER\" />\n            <Label styleClass=\"name-small\" text=\"Number of games\" />\n            <ComboBox fx:id=\"numberOfGamesBox\" prefWidth=\"150.0\" />\n         </children>\n      </VBox>\n   </center>\n   <bottom>\n      <AnchorPane>\n         <children>\n            <Button  fx:id=\"backButton\" mnemonicParsing=\"false\" text=\"Back\" AnchorPane.leftAnchor=\"0.0\" />\n            <Button  fx:id=\"startButton\" mnemonicParsing=\"false\" text=\"Start\" AnchorPane.leftAnchor=\"380.0\" AnchorPane.rightAnchor=\"380.0\" />\n         </children>\n      </AnchorPane>\n   </bottom>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/SimulationResultView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.image.*?>\n<?import javafx.scene.text.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root styleClass=\"panel\" maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"768.0\" prefWidth=\"1024.0\"  type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <center>\n      <VBox alignment=\"CENTER\" fillWidth=\"false\" spacing=\"16.0\">\n         <children>\n            <BorderPane fx:id=\"infoArea\">\n               <center>\n                  <Label styleClass=\"name-big\" alignment=\"BASELINE_CENTER\" text=\"vs.\" BorderPane.alignment=\"CENTER\">\n                     <padding>\n                        <Insets left=\"64.0\" right=\"64.0\" />\n                     </padding></Label>\n               </center>\n               <padding>\n                  <Insets bottom=\"16.0\" left=\"16.0\" right=\"16.0\" top=\"16.0\" />\n               </padding>\n            </BorderPane>\n            <Label styleClass=\"name-small\" fx:id=\"durationLabel\" text=\"&lt;simulation duration&gt;\" textAlignment=\"CENTER\" />\n            <TabPane>\n               <tabs>\n                  <Tab closable=\"false\" text=\"Absolute values (over all games)\">\n                     <content>\n                        <ScrollPane prefViewportHeight=\"440.0\" prefViewportWidth=\"620.0\">\n                           <content>\n                              <TableView fx:id=\"absoluteResultTable\" prefWidth=\"600.0\">\n                                <columns>\n                                  <TableColumn minWidth=\"100.0\" prefWidth=\"200.0\" text=\"Statistic\" />\n                                  <TableColumn minWidth=\"100.0\" prefWidth=\"200.0\" text=\"Player 1\" />\n                                    <TableColumn minWidth=\"100.0\" prefWidth=\"200.0\" text=\"Player 2\" />\n                                </columns>\n                              </TableView>\n                           </content>\n                        </ScrollPane>\n                     </content>\n                  </Tab>\n                  <Tab closable=\"false\" text=\"Average values (per game)\">\n                     <content>\n                        <ScrollPane prefViewportHeight=\"440.0\" prefViewportWidth=\"620.0\">\n                           <content>\n                              <TableView fx:id=\"averageResultTable\" prefWidth=\"600.0\">\n                                 <columns>\n                                    <TableColumn minWidth=\"100.0\" prefWidth=\"200.0\" text=\"Statistic\" />\n                                    <TableColumn minWidth=\"100.0\" prefWidth=\"200.0\" text=\"Player 1\" />\n                                    <TableColumn minWidth=\"100.0\" prefWidth=\"200.0\" text=\"Player 2\" />\n                                 </columns>\n                              </TableView>\n                           </content>\n                        </ScrollPane>\n                     </content>\n                  </Tab>\n               </tabs>\n            </TabPane>\n         </children>\n         <padding>\n            <Insets bottom=\"8.0\" left=\"8.0\" right=\"8.0\" top=\"8.0\" />\n         </padding>\n      </VBox>\n   </center>\n   <bottom>\n      <Button  fx:id=\"doneButton\" mnemonicParsing=\"false\" text=\"Close\" BorderPane.alignment=\"CENTER\">\n         <padding>\n            <Insets bottom=\"8.0\" left=\"8.0\" right=\"8.0\" top=\"8.0\" />\n         </padding>\n      </Button>\n   </bottom>\n   <top>\n      <Text styleClass=\"header\" strokeType=\"OUTSIDE\" strokeWidth=\"0.0\" text=\"Simulation Results\" BorderPane.alignment=\"CENTER\" />\n   </top>\n   <padding>\n      <Insets bottom=\"8.0\" top=\"20.0\" />\n   </padding>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/SpellCardPanel.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.web.*?>\n<?import javafx.scene.image.*?>\n<?import java.net.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root spacing=\"8.0\" type=\"VBox\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <children>\n      <HBox fx:id=\"battlecryPanel\" spacing=\"8.0\">\n         <children>\n            <Label alignment=\"TOP_LEFT\" minWidth=\"84.0\" text=\"Spell:\" />\n            <VBox alignment=\"CENTER_LEFT\" spacing=\"8.0\">\n               <children>\n                  <HBox spacing=\"8.0\">\n                     <children>\n                        <Label minWidth=\"84.0\" text=\"TargetSelection\" />\n                        <ComboBox fx:id=\"targetSelectionBox\" prefWidth=\"150.0\" />\n                     </children>\n                  </HBox>\n                  <HBox layoutX=\"10.0\" layoutY=\"10.0\" spacing=\"8.0\">\n                     <children>\n                        <Label minWidth=\"84.0\" text=\"Spell\" />\n                        <ComboBox fx:id=\"spellBox\" prefWidth=\"150.0\" />\n                     </children>\n                  </HBox>\n               </children>\n            </VBox>\n         </children>\n         <padding>\n            <Insets left=\"16.0\" />\n         </padding>\n      </HBox>\n   </children>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/SummonToken.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.*?>\n<?import javafx.scene.paint.*?>\n<?import java.lang.*?>\n<?import java.net.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.scene.layout.*?>\n<?import javafx.scene.layout.BorderPane?>\n<?import javafx.scene.shape.*?>\n<?import javafx.scene.text.*?>\n\n<fx:root cache=\"true\" prefHeight=\"-1.0\" prefWidth=\"-1.0\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n\t<center>\n\t\t<StackPane fx:id=\"targetAnchor\" prefHeight=\"-1.0\" prefWidth=\"-1.0\" BorderPane.alignment=\"CENTER\">\n\t\t\t<children>\n\t\t\t\t<Circle fx:id=\"defaultToken\" fill=\"#bfbfbf\" radius=\"40.0\" stroke=\"BLACK\" strokeDashOffset=\"0.0\" strokeLineCap=\"SQUARE\" strokeType=\"INSIDE\" strokeWidth=\"2.0\" />\n\t\t\t\t<Polygon fx:id=\"taunt\" fill=\"#bfbfbf\" stroke=\"BLACK\" strokeType=\"INSIDE\" strokeWidth=\"3.0\" translateY=\"5.0\" visible=\"false\">\n\t\t\t\t\t<points>\n\t\t\t\t\t\t<Double fx:value=\"-50.0\" />\n\t\t\t\t\t\t<Double fx:value=\"-20.0\" />\n\t\t\t\t\t\t<Double fx:value=\"50.0\" />\n\t\t\t\t\t\t<Double fx:value=\"-20.0\" />\n\t\t\t\t\t\t<Double fx:value=\"20.0\" />\n\t\t\t\t\t\t<Double fx:value=\"70.0\" />\n\t\t\t\t\t\t<Double fx:value=\"-20.0\" />\n\t\t\t\t\t\t<Double fx:value=\"70.0\" />\n\t\t\t\t\t</points>\n\t\t\t\t</Polygon>\n\t\t\t\t<Ellipse fx:id=\"frozen\" fill=\"#ffffff00\" radiusX=\"50.0\" radiusY=\"30.0\" smooth=\"false\" stroke=\"#0e90e8\" strokeDashOffset=\"10.0\" strokeType=\"OUTSIDE\" strokeWidth=\"5.0\" visible=\"false\" />\n\t\t\t\t<Label fx:id=\"name\" alignment=\"CENTER\" labelFor=\"$name\" maxWidth=\"80.0\" text=\"&lt;minion name&gt;\" textAlignment=\"CENTER\" textOverrun=\"ELLIPSIS\" wrapText=\"true\" StackPane.alignment=\"CENTER\">\n\t\t\t\t\t<font>\n\t\t\t\t\t\t<Font name=\"Arial Bold\" size=\"12.0\" />\n\t\t\t\t\t</font>\n\t\t\t\t</Label>\n\t\t\t\t<StackPane prefHeight=\"-1.0\" prefWidth=\"-1.0\" translateY=\"0.0\" StackPane.alignment=\"BOTTOM_CENTER\">\n\t\t\t\t\t<children>\n\t\t\t\t\t\t<ImageView fx:id=\"deathrattle\" fitHeight=\"40.0\" fitWidth=\"40.0\" pickOnBounds=\"true\" preserveRatio=\"true\" translateY=\"8.0\" visible=\"false\" StackPane.alignment=\"BOTTOM_CENTER\">\n\t\t\t\t\t\t\t<image>\n\t\t\t\t\t\t\t\t<Image url=\"/img/common/deathrattle.png\" />\n\t\t\t\t\t\t\t</image>\n\t\t\t\t\t\t</ImageView>\n\t\t\t\t\t\t<ImageView fitHeight=\"100.0\" fitWidth=\"32.0\" mouseTransparent=\"true\" pickOnBounds=\"true\" preserveRatio=\"true\" translateX=\"-2.0\" translateY=\"3.0\" y=\"0.0\" StackPane.alignment=\"BOTTOM_LEFT\">\n\t\t\t\t\t\t\t<image>\n\t\t\t\t\t\t\t\t<Image url=\"/img/common/attack_value_icon.png\" />\n\t\t\t\t\t\t\t</image>\n\t\t\t\t\t\t</ImageView>\n\t\t\t\t\t\t<Group fx:id=\"attackAnchor\" translateX=\"8.0\" translateY=\"-3.0\" StackPane.alignment=\"BOTTOM_LEFT\" />\n\t\t\t\t\t\t<ImageView fitHeight=\"100.0\" fitWidth=\"30.0\" pickOnBounds=\"true\" preserveRatio=\"true\" translateX=\"2.0\" translateY=\"2.0\" StackPane.alignment=\"BOTTOM_RIGHT\">\n\t\t\t\t\t\t\t<image>\n\t\t\t\t\t\t\t\t<Image url=\"/img/common/health_value_icon.png\" />\n\t\t\t\t\t\t\t</image>\n\t\t\t\t\t\t</ImageView>\n\t\t\t\t\t\t<Group fx:id=\"hpAnchor\" translateX=\"-6.0\" translateY=\"-3.0\" StackPane.alignment=\"BOTTOM_RIGHT\" />\n\t\t\t\t\t</children>\n\t\t\t\t</StackPane>\n\t\t\t\t<Ellipse fx:id=\"divineShield\" radiusX=\"30.0\" radiusY=\"50.0\" stroke=\"BLACK\" strokeType=\"INSIDE\" visible=\"false\">\n\t\t\t\t\t<fill>\n\t\t\t\t\t\t<LinearGradient endX=\"1.0\" endY=\"1.0\">\n\t\t\t\t\t\t\t<stops>\n\t\t\t\t\t\t\t\t<Stop color=\"#f0e80033\" />\n\t\t\t\t\t\t\t\t<Stop color=\"WHITE\" offset=\"1.0\" />\n\t\t\t\t\t\t\t</stops>\n\t\t\t\t\t\t</LinearGradient>\n\t\t\t\t\t</fill>\n\t\t\t\t</Ellipse>\n\t\t\t\t<Text fx:id=\"windfury\" fill=\"#e8964f\" stroke=\"BLACK\" strokeType=\"OUTSIDE\" strokeWidth=\"2.0\" text=\"x2\" translateX=\"-38.0\" translateY=\"12.0\" visible=\"false\">\n\t\t\t\t\t<font>\n\t\t\t\t\t\t<Font name=\"Arial\" size=\"22.0\" />\n\t\t\t\t\t</font>\n\t\t\t\t</Text>\n\t\t\t</children>\n\t\t</StackPane>\n\t</center>\n\t<padding>\n\t\t<Insets bottom=\"4.0\" left=\"4.0\" right=\"4.0\" top=\"4.0\" />\n\t</padding>\n\t<stylesheets>\n\t\t<URL value=\"@/css/gameboard.css\" />\n\t</stylesheets>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/ToolboxView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.chart.*?>\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root styleClass=\"bordered-panel\" orientation=\"VERTICAL\"  type=\"ToolBar\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <items>\n      <Label styleClass=\"default-label\" text=\"Toolbox\" />\n      <Separator prefWidth=\"200.0\" />\n   </items>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/TrainingConfigView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"768.0\" prefWidth=\"1024.0\" styleClass=\"panel\"  type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <top>\n      <Text strokeType=\"OUTSIDE\" strokeWidth=\"0.0\" styleClass=\"header\" text=\"Training\" BorderPane.alignment=\"CENTER\" />\n   </top>\n   <padding>\n      <Insets bottom=\"20.0\" left=\"20.0\" right=\"20.0\" top=\"20.0\" />\n   </padding>\n   <center>\n      <VBox alignment=\"TOP_CENTER\" spacing=\"8.0\" BorderPane.alignment=\"TOP_CENTER\">\n         <children>\n            <Label styleClass=\"name-small\" text=\"Deck Format:\" />\n            <ComboBox fx:id=\"formatBox\" prefWidth=\"150.0\" />\n            <HBox fx:id=\"playerArea\" alignment=\"CENTER\" maxHeight=\"400.0\" BorderPane.alignment=\"CENTER\" />\n            <Label styleClass=\"name-small\" text=\"Number of games (per match)\" />\n            <ComboBox fx:id=\"numberOfGamesBox\" prefWidth=\"150.0\" />\n            <Label layoutX=\"407.0\" layoutY=\"310.0\" styleClass=\"name-small\" text=\"Deck\" />\n            <ComboBox fx:id=\"deckBox\" layoutX=\"427.0\" layoutY=\"324.0\" prefWidth=\"150.0\" />\n            <HBox alignment=\"CENTER\" spacing=\"8.0\">\n               <children>\n                  <VBox>\n                     <children>\n                        <Label styleClass=\"name-small\" text=\"Available Decks\" />\n                        <ScrollPane prefViewportHeight=\"300.0\" prefViewportWidth=\"300.0\">\n                           <content>\n                              <ListView fx:id=\"availableDecksListView\" prefHeight=\"300.0\" prefWidth=\"300.0\" />\n                           </content>\n                        </ScrollPane>\n                     </children>\n                  </VBox>\n                  <VBox alignment=\"CENTER\" spacing=\"8.0\">\n                     <children>\n                        <Button fx:id=\"addButton\" mnemonicParsing=\"false\" text=\"&gt;&gt;\" />\n                        <Button fx:id=\"removeButton\" mnemonicParsing=\"false\" text=\"&lt;&lt;\" />\n                     </children>\n                  </VBox>\n                  <VBox>\n                     <children>\n                        <Label styleClass=\"name-small\" text=\"Selected Decks\" />\n                        <ScrollPane prefViewportHeight=\"300.0\" prefViewportWidth=\"300.0\">\n                           <content>\n                              <ListView fx:id=\"selectedDecksListView\" prefHeight=\"300.0\" prefWidth=\"300.0\" />\n                           </content>\n                        </ScrollPane>\n                     </children>\n                  </VBox>\n               </children>\n            </HBox>\n         </children>\n         <padding>\n            <Insets top=\"20.0\" />\n         </padding>\n      </VBox>\n   </center>\n   <bottom>\n      <AnchorPane>\n         <children>\n            <Button fx:id=\"backButton\" mnemonicParsing=\"false\" text=\"Back\" AnchorPane.leftAnchor=\"0.0\" />\n            <Button fx:id=\"startButton\" mnemonicParsing=\"false\" text=\"Start\" AnchorPane.leftAnchor=\"380.0\" AnchorPane.rightAnchor=\"380.0\" />\n         </children>\n      </AnchorPane>\n   </bottom>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/TrainingModeView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.scene.chart.*?>\n<?import java.net.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import javafx.scene.text.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"768.0\" prefWidth=\"1024.0\" styleClass=\"panel\"  type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <top>\n      <Text strokeType=\"OUTSIDE\" strokeWidth=\"0.0\" styleClass=\"header\" text=\"Training Mode\" BorderPane.alignment=\"CENTER\" />\n   </top>\n   <padding>\n      <Insets bottom=\"20.0\" left=\"20.0\" right=\"20.0\" top=\"20.0\" />\n   </padding>\n   <center>\n      <VBox alignment=\"CENTER\" spacing=\"8.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <Label fx:id=\"trainingLabel\" text=\"Training: Special Tactics Deck\">\n               <styleClass>\n                  <String fx:value=\"info-small\" />\n                  <String fx:value=\"default-label\" />\n               </styleClass>\n            </Label>\n            <Label fx:id=\"progressLabel\" layoutX=\"488.0\" layoutY=\"120.0\" text=\"Label\">\n               <styleClass>\n                  <String fx:value=\"info-small\" />\n                  <String fx:value=\"name-small\" />\n               </styleClass>\n            </Label>\n            <LineChart fx:id=\"resultChart\" title=\"Training results\">\n              <xAxis>\n                <NumberAxis label=\"Games played\" side=\"BOTTOM\" />\n              </xAxis>\n              <yAxis>\n                <NumberAxis label=\"Win rate\" side=\"LEFT\" />\n              </yAxis>\n            </LineChart>\n         </children>\n      </VBox>\n   </center>\n   <bottom>\n      <AnchorPane>\n         <children>\n            <Button fx:id=\"backButton\" mnemonicParsing=\"false\" text=\"Back\" AnchorPane.leftAnchor=\"0.0\" />\n         </children>\n      </AnchorPane>\n   </bottom>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/UserDialog.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.geometry.*?>\n<?import javafx.scene.image.*?>\n<?import javafx.scene.control.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n<fx:root styleClass=\"bordered-dialog\" maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"300.0\" prefWidth=\"400.0\" stylesheets=\"@../css/main.css\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <top>\n      <Label styleClass=\"name-big\" fx:id=\"headerLabel\" text=\"&lt;Header&gt;\" BorderPane.alignment=\"CENTER\">\n         <BorderPane.margin>\n            <Insets top=\"10.0\" />\n         </BorderPane.margin></Label>\n   </top>\n   <center>\n      <HBox fillHeight=\"false\" spacing=\"16.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <ImageView fx:id=\"icon\" fitHeight=\"150.0\" fitWidth=\"96.0\" pickOnBounds=\"true\" preserveRatio=\"true\">\n               <image>\n                  <Image url=\"/img/ui/confirm.png\" />\n               </image>\n            </ImageView>\n            <Label styleClass=\"dialog-text\" fx:id=\"textLabel\" maxWidth=\"240.0\" text=\"Lorem ipsum und der ganze Klabautermann mit langem Text im Nacken\" wrapText=\"true\" />\n         </children>\n         <padding>\n            <Insets bottom=\"16.0\" left=\"16.0\" right=\"16.0\" top=\"16.0\" />\n         </padding>\n      </HBox>\n   </center>\n   <bottom>\n      <HBox alignment=\"CENTER\" spacing=\"32.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <Button  fx:id=\"positiveButton\" minWidth=\"110.0\" mnemonicParsing=\"false\" text=\"Ok\" />\n            <Button  fx:id=\"negativeButton\" minWidth=\"110.0\" mnemonicParsing=\"false\" text=\"Cancel\" />\n         </children>\n         <BorderPane.margin>\n            <Insets />\n         </BorderPane.margin>\n         <padding>\n            <Insets bottom=\"8.0\" left=\"8.0\" right=\"8.0\" top=\"8.0\" />\n         </padding>\n      </HBox>\n   </bottom>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/fxml/WaitForSimulationView.fxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<?import javafx.geometry.*?>\n<?import javafx.scene.control.*?>\n<?import java.lang.*?>\n<?import javafx.scene.layout.*?>\n\n\n<fx:root styleClass=\"bordered-dialog\" maxHeight=\"-Infinity\" maxWidth=\"-Infinity\" minHeight=\"-Infinity\" minWidth=\"-Infinity\" prefHeight=\"200.0\" prefWidth=\"400.0\" stylesheets=\"@../css/main.css\" type=\"BorderPane\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n   <top>\n      <Label styleClass=\"name-big\" text=\"Simulating....\" BorderPane.alignment=\"CENTER\">\n         <padding>\n            <Insets bottom=\"8.0\" left=\"8.0\" right=\"8.0\" top=\"8.0\" />\n         </padding>\n      </Label>\n   </top>\n   <center>\n      <VBox alignment=\"CENTER\" spacing=\"8.0\" BorderPane.alignment=\"CENTER\">\n         <children>\n            <ProgressBar fx:id=\"progressBar\" prefWidth=\"200.0\" progress=\"0.0\" />\n            <Label styleClass=\"dialog-text\" fx:id=\"gamesCompletedLabel\" text=\"&lt;games completed&gt;\" />\n         </children>\n      </VBox>\n   </center>\n</fx:root>\n"
  },
  {
    "path": "app/src/main/resources/logback.xml",
    "content": "<configuration>\n\n\t<appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n\t\t<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder \n\t\t\tby default -->\n\t\t<encoder>\n\t\t\t<pattern>%d{HH:mm:ss} %-5level - %msg%n</pattern>\n\t\t</encoder>\n\t</appender>\n\n\t<appender name=\"LOGFILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n\t\t<file>report.log</file>\n\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.FixedWindowRollingPolicy\">\n      \t\t<fileNamePattern>report.%i.log</fileNamePattern>\n      \t\t<minIndex>1</minIndex>\n      \t\t<maxIndex>3</maxIndex>\n    \t</rollingPolicy>\n\n    \t<triggeringPolicy class=\"ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy\">\n      \t\t<maxFileSize>1000MB</maxFileSize>\n    \t</triggeringPolicy>\n    \t\n\t\t<append>true</append>\n\t\t<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder \n\t\t\tby default -->\n\t\t<encoder>\n\t\t\t<pattern>%d{HH:mm:ss} %-5level - %msg%n</pattern>\n\t\t</encoder>\n\t</appender>\n\t\n\t<root level=\"INFO\">\n\t\t<appender-ref ref=\"STDOUT\"/>\n\t\t<appender-ref ref=\"LOGFILE\" />\n\t</root>\n\n</configuration>"
  },
  {
    "path": "build.gradle",
    "content": "subprojects {\r\n    repositories {\r\n        mavenCentral()\r\n    }\r\n\r\n    apply plugin: 'java'\r\n    apply plugin: 'eclipse'\r\n    apply plugin: 'idea'\r\n\r\n    sourceCompatibility = 1.8\r\n\r\n    project.version = '1.3.0'\r\n\r\n    compileJava {\r\n        options.encoding = 'UTF-8'\r\n        options.compilerArgs << \"-Xlint:unchecked\" << \"-Xlint:deprecation\"\r\n    }\r\n}\r\n"
  },
  {
    "path": "cards/build.gradle",
    "content": "jar {\n    manifest {\n        attributes 'Implementation-Title': rootProject.name.capitalize() + ' Data Files',\n                'Implementation-Version': project.version,\n                'Description': 'Cards, Decks and DeckFormat data files.'\n    }\n}\n\ndependencies {\n    testCompile project(\":game\")\n    testCompile group: 'org.testng', name: 'testng', version: '6.+'\n}\n\ntest {\n    // enable TestNG support (default is JUnit)\n    useTestNG()\n    testLogging {\n        events \"standardError\"\n    }\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/druid/hero_malfurion.json",
    "content": "{\n\t\"name\": \"Malfurion Stormrage\",\n\t\"baseManaCost\": 0,\n\t\"heroPower\": \"hero_power_shapeshift\",\n\t\"type\": \"HERO\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"attributes\": {\n\t\t\"HP\": 30,\n\t\t\"MAX_HP\": 30\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/druid/hero_power_shapeshift.json",
    "content": "{\n\t\"name\": \"Shapeshift\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"+1 Attack this turn. +1 Armor.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffHeroSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"attackBonus\": 1,\n\t\t\"armorBonus\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/druid/minion_ironbark_protector.json",
    "content": "{\n\t\"name\": \"Ironbark Protector\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/druid/spell_claw.json",
    "content": "{\n\t\"name\": \"Claw\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give your hero +2 Attack this turn and 2 Armor.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffHeroSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"attackBonus\": 2,\n\t\t\"armorBonus\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/druid/spell_excess_mana.json",
    "content": "{\n\t\"name\": \"Excess Mana\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Draw a card. (You can only have 10 Mana in your tray.)\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/druid/spell_healing_touch.json",
    "content": "{\n\t\"name\": \"Healing Touch\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Restore 8 Health.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"HealSpell\",\n\t\t\"value\": 8\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/druid/spell_innervate.json",
    "content": "{\n\t\"name\": \"Innervate\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Gain 2 Mana Crystals this turn only.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"GainManaSpell\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/druid/spell_mark_of_the_wild.json",
    "content": "{\n\t\"name\": \"Mark of the Wild\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a minion Taunt and +2/+2. (+2 Attack/+2 Health)\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"value\": 2\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/druid/spell_moonfire.json",
    "content": "{\n\t\"name\": \"Moonfire\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 1 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/druid/spell_savage_roar.json",
    "content": "{\n\t\"name\": \"Savage Roar\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give your characters +2 Attack this turn.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"TemporaryAttackSpell\",\n\t\t\"target\": \"FRIENDLY_CHARACTERS\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/druid/spell_starfire.json",
    "content": "{\n\t\"name\": \"Starfire\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 5 damage. Draw a card.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 5\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/druid/spell_swipe.json",
    "content": "{\n\t\"name\": \"Swipe\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 4 damage to an enemy and 1 damage to all other enemies.\",\n\t\"targetSelection\": \"ENEMY_CHARACTERS\",\n\t\"spell\": {\n\t\t\"class\": \"SwipeSpell\",\n\t\t\"secondaryTarget\": \"ENEMY_CHARACTERS\",\n\t\t\"value\": 4,\n\t\t\"secondaryValue\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/druid/spell_wild_growth.json",
    "content": "{\n\t\"name\": \"Wild Growth\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Gain an empty Mana Crystal.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"EitherOrSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"card\": \"spell_excess_mana\"\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\t\"value\": 1\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"ManaMaxedCondition\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/hero_power_steady_shot.json",
    "content": "{\n\t\"name\": \"Steady Shot\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 2 damage to the enemy hero.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_HERO\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/hero_rexxar.json",
    "content": "{\n\t\"name\": \"Rexxar\",\n\t\"baseManaCost\": 0,\n\t\"heroPower\": \"hero_power_steady_shot\",\n\t\"type\": \"HERO\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"attributes\": {\n\t\t\"HP\": 30,\n\t\t\"MAX_HP\": 30\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/minion_houndmaster.json",
    "content": "{\n\t\"name\": \"Houndmaster\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Give a friendly Beast +2/+2 and Taunt.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"attackBonus\": 2,\n\t\t\t\t\t\"hpBonus\": 2\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"BEAST\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/minion_starving_buzzard.json",
    "content": "{\n\t\"name\": \"Starving Buzzard\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Whenever you summon a Beast, draw a card.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionSummonedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"race\": \"BEAST\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/minion_timber_wolf.json",
    "content": "{\n\t\"name\": \"Timber Wolf\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Your other Beasts have +1 Attack.\",\n\t\"aura\": {\n\t\t\"class\": \"BuffAura\",\n\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 1,\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"BEAST\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/minion_tundra_rhino.json",
    "content": "{\n\t\"name\": \"Tundra Rhino\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Your Beasts have Charge.\",\n\t\"aura\": {\n\t\t\"class\": \"AttributeAura\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"attribute\": \"CHARGE\",\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"BEAST\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/spell_animal_companion.json",
    "content": "{\n\t\"name\": \"Animal Companion\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Summon a random Beast Companion.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonRandomSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_huffer\",\n\t\t\t\"token_misha\",\n\t\t\t\"token_leokk\"\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/spell_arcane_shot.json",
    "content": "{\n\t\"name\": \"Arcane Shot\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 2 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/spell_hunters_mark.json",
    "content": "{\n\t\"name\": \"Hunter's Mark\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Change a minion's Health to 1.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"SetHpSpell\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/spell_kill_command.json",
    "content": "{\n\t\"name\": \"Kill Command\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 3 damage. If you have a Beast, deal 5 damage instead.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": {\n\t\t\t\"class\": \"ConditionalValueProvider\",\n\t\t\t\"ifTrue\": 5,\n\t\t\t\"ifFalse\": 3,\n\t\t\t\"condition\": {\n\t\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\t\"race\": \"BEAST\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/spell_multi-shot.json",
    "content": "{\n\t\"name\": \"Multi-Shot\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 3 damage to two random enemy minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MultiTargetSpell\",\n\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\"value\": 2,\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 3\n\t\t}\n\t},\n\t\"condition\": {\n\t\t\"class\": \"MinionCountCondition\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"operation\": \"GREATER\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/spell_tracking.json",
    "content": "{\n\t\"name\": \"Tracking\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Look at the top three cards of your deck. Draw one and discard the others.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DiscoverDrawSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/token_huffer.json",
    "content": "{\n\t\"name\": \"Huffer\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Charge\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/token_leokk.json",
    "content": "{\n\t\"name\": \"Leokk\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Your other minions have +1 Attack.\",\n\t\"aura\": {\n\t\t\"class\": \"BuffAura\",\n\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 1\n\t},\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/hunter/token_misha.json",
    "content": "{\n\t\"name\": \"Misha\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/mage/hero_jaina.json",
    "content": "{\n\t\"name\": \"Jaina Proudmoore\",\n\t\"baseManaCost\": 0,\n\t\"heroPower\": \"hero_power_fireblast\",\n\t\"type\": \"HERO\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"attributes\": {\n\t\t\"HP\": 30,\n\t\t\"MAX_HP\": 30\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/mage/hero_power_fireblast.json",
    "content": "{\n\t\"name\": \"Fireblast\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 1 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/mage/minion_water_elemental.json",
    "content": "{\n\t\"name\": \"Water Elemental\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Freeze any character damaged by this minion.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageCausedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\"attribute\": \"FROZEN\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/mage/spell_arcane_explosion.json",
    "content": "{\n\t\"name\": \"Arcane Explosion\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 1 damage to all enemy minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/mage/spell_arcane_intellect.json",
    "content": "{\n\t\"name\": \"Arcane Intellect\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Draw 2 cards.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/mage/spell_arcane_missiles.json",
    "content": "{\n\t\"name\": \"Arcane Missiles\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 3 damage randomly split among all enemies.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MissilesSpell\",\n\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\"value\": 1,\n\t\t\"howMany\": 3\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/mage/spell_fireball.json",
    "content": "{\n\t\"name\": \"Fireball\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 6 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 6\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/mage/spell_flamestrike.json",
    "content": "{\n\t\"name\": \"Flamestrike\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 4 damage to all enemy minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\"value\": 4\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/mage/spell_frost_nova.json",
    "content": "{\n\t\"name\": \"Frost Nova\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Freeze all enemy minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\"attribute\": \"FROZEN\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/mage/spell_frostbolt.json",
    "content": "{\n\t\"name\": \"Frostbolt\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 3 damage to a character and Freeze it.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 3\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"FROZEN\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/mage/spell_mirror_image.json",
    "content": "{\n\t\"name\": \"Mirror Image\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Summon two 0/2 minions with Taunt.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_mirror_image\",\n\t\t\t\"token_mirror_image\"\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/mage/spell_polymorph.json",
    "content": "{\n\t\"name\": \"Polymorph\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Transform a minion into a 1/1 Sheep.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"TransformMinionSpell\",\n\t\t\"card\": \"token_sheep\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/mage/token_mirror_image.json",
    "content": "{\n\t\"name\": \"Mirror Image\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/mage/token_sheep.json",
    "content": "{\n\t\"name\": \"Sheep\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_acidic_swamp_ooze.json",
    "content": "{\n\t\"name\": \"Acidic Swamp Ooze\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Destroy your opponent's weapon.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"target\": \"ENEMY_WEAPON\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_archmage.json",
    "content": "{\n\t\"name\": \"Archmage\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Spell Damage +1\",\n\t\"attributes\": {\n\t\t\"SPELL_DAMAGE\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_bloodfen_raptor.json",
    "content": "{\n\t\"name\": \"Bloodfen Raptor\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_bluegill_warrior.json",
    "content": "{\n\t\"name\": \"Bluegill Warrior\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Charge\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_booty_bay_bodyguard.json",
    "content": "{\n\t\"name\": \"Booty Bay Bodyguard\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_boulderfist_ogre.json",
    "content": "{\n\t\"name\": \"Boulderfist Ogre\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_chillwind_yeti.json",
    "content": "{\n\t\"name\": \"Chillwind Yeti\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_core_hound.json",
    "content": "{\n\t\"name\": \"Core Hound\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 9,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_dalaran_mage.json",
    "content": "{\n\t\"name\": \"Dalaran Mage\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Spell Damage +1\",\n\t\"attributes\": {\n\t\t\"SPELL_DAMAGE\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_darkscale_healer.json",
    "content": "{\n\t\"name\": \"Darkscale Healer\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Restore 2 Health to all friendly characters.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_CHARACTERS\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_dragonling_mechanic.json",
    "content": "{\n\t\"name\": \"Dragonling Mechanic\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Summon a 2/1 Mechanical Dragonling.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_mechanical_dragonling\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_elven_archer.json",
    "content": "{\n\t\"name\": \"Elven Archer\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Deal 1 damage.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_frostwolf_grunt.json",
    "content": "{\n\t\"name\": \"Frostwolf Grunt\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_frostwolf_warlord.json",
    "content": "{\n\t\"name\": \"Frostwolf Warlord\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Gain +1/+1 for each other friendly minion on the battlefield.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"EntityCounter\",\n\t\t\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_gnomish_inventor.json",
    "content": "{\n\t\"name\": \"Gnomish Inventor\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Draw a card.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_goldshire_footman.json",
    "content": "{\n\t\"name\": \"Goldshire Footman\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_grimscale_oracle.json",
    "content": "{\n\t\"name\": \"Grimscale Oracle\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Your other Murlocs have +1 Attack.\",\n\t\"aura\": {\n\t\t\"class\": \"BuffAura\",\n\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 1,\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"MURLOC\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_gurubashi_berserker.json",
    "content": "{\n\t\"name\": \"Gurubashi Berserker\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Whenever this minion takes damage, gain +3 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 3\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_ironforge_rifleman.json",
    "content": "{\n\t\"name\": \"Ironforge Rifleman\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Deal 1 damage.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_ironfur_grizzly.json",
    "content": "{\n\t\"name\": \"Ironfur Grizzly\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_kobold_geomancer.json",
    "content": "{\n\t\"name\": \"Kobold Geomancer\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Spell Damage +1\",\n\t\"attributes\": {\n\t\t\"SPELL_DAMAGE\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_lord_of_the_arena.json",
    "content": "{\n\t\"name\": \"Lord of the Arena\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_magma_rager.json",
    "content": "{\n\t\"name\": \"Magma Rager\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_murloc_raider.json",
    "content": "{\n\t\"name\": \"Murloc Raider\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_murloc_tidehunter.json",
    "content": "{\n\t\"name\": \"Murloc Tidehunter\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Battlecry: Summon a 1/1 Murloc Scout.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_murloc_scout\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_nightblade.json",
    "content": "{\n\t\"name\": \"Nightblade\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Deal 3 damage to the enemy hero.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ENEMY_HERO\",\n\t\t\t\"value\": 3\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_novice_engineer.json",
    "content": "{\n\t\"name\": \"Novice Engineer\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Draw a card.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_oasis_snapjaw.json",
    "content": "{\n\t\"name\": \"Oasis Snapjaw\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_ogre_magi.json",
    "content": "{\n\t\"name\": \"Ogre Magi\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Spell Damage +1\",\n\t\"attributes\": {\n\t\t\"SPELL_DAMAGE\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_raid_leader.json",
    "content": "{\n\t\"name\": \"Raid Leader\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Your other minions have +1 Attack.\",\n\t\"aura\": {\n\t\t\"class\": \"BuffAura\",\n\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_razorfen_hunter.json",
    "content": "{\n\t\"name\": \"Razorfen Hunter\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Summon a 1/1 Boar.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_boar\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_reckless_rocketeer.json",
    "content": "{\n\t\"name\": \"Reckless Rocketeer\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Charge\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_river_crocolisk.json",
    "content": "{\n\t\"name\": \"River Crocolisk\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_senjin_shieldmasta.json",
    "content": "{\n\t\"name\": \"Sen'jin Shieldmasta\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_shattered_sun_cleric.json",
    "content": "{\n\t\"name\": \"Shattered Sun Cleric\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Give a friendly minion +1/+1\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_silverback_patriarch.json",
    "content": "{\n\t\"name\": \"Silverback Patriarch\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_stonetusk_boar.json",
    "content": "{\n\t\"name\": \"Stonetusk Boar\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Charge\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_stormpike_commando.json",
    "content": "{\n\t\"name\": \"Stormpike Commando\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Deal 2 damage.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_stormwind_champion.json",
    "content": "{\n\t\"name\": \"Stormwind Champion\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Your other minions have +1/+1.\",\n\t\"aura\": {\n\t\t\"class\": \"BuffAura\",\n\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 1,\n\t\t\"hpBonus\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_stormwind_knight.json",
    "content": "{\n\t\"name\": \"Stormwind Knight\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Charge\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_voodoo_doctor.json",
    "content": "{\n\t\"name\": \"Voodoo Doctor\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Restore 2 Health.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_war_golem.json",
    "content": "{\n\t\"name\": \"War Golem\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/minion_wolfrider.json",
    "content": "{\n\t\"name\": \"Wolfrider\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Charge\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/spell_the_coin.json",
    "content": "{\n\t\"name\": \"The Coin\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Gain 1 Mana Crystals this turn only.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"GainManaSpell\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/token_boar.json",
    "content": "{\n\t\"name\": \"Boar\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/token_mechanical_dragonling.json",
    "content": "{\n\t\"name\": \"Mechanical Dragonling\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/neutral/token_murloc_scout.json",
    "content": "{\n\t\"name\": \"Murloc Scout\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/paladin/hero_power_reinforce.json",
    "content": "{\n\t\"name\": \"Reinforce\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Summon a 1/1 Silver Hand Recruit.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_silver_hand_recruit\"\n\t},\n\t\"condition\": {\n\t\t\"class\": \"MinionCountCondition\",\n\t\t\"operation\": \"LESS\",\n\t\t\"value\": 7\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/paladin/hero_uther.json",
    "content": "{\n\t\"name\": \"Uther Lightbringer\",\n\t\"baseManaCost\": 0,\n\t\"heroPower\": \"hero_power_reinforce\",\n\t\"type\": \"HERO\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"attributes\": {\n\t\t\"HP\": 30,\n\t\t\"MAX_HP\": 30\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/paladin/minion_guardian_of_kings.json",
    "content": "{\n\t\"name\": \"Guardian of Kings\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Restore 6 Health to your hero.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": 6\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/paladin/spell_blessing_of_kings.json",
    "content": "{\n\t\"name\": \"Blessing of Kings\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a minion +4/+4. (+4 Attack/+4 Health)\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"attackBonus\": 4,\n\t\t\"hpBonus\": 4\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/paladin/spell_blessing_of_might.json",
    "content": "{\n\t\"name\": \"Blessing of Might\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a minion +3 Attack.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"attackBonus\": 3\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/paladin/spell_consecration.json",
    "content": "{\n\t\"name\": \"Consecration\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 2 damage to all enemies.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/paladin/spell_hammer_of_wrath.json",
    "content": "{\n\t\"name\": \"Hammer of Wrath\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 3 damage. Draw a card.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 3\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/paladin/spell_hand_of_protection.json",
    "content": "{\n\t\"name\": \"Hand of Protection\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a minion Divine Shield.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"attribute\": \"DIVINE_SHIELD\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/paladin/spell_holy_light.json",
    "content": "{\n\t\"name\": \"Holy Light\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Restore 6 Health.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"HealSpell\",\n\t\t\"value\": 6\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/paladin/spell_humility.json",
    "content": "{\n\t\"name\": \"Humility\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Change a minion's Attack to 1.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"SetAttackSpell\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/paladin/token_silver_hand_recruit.json",
    "content": "{\n\t\"name\": \"Silver Hand Recruit\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/paladin/weapon_lights_justice.json",
    "content": "{\n\t\"name\": \"Light's Justice\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 1,\n\t\"durability\": 4,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/paladin/weapon_truesilver_champion.json",
    "content": "{\n\t\"name\": \"Truesilver Champion\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 4,\n\t\"durability\": 2,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Whenever your hero attacks, restore 2 Health to it.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\t\"actionType\": \"PHYSICAL_ATTACK\",\n\t\t\t\"sourcePlayer\": \"SELF\",\n\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\"sourceEntityType\": \"HERO\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/priest/hero_anduin.json",
    "content": "{\n\t\"name\": \"Anduin Wrynn\",\n\t\"baseManaCost\": 0,\n\t\"heroPower\": \"hero_power_lesser_heal\",\n\t\"type\": \"HERO\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"attributes\": {\n\t\t\"HP\": 30,\n\t\t\"MAX_HP\": 30\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/priest/hero_power_lesser_heal.json",
    "content": "{\n\t\"name\": \"Lesser Heal\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Restore 2 Health.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"HealSpell\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/priest/minion_northshire_cleric.json",
    "content": "{\n\t\"name\": \"Northshire Cleric\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Whenever a minion is healed, draw a card.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"HealingTrigger\",\n\t\t\t\"targetEntityType\": \"MINION\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/priest/spell_divine_spirit.json",
    "content": "{\n\t\"name\": \"Divine Spirit\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Double a minion's Health.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"hpBonus\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"attribute\": \"HP\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/priest/spell_holy_nova.json",
    "content": "{\n\t\"name\": \"Holy Nova\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 2 damage to all enemies. Restore 2 Health to all friendly characters.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\t\"value\": 2\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_CHARACTERS\",\n\t\t\t\t\"value\": 2\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/priest/spell_holy_smite.json",
    "content": "{\n\t\"name\": \"Holy Smite\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 2 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/priest/spell_mind_blast.json",
    "content": "{\n\t\"name\": \"Mind Blast\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 5 damage to the enemy hero.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_HERO\",\n\t\t\"value\": 5\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/priest/spell_mind_control.json",
    "content": "{\n\t\"name\": \"Mind Control\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Take control of an enemy minion.\",\n\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MindControlSpell\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/priest/spell_mind_vision.json",
    "content": "{\n\t\"name\": \"Mind Vision\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Put a copy of a random card in your opponent's hand into your hand.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"CopyCardSpell\",\n\t\t\"value\": 1,\n\t\t\"cardLocation\": \"HAND\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/priest/spell_power_word_shield.json",
    "content": "{\n\t\"name\": \"Power Word: Shield\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a minion +2 Health. Draw a card.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"hpBonus\": 2\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/priest/spell_shadow_word_death.json",
    "content": "{\n\t\"name\": \"Shadow Word: Death\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Destroy a minion with an Attack of 5 or more.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DestroySpell\",\n\t\t\"filter\": {\n\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\"value\": 5\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/priest/spell_shadow_word_pain.json",
    "content": "{\n\t\"name\": \"Shadow Word: Pain\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Destroy a minion with 3 or less Attack.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DestroySpell\",\n\t\t\"filter\": {\n\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\"value\": 3\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/rogue/hero_power_dagger_mastery.json",
    "content": "{\n\t\"name\": \"Dagger Mastery\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Equip a 1/2 Dagger.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ConditionalSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"EquipWeaponSpell\",\n\t\t\t\"card\": \"weapon_wicked_knife\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HasWeaponCondition\",\n\t\t\t\"cardId\": \"weapon_poisoned_blade\",\n\t\t\t\"invert\": \"true\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/rogue/hero_valeera.json",
    "content": "{\n\t\"name\": \"Valeera Sanguinar\",\n\t\"baseManaCost\": 0,\n\t\"heroPower\": \"hero_power_dagger_mastery\",\n\t\"type\": \"HERO\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"attributes\": {\n\t\t\"HP\": 30,\n\t\t\"MAX_HP\": 30\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/rogue/spell_assassinate.json",
    "content": "{\n\t\"name\": \"Assassinate\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Destroy an enemy minion.\",\n\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DestroySpell\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/rogue/spell_backstab.json",
    "content": "{\n\t\"name\": \"Backstab\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 2 damage to an undamaged minion.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 2,\n\t\t\"filter\": {\n\t\t\t\"class\": \"DamagedFilter\",\n\t\t\t\"invert\": true\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/rogue/spell_deadly_poison.json",
    "content": "{\n\t\"name\": \"Deadly Poison\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give your weapon +2 Attack.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffWeaponSpell\",\n\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\"attackBonus\": 2\n\t},\n\t\"condition\": {\n\t\t\"class\": \"HasWeaponCondition\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/rogue/spell_fan_of_knives.json",
    "content": "{\n\t\"name\": \"Fan of Knives\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 1 damage to all enemy minions. Draw a card.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\"value\": 1\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/rogue/spell_sap.json",
    "content": "{\n\t\"name\": \"Sap\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Return an enemy minion to your opponent's hand.\",\n\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"ReturnMinionToHandSpell\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/rogue/spell_shiv.json",
    "content": "{\n\t\"name\": \"Shiv\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 1 damage. Draw a card.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/rogue/spell_sinister_strike.json",
    "content": "{\n\t\"name\": \"Sinister Strike\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 3 damage to the enemy hero.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_HERO\",\n\t\t\"value\": 3\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/rogue/spell_sprint.json",
    "content": "{\n\t\"name\": \"Sprint\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Draw 4 cards.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"value\": 4\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/rogue/spell_vanish.json",
    "content": "{\n\t\"name\": \"Vanish\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Return all minions to their owner's hand.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ReturnMinionToHandSpell\",\n\t\t\"target\": \"ALL_MINIONS\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/rogue/weapon_assassins_blade.json",
    "content": "{\n\t\"name\": \"Assassin's Blade\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 3,\n\t\"durability\": 4,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/rogue/weapon_wicked_knife.json",
    "content": "{\n\t\"name\": \"Wicked Knife\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 1,\n\t\"durability\": 2,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/hero_power_totemic_call.json",
    "content": "{\n\t\"name\": \"Totemic Call\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Summon a random Totem.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonRandomNotOnBoardSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_searing_totem\",\n\t\t\t\"token_healing_totem\",\n\t\t\t\"token_wrath_of_air_totem\",\n\t\t\t\"token_stoneclaw_totem\"\n\t\t]\n\t},\n\t\"condition\": {\n\t\t\"class\": \"AndCondition\",\n\t\t\"conditions\": [\n\t\t\t{\n\t\t\t\t\"class\": \"AndCondition\",\n\t\t\t\t\"conditions\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\t\"cardId\": \"token_searing_totem\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\t\"cardId\": \"token_healing_totem\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\t\"cardId\": \"token_wrath_of_air_totem\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\t\"cardId\": \"token_stoneclaw_totem\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\t\"invert\": true\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\t\"operation\": \"LESS\",\n\t\t\t\t\"value\": 7\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/hero_thrall.json",
    "content": "{\n\t\"name\": \"Thrall\",\n\t\"baseManaCost\": 0,\n\t\"heroPower\": \"hero_power_totemic_call\",\n\t\"type\": \"HERO\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"attributes\": {\n\t\t\"HP\": 30,\n\t\t\"MAX_HP\": 30\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/minion_fire_elemental.json",
    "content": "{\n\t\"name\": \"Fire Elemental\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Deal 3 damage.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 3\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/minion_flametongue_totem.json",
    "content": "{\n\t\"name\": \"Flametongue Totem\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"TOTEM\",\n\t\"description\": \"Adjacent minions have +2 Attack.\",\n\t\"aura\": {\n\t\t\"class\": \"BuffAura\",\n\t\t\"target\": \"ADJACENT_MINIONS\",\n\t\t\"attackBonus\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/minion_windspeaker.json",
    "content": "{\n\t\"name\": \"Windspeaker\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Battlecry: Give a friendly minion Windfury.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"attribute\": \"WINDFURY\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/spell_ancestral_healing.json",
    "content": "{\n\t\"name\": \"Ancestral Healing\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Restore a minion to full Health and give it Taunt.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"attribute\": \"MAX_HP\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/spell_bloodlust.json",
    "content": "{\n\t\"name\": \"Bloodlust\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give your minions +3 Attack this turn.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"TemporaryAttackSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"value\": 3\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/spell_frost_shock.json",
    "content": "{\n\t\"name\": \"Frost Shock\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 1 damage to an enemy character and Freeze it.\",\n\t\"targetSelection\": \"ENEMY_CHARACTERS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"FROZEN\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/spell_hex.json",
    "content": "{\n\t\"name\": \"Hex\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Transform a minion into a 0/1 Frog with Taunt.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"TransformMinionSpell\",\n\t\t\"card\": \"token_frog\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/spell_rockbiter_weapon.json",
    "content": "{\n\t\"name\": \"Rockbiter Weapon\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a friendly character +3 Attack this turn.\",\n\t\"targetSelection\": \"FRIENDLY_CHARACTERS\",\n\t\"spell\": {\n\t\t\"class\": \"TemporaryAttackSpell\",\n\t\t\"value\": 3\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/spell_totemic_might.json",
    "content": "{\n\t\"name\": \"Totemic Might\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give your Totems +2 Health.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"hpBonus\": 2,\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"TOTEM\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/spell_windfury.json",
    "content": "{\n\t\"name\": \"Windfury\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a minion Windfury.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"attribute\": \"WINDFURY\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/token_frog.json",
    "content": "{\n\t\"name\": \"Frog\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/token_healing_totem.json",
    "content": "{\n\t\"name\": \"Healing Totem\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"TOTEM\",\n\t\"description\": \"At the end of your turn, restore 1 Health to all friendly minions.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/token_searing_totem.json",
    "content": "{\n\t\"name\": \"Searing Totem\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"TOTEM\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/token_stoneclaw_totem.json",
    "content": "{\n\t\"name\": \"Stoneclaw Totem\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"TOTEM\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/shaman/token_wrath_of_air_totem.json",
    "content": "{\n\t\"name\": \"Wrath of Air Totem\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"TOTEM\",\n\t\"description\": \"Spell Damage +1\",\n\t\"attributes\": {\n\t\t\"SPELL_DAMAGE\": 1\n\t},\n\t\"collectible\": false,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warlock/hero_guldan.json",
    "content": "{\n\t\"name\": \"Gul'dan\",\n\t\"baseManaCost\": 0,\n\t\"heroPower\": \"hero_power_life_tap\",\n\t\"type\": \"HERO\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"attributes\": {\n\t\t\"HP\": 30,\n\t\t\"MAX_HP\": 30\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warlock/hero_power_life_tap.json",
    "content": "{\n\t\"name\": \"Life Tap\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Draw a card and take 2 damage.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"value\": 2\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warlock/minion_dread_infernal.json",
    "content": "{\n\t\"name\": \"Dread Infernal\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Battlecry: Deal 1 damage to ALL other characters.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ALL_OTHER_CHARACTERS\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warlock/minion_succubus.json",
    "content": "{\n\t\"name\": \"Succubus\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Battlecry: Discard a random card.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscardSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warlock/minion_voidwalker.json",
    "content": "{\n\t\"name\": \"Voidwalker\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warlock/spell_corruption.json",
    "content": "{\n\t\"name\": \"Corruption\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Choose an enemy minion. At the start of your turn, destroy it.\",\n\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"AddSpellTriggerSpell\",\n\t\t\"trigger\": {\n\t\t\t\"eventTrigger\": {\n\t\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t},\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\t\"target\": \"SELF\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warlock/spell_drain_life.json",
    "content": "{\n\t\"name\": \"Drain Life\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 2 damage. Restore 2 Health to your hero.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 2\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"value\": 2\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warlock/spell_hellfire.json",
    "content": "{\n\t\"name\": \"Hellfire\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 3 damage to ALL characters.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_CHARACTERS\",\n\t\t\"value\": 3\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warlock/spell_mortal_coil.json",
    "content": "{\n\t\"name\": \"Mortal Coil\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 1 damage to a minion. If that kills it, draw a card.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"ConditionalEffectSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 1\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"value\": 1\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"IsDeadCondition\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warlock/spell_sacrificial_pact.json",
    "content": "{\n\t\"name\": \"Sacrificial Pact\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Destroy a Demon. Restore 5 Health to your hero.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DestroySpell\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"value\": 5\n\t\t\t}\n\t\t],\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"DEMON\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warlock/spell_shadow_bolt.json",
    "content": "{\n\t\"name\": \"Shadow Bolt\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 4 damage to a minion.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 4\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warlock/spell_soulfire.json",
    "content": "{\n\t\"name\": \"Soulfire\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 4 damage. Discard a random card.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 4\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DiscardSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warrior/hero_garrosh.json",
    "content": "{\n\t\"name\": \"Garrosh Hellscream\",\n\t\"baseManaCost\": 0,\n\t\"heroPower\": \"hero_power_armor_up\",\n\t\"type\": \"HERO\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"attributes\": {\n\t\t\"HP\": 30,\n\t\t\"MAX_HP\": 30\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warrior/hero_power_armor_up.json",
    "content": "{\n\t\"name\": \"Armor Up!\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Gain 2 Armor.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffHeroSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"armorBonus\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warrior/minion_korkron_elite.json",
    "content": "{\n\t\"name\": \"Kor'kron Elite\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Charge\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warrior/minion_warsong_commander.json",
    "content": "{\n\t\"name\": \"Warsong Commander\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Your Charge minions have +1 Attack.\",\n\t\"aura\": {\n\t\t\"class\": \"BuffAura\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 1,\n\t\t\"filter\": {\n\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\"attribute\": \"CHARGE\",\n\t\t\t\"operation\": \"HAS\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warrior/spell_charge.json",
    "content": "{\n\t\"name\": \"Charge\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a friendly minion Charge. It can't attack heroes this turn.\",\n\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"CHARGE\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"CANNOT_ATTACK_HERO_ON_SUMMON\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warrior/spell_cleave.json",
    "content": "{\n\t\"name\": \"Cleave\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 2 damage to two random enemy minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MultiTargetSpell\",\n\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\"value\": 2,\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"condition\": {\n\t\t\"class\": \"MinionCountCondition\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"operation\": \"GREATER\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warrior/spell_execute.json",
    "content": "{\n\t\"name\": \"Execute\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Destroy a damaged enemy minion.\",\n\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DestroySpell\",\n\t\t\"filter\": {\n\t\t\t\"class\": \"DamagedFilter\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warrior/spell_heroic_strike.json",
    "content": "{\n\t\"name\": \"Heroic Strike\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give your hero +4 Attack this turn.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffHeroSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"attackBonus\": 4\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warrior/spell_shield_block.json",
    "content": "{\n\t\"name\": \"Shield Block\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Gain 5 Armor. Draw a card.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"BuffHeroSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"armorBonus\": 5\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warrior/spell_whirlwind.json",
    "content": "{\n\t\"name\": \"Whirlwind\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 1 damage to ALL minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warrior/weapon_arcanite_reaper.json",
    "content": "{\n\t\"name\": \"Arcanite Reaper\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 5,\n\t\"durability\": 2,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/basic/warrior/weapon_fiery_war_axe.json",
    "content": "{\n\t\"name\": \"Fiery War Axe\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 3,\n\t\"durability\": 2,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"BASIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/hero_power_die_insect.json",
    "content": "{\n\t\"name\": \"DIE, INSECT!\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 8 damage to a random enemy.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\"value\": 8,\n\t\t\"randomTarget\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/hero_ragnaros.json",
    "content": "{\n\t\"name\": \"Ragnaros the Firelord\",\n\t\"baseManaCost\": 8,\n\t\"heroPower\": \"hero_power_die_insect\",\n\t\"type\": \"HERO\",\n\t\"heroClass\": \"BOSS\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"attributes\": {\n\t\t\"HP\": 8,\n\t\t\"MAX_HP\": 8\n\t},\n\t\"collectible\": false,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_axe_flinger.json",
    "content": "{\n\t\"name\": \"Axe Flinger\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever this minion takes damage, deal 2 damage to the enemy hero.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ENEMY_HERO\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_blackwing_corruptor.json",
    "content": "{\n\t\"name\": \"Blackwing Corruptor\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: If you're holding a Dragon, deal 3 damage.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 3\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HoldsCardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_blackwing_technician.json",
    "content": "{\n\t\"name\": \"Blackwing Technician\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: If you're holding a Dragon, gain +1/+1.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HoldsCardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_chromaggus.json",
    "content": "{\n\t\"name\": \"Chromaggus\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Whenever you draw a card, put another copy into your hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardDrawnTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"CopyCardSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_core_rager.json",
    "content": "{\n\t\"name\": \"Core Rager\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: If your hand is empty, gain +3/+3.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 3,\n\t\t\t\"hpBonus\": 3\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"CardCountCondition\",\n\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\"value\": 0\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_dark_iron_skulker.json",
    "content": "{\n\t\"name\": \"Dark Iron Skulker\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Deal 2 damage to all undamaged enemy minions.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\"value\": 2,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"DamagedFilter\",\n\t\t\t\t\"invert\": true\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_dragon_consort.json",
    "content": "{\n\t\"name\": \"Dragon Consort\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Battlecry: The next Dragon you play costs (2) less.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"cardType\": \"MINION\",\n\t\t\t\t\"race\": \"DRAGON\",\n\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\"value\": \"2\",\n\t\t\t\t\"expirationTrigger\": {\n\t\t\t\t\t\"class\": \"CardPlayedTrigger\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_dragon_egg.json",
    "content": "{\n\t\"name\": \"Dragon Egg\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever this minion takes damage, summon a 2/1 Whelp.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_black_whelp\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_dragonkin_sorcerer.json",
    "content": "{\n\t\"name\": \"Dragonkin Sorcerer\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Whenever you target this minion with a spell, gain +1/+1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\t\"actionType\": \"SPELL\",\n\t\t\t\"sourcePlayer\": \"SELF\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_drakonid_crusher.json",
    "content": "{\n\t\"name\": \"Drakonid Crusher\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Battlecry: If your opponent has 15 or less Health, gain +3/+3.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 3,\n\t\t\t\"hpBonus\": 3\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"AttributeCondition\",\n\t\t\t\"target\": \"ENEMY_HERO\",\n\t\t\t\"attribute\": \"HP\",\n\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\"value\": 15\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_druid_of_the_flame.json",
    "content": "{\n\t\"name\": \"Druid of the Flame\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Choose One - Transform into a 5/2 minion; or a 2/5 minion.\",\n\t\"options\": [\n\t\t{\n\t\t\t\"description\": \"5/2\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"card\": \"token_flame_lion_form\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"description\": \"2/5\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"card\": \"token_flame_bird_form\"\n\t\t\t}\n\t\t}\n\t],\n\t\"bothOptions\": {\n\t\t\"description\": \"5/5\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"card\": \"token_flame_lionbird_form\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_emperor_thaurissan.json",
    "content": "{\n\t\"name\": \"Emperor Thaurissan\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"At the end of your turn, reduce the Cost of cards in your hand by (1).\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_fireguard_destroyer.json",
    "content": "{\n\t\"name\": \"Fireguard Destroyer\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Gain 1-4 Attack. Overload: (1)\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": {\n\t\t\t\t\"class\": \"RandomValueProvider\",\n\t\t\t\t\"min\": 1,\n\t\t\t\t\"max\": 4\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"OVERLOAD\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_flamewaker.json",
    "content": "{\n\t\"name\": \"Flamewaker\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"After you cast a spell, deal 2 damage randomly split among all enemies.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterSpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\"howMany\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_grim_patron.json",
    "content": "{\n\t\"name\": \"Grim Patron\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever this minion survives damage, summon another Grim Patron.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\",\n\t\t\t\"fireCondition\": {\n\t\t\t\t\"class\": \"IsDeadCondition\",\n\t\t\t\t\"invert\": true\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"minion_grim_patron\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_hungry_dragon.json",
    "content": "{\n\t\"name\": \"Hungry Dragon\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Battlecry: Summon a random 1-Cost minion for your opponent.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\",\n\t\t\t\t\"manaCost\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_imp_gang_boss.json",
    "content": "{\n\t\"name\": \"Imp Gang Boss\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Whenever this minion takes damage, summon a 1/1 Imp.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_imp\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_majordomo_executus.json",
    "content": "{\n\t\"name\": \"Majordomo Executus\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 9,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Deathrattle: Replace your hero with Ragnaros, the Firelord.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ChangeHeroSpell\",\n\t\t\"card\": \"hero_ragnaros\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_nefarian.json",
    "content": "{\n\t\"name\": \"Nefarian\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Battlecry: Add 2 random spells to your hand (from your opponent's class).\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"value\": 2,\n\t\t\t\"card\": \"spell_tail_swipe\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\t\"heroClass\": \"OPPONENT\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_rend_blackhand.json",
    "content": "{\n\t\"name\": \"Rend Blackhand\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: If you're holding a Dragon, destroy a Legendary minion.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"rarity\": \"LEGENDARY\"\n\t\t\t}\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HoldsCardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_twilight_whelp.json",
    "content": "{\n\t\"name\": \"Twilight Whelp\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Battlecry: If you're holding a Dragon, gain +2 Health.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"hpBonus\": 2\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HoldsCardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_volcanic_drake.json",
    "content": "{\n\t\"name\": \"Volcanic Drake\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Costs (1) less for each minion that died this turn.\",\n\t\"manaCostModifier\": {\n\t\t\"class\": \"DeadMinionsThisTurn\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/minion_volcanic_lumberer.json",
    "content": "{\n\t\"name\": \"Volcanic Lumberer\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Taunt. Costs (1) less for each minion that died this turn.\",\n\t\"manaCostModifier\": {\n\t\t\"class\": \"DeadMinionsThisTurn\"\n\t},\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/spell_demonwrath.json",
    "content": "{\n\t\"name\": \"Demonwrath\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 2 damage to all non-Demon minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": 2,\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"DEMON\",\n\t\t\t\"invert\": true\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/spell_dragons_breath.json",
    "content": "{\n\t\"name\": \"Dragon's Breath\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 4 damage. Costs (1) less for each minion that died this turn.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 4\n\t},\n\t\"manaCostModifier\": {\n\t\t\"class\": \"DeadMinionsThisTurn\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/spell_gang_up.json",
    "content": "{\n\t\"name\": \"Gang Up\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Choose a minion. Shuffle 3 copies of it into your deck.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"ShuffleToDeckSpell\",\n\t\t\"howMany\": 3\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/spell_lava_shock.json",
    "content": "{\n\t\"name\": \"Lava Shock\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 2 damage. Unlock your Overloaded Mana Crystals.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 2\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"ClearOverloadSpell\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/spell_quick_shot.json",
    "content": "{\n\t\"name\": \"Quick Shot\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 3 damage. If your hand is empty, draw a card.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"ConditionalEffectSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 3\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"DrawCardSpell\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"CardCountCondition\",\n\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\"value\": 0\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/spell_resurrect.json",
    "content": "{\n\t\"name\": \"Resurrect\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Summon a random friendly minion that died this game.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ResurrectSpell\"\n\t},\n\t\"condition\": {\n\t\t\"class\": \"GraveyardCountCondition\",\n\t\t\"operation\": \"GREATER\",\n\t\t\"value\": 0\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/spell_revenge.json",
    "content": "{\n\t\"name\": \"Revenge\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 1 damage to all minions. If you have 12 or less Health, deal 3 damage instead.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": {\n\t\t\t\"class\": \"ConditionalValueProvider\",\n\t\t\t\"ifTrue\": 3,\n\t\t\t\"ifFalse\": 1,\n\t\t\t\"condition\": {\n\t\t\t\t\"class\": \"AttributeCondition\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"attribute\": \"HP\",\n\t\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\t\"value\": 12\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/spell_solemn_vigil.json",
    "content": "{\n\t\"name\": \"Solemn Vigil\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Draw 2 cards. Costs (1) less for each minion that died this turn.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"value\": 2\n\t},\n\t\"manaCostModifier\": {\n\t\t\"class\": \"DeadMinionsThisTurn\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/spell_tail_swipe.json",
    "content": "{\n\t\"name\": \"Tail Swipe\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"BOSS\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 4 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 4\n\t},\n\t\"collectible\": false,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/token_black_whelp.json",
    "content": "{\r\n\t\"name\": \"Whelp\",\r\n\t\"baseManaCost\": 1,\r\n\t\"type\": \"MINION\",\r\n\t\"baseAttack\": 2,\r\n\t\"baseHp\": 1,\r\n\t\"heroClass\": \"ANY\",\r\n\t\"rarity\": \"FREE\",\r\n\t\"race\": \"DRAGON\",\r\n\t\"description\": \"\",\r\n\t\"collectible\": false,\r\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\r\n\t\"fileFormatVersion\": 1\r\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/token_flame_bird_form.json",
    "content": "{\n\t\"name\": \"Druid of the Flame\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/token_flame_lion_form.json",
    "content": "{\n\t\"name\": \"Druid of the Flame\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/blackrock_mountain/token_flame_lionbird_form.json",
    "content": "{\n\t\"name\": \"Druid of the Flame\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"BLACKROCK_MOUNTAIN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/minion_ancient_of_lore.json",
    "content": "{\n\t\"name\": \"Ancient of Lore\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Choose One - Draw a card; or Restore 5 Health.\",\n\t\"options\": [\n\t\t{\n\t\t\t\"description\": \"Draw a card\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"description\": \"Heal for 5\",\n\t\t\t\"targetSelection\": \"ANY\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"value\": 5\n\t\t\t}\n\t\t}\n\t],\n\t\"bothOptions\": {\n\t\t\"description\": \"Draw a card and heal for 5\",\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\t\"value\": 5\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/minion_ancient_of_war.json",
    "content": "{\n\t\"name\": \"Ancient of War\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Choose One - +5 Attack; or +5 Health and Taunt.\",\n\t\"options\": [\n\t\t{\n\t\t\t\"description\": \"+5 Attack\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"attackBonus\": 5\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"description\": \"+5 Health/Taunt\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\t\"spells\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\t\"hpBonus\": 5\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t}\n\t],\n\t\"bothOptions\": {\n\t\t\"description\": \"+5/+5 and Taunt\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"value\": 5\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/minion_cenarius.json",
    "content": "{\n\t\"name\": \"Cenarius\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Choose One - Give your other minions +2/+2; or Summon two 2/2 Treants with Taunt.\",\n\t\"options\": [\n\t\t{\n\t\t\t\"description\": \"+2/2\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\t\t\"attackBonus\": 2,\n\t\t\t\t\"hpBonus\": 2\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"description\": \"2 Treants\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\t\"spells\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\t\"card\": \"token_treant_taunt\",\n\t\t\t\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\t\"card\": \"token_treant_taunt\",\n\t\t\t\t\t\t\"boardPositionRelative\": \"LEFT\"\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t}\n\t],\n\t\"bothOptions\": {\n\t\t\"description\": \"2 Treants and +2/+2\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_treant_taunt\",\n\t\t\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_treant_taunt\",\n\t\t\t\t\t\"boardPositionRelative\": \"LEFT\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\t\t\t\"attackBonus\": 2,\n\t\t\t\t\t\"hpBonus\": 2\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/minion_druid_of_the_claw.json",
    "content": "{\n\t\"name\": \"Druid of the Claw\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Choose One - Charge; or +2 Health and Taunt.\",\n\t\"options\": [\n\t\t{\n\t\t\t\"description\": \"Charge\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"card\": \"token_cat_form\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"description\": \"+2 Health and Taunt\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"card\": \"token_bear_form\"\n\t\t\t}\n\t\t}\n\t],\n\t\"bothOptions\": {\n\t\t\"description\": \"Charge, +2 Health, and Taunt\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"card\": \"token_catbear_form\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/minion_keeper_of_the_grove.json",
    "content": "{\n\t\"name\": \"Keeper of the Grove\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Choose One - Deal 2 damage; or Silence a minion.\",\n\t\"options\": [\n\t\t{\n\t\t\t\"description\": \"2 damage\",\n\t\t\t\"targetSelection\": \"ANY\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 2\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"description\": \"Silence\",\n\t\t\t\"targetSelection\": \"MINIONS\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"SilenceSpell\"\n\t\t\t}\n\t\t}\n\t],\n\t\"bothOptions\": {\n\t\t\"description\": \"Silence and deal 2 damage\",\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ConditionalSpell\",\n\t\t\t\t\t\"spell\": {\n\t\t\t\t\t\t\"class\": \"SilenceSpell\"\n\t\t\t\t\t},\n\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\"class\": \"CardPropertyCondition\",\n\t\t\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\"value\": 2\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_bite.json",
    "content": "{\n\t\"name\": \"Bite\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Give your hero +4 Attack this turn and 4 Armor.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffHeroSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"attackBonus\": 4,\n\t\t\"armorBonus\": 4\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_force_of_nature.json",
    "content": "{\n\t\"name\": \"Force of Nature\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Summon three 2/2 Treants.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_treant\",\n\t\t\t\"token_treant\",\n\t\t\t\"token_treant\"\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_mark_of_nature.json",
    "content": "{\n\t\"name\": \"Mark of Nature\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"CHOOSE_ONE\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Choose One - Give a minion +4 Attack; or +4 Health and Taunt.\",\n\t\"targetSelection\": \"NONE\",\n\t\"options\": [\n\t\t\"spell_mark_of_nature_1\",\n\t\t\"spell_mark_of_nature_2\"\n\t],\n\t\"bothOptions\": \"spell_mark_of_nature_3\",\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_mark_of_nature_1.json",
    "content": "{\n\t\"name\": \"+4 Attack\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a minion +4 Attack\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"attackBonus\": 4\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_mark_of_nature_2.json",
    "content": "{\n\t\"name\": \"+4 Health, Taunt\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"+4 Health and Taunt\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"hpBonus\": 4\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_mark_of_nature_3.json",
    "content": "{\n\t\"name\": \"+4/+4, Taunt\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a minion +4/+4 and Taunt\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"value\": 4\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_naturalize.json",
    "content": "{\n\t\"name\": \"Naturalize\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Destroy a minion. Your opponent draws 2 cards.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DestroySpell\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\t\"value\": 2\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_nourish.json",
    "content": "{\n\t\"name\": \"Nourish\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"CHOOSE_ONE\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Choose One - Gain 2 Mana Crystals; or Draw 3 cards.\",\n\t\"targetSelection\": \"NONE\",\n\t\"options\": [\n\t\t\"spell_nourish_1\",\n\t\t\"spell_nourish_2\"\n\t],\n\t\"bothOptions\": \"spell_nourish_3\",\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_nourish_1.json",
    "content": "{\n\t\"name\": \"+2 Mana Crystals\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Gain 2 Mana Crystals\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\"fullManaCrystals\": true,\n\t\t\"value\": 2\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_nourish_2.json",
    "content": "{\n\t\"name\": \"Draw 3 cards\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Draw 3 cards\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"value\": 3\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_nourish_3.json",
    "content": "{\n\t\"name\": \"+2 Mana Crystals and draw 3 cards\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Gain 2 Mana Crystals and Draw 3 cards\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\t\t\"fullManaCrystals\": true,\n\t\t\t\t\"value\": 2\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 3\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_power_of_the_wild.json",
    "content": "{\n\t\"name\": \"Power of the Wild\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"CHOOSE_ONE\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Choose One - Give your minions +1/+1; or Summon a 3/2 Panther.\",\n\t\"targetSelection\": \"NONE\",\n\t\"options\": [\n\t\t\"spell_power_of_the_wild_1\",\n\t\t\"spell_power_of_the_wild_2\"\n\t],\n\t\"bothOptions\": \"spell_power_of_the_wild_3\",\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_power_of_the_wild_1.json",
    "content": "{\n\t\"name\": \"+1/+1\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give your minions +1/+1\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 1,\n\t\t\"hpBonus\": 1\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_power_of_the_wild_2.json",
    "content": "{\n\t\"name\": \"Summon Panther\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Summon a 3/2 Panther.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_panther\"\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_power_of_the_wild_3.json",
    "content": "{\n\t\"name\": \"Summon Panther and +1/+1\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Summon a 3/2 Panther. Give your minions +1/+1.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\"card\": \"token_panther\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\"attackBonus\": 1,\n\t\t\t\t\"hpBonus\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_savagery.json",
    "content": "{\n\t\"name\": \"Savagery\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal damage equal to your hero's Attack to a minion.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"attribute\": \"ATTACK\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_soul_of_the_forest.json",
    "content": "{\n\t\"name\": \"Soul of the Forest\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give your minions \\\"Deathrattle: Summon a 2/2 Treant.\\\"\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"AddDeathrattleSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_treant\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_starfall.json",
    "content": "{\n\t\"name\": \"Starfall\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"CHOOSE_ONE\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Choose One - Deal 5 damage to a minion; or 2 damage to all enemy minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"options\": [\n\t\t\"spell_starfall_1\",\n\t\t\"spell_starfall_2\"\n\t],\n\t\"bothOptions\": \"spell_starfall_3\",\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_starfall_1.json",
    "content": "{\n\t\"name\": \"5 damage single\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 5 damage to a minion\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 5\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_starfall_2.json",
    "content": "{\n\t\"name\": \"2 damage AoE\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 2 damage to all enemy minions\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_starfall_3.json",
    "content": "{\n\t\"name\": \"7 damage single and 2 damage AoE\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 7 damage to a minion. Deal 2 damage to all other enemy minions.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"SwipeSpell\",\n\t\t\"secondaryTarget\": \"ENEMY_MINIONS\",\n\t\t\"value\": 7,\n\t\t\"secondaryValue\": 2\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_wrath.json",
    "content": "{\n\t\"name\": \"Wrath\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"CHOOSE_ONE\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Choose One - Deal 3 damage to a minion; or 1 damage and draw a card.\",\n\t\"targetSelection\": \"NONE\",\n\t\"options\": [\n\t\t\"spell_wrath_1\",\n\t\t\"spell_wrath_2\"\n\t],\n\t\"bothOptions\": \"spell_wrath_3\",\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_wrath_1.json",
    "content": "{\n\t\"name\": \"3 damage\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 3 damage to a minion\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 3\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_wrath_2.json",
    "content": "{\n\t\"name\": \"1 damage + draw card\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 1 damage to a minion and draw a card.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": \"1\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/spell_wrath_3.json",
    "content": "{\n\t\"name\": \"4 damage and draw a card\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 3 damage to a minion\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 4\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\"value\": \"1\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/token_bear_form.json",
    "content": "{\n\t\"name\": \"Druid of the Claw (Bear)\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/token_cat_form.json",
    "content": "{\n\t\"name\": \"Druid of the Claw (Cat)\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Charge\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/token_catbear_form.json",
    "content": "{\n\t\"name\": \"Druid of the Claw (CatBear)\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/token_panther.json",
    "content": "{\n\t\"name\": \"Panther\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/token_treant.json",
    "content": "{\n\t\"name\": \"Treant\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/druid/token_treant_taunt.json",
    "content": "{\n\t\"name\": \"Treant\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/minion_king_krush.json",
    "content": "{\n\t\"name\": \"King Krush\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Charge\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/minion_savannah_highmane.json",
    "content": "{\n\t\"name\": \"Savannah Highmane\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Deathrattle: Summon two 2/2 Hyenas.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_hyena\",\n\t\t\t\"token_hyena\"\n\t\t]\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/minion_scavenging_hyena.json",
    "content": "{\n\t\"name\": \"Scavenging Hyena\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Whenever a friendly Beast dies, gain +2/+1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionDeathTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"race\": \"BEAST\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/secret_explosive_trap.json",
    "content": "{\n\t\"name\": \"Explosive Trap\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Secret: When your hero is attacked, deal 2 damage to all enemies.\",\n\t\"trigger\": {\n\t\t\"class\": \"PhysicalAttackTrigger\",\n\t\t\"sourcePlayer\": \"BOTH\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"targetEntityType\": \"HERO\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/secret_freezing_trap.json",
    "content": "{\n\t\"name\": \"Freezing Trap\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Secret: When an enemy minion attacks, return it to its owner's hand and it costs (2) more.\",\n\t\"trigger\": {\n\t\t\"class\": \"PhysicalAttackTrigger\",\n\t\t\"sourcePlayer\": \"OPPONENT\",\n\t\t\"sourceEntityType\": \"MINION\",\n\t\t\"fireCondition\": {\n\t\t\t\"class\": \"IsDeadCondition\",\n\t\t\t\"target\": \"ATTACKER\",\n\t\t\t\"invert\": true\n\t\t}\n\t},\n\t\"spell\": {\n\t\t\"class\": \"ReturnMinionToHandSpell\",\n\t\t\"target\": \"ATTACKER\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"target\": \"EVENT_CARD\",\n\t\t\t\t\"operation\": \"ADD\",\n\t\t\t\t\"value\": 2\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/secret_misdirection.json",
    "content": "{\n\t\"name\": \"Misdirection\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Secret: When a character attacks your hero, instead he attacks another random character.\",\n\t\"trigger\": {\n\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\"actionType\": \"PHYSICAL_ATTACK\",\n\t\t\"sourcePlayer\": \"BOTH\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"targetEntityType\": \"HERO\",\n\t\t\"fireCondition\": {\n\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\"operation\": \"GREATER\",\n\t\t\t\"value\": 0\n\t\t}\n\t},\n\t\"spell\": {\n\t\t\"class\": \"MisdirectSpell\",\n\t\t\"target\": \"EVENT_TARGET\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/secret_snake_trap.json",
    "content": "{\n\t\"name\": \"Snake Trap\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Secret: When one of your minions is attacked, summon three 1/1 Snakes.\",\n\t\"trigger\": {\n\t\t\"class\": \"PhysicalAttackTrigger\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"targetEntityType\": \"MINION\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_snake\",\n\t\t\t\"token_snake\",\n\t\t\t\"token_snake\"\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/secret_snipe.json",
    "content": "{\n\t\"name\": \"Snipe\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Secret: When your opponent plays a minion, deal 4 damage to it.\",\n\t\"trigger\": {\n\t\t\"class\": \"MinionPlayedTrigger\",\n\t\t\"targetPlayer\": \"OPPONENT\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"EVENT_TARGET\",\n\t\t\"value\": 4\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/spell_bestial_wrath.json",
    "content": "{\n\t\"name\": \"Bestial Wrath\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Give a friendly Beast +2 Attack and Immune this turn.\",\n\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"TemporaryAttackSpell\",\n\t\t\t\t\"value\": 2\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"IMMUNE\",\n\t\t\t\t\"revertTrigger\": {\n\t\t\t\t\t\"class\": \"TurnEndTrigger\"\n\t\t\t\t}\n\t\t\t}\n\t\t],\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"BEAST\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/spell_deadly_shot.json",
    "content": "{\n\t\"name\": \"Deadly Shot\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Destroy a random enemy minion.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DestroySpell\",\n\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\"randomTarget\": true\n\t},\n\t\"condition\": {\n\t\t\"class\": \"MinionCountCondition\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"operation\": \"GREATER\",\n\t\t\"value\": 0\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/spell_explosive_shot.json",
    "content": "{\n\t\"name\": \"Explosive Shot\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 5 damage to a minion and 2 damage to adjacent ones.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"AdjacentEffectSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 5\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/spell_flare.json",
    "content": "{\n\t\"name\": \"Flare\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"All minions lose Stealth. Destroy all enemy Secrets. Draw a card.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"RemoveAttributeSpell\",\n\t\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\t\"attribute\": \"STEALTH\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DestroySecretsSpell\",\n\t\t\t\t\"targetPlayer\": \"OPPONENT\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/spell_unleash_the_hounds.json",
    "content": "{\n\t\"name\": \"Unleash the Hounds\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"For each enemy minion, summon a 1/1 Hound with Charge.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"value\": {\n\t\t\t\"class\": \"EntityCounter\",\n\t\t\t\"target\": \"ENEMY_MINIONS\"\n\t\t},\n\t\t\"card\": \"token_hound\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/token_hound.json",
    "content": "{\n\t\"name\": \"Hound\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Charge\",\n\t\"race\": \"BEAST\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/token_hyena.json",
    "content": "{\n\t\"name\": \"Hyena\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/token_snake.json",
    "content": "{\n\t\"name\": \"Snake\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/weapon_eaglehorn_bow.json",
    "content": "{\n\t\"name\": \"Eaglehorn Bow\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 3,\n\t\"durability\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever a Secret is revealed, gain +1 Durability.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SecretRevealedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyDurabilitySpell\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/hunter/weapon_gladiators_longbow.json",
    "content": "{\n\t\"name\": \"Gladiator's Longbow\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 5,\n\t\"durability\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Your hero is Immune while attacking.\",\n\t\"onEquip\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"attribute\": \"IMMUNE_WHILE_ATTACKING\"\n\t},\n\t\"onUnequip\": {\n\t\t\"class\": \"RemoveAttributeSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"attribute\": \"IMMUNE_WHILE_ATTACKING\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/minion_archmage_antonidas.json",
    "content": "{\n\t\"name\": \"Archmage Antonidas\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Whenever you cast a spell, put a 'Fireball' spell into your hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"card\": \"spell_fireball\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/minion_ethereal_arcanist.json",
    "content": "{\n\t\"name\": \"Ethereal Arcanist\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"If you control a Secret at the end of your turn, gain +2/+2.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"ControlsSecretCondition\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"hpBonus\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/minion_kirin_tor_mage.json",
    "content": "{\n\t\"name\": \"Kirin Tor Mage\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: The next Secret you play this turn costs (0).\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"OneTurnCostModifier\",\n\t\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\t\"operation\": \"SET\",\n\t\t\t\t\"value\": 0,\n\t\t\t\t\"expirationTrigger\": {\n\t\t\t\t\t\"class\": \"SecretPlayedTrigger\"\n\t\t\t\t},\n\t\t\t\t\"requiredAttribute\": \"SECRET\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/minion_mana_wyrm.json",
    "content": "{\n\t\"name\": \"Mana Wyrm\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever you cast a spell, gain +1 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/minion_sorcerers_apprentice.json",
    "content": "{\n\t\"name\": \"Sorcerer's Apprentice\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Your spells cost (1) less.\",\n\t\"cardCostModifier\": {\n\t\t\"class\": \"CardCostModifier\",\n\t\t\"cardType\": \"SPELL\",\n\t\t\"value\": -1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/secret_counterspell.json",
    "content": "{\n\t\"name\": \"Counterspell\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Secret: When your opponent casts a spell, Counter it.\",\n\t\"trigger\": {\n\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\"sourcePlayer\": \"OPPONENT\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"target\": \"EVENT_TARGET\",\n\t\t\"attribute\": \"COUNTERED\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/secret_ice_barrier.json",
    "content": "{\n\t\"name\": \"Ice Barrier\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Secret: When your hero is attacked, gain 8 Armor.\",\n\t\"trigger\": {\n\t\t\"class\": \"PhysicalAttackTrigger\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"targetEntityType\": \"HERO\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"BuffHeroSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"armorBonus\": 8\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/secret_ice_block.json",
    "content": "{\n\t\"name\": \"Ice Block\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Secret: When your hero takes fatal damage, prevent it and become Immune this turn.\",\n\t\"trigger\": {\n\t\t\"class\": \"FatalDamageTrigger\",\n\t\t\"sourcePlayer\": \"BOTH\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"targetEntityType\": \"HERO\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"ModifyDamageSpell\",\n\t\t\t\t\"operation\": \"SET\",\n\t\t\t\t\"value\": 0\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"attribute\": \"IMMUNE\",\n\t\t\t\t\"revertTrigger\": {\n\t\t\t\t\t\"class\": \"TurnEndTrigger\"\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/secret_mirror_entity.json",
    "content": "{\n\t\"name\": \"Mirror Entity\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Secret: When your opponent plays a minion, summon a copy of it.\",\n\t\"trigger\": {\n\t\t\"class\": \"MinionPlayedTrigger\",\n\t\t\"targetPlayer\": \"OPPONENT\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"CloneMinionSpell\",\n\t\t\"target\": \"EVENT_TARGET\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/secret_spellbender.json",
    "content": "{\n\t\"name\": \"Spellbender\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Secret: When an enemy casts a spell on a minion, summon a 1/3 as the new target.\",\n\t\"trigger\": {\n\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\"actionType\": \"SPELL\",\n\t\t\"sourcePlayer\": \"OPPONENT\",\n\t\t\"targetPlayer\": \"BOTH\",\n\t\t\"targetEntityType\": \"MINION\",\n\t\t\"queueCondition\": {\n\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\"operation\": \"LESS\",\n\t\t\t\"value\": 7\n\t\t},\n\t\t\"fireCondition\": {\n\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\"operation\": \"LESS\",\n\t\t\t\"value\": 7\n\t\t}\n\t},\n\t\"spell\": {\n\t\t\"class\": \"SummonNewAttackTargetSpell\",\n\t\t\"card\": \"token_spellbender\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/secret_vaporize.json",
    "content": "{\n\t\"name\": \"Vaporize\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Secret: When a minion attacks your hero, destroy it.\",\n\t\"trigger\": {\n\t\t\"class\": \"PhysicalAttackTrigger\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"sourceEntityType\": \"MINION\",\n\t\t\"targetEntityType\": \"HERO\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"DestroySpell\",\n\t\t\"target\": \"ATTACKER\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/spell_blizzard.json",
    "content": "{\n\t\"name\": \"Blizzard\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 2 damage to all enemy minions and Freeze them.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\"value\": 2\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\"attribute\": \"FROZEN\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/spell_cone_of_cold.json",
    "content": "{\n\t\"name\": \"Cone of Cold\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Freeze a minion and the minions next to it, and deal 1 damage to them.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"AdjacentEffectSpell\",\n\t\t\t\t\"spell1\": {\n\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AdjacentEffectSpell\",\n\t\t\t\t\"spell1\": {\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"attribute\": \"FROZEN\"\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/spell_pyroblast.json",
    "content": "{\n\t\"name\": \"Pyroblast\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deal 10 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 10\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/mage/token_spellbender.json",
    "content": "{\n\t\"name\": \"Spellbender\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_abomination.json",
    "content": "{\n\t\"name\": \"Abomination\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Taunt. Deathrattle: Deal 2 damage to ALL characters.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_CHARACTERS\",\n\t\t\"value\": 2\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_abusive_sergeant.json",
    "content": "{\n\t\"name\": \"Abusive Sergeant\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give a minion +2 Attack this turn.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"TemporaryAttackSpell\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_acolyte_of_pain.json",
    "content": "{\n\t\"name\": \"Acolyte of Pain\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever this minion takes damage, draw a card.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_alarm-o-bot.json",
    "content": "{\n\t\"name\": \"Alarm-o-Bot\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"At the start of your turn, swap this minion with a random one in your hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"HoldsCardCondition\",\n\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"custom.AlarmOBotSpell\",\n\t\t\t\"target\": \"SELF\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_alexstrasza.json",
    "content": "{\n\t\"name\": \"Alexstrasza\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Battlecry: Set a hero's remaining Health to 15.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"HEROES\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SetHeroHpSpell\",\n\t\t\t\"value\": 15\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_amani_berserker.json",
    "content": "{\n\t\"name\": \"Amani Berserker\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Enrage: +3 Attack\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"EnrageChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"EnrageSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 3\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"ENRAGABLE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_ancient_brewmaster.json",
    "content": "{\n\t\"name\": \"Ancient Brewmaster\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Return a friendly minion from the battlefield to your hand.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReturnMinionToHandSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_ancient_mage.json",
    "content": "{\n\t\"name\": \"Ancient Mage\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Give adjacent minions Spell Damage +1.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\"target\": \"ADJACENT_MINIONS\",\n\t\t\t\"attribute\": \"SPELL_DAMAGE\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_ancient_watcher.json",
    "content": "{\n\t\"name\": \"Ancient Watcher\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Can't Attack.\",\n\t\"attributes\": {\n\t\t\"CANNOT_ATTACK\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_angry_chicken.json",
    "content": "{\n\t\"name\": \"Angry Chicken\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Enrage: +5 Attack\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"EnrageChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"EnrageSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 5\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"ENRAGABLE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_arcane_golem.json",
    "content": "{\n\t\"name\": \"Arcane Golem\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Give your opponent a Mana Crystal.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_argent_commander.json",
    "content": "{\n\t\"name\": \"Argent Commander\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Charge, Divine Shield\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true,\n\t\t\"DIVINE_SHIELD\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_argent_squire.json",
    "content": "{\n\t\"name\": \"Argent Squire\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Divine Shield\",\n\t\"attributes\": {\n\t\t\"DIVINE_SHIELD\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_baron_geddon.json",
    "content": "{\n\t\"name\": \"Baron Geddon\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"At the end of your turn, deal 2 damage to ALL other characters.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ALL_OTHER_CHARACTERS\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_big_game_hunter.json",
    "content": "{\n\t\"name\": \"Big Game Hunter\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Destroy a minion with an Attack of 7 or more.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\"operation\": \"GREATER\",\n\t\t\t\t\"value\": 6\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_blood_knight.json",
    "content": "{\n\t\"name\": \"Blood Knight\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: All minions lose Divine Shield. Gain +3/+3 for each Shield lost.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"value\": {\n\t\t\t\t\t\t\"class\": \"AttributeCounter\",\n\t\t\t\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\t\t\t\"attribute\": \"DIVINE_SHIELD\",\n\t\t\t\t\t\t\"multiplier\": 3\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"RemoveAttributeSpell\",\n\t\t\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\t\t\"attribute\": \"DIVINE_SHIELD\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_bloodmage_thalnos.json",
    "content": "{\n\t\"name\": \"Bloodmage Thalnos\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Spell Damage +1. Deathrattle: Draw a card.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"value\": 1\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true,\n\t\t\"SPELL_DAMAGE\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_bloodsail_corsair.json",
    "content": "{\n\t\"name\": \"Bloodsail Corsair\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Battlecry: Remove 1 Durability from your opponent's weapon.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyDurabilitySpell\",\n\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\"value\": -1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_bloodsail_raider.json",
    "content": "{\n\t\"name\": \"Bloodsail Raider\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Battlecry: Gain Attack equal to the Attack of your weapon.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_cairne_bloodhoof.json",
    "content": "{\n\t\"name\": \"Cairne Bloodhoof\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Deathrattle: Summon a 4/5 Baine Bloodhoof.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_baine_bloodhoof\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_captain_greenskin.json",
    "content": "{\n\t\"name\": \"Captain Greenskin\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Battlecry: Give your weapon +1/+1.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffWeaponSpell\",\n\t\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_coldlight_oracle.json",
    "content": "{\n\t\"name\": \"Coldlight Oracle\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Battlecry: Each player draws 2 cards.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_coldlight_seer.json",
    "content": "{\n\t\"name\": \"Coldlight Seer\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Battlecry: Give your other Murlocs +2 Health.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\t\"hpBonus\": 2,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"MURLOC\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_crazed_alchemist.json",
    "content": "{\n\t\"name\": \"Crazed Alchemist\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Swap the Attack and Health of a minion.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SwapAttackAndHpSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_cult_master.json",
    "content": "{\n\t\"name\": \"Cult Master\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever one of your other minions dies, draw a card.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionDeathTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_dark_iron_dwarf.json",
    "content": "{\n\t\"name\": \"Dark Iron Dwarf\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give a minion +2 Attack this turn.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"TemporaryAttackSpell\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_deathwing.json",
    "content": "{\n\t\"name\": \"Deathwing\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 12,\n\t\"baseHp\": 12,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Battlecry: Destroy all other minions and discard your hand.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\t\t\"target\": \"ALL_OTHER_MINIONS\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DiscardSpell\",\n\t\t\t\t\t\"value\": -1\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_defender_of_argus.json",
    "content": "{\n\t\"name\": \"Defender of Argus\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Give adjacent minions +1/+1 and Taunt.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"target\": \"ADJACENT_MINIONS\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"attackBonus\": 1,\n\t\t\t\t\t\"hpBonus\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_demolisher.json",
    "content": "{\n\t\"name\": \"Demolisher\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"At the start of your turn, deal 2 damage to a random enemy.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\"value\": 2,\n\t\t\t\"howMany\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_dire_wolf_alpha.json",
    "content": "{\n\t\"name\": \"Dire Wolf Alpha\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Adjacent minions have +1 Attack.\",\n\t\"aura\": {\n\t\t\"class\": \"BuffAura\",\n\t\t\"target\": \"ADJACENT_MINIONS\",\n\t\t\"attackBonus\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_doomsayer.json",
    "content": "{\n\t\"name\": \"Doomsayer\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"At the start of your turn, destroy ALL minions.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"target\": \"ALL_MINIONS\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_dread_corsair.json",
    "content": "{\n\t\"name\": \"Dread Corsair\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Taunt. Costs (1) less per Attack of your weapon.\",\n\t\"manaCostModifier\": {\n\t\t\"class\": \"AttributeValueProvider\",\n\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\"attribute\": \"ATTACK\"\n\t},\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_earthen_ring_farseer.json",
    "content": "{\n\t\"name\": \"Earthen Ring Farseer\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Restore 3 Health.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"value\": 3\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_emperor_cobra.json",
    "content": "{\n\t\"name\": \"Emperor Cobra\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Destroy any minion damaged by this minion.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageCausedTrigger\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_faceless_manipulator.json",
    "content": "{\n\t\"name\": \"Faceless Manipulator\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Choose a minion and become a copy of it.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"custom.FacelessSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_faerie_dragon.json",
    "content": "{\n\t\"name\": \"Faerie Dragon\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Can't be targeted by Spells or Hero Powers.\",\n\t\"attributes\": {\n\t\t\"UNTARGETABLE_BY_SPELLS\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_fen_creeper.json",
    "content": "{\n\t\"name\": \"Fen Creeper\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_flesheating_ghoul.json",
    "content": "{\n\t\"name\": \"Flesheating Ghoul\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever a minion dies, gain +1 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionDeathTrigger\",\n\t\t\t\"targetPlayer\": \"BOTH\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_frost_elemental.json",
    "content": "{\n\t\"name\": \"Frost Elemental\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Freeze a character.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"attribute\": \"FROZEN\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_gadgetzan_auctioneer.json",
    "content": "{\n\t\"name\": \"Gadgetzan Auctioneer\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever you cast a spell, draw a card.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_gruul.json",
    "content": "{\n\t\"name\": \"Gruul\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"At the end of each turn, gain +1/+1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"BOTH\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_harrison_jones.json",
    "content": "{\n\t\"name\": \"Harrison Jones\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Destroy your opponent's weapon and draw cards equal to its Durability.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\t\"value\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"ENEMY_WEAPON\",\n\t\t\t\t\t\t\"attribute\": \"HP\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\t\t\"target\": \"ENEMY_WEAPON\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_harvest_golem.json",
    "content": "{\n\t\"name\": \"Harvest Golem\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Deathrattle: Summon a 2/1 Damaged Golem.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_damaged_golem\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_hogger.json",
    "content": "{\n\t\"name\": \"Hogger\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"At the end of your turn, summon a 2/2 Gnoll with Taunt.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_gnoll\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_hungry_crab.json",
    "content": "{\n\t\"name\": \"Hungry Crab\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Destroy a Murloc and gain +2/+2.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DestroySpell\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"attackBonus\": 2,\n\t\t\t\t\t\"hpBonus\": 2\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"MURLOC\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_illidan_stormrage.json",
    "content": "{\n\t\"name\": \"Illidan Stormrage\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Whenever you play a card, summon a 2/1 Flame of Azzinoth.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardPlayedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_flame_of_azzinoth\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_imp_master.json",
    "content": "{\n\t\"name\": \"Imp Master\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"At the end of your turn, deal 1 damage to this minion and summon a 1/1 Imp.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_imp\",\n\t\t\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_injured_blademaster.json",
    "content": "{\n\t\"name\": \"Injured Blademaster\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Deal 4 damage to HIMSELF.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": 4\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_ironbeak_owl.json",
    "content": "{\n\t\"name\": \"Ironbeak Owl\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Silence a minion.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SilenceSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_jungle_panther.json",
    "content": "{\n\t\"name\": \"Jungle Panther\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Stealth\",\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_king_mukla.json",
    "content": "{\n\t\"name\": \"King Mukla\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Give your opponent 2 Bananas.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\"cards\": [\n\t\t\t\t\"spell_bananas\",\n\t\t\t\t\"spell_bananas\"\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_knife_juggler.json",
    "content": "{\n\t\"name\": \"Knife Juggler\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"After you summon a minion, deal 1 damage to a random enemy.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterMinionSummonedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"hostTargetType\": \"IGNORE_AS_TARGET\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\"value\": 1,\n\t\t\t\"howMany\": 1,\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_leeroy_jenkins.json",
    "content": "{\n\t\"name\": \"Leeroy Jenkins\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"NONE\",\n\t\"description\": \"Charge. Battlecry: Summon two 1/1 Whelps for your opponent.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\"cards\": [\n\t\t\t\t\"token_whelp\",\n\t\t\t\t\"token_whelp\"\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_leper_gnome.json",
    "content": "{\n\t\"name\": \"Leper Gnome\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Deal 2 damage to the enemy hero.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_HERO\",\n\t\t\"value\": 2\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_lightwarden.json",
    "content": "{\n\t\"name\": \"Lightwarden\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever a character is healed, gain +2 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"HealingTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_loot_hoarder.json",
    "content": "{\n\t\"name\": \"Loot Hoarder\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Draw a card.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"value\": 1\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_lorewalker_cho.json",
    "content": "{\n\t\"name\": \"Lorewalker Cho\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Whenever a player casts a spell, put a copy into the other player's hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"BOTH\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"CopyCardSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\"targetPlayer\": \"INACTIVE\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_mad_bomber.json",
    "content": "{\n\t\"name\": \"Mad Bomber\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Deal 3 damage randomly split between all other characters.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ALL_OTHER_CHARACTERS\",\n\t\t\t\"value\": 1,\n\t\t\t\"howMany\": 3,\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_malygos.json",
    "content": "{\n\t\"name\": \"Malygos\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 12,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Spell Damage +5\",\n\t\"attributes\": {\n\t\t\"SPELL_DAMAGE\": 5\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_mana_addict.json",
    "content": "{\n\t\"name\": \"Mana Addict\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever you cast a spell, gain +2 Attack this turn.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"TemporaryAttackSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_mana_wraith.json",
    "content": "{\n\t\"name\": \"Mana Wraith\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"ALL minions cost (1) more.\",\n\t\"cardCostModifier\": {\n\t\t\"class\": \"CardCostModifier\",\n\t\t\"cardType\": \"MINION\",\n\t\t\"targetPlayer\": \"BOTH\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_master_swordsmith.json",
    "content": "{\n\t\"name\": \"Master Swordsmith\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"At the end of your turn, give another random friendly minion +1 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_millhouse_manastorm.json",
    "content": "{\n\t\"name\": \"Millhouse Manastorm\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Enemy spells cost (0) next turn.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\t\"operation\": \"SET\",\n\t\t\t\t\"value\": 0,\n\t\t\t\t\"expirationTrigger\": {\n\t\t\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\t\t\"targetPlayer\": \"OPPONENT\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_mind_control_tech.json",
    "content": "{\n\t\"name\": \"Mind Control Tech\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: If your opponent has 4 or more minions, take control of one at random.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MindControlSpell\",\n\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\"randomTarget\": true\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\"value\": 4\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_mogushan_warden.json",
    "content": "{\n\t\"name\": \"Mogu'shan Warden\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_molten_giant.json",
    "content": "{\n\t\"name\": \"Molten Giant\",\n\t\"baseManaCost\": 25,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Costs (1) less for each damage your hero has taken.\",\n\t\"manaCostModifier\": {\n\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\"operation\": \"SUBTRACT\",\n\t\t\"value1\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"attribute\": \"MAX_HP\"\n\t\t},\n\t\t\"value2\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"attribute\": \"HP\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_mountain_giant.json",
    "content": "{\n\t\"name\": \"Mountain Giant\",\n\t\"baseManaCost\": 12,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Costs (1) less for each other card in your hand.\",\n\t\"manaCostModifier\": {\n\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"playerAttribute\": \"HAND_COUNT\",\n\t\t\"offset\": -1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_murloc_tidecaller.json",
    "content": "{\n\t\"name\": \"Murloc Tidecaller\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Whenever you summon a Murloc, gain +1 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionSummonedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"race\": \"MURLOC\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_murloc_warleader.json",
    "content": "{\n\t\"name\": \"Murloc Warleader\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Your other Murlocs have +2/+1.\",\n\t\"aura\": {\n\t\t\"class\": \"BuffAura\",\n\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 2,\n\t\t\"hpBonus\": 1,\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"MURLOC\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_nat_pagle.json",
    "content": "{\n\t\"name\": \"Nat Pagle\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"At the start of your turn, you have a 50% chance to draw an extra card.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"ConditionalValueProvider\",\n\t\t\t\t\"ifTrue\": 1,\n\t\t\t\t\"ifFalse\": 0,\n\t\t\t\t\"condition\": {\n\t\t\t\t\t\"class\": \"RandomCondition\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_nozdormu.json",
    "content": "{\n\t\"name\": \"Nozdormu\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Players only have 15 seconds to take their turns.\",\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_onyxia.json",
    "content": "{\n\t\"name\": \"Onyxia\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Battlecry: Summon 1/1 Whelps until your side of the battlefield is full.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_whelp\",\n\t\t\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_whelp\",\n\t\t\t\t\t\"boardPositionRelative\": \"LEFT\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_whelp\",\n\t\t\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_whelp\",\n\t\t\t\t\t\"boardPositionRelative\": \"LEFT\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_whelp\",\n\t\t\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_whelp\",\n\t\t\t\t\t\"boardPositionRelative\": \"LEFT\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_pint-sized_summoner.json",
    "content": "{\n\t\"name\": \"Pint-Sized Summoner\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"The first minion you play each turn costs (1) less.\",\n\t\"cardCostModifier\": {\n\t\t\"class\": \"ToggleCostModifier\",\n\t\t\"cardType\": \"MINION\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"value\": -1,\n\t\t\"toggleOnTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\"\n\t\t},\n\t\t\"toggleOffTrigger\": {\n\t\t\t\"class\": \"CardPlayedTrigger\",\n\t\t\t\"cardType\": \"MINION\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_priestess_of_elune.json",
    "content": "{\n\t\"name\": \"Priestess of Elune\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Restore 4 Health to your hero.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": 4\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_questing_adventurer.json",
    "content": "{\n\t\"name\": \"Questing Adventurer\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever you play a card, gain +1/+1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardPlayedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_raging_worgen.json",
    "content": "{\n\t\"name\": \"Raging Worgen\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Enrage: Windfury and +1 Attack\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"EnrageChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"EnrageSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"WINDFURY\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"ENRAGABLE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_ravenholdt_assassin.json",
    "content": "{\n\t\"name\": \"Ravenholdt Assassin\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Stealth\",\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_scarlet_crusader.json",
    "content": "{\n\t\"name\": \"Scarlet Crusader\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Divine Shield\",\n\t\"attributes\": {\n\t\t\"DIVINE_SHIELD\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_sea_giant.json",
    "content": "{\n\t\"name\": \"Sea Giant\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Costs (1) less for each other minion on the battlefield.\",\n\t\"manaCostModifier\": {\n\t\t\"class\": \"EntityCounter\",\n\t\t\"target\": \"ALL_OTHER_MINIONS\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_secretkeeper.json",
    "content": "{\n\t\"name\": \"Secretkeeper\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever a Secret is played, gain +1/+1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SecretPlayedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_shieldbearer.json",
    "content": "{\n\t\"name\": \"Shieldbearer\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_silver_hand_knight.json",
    "content": "{\n\t\"name\": \"Silver Hand Knight\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Summon a 2/2 Squire.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_squire\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_silvermoon_guardian.json",
    "content": "{\n\t\"name\": \"Silvermoon Guardian\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Divine Shield\",\n\t\"attributes\": {\n\t\t\"DIVINE_SHIELD\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_southsea_captain.json",
    "content": "{\n\t\"name\": \"Southsea Captain\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Your other Pirates have +1/+1.\",\n\t\"aura\": {\n\t\t\"class\": \"BuffAura\",\n\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 1,\n\t\t\"hpBonus\": 1,\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"PIRATE\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_southsea_deckhand.json",
    "content": "{\n\t\"name\": \"Southsea Deckhand\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Has Charge while you have a weapon equipped.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"BoardChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ConditionalEffectSpell\",\n\t\t\t\"exclusive\": true,\n\t\t\t\"spell1\": {\n\t\t\t\t\"class\": \"RemoveAttributeSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"attribute\": \"CHARGE\"\n\t\t\t},\n\t\t\t\"spell2\": {\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"attribute\": \"CHARGE\"\n\t\t\t},\n\t\t\t\"condition\": {\n\t\t\t\t\"class\": \"HasWeaponCondition\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_spellbreaker.json",
    "content": "{\n\t\"name\": \"Spellbreaker\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Silence a minion.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SilenceSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_spiteful_smith.json",
    "content": "{\n\t\"name\": \"Spiteful Smith\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Enrage: Your weapon has +2 Attack.\",\n\t\"aura\": {\n\t\t\"class\": \"EnrageAura\",\n\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\"applyEffect\": {\n\t\t\t\"class\": \"BuffWeaponSpell\",\n\t\t\t\"attackBonus\": 2\n\t\t},\n\t\t\"removeEffect\": {\n\t\t\t\"class\": \"BuffWeaponSpell\",\n\t\t\t\"attackBonus\": -2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"ENRAGABLE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_stampeding_kodo.json",
    "content": "{\n\t\"name\": \"Stampeding Kodo\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Destroy a random enemy minion with 2 or less Attack.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\"randomTarget\": true,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\t\"value\": 2\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_stranglethorn_tiger.json",
    "content": "{\n\t\"name\": \"Stranglethorn Tiger\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Stealth\",\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_sunfury_protector.json",
    "content": "{\n\t\"name\": \"Sunfury Protector\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Give adjacent minions Taunt.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"ADJACENT_MINIONS\",\n\t\t\t\"attribute\": \"TAUNT\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_sunwalker.json",
    "content": "{\n\t\"name\": \"Sunwalker\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Taunt. Divine Shield\",\n\t\"attributes\": {\n\t\t\"DIVINE_SHIELD\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_tauren_warrior.json",
    "content": "{\n\t\"name\": \"Tauren Warrior\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt. Enrage: +3 Attack\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"EnrageChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"EnrageSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 3\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"ENRAGABLE\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_the_beast.json",
    "content": "{\n\t\"name\": \"The Beast\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 9,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Deathrattle: Summon a 3/3 Finkle Einhorn for your opponent.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"card\": \"token_finkle_einhorn\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_the_black_knight.json",
    "content": "{\n\t\"name\": \"The Black Knight\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Destroy an enemy minion with Taunt.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"TAUNT\",\n\t\t\t\t\"operation\": \"HAS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_thrallmar_farseer.json",
    "content": "{\n\t\"name\": \"Thrallmar Farseer\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Windfury\",\n\t\"attributes\": {\n\t\t\"WINDFURY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_tinkmaster_overspark.json",
    "content": "{\n\t\"name\": \"Tinkmaster Overspark\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Transform another random minion into a 5/5 Devilsaur or a 1/1 Squirrel.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"EitherOrSpell\",\n\t\t\t\"spell1\": {\n\t\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\t\"target\": \"ALL_OTHER_MINIONS\",\n\t\t\t\t\"card\": \"token_devilsaur\",\n\t\t\t\t\"randomTarget\": true\n\t\t\t},\n\t\t\t\"spell2\": {\n\t\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\t\"target\": \"ALL_OTHER_MINIONS\",\n\t\t\t\t\"card\": \"token_squirrel\",\n\t\t\t\t\"randomTarget\": true\n\t\t\t},\n\t\t\t\"condition\": {\n\t\t\t\t\"class\": \"RandomCondition\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_twilight_drake.json",
    "content": "{\n\t\"name\": \"Twilight Drake\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Battlecry: Gain +1 Health for each card in your hand.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"hpBonus\": {\n\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\"playerAttribute\": \"HAND_COUNT\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_venture_co_mercenary.json",
    "content": "{\n\t\"name\": \"Venture Co. Mercenary\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Your minions cost (3) more.\",\n\t\"cardCostModifier\": {\n\t\t\"class\": \"CardCostModifier\",\n\t\t\"cardType\": \"MINION\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"value\": 3\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_violet_teacher.json",
    "content": "{\n\t\"name\": \"Violet Teacher\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever you cast a spell, summon a 1/1 Violet Apprentice.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_violet_apprentice\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_wild_pyromancer.json",
    "content": "{\n\t\"name\": \"Wild Pyromancer\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"After you cast a spell, deal 1 damage to ALL minions.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterSpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_windfury_harpy.json",
    "content": "{\n\t\"name\": \"Windfury Harpy\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Windfury\",\n\t\"attributes\": {\n\t\t\"WINDFURY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_wisp.json",
    "content": "{\n\t\"name\": \"Wisp\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_worgen_infiltrator.json",
    "content": "{\n\t\"name\": \"Worgen Infiltrator\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Stealth\",\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_young_dragonhawk.json",
    "content": "{\n\t\"name\": \"Young Dragonhawk\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Windfury\",\n\t\"attributes\": {\n\t\t\"WINDFURY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_young_priestess.json",
    "content": "{\n\t\"name\": \"Young Priestess\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"At the end of your turn, give another random friendly minion +1 Health.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\t\"hpBonus\": 1,\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_youthful_brewmaster.json",
    "content": "{\n\t\"name\": \"Youthful Brewmaster\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Return a friendly minion from the battlefield to your hand.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReturnMinionToHandSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/minion_ysera.json",
    "content": "{\n\t\"name\": \"Ysera\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 12,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"At the end of your turn, add a Dream Card to your hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveRandomCardSpell\",\n\t\t\t\"cards\": [\n\t\t\t\t\"token_emerald_drake\",\n\t\t\t\t\"token_laughing_sister\",\n\t\t\t\t\"spell_nightmare\",\n\t\t\t\t\"spell_ysera_awakens\",\n\t\t\t\t\"spell_dream\"\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/spell_bananas.json",
    "content": "{\n\t\"name\": \"Bananas\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a minion +1/+1.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"attackBonus\": 1,\n\t\t\"hpBonus\": 1\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/spell_dream.json",
    "content": "{\n\t\"name\": \"Dream\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Return a minion to its owner's hand.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"ReturnMinionToHandSpell\"\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/spell_nightmare.json",
    "content": "{\n\t\"name\": \"Nightmare\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a minion +5/+5. At the start of your next turn, destroy it.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"attackBonus\": 5,\n\t\t\t\t\"hpBonus\": 5\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddSpellTriggerSpell\",\n\t\t\t\t\"trigger\": {\n\t\t\t\t\t\"eventTrigger\": {\n\t\t\t\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t\t\t},\n\t\t\t\t\t\"spell\": {\n\t\t\t\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\t\t\t\"target\": \"SELF\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/spell_ysera_awakens.json",
    "content": "{\n\t\"name\": \"Ysera Awakens\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 5 damage to all characters except Ysera.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_CHARACTERS\",\n\t\t\"value\": 5,\n\t\t\"filter\": {\n\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\"cardId\": \"minion_ysera\",\n\t\t\t\"invert\": true\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_baine_bloodhoof.json",
    "content": "{\n\t\"name\": \"Baine Bloodhoof\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_chicken.json",
    "content": "{\n\t\"name\": \"Chicken\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Hey Chicken!\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_damaged_golem.json",
    "content": "{\n\t\"name\": \"Damaged Golem\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_devilsaur.json",
    "content": "{\n\t\"name\": \"Devilsaur\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_emerald_drake.json",
    "content": "{\n\t\"name\": \"Emerald Drake\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_finkle_einhorn.json",
    "content": "{\n\t\"name\": \"Finkle Einhorn\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_flame_of_azzinoth.json",
    "content": "{\n\t\"name\": \"Flame of Azzinoth\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_gnoll.json",
    "content": "{\n\t\"name\": \"Gnoll\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_imp.json",
    "content": "{\n\t\"name\": \"Imp\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_laughing_sister.json",
    "content": "{\n\t\"name\": \"Laughing Sister\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Can't be targeted by spells or Hero Powers.\",\n\t\"attributes\": {\n\t\t\"UNTARGETABLE_BY_SPELLS\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_murloc.json",
    "content": "{\n\t\"name\": \"Murloc\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_squire.json",
    "content": "{\n\t\"name\": \"Squire\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_squirrel.json",
    "content": "{\n\t\"name\": \"Squirrel\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_violet_apprentice.json",
    "content": "{\n\t\"name\": \"Violet Apprentice\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/neutral/token_whelp.json",
    "content": "{\n\t\"name\": \"Whelp\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/minion_aldor_peacekeeper.json",
    "content": "{\n\t\"name\": \"Aldor Peacekeeper\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Change an enemy minion's Attack to 1.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SetAttackSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/minion_argent_protector.json",
    "content": "{\n\t\"name\": \"Argent Protector\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give a friendly minion Divine Shield.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"attribute\": \"DIVINE_SHIELD\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/minion_tirion_fordring.json",
    "content": "{\n\t\"name\": \"Tirion Fordring\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Divine Shield. Taunt. Deathrattle: Equip a 5/3 Ashbringer.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"EquipWeaponSpell\",\n\t\t\"card\": \"weapon_ashbringer\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true,\n\t\t\"DIVINE_SHIELD\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/secret_eye_for_an_eye.json",
    "content": "{\n\t\"name\": \"Eye for an Eye\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Secret: When your hero takes damage, deal that much damage to the enemy hero.\",\n\t\"trigger\": {\n\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"targetEntityType\": \"HERO\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_HERO\",\n\t\t\"value\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"attribute\": \"LAST_HIT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/secret_noble_sacrifice.json",
    "content": "{\n\t\"name\": \"Noble Sacrifice\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Secret: When an enemy attacks, summon a 2/1 Defender as the new target.\",\n\t\"trigger\": {\n\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\"actionType\": \"PHYSICAL_ATTACK\",\n\t\t\"sourcePlayer\": \"OPPONENT\",\n\t\t\"targetPlayer\": \"BOTH\",\n\t\t\"queueCondition\": {\n\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\"operation\": \"LESS\",\n\t\t\t\"value\": 7\n\t\t},\n\t\t\"fireCondition\": {\n\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\"operation\": \"LESS\",\n\t\t\t\"value\": 7\n\t\t}\n\t},\n\t\"spell\": {\n\t\t\"class\": \"SummonNewAttackTargetSpell\",\n\t\t\"card\": \"token_defender\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/secret_redemption.json",
    "content": "{\n\t\"name\": \"Redemption\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Secret: When one of your minions dies, return it to life with 1 Health.\",\n\t\"trigger\": {\n\t\t\"class\": \"MinionDeathTrigger\",\n\t\t\"targetPlayer\": \"SELF\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"ReviveMinionSpell\",\n\t\t\"target\": \"EVENT_TARGET\",\n\t\t\"hpBonus\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/secret_repentance.json",
    "content": "{\n\t\"name\": \"Repentance\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Secret: When your opponent plays a minion, reduce its Health to 1.\",\n\t\"trigger\": {\n\t\t\"class\": \"MinionPlayedTrigger\",\n\t\t\"targetPlayer\": \"OPPONENT\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"SetHpSpell\",\n\t\t\"target\": \"EVENT_TARGET\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/spell_avenging_wrath.json",
    "content": "{\n\t\"name\": \"Avenging Wrath\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deal 8 damage randomly split among all enemies.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MissilesSpell\",\n\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\"value\": 1,\n\t\t\"howMany\": 8\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/spell_blessed_champion.json",
    "content": "{\n\t\"name\": \"Blessed Champion\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Double a minion's Attack.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DoubleAttackSpell\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/spell_blessing_of_wisdom.json",
    "content": "{\n\t\"name\": \"Blessing of Wisdom\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Choose a minion. Whenever it attacks, draw a card.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"AddSpellTriggerSpell\",\n\t\t\"trigger\": {\n\t\t\t\"eventTrigger\": {\n\t\t\t\t\"class\": \"PhysicalAttackTrigger\",\n\t\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t\t},\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t},\n\t\t\t\"persistentOwner\": true\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/spell_divine_favor.json",
    "content": "{\n\t\"name\": \"Divine Favor\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Draw cards until you have as many in hand as your opponent.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"value\": {\n\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\"value1\": {\n\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\t\"playerAttribute\": \"HAND_COUNT\"\n\t\t\t},\n\t\t\t\"value2\": {\n\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\"playerAttribute\": \"HAND_COUNT\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/spell_equality.json",
    "content": "{\n\t\"name\": \"Equality\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Change the Health of ALL minions to 1.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SetHpSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/spell_holy_wrath.json",
    "content": "{\n\t\"name\": \"Holy Wrath\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Draw a card and deal damage equal to its cost.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"custom.HolyWrathSpell\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/spell_lay_on_hands.json",
    "content": "{\n\t\"name\": \"Lay on Hands\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Restore 8 Health. Draw 3 cards.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"value\": 8\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 3\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/token_defender.json",
    "content": "{\n\t\"name\": \"Defender\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/weapon_ashbringer.json",
    "content": "{\n\t\"name\": \"Ashbringer\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 5,\n\t\"durability\": 3,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/paladin/weapon_sword_of_justice.json",
    "content": "{\n\t\"name\": \"Sword of Justice\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 1,\n\t\"durability\": 5,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"After you summon a minion, give it +1/+1 and this loses 1 Durability.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterMinionSummonedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"fireCondition\": {\n\t\t\t\t\"class\": \"HasWeaponCondition\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\t\"attackBonus\": 1,\n\t\t\t\t\t\"hpBonus\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyDurabilitySpell\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\t\"value\": -1\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/hero_power_mind_shatter.json",
    "content": "{\n\t\"name\": \"Mind Shatter\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 3 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 3\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/hero_power_mind_spike.json",
    "content": "{\n\t\"name\": \"Mind Spike\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 2 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/minion_auchenai_soulpriest.json",
    "content": "{\n\t\"name\": \"Auchenai Soulpriest\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Your cards and powers that restore Health now deal damage instead.\",\n\t\"attributes\": {\n\t\t\"INVERT_HEALING\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/minion_cabal_shadow_priest.json",
    "content": "{\n\t\"name\": \"Cabal Shadow Priest\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Take control of an enemy minion that has 2 or less Attack.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MindControlSpell\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\t\"value\": 2\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/minion_lightspawn.json",
    "content": "{\n\t\"name\": \"Lightspawn\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"This minion's Attack is always equal to its Health.\",\n\t\"attributes\": {\n\t\t\"ATTACK_EQUALS_HP\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/minion_lightwell.json",
    "content": "{\n\t\"name\": \"Lightwell\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"At the start of your turn, restore 3 Health to a damaged friendly character.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_CHARACTERS\",\n\t\t\t\"value\": 3,\n\t\t\t\"randomTarget\": true,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"DamagedFilter\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/minion_prophet_velen.json",
    "content": "{\n\t\"name\": \"Prophet Velen\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Double the damage and healing of your spells and Hero Power.\",\n\t\"attributes\": {\n\t\t\"HEAL_AMPLIFY_MULTIPLIER\": 2,\n\t\t\"SPELL_AMPLIFY_MULTIPLIER\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/minion_temple_enforcer.json",
    "content": "{\n\t\"name\": \"Temple Enforcer\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give a friendly minion +3 Health.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"hpBonus\": 3\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/spell_circle_of_healing.json",
    "content": "{\n\t\"name\": \"Circle of Healing\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Restore 4 Health to ALL minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"HealSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": 4\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/spell_holy_fire.json",
    "content": "{\n\t\"name\": \"Holy Fire\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 5 damage. Restore 5 Health to your hero.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 5\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"value\": 5\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/spell_inner_fire.json",
    "content": "{\n\t\"name\": \"Inner Fire\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Change a minion's Attack to be equal to its Health.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"SetAttackSpell\",\n\t\t\"value\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"attribute\": \"HP\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/spell_mass_dispel.json",
    "content": "{\n\t\"name\": \"Mass Dispel\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Silence all enemy minions. Draw a card.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"SilenceSpell\",\n\t\t\t\t\"target\": \"ENEMY_MINIONS\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/spell_mindgames.json",
    "content": "{\n\t\"name\": \"Mindgames\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Put a copy of a random minion from your opponent's deck into the battlefield.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\"card\": \"token_shadow_of_nothing\",\n\t\t\"cardSource\": {\n\t\t\t\"class\": \"DeckSource\",\n\t\t\t\"targetPlayer\": \"OPPONENT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/spell_shadow_madness.json",
    "content": "{\n\t\"name\": \"Shadow Madness\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Gain control of an enemy minion with 3 or less Attack until end of turn.\",\n\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"custom.ShadowMadnessSpell\",\n\t\t\"filter\": {\n\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\"value\": 3\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/spell_shadowform.json",
    "content": "{\n\t\"name\": \"Shadowform\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Your Hero Power becomes 'Deal 2 damage'. If already in Shadowform: 3 damage.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"EitherOrSpell\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"ConditionalSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ChangeHeroPowerSpell\",\n\t\t\t\t\"card\": \"hero_power_mind_shatter\"\n\t\t\t},\n\t\t\t\"condition\": {\n\t\t\t\t\"class\": \"HasHeroPowerCondition\",\n\t\t\t\t\"cardId\": \"hero_power_mind_shatter\",\n\t\t\t\t\"invert\": true\n\t\t\t}\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\t\"attribute\": \"SHADOWFORM\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ChangeHeroPowerSpell\",\n\t\t\t\t\t\"card\": \"hero_power_mind_spike\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"AttributeCondition\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"attribute\": \"SHADOWFORM\",\n\t\t\t\"operation\": \"HAS\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/spell_silence.json",
    "content": "{\n\t\"name\": \"Silence\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Silence a minion.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"SilenceSpell\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/spell_thoughtsteal.json",
    "content": "{\n\t\"name\": \"Thoughtsteal\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Copy 2 cards from your opponent's deck and put them into your hand.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"CopyCardSpell\",\n\t\t\"value\": 2,\n\t\t\"cardLocation\": \"DECK\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/priest/token_shadow_of_nothing.json",
    "content": "{\n\t\"name\": \"Shadow of Nothing\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Mindgames whiffed! Your opponent had no minions!\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/minion_defias_ringleader.json",
    "content": "{\n\t\"name\": \"Defias Ringleader\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Combo: Summon a 2/1 Defias Bandit.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_defias_bandit\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"ComboCondition\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/minion_edwin_vancleef.json",
    "content": "{\n\t\"name\": \"Edwin VanCleef\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Combo: Gain +2/+2 for each card played earlier this turn.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"COMBO\",\n\t\t\t\t\"multiplier\": 2\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/minion_kidnapper.json",
    "content": "{\n\t\"name\": \"Kidnapper\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Combo: Return a minion to its owner's hand.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReturnMinionToHandSpell\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"ComboCondition\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/minion_master_of_disguise.json",
    "content": "{\n\t\"name\": \"Master of Disguise\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Give a friendly minion Stealth until your next turn.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"attribute\": \"STEALTH\",\n\t\t\t\"revertTrigger\": {\n\t\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/minion_patient_assassin.json",
    "content": "{\n\t\"name\": \"Patient Assassin\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Stealth. Destroy any minion damaged by this minion.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageCausedTrigger\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/minion_si7_agent.json",
    "content": "{\n\t\"name\": \"SI:7 Agent\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Combo: Deal 2 damage.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 2\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"ComboCondition\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/spell_betrayal.json",
    "content": "{\n\t\"name\": \"Betrayal\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"An enemy minion deals its damage to the minions next to it.\",\n\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"custom.BetrayalSpell\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/spell_blade_flurry.json",
    "content": "{\n\t\"name\": \"Blade Flurry\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Destroy your weapon and deal its damage to all enemy minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\t\"target\": \"FRIENDLY_WEAPON\"\n\t\t\t}\n\t\t]\n\t},\n\t\"condition\": {\n\t\t\"class\": \"HasWeaponCondition\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/spell_cold_blood.json",
    "content": "{\n\t\"name\": \"Cold Blood\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a minion +2 Attack. Combo: +4 Attack instead.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"ComboSpell\",\n\t\t\"exclusive\": true,\n\t\t\"spell1\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"attackBonus\": 2\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"attackBonus\": 4\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/spell_eviscerate.json",
    "content": "{\n\t\"name\": \"Eviscerate\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 2 damage. Combo: Deal 4 damage instead.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"ComboSpell\",\n\t\t\"exclusive\": true,\n\t\t\"spell1\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 2\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 4\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/spell_headcrack.json",
    "content": "{\n\t\"name\": \"Headcrack\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 2 damage to the enemy hero. Combo: Return this to your hand next turn.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ComboSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ENEMY_HERO\",\n\t\t\t\"value\": 2\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"AddSpellTriggerSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"trigger\": {\n\t\t\t\t\"eventTrigger\": {\n\t\t\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t\t},\n\t\t\t\t\"spell\": {\n\t\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\t\"card\": \"spell_headcrack\"\n\t\t\t\t},\n\t\t\t\t\"oneTurn\": true\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/spell_preparation.json",
    "content": "{\n\t\"name\": \"Preparation\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"The next spell you cast this turn costs (3) less.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\"cardCostModifier\": {\n\t\t\t\"class\": \"OneTurnCostModifier\",\n\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\"value\": 3,\n\t\t\t\"expirationTrigger\": {\n\t\t\t\t\"class\": \"CardPlayedTrigger\",\n\t\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/spell_shadowstep.json",
    "content": "{\n\t\"name\": \"Shadowstep\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Return a friendly minion to your hand. It costs (2) less.\",\n\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"ReturnMinionToHandSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"target\": \"EVENT_CARD\",\n\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\"value\": 2\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/token_defias_bandit.json",
    "content": "{\n\t\"name\": \"Defias Bandit\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/rogue/weapon_perditions_blade.json",
    "content": "{\n\t\"name\": \"Perdition's Blade\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 2,\n\t\"durability\": 2,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Deal 1 damage. Combo: Deal 2 instead.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ComboSpell\",\n\t\t\t\"exclusive\": true,\n\t\t\t\"spell1\": {\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t},\n\t\t\t\"spell2\": {\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 2\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/minion_al_akir_the_windlord.json",
    "content": "{\n\t\"name\": \"Al'Akir the Windlord\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Windfury, Charge, Divine Shield, Taunt\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true,\n\t\t\"DIVINE_SHIELD\": true,\n\t\t\"TAUNT\": true,\n\t\t\"WINDFURY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/minion_dust_devil.json",
    "content": "{\n\t\"name\": \"Dust Devil\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Windfury. Overload: (2)\",\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 2,\n\t\t\"WINDFURY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/minion_earth_elemental.json",
    "content": "{\n\t\"name\": \"Earth Elemental\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Taunt. Overload: (3)\",\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 3,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/minion_mana_tide_totem.json",
    "content": "{\n\t\"name\": \"Mana Tide Totem\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"TOTEM\",\n\t\"description\": \"At the end of your turn, draw a card.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/minion_unbound_elemental.json",
    "content": "{\n\t\"name\": \"Unbound Elemental\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever you play a card with Overload, gain +1/+1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardPlayedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"requiredAttribute\": \"OVERLOAD\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/spell_ancestral_spirit.json",
    "content": "{\n\t\"name\": \"Ancestral Spirit\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Give a minion 'Deathrattle: Resummon this minion.'\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"AddDeathrattleSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReviveMinionSpell\",\n\t\t\t\"target\": \"SELF\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/spell_earth_shock.json",
    "content": "{\n\t\"name\": \"Earth Shock\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Silence a minion, then deal 1 damage to it.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"SilenceSpell\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/spell_far_sight.json",
    "content": "{\n\t\"name\": \"Far Sight\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Draw a card. That card costs (3) less.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DrawCardAndDoSomethingSpell\",\n\t\t\"value\": 1,\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"target\": \"EVENT_CARD\",\n\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\"value\": 3\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/spell_feral_spirit.json",
    "content": "{\n\t\"name\": \"Feral Spirit\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Summon two 2/3 Spirit Wolves with Taunt. Overload: (2)\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_spirit_wolf\",\n\t\t\t\"token_spirit_wolf\"\n\t\t]\n\t},\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/spell_forked_lightning.json",
    "content": "{\n\t\"name\": \"Forked Lightning\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 2 damage to 2 random enemy minions. Overload: (2)\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MultiTargetSpell\",\n\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\"value\": 2,\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"condition\": {\n\t\t\"class\": \"MinionCountCondition\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"operation\": \"GREATER\",\n\t\t\"value\": 1\n\t},\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/spell_lava_burst.json",
    "content": "{\n\t\"name\": \"Lava Burst\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 5 damage. Overload: (2)\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 5\n\t},\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/spell_lightning_bolt.json",
    "content": "{\n\t\"name\": \"Lightning Bolt\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 3 damage. Overload: (1)\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 3\n\t},\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/spell_lightning_storm.json",
    "content": "{\n\t\"name\": \"Lightning Storm\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 2-3 damage to all enemy minions. Overload: (2)\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\"value\": {\n\t\t\t\"class\": \"RandomValueProvider\",\n\t\t\t\"min\": 2,\n\t\t\t\"max\": 3\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/token_spirit_wolf.json",
    "content": "{\n\t\"name\": \"Spirit Wolf\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/weapon_doomhammer.json",
    "content": "{\n\t\"name\": \"Doomhammer\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 2,\n\t\"durability\": 8,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Windfury, Overload: (2)\",\n\t\"onEquip\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"attribute\": \"WINDFURY\"\n\t},\n\t\"onUnequip\": {\n\t\t\"class\": \"RemoveAttributeSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"attribute\": \"WINDFURY\"\n\t},\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/shaman/weapon_stormforged_axe.json",
    "content": "{\n\t\"name\": \"Stormforged Axe\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 2,\n\t\"durability\": 3,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Overload: (1)\",\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/hero_jaraxxus.json",
    "content": "{\n\t\"name\": \"Lord Jaraxxus\",\n\t\"baseManaCost\": 9,\n\t\"heroPower\": \"hero_power_inferno\",\n\t\"type\": \"HERO\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DEMON\",\n\t\"attributes\": {\n\t\t\"HP\": 15,\n\t\t\"MAX_HP\": 15\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/hero_power_inferno.json",
    "content": "{\n\t\"name\": \"INFERNO!\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Summon a 6/6 Infernal.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_infernal\"\n\t},\n\t\"condition\": {\n\t\t\"class\": \"MinionCountCondition\",\n\t\t\"operation\": \"LESS\",\n\t\t\"value\": 7\n\t},\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/minion_blood_imp.json",
    "content": "{\n\t\"name\": \"Blood Imp\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Stealth. At the end of your turn, give another random friendly minion +1 Health.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\t\"hpBonus\": 1,\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/minion_doomguard.json",
    "content": "{\n\t\"name\": \"Doomguard\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Charge. Battlecry: Discard two random cards.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscardSpell\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/minion_felguard.json",
    "content": "{\n\t\"name\": \"Felguard\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Taunt. Battlecry: Destroy one of your Mana Crystals.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\t\"value\": -1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/minion_flame_imp.json",
    "content": "{\n\t\"name\": \"Flame Imp\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Battlecry: Deal 3 damage to your hero.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": 3\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/minion_lord_jaraxxus.json",
    "content": "{\n\t\"name\": \"Lord Jaraxxus\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 15,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Battlecry: Destroy your hero and replace him with Lord Jaraxxus.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\t\t\"target\": \"SELF\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ChangeHeroSpell\",\n\t\t\t\t\t\"card\": \"hero_jaraxxus\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"EquipWeaponSpell\",\n\t\t\t\t\t\"card\": \"weapon_blood_fury\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"OverrideTargetSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HERO\" \n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/minion_pit_lord.json",
    "content": "{\n\t\"name\": \"Pit Lord\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Battlecry: Deal 5 damage to your hero.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": 5\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/minion_summoning_portal.json",
    "content": "{\n\t\"name\": \"Summoning Portal\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Your minions cost (2) less, but not less than (1).\",\n\t\"cardCostModifier\": {\n\t\t\"class\": \"CardCostModifier\",\n\t\t\"cardType\": \"MINION\",\n\t\t\"value\": -2,\n\t\t\"minValue\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/minion_void_terror.json",
    "content": "{\n\t\"name\": \"Void Terror\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Battlecry: Destroy the minions on either side of this minion and gain their Attack and Health.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"attackBonus\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"ADJACENT_MINIONS\",\n\t\t\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t\t\t},\n\t\t\t\t\t\"hpBonus\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"ADJACENT_MINIONS\",\n\t\t\t\t\t\t\"attribute\": \"HP\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\t\t\"target\": \"ADJACENT_MINIONS\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/spell_bane_of_doom.json",
    "content": "{\n\t\"name\": \"Bane of Doom\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deal 2 damage to a character.  If that kills it, summon a random Demon.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"ConditionalEffectSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 2\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"DEMON\"\n\t\t\t}\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"IsDeadCondition\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/spell_demonfire.json",
    "content": "{\n\t\"name\": \"Demonfire\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 2 damage to a minion. If it's a friendly Demon, give it +2/+2 instead.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"EitherOrSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"hpBonus\": 2\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 2\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"AndCondition\",\n\t\t\t\"conditions\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"OwnedByPlayerCondition\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"RaceCondition\",\n\t\t\t\t\t\"race\": \"DEMON\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/spell_sense_demons.json",
    "content": "{\n\t\"name\": \"Sense Demons\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Put 2 random Demons from your deck into your hand.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"FromDeckToHandSpell\",\n\t\t\"value\": 2,\n\t\t\"card\": \"token_worthless_imp\",\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"race\": \"DEMON\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/spell_shadowflame.json",
    "content": "{\n\t\"name\": \"Shadowflame\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Destroy a friendly minion and deal its Attack damage to all enemy minions.\",\n\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DestroySpell\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"TARGET\",\n\t\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/spell_siphon_soul.json",
    "content": "{\n\t\"name\": \"Siphon Soul\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Destroy a minion. Restore 3 Health to your hero.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DestroySpell\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"value\": 3\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/spell_twisting_nether.json",
    "content": "{\n\t\"name\": \"Twisting Nether\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Destroy all minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DestroySpell\",\n\t\t\"target\": \"ALL_MINIONS\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/token_infernal.json",
    "content": "{\n\t\"name\": \"Infernal\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/token_worthless_imp.json",
    "content": "{\n\t\"name\": \"Worthless Imp\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"You are out of demons! At least there are always imps...\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warlock/weapon_blood_fury.json",
    "content": "{\n\t\"name\": \"Blood Fury\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 3,\n\t\"durability\": 8,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/minion_arathi_weaponsmith.json",
    "content": "{\n\t\"name\": \"Arathi Weaponsmith\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Equip a 2/2 weapon.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"EquipWeaponSpell\",\n\t\t\t\"card\": \"weapon_battle_axe\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/minion_armorsmith.json",
    "content": "{\n\t\"name\": \"Armorsmith\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever a friendly minion takes damage, gain 1 Armor.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"targetEntityType\": \"MINION\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffHeroSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"armorBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/minion_cruel_taskmaster.json",
    "content": "{\n\t\"name\": \"Cruel Taskmaster\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Deal 1 damage to a minion and give it +2 Attack.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"attackBonus\": 2\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/minion_frothing_berserker.json",
    "content": "{\n\t\"name\": \"Frothing Berserker\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever a minion takes damage, gain +1 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\"targetEntityType\": \"MINION\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/minion_grommash_hellscream.json",
    "content": "{\n\t\"name\": \"Grommash Hellscream\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 9,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Charge. Enrage: +6 Attack\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"EnrageChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"EnrageSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 6\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"CHARGE\": true,\n\t\t\"ENRAGABLE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/spell_battle_rage.json",
    "content": "{\n\t\"name\": \"Battle Rage\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Draw a card for each damaged friendly character.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"value\": {\n\t\t\t\"class\": \"EntityCounter\",\n\t\t\t\"target\": \"FRIENDLY_CHARACTERS\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"DamagedFilter\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/spell_brawl.json",
    "content": "{\n\t\"name\": \"Brawl\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Destroy all minions except one.  (chosen randomly)\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DestroyAllExceptOneSpell\",\n\t\t\"target\": \"ALL_MINIONS\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/spell_commanding_shout.json",
    "content": "{\n\t\"name\": \"Commanding Shout\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Your minions can't be reduced below 1 Health this turn. Draw a card.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\"attribute\": \"CANNOT_REDUCE_HP_BELOW_1\",\n\t\t\t\t\"revertTrigger\": {\n\t\t\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddSpellTriggerSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"trigger\": {\n\t\t\t\t\t\"eventTrigger\": {\n\t\t\t\t\t\t\"class\": \"BeforeMinionSummonedTrigger\",\n\t\t\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t\t\t},\n\t\t\t\t\t\"spell\": {\n\t\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\t\t\"attribute\": \"CANNOT_REDUCE_HP_BELOW_1\",\n\t\t\t\t\t\t\"revertTrigger\": {\n\t\t\t\t\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"oneTurn\": true\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/spell_inner_rage.json",
    "content": "{\n\t\"name\": \"Inner Rage\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 1 damage to a minion and give it +2 Attack.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"attackBonus\": 2\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/spell_mortal_strike.json",
    "content": "{\n\t\"name\": \"Mortal Strike\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 4 damage. If you have 12 or less Health, deal 6 instead.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": {\n\t\t\t\"class\": \"ConditionalValueProvider\",\n\t\t\t\"ifTrue\": 6,\n\t\t\t\"ifFalse\": 4,\n\t\t\t\"condition\": {\n\t\t\t\t\"class\": \"AttributeCondition\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"attribute\": \"HP\",\n\t\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\t\"value\": 12\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/spell_rampage.json",
    "content": "{\n\t\"name\": \"Rampage\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a damaged minion +3/+3.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"attackBonus\": 3,\n\t\t\"hpBonus\": 3,\n\t\t\"filter\": {\n\t\t\t\"class\": \"DamagedFilter\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/spell_shield_slam.json",
    "content": "{\n\t\"name\": \"Shield Slam\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deal 1 damage to a minion for each Armor you have.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"attribute\": \"ARMOR\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/spell_slam.json",
    "content": "{\n\t\"name\": \"Slam\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 2 damage to a minion. If it survives, draw a card.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"ConditionalEffectSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 2\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"value\": 1\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"IsDeadCondition\",\n\t\t\t\"invert\": true\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/spell_upgrade.json",
    "content": "{\n\t\"name\": \"Upgrade!\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"If you have a weapon, give it +1/+1.  Otherwise equip a 1/3 weapon.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"EitherOrSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"BuffWeaponSpell\",\n\t\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"EquipWeaponSpell\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"card\": \"weapon_heavy_axe\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HasWeaponCondition\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/weapon_battle_axe.json",
    "content": "{\n\t\"name\": \"Battle Axe\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 2,\n\t\"durability\": 2,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/weapon_gorehowl.json",
    "content": "{\n\t\"name\": \"Gorehowl\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 7,\n\t\"durability\": 1,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Attacking a minion costs 1 Attack instead of 1 Durability.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterPhysicalAttackTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\",\n\t\t\t\"sourceEntityType\": \"HERO\",\n\t\t\t\"targetEntityType\": \"MINION\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffWeaponSpell\",\n\t\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\t\"attackBonus\": -1,\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/classic/warrior/weapon_heavy_axe.json",
    "content": "{\n\t\"name\": \"Heavy Axe\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 1,\n\t\"durability\": 3,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/druid/minion_anodized_robo_cub.json",
    "content": "{\n\t\"name\": \"Anodized Robo Cub\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Taunt. Choose One - +1 Attack; or +1 Health.\",\n\t\"options\": [\n\t\t{\n\t\t\t\"description\": \"+1 Attack\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"attackBonus\": 1\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"description\": \"+1 Health\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"hpBonus\": 1\n\t\t\t}\n\t\t}\n\t],\n\t\"bothOptions\": {\n\t\t\"description\": \"+1/+1\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/druid/minion_druid_of_the_fang.json",
    "content": "{\n\t\"name\": \"Druid of the Fang\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: If you have a Beast, transform this minion into a 7/7.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"card\": \"token_cobra_form\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"BEAST\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/druid/minion_grove_tender.json",
    "content": "{\n\t\"name\": \"Grove Tender\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Choose One - Give each player a Mana Crystal; or Each player draws a card.\",\n\t\"options\": [\n\t\t{\n\t\t\t\"description\": \"Mana Crystal\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\t\"fullManaCrystals\": true,\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"description\": \"Draw\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t}\n\t],\n\t\"bothOptions\": {\n\t\t\"description\": \"Mana Crystal and Draw\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\t\t\"fullManaCrystals\": true,\n\t\t\t\t\t\"value\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/druid/minion_malorne.json",
    "content": "{\n\t\"name\": \"Malorne\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 9,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Deathrattle: Shuffle this minion into your deck.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ShuffleMinionToDeckSpell\",\n\t\t\"target\": \"SELF\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/druid/minion_mech-bear-cat.json",
    "content": "{\n\t\"name\": \"Mech-Bear-Cat\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Whenever this minion takes damage, add a Spare Part card to your hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveRandomCardSpell\",\n\t\t\t\"cards\": [\n\t\t\t\t\"spell_armor_plating\",\n\t\t\t\t\"spell_emergency_coolant\",\n\t\t\t\t\"spell_finicky_cloakfield\",\n\t\t\t\t\"spell_reversing_switch\",\n\t\t\t\t\"spell_rusty_horn\",\n\t\t\t\t\"spell_time_rewinder\",\n\t\t\t\t\"spell_whirling_blades\"\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/druid/spell_dark_wispers.json",
    "content": "{\n\t\"name\": \"Dark Wispers\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"CHOOSE_ONE\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Choose One - Summon 5 Wisps; or Give a minion +5/+5 and Taunt.\",\n\t\"targetSelection\": \"NONE\",\n\t\"options\": [\n\t\t\"spell_dark_wispers_1\",\n\t\t\"spell_dark_wispers_2\"\n\t],\n\t\"bothOptions\": \"spell_dark_wispers_3\",\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/druid/spell_dark_wispers_1.json",
    "content": "{\n\t\"name\": \"Summon 5 Wisps\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Summon 5 Wisps\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"minion_wisp\",\n\t\t\t\"minion_wisp\",\n\t\t\t\"minion_wisp\",\n\t\t\t\"minion_wisp\",\n\t\t\t\"minion_wisp\"\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/druid/spell_dark_wispers_2.json",
    "content": "{\n\t\"name\": \"+5/5 and Taunt\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"+5/5 and Taunt\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"attackBonus\": 5,\n\t\t\t\t\"hpBonus\": 5\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/druid/spell_dark_wispers_3.json",
    "content": "{\n\t\"name\": \"+5/5 and Taunt\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Summon 5 Wisps and give a minion +5/5 and Taunt\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\"cards\": [\n\t\t\t\t\t\"minion_wisp\",\n\t\t\t\t\t\"minion_wisp\",\n\t\t\t\t\t\"minion_wisp\",\n\t\t\t\t\t\"minion_wisp\",\n\t\t\t\t\t\"minion_wisp\"\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"attackBonus\": 5,\n\t\t\t\t\"hpBonus\": 5\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/druid/spell_recycle.json",
    "content": "{\n\t\"name\": \"Recycle\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Shuffle an enemy minion into your opponent's deck.\",\n\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"ShuffleMinionToDeckSpell\",\n\t\t\"targetPlayer\": \"OWNER\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/druid/spell_tree_of_life.json",
    "content": "{\n\t\"name\": \"Tree of Life\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Restore all characters to full Health.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"HealSpell\",\n\t\t\"target\": \"ALL_CHARACTERS\",\n\t\t\"value\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"attribute\": \"MAX_HP\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/druid/token_cobra_form.json",
    "content": "{\n\t\"name\": \"Druid Of The Fang (Cobra Form)\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/hunter/minion_gahzrilla.json",
    "content": "{\n\t\"name\": \"Gahz'rilla\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 9,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Whenever this minion takes damage, double its Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DoubleAttackSpell\",\n\t\t\t\"target\": \"SELF\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/hunter/minion_king_of_beasts.json",
    "content": "{\n\t\"name\": \"King of Beasts\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Taunt. Battlecry: Gain +1 Attack for each other Beast you have.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": {\n\t\t\t\t\"class\": \"EntityCounter\",\n\t\t\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\t\t\"filter\": {\n\t\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\t\"race\": \"BEAST\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/hunter/minion_metaltooth_leaper.json",
    "content": "{\n\t\"name\": \"Metaltooth Leaper\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Battlecry: Give your other Mechs +2 Attack.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"MECH\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/hunter/minion_steamwheedle_sniper.json",
    "content": "{\n\t\"name\": \"Steamwheedle Sniper\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Your Hero Power can target minions.\",\n\t\"attributes\": {\n\t\t\"HERO_POWER_CAN_TARGET_MINIONS\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/hunter/spell_call_pet.json",
    "content": "{\n\t\"name\": \"Call Pet\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Draw a card, if it's a Beast it costs (4) less.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DrawCardAndDoSomethingSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ConditionalSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"cardCostModifier\": {\n\t\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\t\"target\": \"EVENT_CARD\",\n\t\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\t\"value\": 4\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"condition\": {\n\t\t\t\t\"class\": \"RaceCondition\",\n\t\t\t\t\"race\": \"BEAST\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/hunter/spell_cobra_shot.json",
    "content": "{\n\t\"name\": \"Cobra Shot\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 3 damage to a minion and the enemy hero.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 3\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"target\": \"ENEMY_HERO\",\n\t\t\t\t\"value\": 3\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/hunter/spell_feign_death.json",
    "content": "{\n\t\"name\": \"Feign Death\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Trigger all Deathrattles on your minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"TriggerDeathrattleSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/hunter/weapon_glaivezooka.json",
    "content": "{\n\t\"name\": \"Glaivezooka\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 2,\n\t\"durability\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give a random friendly minion +1 Attack.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"randomTarget\": \"true\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/mage/minion_flame_leviathan.json",
    "content": "{\n\t\"name\": \"Flame Leviathan\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"MECH\",\n\t\"description\": \"When you draw this, deal 2 damage to all characters.\",\n\t\"passiveTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardDrawnTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ALL_CHARACTERS\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/mage/minion_goblin_blastmage.json",
    "content": "{\n\t\"name\": \"Goblin Blastmage\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: If you control a Mech, deal 4 damage randomly split among enemy characters.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\"howMany\": 4\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"MECH\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/mage/minion_snowchugger.json",
    "content": "{\n\t\"name\": \"Snowchugger\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Freeze any character damaged by this minion.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageCausedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\"attribute\": \"FROZEN\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/mage/minion_soot_spewer.json",
    "content": "{\n\t\"name\": \"Soot Spewer\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Spell Damage +1\",\n\t\"attributes\": {\n\t\t\"SPELL_DAMAGE\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/mage/minion_wee_spellstopper.json",
    "content": "{\n\t\"name\": \"Wee Spellstopper\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Adjacent minions can't be targeted by spells or Hero Powers.\",\n\t\"aura\": {\n\t\t\"class\": \"AttributeAura\",\n\t\t\"target\": \"ADJACENT_MINIONS\",\n\t\t\"attribute\": \"AURA_UNTARGETABLE_BY_SPELLS\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/mage/spell_echo_of_medivh.json",
    "content": "{\n\t\"name\": \"Echo of Medivh\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Put a copy of each friendly minion into your hand.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"PutCopyInHandSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/mage/spell_flamecannon.json",
    "content": "{\n\t\"name\": \"Flamecannon\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 4 damage to a random enemy minion.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\"value\": 4,\n\t\t\"randomTarget\": true\n\t},\n\t\"condition\": {\n\t\t\"class\": \"MinionCountCondition\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"operation\": \"GREATER\",\n\t\t\"value\": 0\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/mage/spell_unstable_portal.json",
    "content": "{\n\t\"name\": \"Unstable Portal\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Add a random minion to your hand. It costs (3) less.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ReceiveCardAndDoSomethingSpell\",\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"cardType\": \"MINION\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"target\": \"EVENT_CARD\",\n\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\"value\": 3\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_annoy-o-tron.json",
    "content": "{\n\t\"name\": \"Annoy-o-Tron\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Taunt. Divine Shield\",\n\t\"attributes\": {\n\t\t\"DIVINE_SHIELD\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_antique_healbot.json",
    "content": "{\n\t\"name\": \"Antique Healbot\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Battlecry: Restore 8 Health to your hero.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": 8\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_arcane_nullifier_x-21.json",
    "content": "{\n\t\"name\": \"Arcane Nullifier X-21\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Taunt. Can't be targeted by spells or Hero Powers.\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true,\n\t\t\"UNTARGETABLE_BY_SPELLS\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_blingtron_3000.json",
    "content": "{\n\t\"name\": \"Blingtron 3000\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Battlecry: Equip a random weapon for each player\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"EquipRandomWeaponSpell\",\n\t\t\t\"targetPlayer\": \"BOTH\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_bomb_lobber.json",
    "content": "{\n\t\"name\": \"Bomb Lobber\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Deal 4 damage to a random enemy minion.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\"value\": 4,\n\t\t\t\"howMany\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_burly_rockjaw_trogg.json",
    "content": "{\n\t\"name\": \"Burly Rockjaw Trogg\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Each time your opponent cast a spell, gain +2 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"OPPONENT\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_clockwork_giant.json",
    "content": "{\n\t\"name\": \"Clockwork Giant\",\n\t\"baseManaCost\": 12,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Costs (1) less Mana for each card in your opponent's hand.\",\n\t\"manaCostModifier\": {\n\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"playerAttribute\": \"HAND_COUNT\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_clockwork_gnome.json",
    "content": "{\n\t\"name\": \"Clockwork Gnome\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Deathrattle: Put a Spare Part card in your hand.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ReceiveRandomCardSpell\",\n\t\t\"cards\": [\n\t\t\t\"spell_armor_plating\",\n\t\t\t\"spell_emergency_coolant\",\n\t\t\t\"spell_finicky_cloakfield\",\n\t\t\t\"spell_reversing_switch\",\n\t\t\t\"spell_rusty_horn\",\n\t\t\t\"spell_time_rewinder\",\n\t\t\t\"spell_whirling_blades\"\n\t\t]\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_cogmaster.json",
    "content": "{\n\t\"name\": \"Cogmaster\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Has +2 Attack while you have a Mech.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"BoardChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ConditionalAttackBonusSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"ConditionalValueProvider\",\n\t\t\t\t\"ifTrue\": 2,\n\t\t\t\t\"ifFalse\": 0,\n\t\t\t\t\"condition\": {\n\t\t\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\t\t\"race\": \"MECH\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_dr_boom.json",
    "content": "{\n\t\"name\": \"Dr. Boom\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Summon two 1/1 Boom Bots. WARNING: Bots may explode.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_boom_bot\",\n\t\t\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_boom_bot\",\n\t\t\t\t\t\"boardPositionRelative\": \"LEFT\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_enhance-o_mechano.json",
    "content": "{\n\t\"name\": \"Enhance-o Mechano\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Battlecry: Give your other minions Windfury, Taunt, or Divine Shield. (at random)\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"RandomlyCastSpell\",\n\t\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"attribute\": \"WINDFURY\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"attribute\": \"DIVINE_SHIELD\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_explosive_sheep.json",
    "content": "{\n\t\"name\": \"Explosive Sheep\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Deathrattle: Deal 2 damage to all minions.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": 2\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_fel_reaver.json",
    "content": "{\n\t\"name\": \"Fel Reaver\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Whenever your opponent plays a card, discard the top 3 cards of your deck.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardPlayedTrigger\",\n\t\t\t\"targetPlayer\": \"OPPONENT\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscardCardsFromDeckSpell\",\n\t\t\t\"value\": 3\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_flying_machine.json",
    "content": "{\n\t\"name\": \"Flying Machine\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Windfury\",\n\t\"attributes\": {\n\t\t\"WINDFURY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_foe_reaper_4000.json",
    "content": "{\n\t\"name\": \"Foe Reaper 4000\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 9,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Also damages the minions next to whomever he attacks.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterPhysicalAttackTrigger\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"AdjacentEffectSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\"spell2\": {\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_force-tank_max.json",
    "content": "{\n\t\"name\": \"Force-Tank MAX\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Divine Shield\",\n\t\"attributes\": {\n\t\t\"DIVINE_SHIELD\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_gazlowe.json",
    "content": "{\n\t\"name\": \"Gazlowe\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Whenever you cast a 1-mana spell, add a random Mech to your hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"ComparisonCondition\",\n\t\t\t\t\"value1\": {\n\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\"playerAttribute\": \"LAST_MANA_COST\"\n\t\t\t\t},\n\t\t\t\t\"value2\": 1,\n\t\t\t\t\"operation\": \"EQUAL\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"MECH\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_gilblin_stalker.json",
    "content": "{\n\t\"name\": \"Gilblin Stalker\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Stealth\",\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_gnomeregan_infantry.json",
    "content": "{\n\t\"name\": \"Gnomeregan Infantry\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Charge. Taunt\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_gnomish_experimenter.json",
    "content": "{\n\t\"name\": \"Gnomish Experimenter\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Draw a card, if it is a minion turn it into a chicken.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardAndDoSomethingSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ConditionalSpell\",\n\t\t\t\t\"spell\": {\n\t\t\t\t\t\"class\": \"TransformCardSpell\",\n\t\t\t\t\t\"card\": \"token_chicken_gvg\"\n\t\t\t\t},\n\t\t\t\t\"condition\": {\n\t\t\t\t\t\"class\": \"CardPropertyCondition\",\n\t\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_goblin_sapper.json",
    "content": "{\n\t\"name\": \"Goblin Sapper\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Has +4 Attack while your opponent has 6 or more cards in hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"GameStateChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ConditionalAttackBonusSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"ConditionalValueProvider\",\n\t\t\t\t\"ifTrue\": 4,\n\t\t\t\t\"ifFalse\": 0,\n\t\t\t\t\"condition\": {\n\t\t\t\t\t\"class\": \"CardCountCondition\",\n\t\t\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\t\"value\": 6\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_hemet_nesingwary.json",
    "content": "{\n\t\"name\": \"Hemet Nesingwary\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Destroy a Beast.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"BEAST\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_hobgoblin.json",
    "content": "{\n\t\"name\": \"Hobgoblin\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Whenever you play a 1-Attack minion, give it +2/+2.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"BeforeMinionPlayedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"AttributeCondition\",\n\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\"operation\": \"EQUAL\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"hpBonus\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_illuminator.json",
    "content": "{\n\t\"name\": \"Illuminator\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"If you control a Secret at the end of your turn, restore 4 health to your hero.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"fireCondition\": {\n\t\t\t\t\"class\": \"ControlsSecretCondition\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": 4\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_jeeves.json",
    "content": "{\n\t\"name\": \"Jeeves\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"At the end of each player's turn, that player draws until they have 3 cards.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"BOTH\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"targetPlayer\": \"ACTIVE\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\"value1\": 3,\n\t\t\t\t\"value2\": {\n\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\t\"playerAttribute\": \"HAND_COUNT\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_junkbot.json",
    "content": "{\n\t\"name\": \"Junkbot\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Whenever a friendly Mech dies, gain +2/+2.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionDeathTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"race\": \"MECH\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"hpBonus\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_kezan_mystic.json",
    "content": "{\n\t\"name\": \"Kezan Mystic\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Take control of a random enemy Secret.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"StealRandomSecretSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_lil_exorcist.json",
    "content": "{\n\t\"name\": \"Lil' Exorcist\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Taunt. Battlecry: Gain +1/+1 for each enemy Deathrattle minion.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"AttributeCounter\",\n\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\"attribute\": \"DEATHRATTLES\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_lost_tallstrider.json",
    "content": "{\n\t\"name\": \"Lost Tallstrider\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_madder_bomber.json",
    "content": "{\n\t\"name\": \"Madder Bomber\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Deal 6 damage randomly split between all other characters.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ALL_OTHER_CHARACTERS\",\n\t\t\t\"value\": 1,\n\t\t\t\"howMany\": 6\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_mechanical_yeti.json",
    "content": "{\n\t\"name\": \"Mechanical Yeti\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Deathrattle: Give each player a Spare Part.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ReceiveRandomCardSpell\",\n\t\t\"targetPlayer\": \"BOTH\",\n\t\t\"cards\": [\n\t\t\t\"spell_armor_plating\",\n\t\t\t\"spell_emergency_coolant\",\n\t\t\t\"spell_finicky_cloakfield\",\n\t\t\t\"spell_reversing_switch\",\n\t\t\t\"spell_rusty_horn\",\n\t\t\t\"spell_time_rewinder\",\n\t\t\t\"spell_whirling_blades\"\n\t\t]\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_mechwarper.json",
    "content": "{\n\t\"name\": \"Mechwarper\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Your Mechs cost (1) less.\",\n\t\"cardCostModifier\": {\n\t\t\"class\": \"CardCostModifier\",\n\t\t\"cardType\": \"MINION\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"race\": \"MECH\",\n\t\t\"value\": -1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_mekgineer_thermaplugg.json",
    "content": "{\n\t\"name\": \"Mekgineer Thermaplugg\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 9,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Whenever an enemy minion dies, summon a Leper Gnome.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionDeathTrigger\",\n\t\t\t\"targetPlayer\": \"OPPONENT\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"minion_leper_gnome\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_micro_machine.json",
    "content": "{\n\t\"name\": \"Micro Machine\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"At the start of each turn, gain +1 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"BOTH\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_mimirons_head.json",
    "content": "{\n\t\"name\": \"Mimiron's Head\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"MECH\",\n\t\"description\": \"At the start of your turn, if you have at least 3 Mechs, destroy them all, and form V-07-TR-0N.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"fireCondition\": {\n\t\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\t\"value\": 3,\n\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\t\"race\": \"MECH\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\t\t\"race\": \"MECH\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ForceDeathPhaseSpell\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_v-07-tr-0n\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_mini-mage.json",
    "content": "{\n\t\"name\": \"Mini-Mage\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Stealth. Spell Damage +1\",\n\t\"attributes\": {\n\t\t\"SPELL_DAMAGE\": 1,\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_mogor_the_ogre.json",
    "content": "{\n\t\"name\": \"Mogor the Ogre\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"All minions have a 50% chance to attack the wrong enemy.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\t\"actionType\": \"PHYSICAL_ATTACK\",\n\t\t\t\"sourcePlayer\": \"BOTH\",\n\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\"sourceEntityType\": \"MINION\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"RandomCondition\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"FumbleSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_ogre_brute.json",
    "content": "{\n\t\"name\": \"Ogre Brute\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"50% chance to attack the wrong enemy.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\t\"actionType\": \"PHYSICAL_ATTACK\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"RandomCondition\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"FumbleSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_piloted_shredder.json",
    "content": "{\n\t\"name\": \"Piloted Shredder\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Deathrattle: Summon a random 2-cost minion.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"manaCost\": 2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_piloted_sky_golem.json",
    "content": "{\n\t\"name\": \"Piloted Sky Golem\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Deathrattle: Summon a random 4-Cost minion.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"manaCost\": 4\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_puddlestomper.json",
    "content": "{\n\t\"name\": \"Puddlestomper\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_recombobulator.json",
    "content": "{\n\t\"name\": \"Recombobulator\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Transform a friendly minion into a random minion with the same Cost.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"TransformToRandomMinionSpell\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"manaCost\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"TARGET\",\n\t\t\t\t\t\"attribute\": \"BASE_MANA_COST\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_salty_dog.json",
    "content": "{\n\t\"name\": \"Salty Dog\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_ships_cannon.json",
    "content": "{\n\t\"name\": \"Ship's Cannon\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"After you summon a Pirate, deal 2 damage to a random enemy.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterMinionSummonedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"race\": \"PIRATE\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\"value\": 2,\n\t\t\t\"howMany\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_sneeds_old_shredder.json",
    "content": "{\n\t\"name\": \"Sneed's Old Shredder\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Deathrattle: Summon a random legendary minion.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"rarity\": \"LEGENDARY\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_spider_tank.json",
    "content": "{\n\t\"name\": \"Spider Tank\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_stonesplinter_trogg.json",
    "content": "{\n\t\"name\": \"Stonesplinter Trogg\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever your opponent casts a spell, gain +1 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"OPPONENT\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_target_dummy.json",
    "content": "{\n\t\"name\": \"Target Dummy\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_tinkertown_technician.json",
    "content": "{\n\t\"name\": \"Tinkertown Technician\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: If you control a Mech, gain +1/+1 and put a Spare Part in your hand.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"attackBonus\": 1,\n\t\t\t\t\t\"hpBonus\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ReceiveRandomCardSpell\",\n\t\t\t\t\t\"cards\": [\n\t\t\t\t\t\t\"spell_armor_plating\",\n\t\t\t\t\t\t\"spell_emergency_coolant\",\n\t\t\t\t\t\t\"spell_finicky_cloakfield\",\n\t\t\t\t\t\t\"spell_reversing_switch\",\n\t\t\t\t\t\t\"spell_rusty_horn\",\n\t\t\t\t\t\t\"spell_time_rewinder\",\n\t\t\t\t\t\t\"spell_whirling_blades\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"MECH\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_toshley.json",
    "content": "{\n\t\"name\": \"Toshley\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry and Deathrattle: Add a Spare Part to your hand.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveRandomCardSpell\",\n\t\t\t\"cards\": [\n\t\t\t\t\"spell_armor_plating\",\n\t\t\t\t\"spell_emergency_coolant\",\n\t\t\t\t\"spell_finicky_cloakfield\",\n\t\t\t\t\"spell_reversing_switch\",\n\t\t\t\t\"spell_rusty_horn\",\n\t\t\t\t\"spell_time_rewinder\",\n\t\t\t\t\"spell_whirling_blades\"\n\t\t\t]\n\t\t}\n\t},\n\t\"deathrattle\": {\n\t\t\"class\": \"ReceiveRandomCardSpell\",\n\t\t\"cards\": [\n\t\t\t\"spell_armor_plating\",\n\t\t\t\"spell_emergency_coolant\",\n\t\t\t\"spell_finicky_cloakfield\",\n\t\t\t\"spell_reversing_switch\",\n\t\t\t\"spell_rusty_horn\",\n\t\t\t\"spell_time_rewinder\",\n\t\t\t\"spell_whirling_blades\"\n\t\t]\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/minion_troggzor_the_earthinator.json",
    "content": "{\n\t\"name\": \"Troggzor the Earthinator\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Whenever your opponent casts a spell, summon a Burly Rockjaw Trogg.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"OPPONENT\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"minion_burly_rockjaw_trogg\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/token_boom_bot.json",
    "content": "{\n\t\"name\": \"Boom Bot\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Deathrattle: Deal 1-4 damage to a random enemy.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"MissilesSpell\",\n\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\"value\": {\n\t\t\t\"class\": \"RandomValueProvider\",\n\t\t\t\"min\": 1,\n\t\t\t\"max\": 4\n\t\t},\n\t\t\"howMany\": 1\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/token_chicken_gvg.json",
    "content": "{\n\t\"name\": \"Chicken\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/neutral/token_v-07-tr-0n.json",
    "content": "{\n\t\"name\": \"V-07-TR-0N\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Charge. Mega-Windfury (Can attack four times a turn.)\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true,\n\t\t\"MEGA_WINDFURY\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/paladin/minion_bolvar_fordragon.json",
    "content": "{\n\t\"name\": \"Bolvar Fordragon\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Whenever a friendly minion dies while this in your hand, gain +1 Attack.\",\n\t\"passiveTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionDeathTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"ATTACK_BONUS\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/paladin/minion_cobalt_guardian.json",
    "content": "{\n\t\"name\": \"Cobalt Guardian\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Whenever you summon a Mech, gain Divine Shield.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionSummonedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"race\": \"MECH\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"DIVINE_SHIELD\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/paladin/minion_quartermaster.json",
    "content": "{\n\t\"name\": \"Quartermaster\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Give your Silver Hand Recruits +2/+2.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"hpBonus\": 2,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\"cardId\": \"token_silver_hand_recruit\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/paladin/minion_scarlet_purifier.json",
    "content": "{\n\t\"name\": \"Scarlet Purifier\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Deal 2 damage to all minions with Deathrattle.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\"value\": 2,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"DEATHRATTLES\",\n\t\t\t\t\"operation\": \"HAS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/paladin/minion_shielded_minibot.json",
    "content": "{\n\t\"name\": \"Shielded Minibot\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Divine Shield\",\n\t\"attributes\": {\n\t\t\"DIVINE_SHIELD\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/paladin/spell_muster_for_battle.json",
    "content": "{\n\t\"name\": \"Muster for Battle\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Summon three 1/1 Silver Hand Recruits. Equip a 1/4 Weapon.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\"cards\": [\n\t\t\t\t\t\"token_silver_hand_recruit\",\n\t\t\t\t\t\"token_silver_hand_recruit\",\n\t\t\t\t\t\"token_silver_hand_recruit\"\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"EquipWeaponSpell\",\n\t\t\t\t\"card\": \"weapon_lights_justice\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/paladin/spell_seal_of_light.json",
    "content": "{\n\t\"name\": \"Seal of Light\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Restore 4 Health to your hero and gain +2 Attack this turn.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"value\": 4\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"BuffHeroSpell\",\n\t\t\t\t\"attackBonus\": 2\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/paladin/weapon_coghammer.json",
    "content": "{\n\t\"name\": \"Coghammer\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 2,\n\t\"durability\": 3,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Give a random friendly minion Divine Shield and Taunt.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"attribute\": \"DIVINE_SHIELD\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/priest/minion_shadowbomber.json",
    "content": "{\n\t\"name\": \"Shadowbomber\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Deal 3 damage to each hero.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\"target\": \"ENEMY_HERO\",\n\t\t\t\t\t\"value\": 3\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\t\"value\": 3\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/priest/minion_shadowboxer.json",
    "content": "{\n\t\"name\": \"Shadowboxer\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Whenever a character is healed, deal 1 damage to a random enemy.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"HealingTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\"value\": 1,\n\t\t\t\"howMany\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/priest/minion_shrinkmeister.json",
    "content": "{\n\t\"name\": \"Shrinkmeister\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give a minion -2 Attack this turn.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"TemporaryAttackSpell\",\n\t\t\t\"value\": -2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/priest/minion_upgraded_repair_bot.json",
    "content": "{\n\t\"name\": \"Upgraded Repair Bot\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Battlecry: Give a friendly Mech +4 Health\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"hpBonus\": 4,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"MECH\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/priest/minion_voljin.json",
    "content": "{\n\t\"name\": \"Vol'jin\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Swap Health with another minion.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SwapHpSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/priest/spell_light_of_the_naaru.json",
    "content": "{\n\t\"name\": \"Light of the Naaru\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Restore 3 Health. If the target is still damaged, summon a Lightwarden.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"ConditionalEffectSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"value\": 3\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"minion_lightwarden\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"IsDamagedCondition\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/priest/spell_lightbomb.json",
    "content": "{\n\t\"name\": \"Lightbomb\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deal damage to each minion equal to its Attack.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"attribute\": \"ATTACK\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/priest/spell_velens_chosen.json",
    "content": "{\n\t\"name\": \"Velen's Chosen\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a minion +2/+4 and Spell Damage +1\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"attackBonus\": 2,\n\t\t\t\t\"hpBonus\": 4\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\"attribute\": \"SPELL_DAMAGE\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/rogue/minion_goblin_auto-barber.json",
    "content": "{\n\t\"name\": \"Goblin Auto-Barber\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Battlecry: Give your weapon +1 Attack.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffWeaponSpell\",\n\t\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/rogue/minion_iron_sensei.json",
    "content": "{\n\t\"name\": \"Iron Sensei\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"At the end of your turn, give another friendly Mech +2/+2.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"hpBonus\": 2,\n\t\t\t\"randomTarget\": true,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"MECH\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/rogue/minion_ogre_ninja.json",
    "content": "{\n\t\"name\": \"Ogre Ninja\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Stealth. 50% chance to attack the wrong enemy.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\t\"actionType\": \"PHYSICAL_ATTACK\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"RandomCondition\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"FumbleSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/rogue/minion_one-eyed_cheat.json",
    "content": "{\n\t\"name\": \"One-eyed Cheat\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Whenever you summon a Pirate, gain Stealth.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionSummonedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"race\": \"PIRATE\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"STEALTH\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/rogue/minion_trade_prince_gallywix.json",
    "content": "{\n\t\"name\": \"Trade Prince Gallywix\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Whenever your opponent casts a spell, gain a copy of it and give them a Coin.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"OPPONENT\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"CardPropertyCondition\",\n\t\t\t\t\"cardId\": \"spell_gallywixs_coin\",\n\t\t\t\t\"invert\": true\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"CopyCardSpell\",\n\t\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\t\t\"card\": \"spell_gallywixs_coin\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/rogue/spell_gallywixs_coin.json",
    "content": "{\n\t\"name\": \"Gallywix's Coin\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Gain 1 Mana Crystal this turn only. (Won't trigger Gallywix.)\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"GainManaSpell\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/rogue/spell_sabotage.json",
    "content": "{\n\t\"name\": \"Sabotage\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Destroy a random enemy minion. Combo: And your opponent's weapon.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ComboSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\"randomTarget\": true\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"target\": \"ENEMY_WEAPON\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/rogue/spell_tinkers_sharpsword_oil.json",
    "content": "{\n\t\"name\": \"Tinker's Sharpsword Oil\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give your weapon +3 Attack. Combo: Give a random friendly minion +3 Attack.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ComboSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"BuffWeaponSpell\",\n\t\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\t\"attackBonus\": 3\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\"attackBonus\": 3,\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/rogue/weapon_cogmasters_wrench.json",
    "content": "{\n\t\"name\": \"Cogmaster's Wrench\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 1,\n\t\"durability\": 3,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Has +2 Attack while you have a Mech.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"BoardChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ConditionalAttackBonusSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"ConditionalValueProvider\",\n\t\t\t\t\"ifTrue\": 2,\n\t\t\t\t\"ifFalse\": 0,\n\t\t\t\t\"condition\": {\n\t\t\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\t\t\"race\": \"MECH\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/shaman/minion_dunemaul_shaman.json",
    "content": "{\n\t\"name\": \"Dunemaul Shaman\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Windfury, Overload: (1) 50% chance to attack the wrong enemy.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\t\"actionType\": \"PHYSICAL_ATTACK\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"RandomCondition\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"FumbleSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 1,\n\t\t\"WINDFURY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/shaman/minion_neptulon.json",
    "content": "{\n\t\"name\": \"Neptulon\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Add 4 random Murlocs to your hand. Overload: (3)\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"value\": 4,\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"MURLOC\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"OVERLOAD\": 3\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/shaman/minion_siltfin_spiritwalker.json",
    "content": "{\n\t\"name\": \"Siltfin Spiritwalker\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Whenever another friendly Murloc dies, draw a card. Overload: (1)\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionDeathTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"race\": \"MURLOC\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/shaman/minion_vitality_totem.json",
    "content": "{\n\t\"name\": \"Vitality Totem\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"TOTEM\",\n\t\"description\": \"At the end of your turn, restore 4 Health to your hero.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": 4\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/shaman/minion_whirling_zap-o-matic.json",
    "content": "{\n\t\"name\": \"Whirling Zap-o-matic\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Windfury\",\n\t\"attributes\": {\n\t\t\"WINDFURY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/shaman/spell_ancestors_call.json",
    "content": "{\n\t\"name\": \"Ancestor's Call\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Put a random minion from each player's hand into the battlefield.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"PutRandomMinionOnBoardSpell\",\n\t\t\"targetPlayer\": \"BOTH\",\n\t\t\"cardLocation\": \"HAND\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/shaman/spell_crackle.json",
    "content": "{\n\t\"name\": \"Crackle\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 3-6 damage. Overload: (1)\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": {\n\t\t\t\"class\": \"RandomValueProvider\",\n\t\t\t\"min\": 3,\n\t\t\t\"max\": 6\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/shaman/weapon_powermace.json",
    "content": "{\n\t\"name\": \"Powermace\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 3,\n\t\"durability\": 2,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Give a random friendly Mech +2/+2.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 2,\n\t\t\"hpBonus\": 2,\n\t\t\"randomTarget\": true,\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"MECH\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/spare_parts/spell_armor_plating.json",
    "content": "{\n\t\"name\": \"Armor Plating\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a minion +1 Health.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"hpBonus\": 1\n\t},\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/spare_parts/spell_emergency_coolant.json",
    "content": "{\n\t\"name\": \"Emergency Coolant\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Freeze a minion.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"attribute\": \"FROZEN\"\n\t},\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/spare_parts/spell_finicky_cloakfield.json",
    "content": "{\n\t\"name\": \"Finicky Cloakfield\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a friendly minion Stealth until your next turn.\",\n\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"attribute\": \"STEALTH\",\n\t\t\"revertTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/spare_parts/spell_reversing_switch.json",
    "content": "{\n\t\"name\": \"Reversing Switch\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Swap a minion's Attack and Health\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"SwapAttackAndHpSpell\"\n\t},\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/spare_parts/spell_rusty_horn.json",
    "content": "{\n\t\"name\": \"Rusty Horn\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a minion Taunt.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"attribute\": \"TAUNT\"\n\t},\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/spare_parts/spell_time_rewinder.json",
    "content": "{\n\t\"name\": \"Time Rewinder\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Return a friendly minion to your hand.\",\n\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"ReturnMinionToHandSpell\"\n\t},\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/spare_parts/spell_whirling_blades.json",
    "content": "{\n\t\"name\": \"Whirling Blades\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a minion +1 Attack.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"attackBonus\": 1\n\t},\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warlock/minion_anima_golem.json",
    "content": "{\n\t\"name\": \"Anima Golem\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 9,\n\t\"baseHp\": 9,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"MECH\",\n\t\"description\": \"At the end of each turn, destroy this minion if it's your only one.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\t\"targetPlayer\": \"OWNER\",\n\t\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"target\": \"SELF\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warlock/minion_fel_cannon.json",
    "content": "{\n\t\"name\": \"Fel Cannon\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"At the end of your turn, deal 2 damage to a non-Mech minion.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\"value\": 2,\n\t\t\t\"howMany\": 1,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"MECH\",\n\t\t\t\t\"invert\": true\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warlock/minion_floating_watcher.json",
    "content": "{\n\t\"name\": \"Floating Watcher\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Whenever your hero takes damage on your turn, gain +2/+2.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"targetEntityType\": \"HERO\",\n\t\t\t\"fireCondition\": {\n\t\t\t\t\"class\": \"OwnedByPlayerCondition\",\n\t\t\t\t\"targetPlayer\": \"ACTIVE\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"hpBonus\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warlock/minion_malganis.json",
    "content": "{\n\t\"name\": \"Mal'Ganis\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 9,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Your other Demons have +2/+2. Your hero is Immune.\",\n\t\"aura\": {\n\t\t\"class\": \"BuffAura\",\n\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 2,\n\t\t\"hpBonus\": 2,\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"DEMON\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"IMMUNE_HERO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warlock/minion_mistress_of_pain.json",
    "content": "{\n\t\"name\": \"Mistress of Pain\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Whenever this minion deals damage, restore that much Health to your hero.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageCausedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\"attribute\": \"LAST_HIT\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warlock/spell_darkbomb.json",
    "content": "{\n\t\"name\": \"Darkbomb\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 3 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 3\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warlock/spell_demonheart.json",
    "content": "{\n\t\"name\": \"Demonheart\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deal 5 damage to a minion. If it's a friendly Demon, give it +5/+5 instead.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"EitherOrSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"attackBonus\": 5,\n\t\t\t\"hpBonus\": 5\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 5\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"AndCondition\",\n\t\t\t\"conditions\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"OwnedByPlayerCondition\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"RaceCondition\",\n\t\t\t\t\t\"race\": \"DEMON\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warlock/spell_imp-losion.json",
    "content": "{\n\t\"name\": \"Imp-losion\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 2-4 damage to a minion. Summon a 1/1 Imp for each damage dealt.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"RandomValueProvider\",\n\t\t\t\t\t\"min\": 2,\n\t\t\t\t\t\"max\": 4\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"attribute\": \"LAST_HIT\"\n\t\t\t\t},\n\t\t\t\t\"card\": \"token_imp\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warrior/minion_iron_juggernaut.json",
    "content": "{\n\t\"name\": \"Iron Juggernaut\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Battlecry: Shuffle a Mine into your opponent's deck. When drawn, it explodes for 10 damage.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"ShuffleToDeckSpell\",\n\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\"card\": \"spell_burrowing_mine\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warrior/minion_screwjank_clunker.json",
    "content": "{\n\t\"name\": \"Screwjank Clunker\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Battlecry: Give a friendly Mech +2/+2.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"hpBonus\": 2,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"MECH\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warrior/minion_shieldmaiden.json",
    "content": "{\n\t\"name\": \"Shieldmaiden\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Gain 5 Armor.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffHeroSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"armorBonus\": 5\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warrior/minion_siege_engine.json",
    "content": "{\n\t\"name\": \"Siege Engine\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Whenever you gain Armor, give this minion +1 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"ArmorGainedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warrior/minion_warbot.json",
    "content": "{\n\t\"name\": \"Warbot\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Enrage: +1 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"EnrageChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"EnrageSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"ENRAGABLE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warrior/spell_bouncing_blade.json",
    "content": "{\n\t\"name\": \"Bouncing Blade\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deals 1 damage to a random minion. Repeat until a minion dies.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"CastRepeatedlySpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"howMany\": 80,\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 1\n\t\t},\n\t\t\"randomTarget\": true,\n\t\t\"condition\": {\n\t\t\t\"class\": \"IsDeadCondition\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warrior/spell_burrowing_mine.json",
    "content": "{\n\t\"name\": \"Burrowing Mine\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"When you draw this, it explodes. You take 10 damage and draw a card.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"NullSpell\"\n\t},\n\t\"passiveTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardDrawnTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\t\"value\": 10,\n\t\t\t\t\t\"ignoreSpellDamage\": true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"RemoveCardSpell\",\n\t\t\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DrawCardSpell\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warrior/spell_crush.json",
    "content": "{\n\t\"name\": \"Crush\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Destroy a minion. If you have a damaged minion, this costs (4) less.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DestroySpell\"\n\t},\n\t\"manaCostModifier\": {\n\t\t\"class\": \"ConditionalValueProvider\",\n\t\t\"ifTrue\": 4,\n\t\t\"ifFalse\": 0,\n\t\t\"condition\": {\n\t\t\t\"class\": \"HasEntityCondition\",\n\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"DamagedFilter\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/goblins_vs_gnomes/warrior/weapon_ogre_warmaul.json",
    "content": "{\n\t\"name\": \"Ogre Warmaul\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 4,\n\t\"durability\": 2,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"50% chance to attack the wrong enemy.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\t\"actionType\": \"PHYSICAL_ATTACK\",\n\t\t\t\"sourcePlayer\": \"SELF\",\n\t\t\t\"sourceEntityType\": \"HERO\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"RandomCondition\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"FumbleSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"GOBLINS_VS_GNOMES\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/hall_of_fame/minion_azure_drake.json",
    "content": "{\n\t\"name\": \"Azure Drake\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Spell Damage +1. Battlecry: Draw a card.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"SPELL_DAMAGE\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"HALL_OF_FAME\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/hall_of_fame/minion_captains_parrot.json",
    "content": "{\n\t\"name\": \"Captain's Parrot\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Put a random Pirate from your deck into your hand.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"FromDeckToHandSpell\",\n\t\t\t\"value\": 1,\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"PIRATE\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"HALL_OF_FAME\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/hall_of_fame/minion_elite_tauren_chieftain.json",
    "content": "{\n\t\"name\": \"Elite Tauren Chieftain\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Give both players the power to ROCK! (with a Power Chord card)\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveRandomCardSpell\",\n\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\"cards\": [\n\t\t\t\t\"spell_i_am_murloc\",\n\t\t\t\t\"spell_power_of_the_horde\",\n\t\t\t\t\"spell_rogues_do_it\"\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"HALL_OF_FAME\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/hall_of_fame/minion_gelbin_mekkatorque.json",
    "content": "{\n\t\"name\": \"Gelbin Mekkatorque\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Summon an AWESOME invention.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonRandomSpell\",\n\t\t\t\"cards\": [\n\t\t\t\t\"token_emboldener_3000\",\n\t\t\t\t\"token_homing_chicken\",\n\t\t\t\t\"token_poultryizer\",\n\t\t\t\t\"token_repair_bot\"\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"HALL_OF_FAME\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/hall_of_fame/minion_old_murk-eye.json",
    "content": "{\n\t\"name\": \"Old Murk-Eye\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Charge. Has +1 Attack for each other Murloc on the battlefield.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"BoardChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ConditionalAttackBonusSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"EntityCounter\",\n\t\t\t\t\"target\": \"ALL_OTHER_MINIONS\",\n\t\t\t\t\"filter\": {\n\t\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\t\"race\": \"MURLOC\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"HALL_OF_FAME\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/hall_of_fame/minion_ragnaros_the_firelord.json",
    "content": "{\n\t\"name\": \"Ragnaros the Firelord\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Can't Attack.  At the end of your turn, deal 8 damage to a random enemy.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\"value\": 8,\n\t\t\t\"howMany\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"CANNOT_ATTACK\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"HALL_OF_FAME\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/hall_of_fame/minion_sylvanas_windrunner.json",
    "content": "{\n\t\"name\": \"Sylvanas Windrunner\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Deathrattle: Take control of a random enemy minion.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"MindControlSpell\",\n\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\"randomTarget\": true\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"HALL_OF_FAME\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/hall_of_fame/spell_conceal.json",
    "content": "{\n\t\"name\": \"Conceal\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give your minions Stealth until your next turn.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"attribute\": \"STEALTH\",\n\t\t\"filter\": {\n\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\"attribute\": \"STEALTH\",\n\t\t\t\"operation\": \"HAS\",\n\t\t\t\"invert\": true\n\t\t},\n\t\t\"revertTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"HALL_OF_FAME\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/hall_of_fame/spell_ice_lance.json",
    "content": "{\n\t\"name\": \"Ice Lance\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Freeze a character. If it was already Frozen, deal 4 damage instead.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"EitherOrSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 4\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"attribute\": \"FROZEN\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"AttributeCondition\",\n\t\t\t\"attribute\": \"FROZEN\",\n\t\t\t\"operation\": \"HAS\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"HALL_OF_FAME\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/hall_of_fame/spell_power_overwhelming.json",
    "content": "{\n\t\"name\": \"Power Overwhelming\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a friendly minion +4/+4 until end of turn. Then, it dies. Horribly.\",\n\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"attackBonus\": 4,\n\t\t\t\t\"hpBonus\": 4\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddSpellTriggerSpell\",\n\t\t\t\t\"trigger\": {\n\t\t\t\t\t\"eventTrigger\": {\n\t\t\t\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t\t\t},\n\t\t\t\t\t\"spell\": {\n\t\t\t\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\t\t\t\"target\": \"SELF\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"HALL_OF_FAME\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_ancient_shade.json",
    "content": "{\n\t\"name\": \"Ancient Shade\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Shuffle an 'Ancient Curse' into your deck that deals 7 damage to you when drawn.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ShuffleToDeckSpell\",\n\t\t\t\"card\": \"spell_ancient_curse\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_animated_armor.json",
    "content": "{\n\t\"name\": \"Animated Armor\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Your hero can only take 1 damage at a time.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"PreDamageTrigger\",\n\t\t\t\"sourcePlayer\": \"BOTH\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"targetEntityType\": \"HERO\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyDamageSpell\",\n\t\t\t\"operation\": \"SET\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_anubisath_sentinel.json",
    "content": "{\n\t\"name\": \"Anubisath Sentinel\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Give a random friendly minion +3/+3.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"attackBonus\": 3,\n\t\t\"hpBonus\": 3,\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"randomTarget\": true\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_archthief_rafaam.json",
    "content": "{\n\t\"name\": \"Arch-Thief Rafaam\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Discover a powerful Artifact.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverCardSpell\",\n\t\t\t\"cards\": [\n\t\t\t\t\"spell_lantern_of_power\",\n\t\t\t\t\"spell_mirror_of_doom\",\n\t\t\t\t\"spell_timepiece_of_horror\"\n\t\t\t],\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ReceiveCardSpell\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_brann_bronzebeard.json",
    "content": "{\n\t\"name\": \"Brann Bronzebeard\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Your Battlecries trigger twice.\",\n\t\"attributes\": {\n\t\t\"DOUBLE_BATTLECRIES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_dark_peddler.json",
    "content": "{\n\t\"name\": \"Dark Peddler\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Discover a 1-Cost card.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t},\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"manaCost\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_desert_camel.json",
    "content": "{\n\t\"name\": \"Desert Camel\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Put a random 1-Cost minion from each deck into the battlefield.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"PutRandomMinionOnBoardSpell\",\n\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\"cardLocation\": \"DECK\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\",\n\t\t\t\t\"manaCost\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_djinni_of_zephyrs.json",
    "content": "{\n\t\"name\": \"Djinni of Zephyrs\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"After you target another friendly minion with a spell, cast it on this one.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterSpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_AS_TARGET\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"RecastSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"card\": \"PENDING_CARD\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_eerie_statue.json",
    "content": "{\n\t\"name\": \"Eerie Statue\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Can't attack unless it's the only minion on the battlefield.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"BoardChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"EitherOrSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"spell1\": {\n\t\t\t\t\"class\": \"RemoveAttributeSpell\",\n\t\t\t\t\"attribute\": \"CANNOT_ATTACK\"\n\t\t\t},\n\t\t\t\"spell2\": {\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"CANNOT_ATTACK\"\n\t\t\t},\n\t\t\t\"condition\": {\n\t\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\t\"operation\": \"EQUAL\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_elise_starseeker.json",
    "content": "{\n\t\"name\": \"Elise Starseeker\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Shuffle the 'Map to the Golden Monkey' into your deck.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ShuffleToDeckSpell\",\n\t\t\t\"card\": \"spell_map_to_the_golden_monkey\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_ethereal_conjurer.json",
    "content": "{\n\t\"name\": \"Ethereal Conjurer\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Discover a spell.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t},\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"SPELL\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_fierce_monkey.json",
    "content": "{\n\t\"name\": \"Fierce Monkey\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_fossilized_devilsaur.json",
    "content": "{\n\t\"name\": \"Fossilized Devilsaur\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: If you control a Beast, gain Taunt.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"TAUNT\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"BEAST\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_gorillabot_a3.json",
    "content": "{\n\t\"name\": \"Gorillabot A-3\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Battlecry: If you control another Mech, Discover a Mech.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t},\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\",\n\t\t\t\t\"race\": \"MECH\"\n\t\t\t}\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"MECH\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_huge_toad.json",
    "content": "{\n\t\"name\": \"Huge Toad\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Deathrattle: Deal 1 damage to a random enemy.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"MissilesSpell\",\n\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\"value\": 1,\n\t\t\"howMany\": 1\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_jeweled_scarab.json",
    "content": "{\n\t\"name\": \"Jeweled Scarab\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Discover a 3-Cost card.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t},\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"manaCost\": 3\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_jungle_moonkin.json",
    "content": "{\n\t\"name\": \"Jungle Moonkin\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Both players have Spell Damage +2\",\n\t\"attributes\": {\n\t\t\"OPPONENT_SPELL_DAMAGE\": 2,\n\t\t\"SPELL_DAMAGE\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_keeper_of_uldaman.json",
    "content": "{\n\t\"name\": \"Keeper of Uldaman\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Set a minion's Attack and Health to 3.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SetAttackSpell\",\n\t\t\t\t\t\"value\": 3\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SetHpSpell\",\n\t\t\t\t\t\"value\": 3\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_mounted_raptor.json",
    "content": "{\n\t\"name\": \"Mounted Raptor\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Deathrattle: Summon a random 1-Cost minion.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"manaCost\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_murloc_tinyfin.json",
    "content": "{\n\t\"name\": \"Murloc Tinyfin\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_museum_curator.json",
    "content": "{\n\t\"name\": \"Museum Curator\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Discover a Deathrattle card.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t},\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"attribute\": \"DEATHRATTLES\",\n\t\t\t\t\"operation\": \"HAS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_naga_sea_witch.json",
    "content": "{\n\t\"name\": \"Naga Sea Witch\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Your cards cost (5).\",\n\t\"cardCostModifier\": {\n\t\t\"class\": \"CardCostModifier\",\n\t\t\"operation\": \"SET\",\n\t\t\"value\": 5\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_obsidian_destroyer.json",
    "content": "{\n\t\"name\": \"Obsidian Destroyer\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"At the end of your turn, summon a 1/1 Scarab with Taunt.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_scarab\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_pit_snake.json",
    "content": "{\n\t\"name\": \"Pit Snake\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Destroy any minion damaged by this minion.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageCausedTrigger\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_reliquary_seeker.json",
    "content": "{\n\t\"name\": \"Reliquary Seeker\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: If you have 6 other minions, gain +4/+4.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 4,\n\t\t\t\"hpBonus\": 4\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"operation\": \"EQUAL\",\n\t\t\t\"value\": 7\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_reno_jackson.json",
    "content": "{\n\t\"name\": \"Reno Jackson\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: If your deck contains no duplicates, fully heal your Hero.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"attribute\": \"MAX_HP\"\n\t\t\t}\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HighlanderDeckCondition\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_rumbling_elemental.json",
    "content": "{\n\t\"name\": \"Rumbling Elemental\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"After you play a Battlecry minion, deal 2 damage to a random enemy.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterMinionPlayedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"requiredAttribute\": \"BATTLECRY\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\"value\": 2,\n\t\t\t\"howMany\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_sir_finley_mrrgglton.json",
    "content": "{\n\t\"name\": \"Sir Finley Mrrgglton\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Battlecry: Discover a new basic Hero Power.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverCardSpell\",\n\t\t\t\"cards\": [\n\t\t\t\t\"hero_power_shapeshift\",\n\t\t\t\t\"hero_power_steady_shot\",\n\t\t\t\t\"hero_power_fireblast\",\n\t\t\t\t\"hero_power_reinforce\",\n\t\t\t\t\"hero_power_lesser_heal\",\n\t\t\t\t\"hero_power_dagger_mastery\",\n\t\t\t\t\"hero_power_totemic_call\",\n\t\t\t\t\"hero_power_life_tap\",\n\t\t\t\t\"hero_power_armor_up\"\n\t\t\t],\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ChangeHeroPowerSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t},\n\t\t\t\"cannotReceiveOwned\": true\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_summoning_stone.json",
    "content": "{\n\t\"name\": \"Summoning Stone\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever you cast a spell, summon a minion of the same Cost.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"manaCost\": {\n\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\"playerAttribute\": \"LAST_MANA_COST\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_tomb_pillager.json",
    "content": "{\n\t\"name\": \"Tomb Pillager\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Add a Coin to your Hand.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\"card\": \"spell_the_coin\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_tomb_spider.json",
    "content": "{\n\t\"name\": \"Tomb Spider\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Discover a Beast.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t},\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"BEAST\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_tunnel_trogg.json",
    "content": "{\n\t\"name\": \"Tunnel Trogg\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever you Overload, gain +1 Attack per locked Mana Crystal.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"OverloadTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\"attribute\": \"OVERLOAD\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_unearthed_raptor.json",
    "content": "{\n\t\"name\": \"Unearthed Raptor\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Choose a friendly minion. Gain a copy of its Deathrattle effect.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"CopyDeathrattleSpell\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"DEATHRATTLES\",\n\t\t\t\t\"operation\": \"HAS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/minion_wobbling_runts.json",
    "content": "{\n\t\"name\": \"Wobbling Runts\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deathrattle: Summon three 2/2 Runts.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_grumbly_runt\",\n\t\t\t\"token_rascally_runt\",\n\t\t\t\"token_wily_runt\"\n\t\t]\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/secret_dart_trap.json",
    "content": "{\n\t\"name\": \"Dart Trap\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Secret: When an opposing Hero Power is used, deal 5 damage to a random enemy.\",\n\t\"targetSelection\": \"NONE\",\n\t\"trigger\": {\n\t\t\"class\": \"InspireTrigger\",\n\t\t\"targetPlayer\": \"OPPONENT\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"MissilesSpell\",\n\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\"value\": 5,\n\t\t\"howMany\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/secret_sacred_trial.json",
    "content": "{\n\t\"name\": \"Sacred Trial\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Secret: When your opponent has at least 3 or more minions and plays another, destroy it.\",\n\t\"targetSelection\": \"NONE\",\n\t\"trigger\": {\n\t\t\"class\": \"MinionPlayedTrigger\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"queueCondition\": {\n\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\"operation\": \"GREATER\",\n\t\t\t\"value\": 3\n\t\t}\n\t},\n\t\"spell\": {\n\t\t\"class\": \"DestroySpell\",\n\t\t\"target\": \"EVENT_TARGET\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_ancient_curse.json",
    "content": "{\n\t\"name\": \"Ancient Curse\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"When you draw this, take 7 damage and draw a card.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"NullSpell\"\n\t},\n\t\"passiveTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardDrawnTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\t\"value\": 7,\n\t\t\t\t\t\"ignoreSpellDamage\": true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"RemoveCardSpell\",\n\t\t\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DrawCardSpell\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_anyfin_can_happen.json",
    "content": "{\n\t\"name\": \"Anyfin Can Happen\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Summon 7 Murlocs that died this game.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ResurrectFromBothSpell\",\n\t\t\"value\": 7,\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"race\": \"MURLOC\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_curse_of_rafaam.json",
    "content": "{\n\t\"name\": \"Curse of Rafaam\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give your opponent a 'Cursed!' card. While they hold it, they take 2 damage on their turn.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"card\": \"spell_cursed\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_cursed.json",
    "content": "{\n\t\"name\": \"Cursed!\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"While this is in your hand, take 2 damage at the start of your turn.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"NullSpell\"\n\t},\n\t\"passiveTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": 2,\n\t\t\t\"ignoreSpellDamage\": true\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_entomb.json",
    "content": "{\n\t\"name\": \"Entomb\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Choose an enemy minion. Shuffle it into your deck.\",\n\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"ShuffleMinionToDeckSpell\",\n\t\t\"targetPlayer\": \"SELF\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_everyfin_is_awesome.json",
    "content": "{\n\t\"name\": \"Everyfin is Awesome\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Give your minions +2/+2. Costs (1) less for each Murloc you control.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 2,\n\t\t\"hpBonus\": 2\n\t},\n\t\"manaCostModifier\": {\n\t\t\"class\": \"EntityCounter\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"MURLOC\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_excavated_evil.json",
    "content": "{\n\t\"name\": \"Excavated Evil\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 3 damage to all minions. Shuffle this card into your opponent's deck.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\t\"value\": 3\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"ShuffleToDeckSpell\",\n\t\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\t\"card\": \"spell_excavated_evil\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_explorers_hat.json",
    "content": "{\n\t\"name\": \"Explorer's Hat\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Give a minion +1/+1 and \\\"Deathrattle: Add an Explorer's Hat to your Hand.\\\"\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"attackBonus\": 1,\n\t\t\t\t\"hpBonus\": 1\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddDeathrattleSpell\",\n\t\t\t\t\"spell\": {\n\t\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\t\"card\": \"spell_explorers_hat\"\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_forgotten_torch.json",
    "content": "{\n\t\"name\": \"Forgotten Torch\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 3 damage. Shuffle a 'Roaring Torch' into your deck that deals 6 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 3\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"ShuffleToDeckSpell\",\n\t\t\t\t\"target\": \"NONE\",\n\t\t\t\t\"card\": \"spell_roaring_torch\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_lantern_of_power.json",
    "content": "{\n\t\"name\": \"Lantern of Power\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a minion +10/+10.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"attackBonus\": 10,\n\t\t\"hpBonus\": 10\n\t},\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_map_to_the_golden_monkey.json",
    "content": "{\n\t\"name\": \"Map to the Golden Monkey\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Shuffle the Golden Monkey into your Deck. Draw a card.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"ShuffleToDeckSpell\",\n\t\t\t\t\"card\": \"token_golden_monkey\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_mirror_of_doom.json",
    "content": "{\n\t\"name\": \"Mirror of Doom\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Fill your board with 3/3 Mummy Zombies.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_mummy_zombie\",\n\t\t\t\"token_mummy_zombie\",\n\t\t\t\"token_mummy_zombie\",\n\t\t\t\"token_mummy_zombie\",\n\t\t\t\"token_mummy_zombie\",\n\t\t\t\"token_mummy_zombie\",\n\t\t\t\"token_mummy_zombie\"\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_raven_idol.json",
    "content": "{\n\t\"name\": \"Raven Idol\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"CHOOSE_ONE\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Choose One - Discover a minion; or Discover a spell.\",\n\t\"targetSelection\": \"NONE\",\n\t\"options\": [\n\t\t\"spell_raven_idol_1\",\n\t\t\"spell_raven_idol_2\"\n\t],\n\t\"bothOptions\": \"spell_raven_idol_3\",\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_raven_idol_1.json",
    "content": "{\n\t\"name\": \"Discover a minion\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Discover a minion.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"cardType\": \"MINION\"\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_raven_idol_2.json",
    "content": "{\n\t\"name\": \"Discover a spell\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Discover a spell.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"cardType\": \"SPELL\"\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_raven_idol_3.json",
    "content": "{\n\t\"name\": \"Discover a minion and a spell\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Discover a minion. Discover a spell.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\t\t\"spell\": {\n\t\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t\t},\n\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\t\t\"spell\": {\n\t\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t\t},\n\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"cardType\": \"SPELL\"\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_roaring_torch.json",
    "content": "{\n\t\"name\": \"Roaring Torch\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 6 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 6\n\t},\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/spell_timepiece_of_horror.json",
    "content": "{\n\t\"name\": \"Timepiece of Horror\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 10 damage randomly split among all enemies.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MissilesSpell\",\n\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\"value\": 1,\n\t\t\"howMany\": 10\n\t},\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/token_golden_monkey.json",
    "content": "{\n\t\"name\": \"Golden Monkey\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Taunt. Battlecry: Replace your hand and deck with Legendary minions.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ReplaceCardLocationSpell\",\n\t\t\t\t\t\"cardLocation\": \"HAND\",\n\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\"cardType\": \"MINION\",\n\t\t\t\t\t\t\"rarity\": \"LEGENDARY\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ReplaceCardLocationSpell\",\n\t\t\t\t\t\"cardLocation\": \"DECK\",\n\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\"cardType\": \"MINION\",\n\t\t\t\t\t\t\"rarity\": \"LEGENDARY\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/token_grumbly_runt.json",
    "content": "{\n\t\"name\": \"Grumbly Runt\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/token_mummy_zombie.json",
    "content": "{\n\t\"name\": \"Mummy Zombie\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/token_rascally_runt.json",
    "content": "{\n\t\"name\": \"Rascally Runt\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/token_scarab.json",
    "content": "{\n\t\"name\": \"Scarab\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/token_wily_runt.json",
    "content": "{\n\t\"name\": \"Wily Runt\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/league_of_explorers/weapon_cursed_blade.json",
    "content": "{\n\t\"name\": \"Cursed Blade\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 2,\n\t\"durability\": 3,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Double all damage dealt to your hero.\",\n\t\"onEquip\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"attribute\": \"TAKE_DOUBLE_DAMAGE\"\n\t},\n\t\"onUnequip\": {\n\t\t\"class\": \"RemoveAttributeSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"attribute\": \"TAKE_DOUBLE_DAMAGE\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"LEAGUE_OF_EXPLORERS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/druid/minion_celestial_dreamer.json",
    "content": "{\n\t\"name\": \"Celestial Dreamer\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: If a friendly minion has 5 or more Attack, gain +2/+2.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"value\": 2,\n\t\t\t\"target\": \"SELF\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HasEntityCondition\",\n\t\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\"value\": 5\n\t\t\t} \n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/druid/minion_jade_behemoth.json",
    "content": "{\n\t\"name\": \"Jade Behemoth\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt. Battlecry: Summon a Jade Golem.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"CreateSummonSpell\",\n\t\t\t\"name\": \"Jade Golem\",\n\t\t\t\"attackBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"hpBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"mana\": {\n\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\"operation\": \"MAXIMUM\",\n\t\t\t\t\"value1\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"offset\": 1\n\t\t\t\t},\n\t\t\t\t\"value2\": 10\n\t\t\t},\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/druid/minion_kun_the_forgotten_king.json",
    "content": "{\n\t\"name\": \"Kun the Forgotten King\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Choose One - Gain 10 Armor; or Refresh your Mana Crystals.\",\n\t\"options\": [\n\t\t{\n\t\t\t\"description\": \"Gain 10 Armor\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"BuffHeroSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"armorBonus\": 10\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"description\": \"Refresh Mana\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"GainManaSpell\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\t\"playerAttribute\": \"MAX_MANA\"\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": {\n\t\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\t\"playerAttribute\": \"MANA\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t],\n\t\"bothOptions\": {\n\t\t\"description\": \"10 Armor and Refresh\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffHeroSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\t\"armorBonus\": 10\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"GainManaSpell\",\n\t\t\t\t\t\"value\": {\n\t\t\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\t\t\"playerAttribute\": \"MAX_MANA\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"value2\": {\n\t\t\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\t\t\"playerAttribute\": \"MANA\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/druid/minion_virmen_sensei.json",
    "content": "{\n\t\"name\": \"Virmen Sensei\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Give a friendly Beast +2/+2.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"value\": 2,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"BEAST\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/druid/spell_jade_blossom.json",
    "content": "{\n\t\"name\": \"Jade Blossom\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Summon a Jade Golem. Gain an empty Mana Crystal.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"CreateSummonSpell\",\n\t\t\t\t\"name\": \"Jade Golem\",\n\t\t\t\t\"attackBonus\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"offset\": 1\n\t\t\t\t},\n\t\t\t\t\"hpBonus\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"offset\": 1\n\t\t\t\t},\n\t\t\t\t\"mana\": {\n\t\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\t\"operation\": \"MAXIMUM\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\t\"offset\": 1\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 10\n\t\t\t\t},\n\t\t\t\t\"spell\": {\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/druid/spell_jade_idol.json",
    "content": "{\n\t\"name\": \"Jade Idol\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"CHOOSE_ONE\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Choose One - Summon a Jade Golem; or Shuffle 3 copies of this card into your deck.\",\n\t\"targetSelection\": \"NONE\",\n\t\"options\": [\n\t\t\"spell_jade_idol_1\",\n\t\t\"spell_jade_idol_2\"\n\t],\n\t\"bothOptions\": \"spell_jade_idol_3\",\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/druid/spell_jade_idol_1.json",
    "content": "{\n\t\"name\": \"Summon a Jade Golem\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Summon a Jade Golem.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"CreateSummonSpell\",\n\t\t\"name\": \"Jade Golem\",\n\t\t\"attackBonus\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\"offset\": 1\n\t\t},\n\t\t\"hpBonus\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\"offset\": 1\n\t\t},\n\t\t\"mana\": {\n\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\"operation\": \"MAXIMUM\",\n\t\t\t\"value1\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"value2\": 10\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/druid/spell_jade_idol_2.json",
    "content": "{\n\t\"name\": \"Shuffle 3 Copies\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Shuffle 3 copies of this into your deck.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ShuffleToDeckSpell\",\n\t\t\"value\": 3,\n\t\t\"target\": \"NONE\",\n\t\t\"card\": \"spell_jade_idol\"\n\t},\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/druid/spell_jade_idol_3.json",
    "content": "{\n\t\"name\": \"Summon Jade Golem and Add 3\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Summon a Jade Golem. Shuffle 3 copies of this card into your deck.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"ShuffleToDeckSpell\",\n\t\t\t\t\"value\": 3,\n\t\t\t\t\"target\": \"NONE\",\n\t\t\t\t\"card\": \"spell_jade_idol\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"CreateSummonSpell\",\n\t\t\t\t\"name\": \"Jade Golem\",\n\t\t\t\t\"attackBonus\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"offset\": 1\n\t\t\t\t},\n\t\t\t\t\"hpBonus\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"offset\": 1\n\t\t\t\t},\n\t\t\t\t\"mana\": {\n\t\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\t\"operation\": \"MAXIMUM\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\t\"offset\": 1\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 10\n\t\t\t\t},\n\t\t\t\t\"spell\": {\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/druid/spell_lunar_visions.json",
    "content": "{\n\t\"name\": \"Lunar Visions\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Draw 2 cards. Minions drawn cost (2) less.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DrawCardAndDoSomethingSpell\",\n\t\t\"value\": 2,\n\t\t\"spell\": {\n\t\t\t\"class\": \"ConditionalSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"cardCostModifier\": {\n\t\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\t\"target\": \"EVENT_CARD\",\n\t\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\t\"value\": 2\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"condition\": {\n\t\t\t\t\"class\": \"CardPropertyCondition\",\n\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/druid/spell_mark_of_the_lotus.json",
    "content": "{\n\t\"name\": \"Mark of the Lotus\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give your minions +1/+1.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/druid/spell_pilfered_power.json",
    "content": "{\n\t\"name\": \"Pilfered Power\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Gain an empty Mana Crystal for each friendly minion.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"EitherOrSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"card\": \"spell_excess_mana\"\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"EntityCounter\",\n\t\t\t\t\"target\": \"FRIENDLY_MINIONS\"\n\t\t\t}\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"AndCondition\",\n\t\t\t\"conditions\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ManaMaxedCondition\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"HasEntityCondition\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\"\n\t\t\t\t}\n\t\t\t]\n\t\t\t\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/grimy_goons/minion_don_hancho.json",
    "content": "{\n\t\"name\": \"Don Han'Cho\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"heroClasses\": [\n\t\t\"HUNTER\",\n\t\t\"PALADIN\",\n\t\t\"WARRIOR\"\n\t],\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Give a random minion in your hand +5/+5.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\"attackBonus\": 5,\n\t\t\t\"hpBonus\": 5,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t},\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/grimy_goons/minion_grimestreet_informant.json",
    "content": "{\n\t\"name\": \"Grimestreet Informant\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"heroClasses\": [\n\t\t\"HUNTER\",\n\t\t\"PALADIN\",\n\t\t\"WARRIOR\"\n\t],\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Discover a Hunter, Paladin, or Warrior card.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverFilteredCardSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t},\n\t\t\t\"cardFilters\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"heroClass\": \"HUNTER\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"heroClass\": \"PALADIN\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"heroClass\": \"WARRIOR\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/grimy_goons/minion_grimestreet_smuggler.json",
    "content": "{\n\t\"name\": \"Grimestreet Smuggler\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"heroClasses\": [\n\t\t\"HUNTER\",\n\t\t\"PALADIN\",\n\t\t\"WARRIOR\"\n\t],\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give a random minion in your hand +1/+1.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t},\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/hunter/minion_alleycat.json",
    "content": "{\n\t\"name\": \"Alleycat\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Summon a 1/1 Cat.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_tabbycat\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/hunter/minion_dispatch_kodo.json",
    "content": "{\n\t\"name\": \"Dispatch Kodo\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Deal damage equal to this minion's Attack.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/hunter/minion_knuckles.json",
    "content": "{\n\t\"name\": \"Knuckles\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"After this attacks a minion, it also hits the enemy Hero.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterPhysicalAttackTrigger\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ENEMY_HERO\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/hunter/minion_rat_pack.json",
    "content": "{\n\t\"name\": \"Rat Pack\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Deathrattle: Summon a number of 1/1 Rats equal to this minion's Attack.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_rat\",\n\t\t\"value\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"ATTACK\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/hunter/minion_shaky_zipgunner.json",
    "content": "{\n\t\"name\": \"Shaky Zipgunner\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Give a random minion in your hand +2/+2.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\"attackBonus\": 2,\n\t\t\"hpBonus\": 2,\n\t\t\"filter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"cardType\": \"MINION\"\n\t\t},\n\t\t\"randomTarget\": true\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/hunter/minion_trogg_beastrager.json",
    "content": "{\n\t\"name\": \"Trogg Beastrager\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Give a random Beast in your Hand +1/+1.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\",\n\t\t\t\t\"race\": \"BEAST\"\n\t\t\t},\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/hunter/secret_hidden_cache.json",
    "content": "{\n\t\"name\": \"Hidden Cache\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Secret: When your opponent plays a minion, give a random minion in your hand +2/+2.\",\n\t\"trigger\": {\n\t\t\"class\": \"MinionPlayedTrigger\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"fireCondition\": {\n\t\t\t\"class\": \"HoldsCardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t}\n\t\t}\n\t},\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\"attackBonus\": 2,\n\t\t\"hpBonus\": 2,\n\t\t\"filter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"cardType\": \"MINION\"\n\t\t},\n\t\t\"randomTarget\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/hunter/spell_smugglers_crate.json",
    "content": "{\n\t\"name\": \"Smuggler's Crate\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a random Beast in your hand +2/+2.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\"attackBonus\": 2,\n\t\t\"hpBonus\": 2,\n\t\t\"filter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"cardType\": \"MINION\",\n\t\t\t\"race\": \"BEAST\"\n\t\t},\n\t\t\"randomTarget\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/hunter/token_piranha.json",
    "content": "{\n\t\"name\": \"Piranha\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/hunter/token_rat.json",
    "content": "{\n\t\"name\": \"Rat\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/hunter/token_tabbycat.json",
    "content": "{\n\t\"name\": \"Tabbycat\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/hunter/weapon_piranha_launcher.json",
    "content": "{\n\t\"name\": \"Piranha Launcher\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 2,\n\t\"durability\": 4,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"After your Hero attacks, summon a 1/1 Piranha.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterPhysicalAttackTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\",\n\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\"sourceEntityType\": \"HERO\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_piranha\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/jade_lotus/minion_aya_blackpaw.json",
    "content": "{\n\t\"name\": \"Aya Blackpaw\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"heroClasses\": [\n\t\t\"DRUID\",\n\t\t\"ROGUE\",\n\t\t\"SHAMAN\"\n\t],\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry and Deathrattle: Summon a Jade Golem.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"CreateSummonSpell\",\n\t\t\t\"name\": \"Jade Golem\",\n\t\t\t\"attackBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"hpBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"mana\": {\n\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\"operation\": \"MAXIMUM\",\n\t\t\t\t\"value1\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"offset\": 1\n\t\t\t\t},\n\t\t\t\t\"value2\": 10\n\t\t\t},\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"deathrattle\": {\n\t\t\"class\": \"CreateSummonSpell\",\n\t\t\"name\": \"Jade Golem\",\n\t\t\"attackBonus\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\"offset\": 1\n\t\t},\n\t\t\"hpBonus\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\"offset\": 1\n\t\t},\n\t\t\"mana\": {\n\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\"operation\": \"MAXIMUM\",\n\t\t\t\"value1\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"value2\": 10\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/jade_lotus/minion_jade_spirit.json",
    "content": "{\n\t\"name\": \"Jade Spirit\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"heroClasses\": [\n\t\t\"DRUID\",\n\t\t\"ROGUE\",\n\t\t\"SHAMAN\"\n\t],\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Summon a Jade Golem.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"CreateSummonSpell\",\n\t\t\t\"name\": \"Jade Golem\",\n\t\t\t\"attackBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"hpBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"mana\": {\n\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\"operation\": \"MAXIMUM\",\n\t\t\t\t\"value1\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"offset\": 1\n\t\t\t\t},\n\t\t\t\t\"value2\": 10\n\t\t\t},\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/jade_lotus/minion_lotus_agents.json",
    "content": "{\n\t\"name\": \"Lotus Agents\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"heroClasses\": [\n\t\t\"DRUID\",\n\t\t\"ROGUE\",\n\t\t\"SHAMAN\"\n\t],\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Discover a Druid, Rogue, or Shaman card.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverFilteredCardSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t},\n\t\t\t\"cardFilters\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"heroClass\": \"DRUID\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"heroClass\": \"ROGUE\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"heroClass\": \"SHAMAN\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/kabal/minion_kabal_chemist.json",
    "content": "{\n\t\"name\": \"Kabal Chemist\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"heroClasses\": [\n\t\t\"MAGE\",\n\t\t\"PRIEST\",\n\t\t\"WARLOCK\"\n\t],\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Add a random Potion to your hand.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveRandomCardSpell\",\n\t\t\t\"cards\": [\n\t\t\t\t\"spell_volcanic_potion\",\n\t\t\t\t\"spell_dragonfire_potion\",\n\t\t\t\t\"spell_potion_of_madness\",\n\t\t\t\t\"spell_pint-size_potion\",\n\t\t\t\t\"spell_felfire_potion\",\n\t\t\t\t\"secret_potion_of_polymorph\",\n\t\t\t\t\"spell_bloodfury_potion\",\n\t\t\t\t\"spell_freezing_potion\",\n\t\t\t\t\"spell_greater_healing_potion\",\n\t\t\t\t\"spell_blastcrystal_potion\"\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/kabal/minion_kabal_courier.json",
    "content": "{\n\t\"name\": \"Kabal Courier\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"heroClasses\": [\n\t\t\"MAGE\",\n\t\t\"PRIEST\",\n\t\t\"WARLOCK\"\n\t],\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Discover a Mage, Priest, or Warlock card.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverFilteredCardSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t},\n\t\t\t\"cardFilters\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"heroClass\": \"MAGE\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"heroClass\": \"PRIEST\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"heroClass\": \"WARLOCK\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/kabal/minion_kazakus.json",
    "content": "{\n\t\"name\": \"Kazakus\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"heroClasses\": [\n\t\t\"MAGE\",\n\t\t\"PRIEST\",\n\t\t\"WARLOCK\"\n\t],\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: If your deck has no duplicates, create a custom spell.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverOptionSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"CreateCardSpell\",\n\t\t\t\t\t\"name\": \"Lesser Potion\",\n\t\t\t\t\t\"secondaryName\": \"Kazakus Potion\",\n\t\t\t\t\t\"description\": \"Create a 1-Cost Spell.\",\n\t\t\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\t\t\"mana\": 1,\n\t\t\t\t\t\"spells\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\t\t\"name\": \"Felbloom\",\n\t\t\t\t\t\t\t\"description\": \"Deal 2 damage to all minions.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\t\t\t\t\"value\": 2\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\t\t\"name\": \"Heart of Fire\",\n\t\t\t\t\t\t\t\"description\": \"Deal 3 damage.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"ANY\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"value\": 3\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\t\t\"name\": \"Icecap\",\n\t\t\t\t\t\t\t\"description\": \"Freeze a random enemy minion.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"attribute\": \"FROZEN\",\n\t\t\t\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\t\t\t\"randomTarget\": true\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"ResurrectSpell\",\n\t\t\t\t\t\t\t\"name\": \"Ichor of Undeath\",\n\t\t\t\t\t\t\t\"description\": \"Summon a friendly minion that died this game.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"value\": 1\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\t\t\"name\": \"Netherbloom\",\n\t\t\t\t\t\t\t\"description\": \"Summon a 2/2 Demon.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"card\": \"token_lesser_demon\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\t\t\"name\": \"Goldthorn\",\n\t\t\t\t\t\t\t\"description\": \"Give your minions +2 Health.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\t\t\"hpBonus\": 2\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"BuffHeroSpell\",\n\t\t\t\t\t\t\t\"name\": \"Stonescale Oil\",\n\t\t\t\t\t\t\t\"description\": \"Gain 4 Armor.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\t\t\t\"armorBonus\": 4\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\t\t\t\"name\": \"Kingsblood\",\n\t\t\t\t\t\t\t\"description\": \"Draw a card.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"value\": 1\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\t\t\t\"name\": \"Shadow Oil\",\n\t\t\t\t\t\t\t\"description\": \"Add a random Demon to your hand.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\t\t\t\t\"race\": \"DEMON\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\t\"exclusive\": true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"CreateCardSpell\",\n\t\t\t\t\t\"name\": \"Greater Potion\",\n\t\t\t\t\t\"secondaryName\": \"Kazakus Potion\",\n\t\t\t\t\t\"description\": \"Create a 5-Cost Spell.\",\n\t\t\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\t\t\"mana\": 5,\n\t\t\t\t\t\"spells\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\t\t\t\t\"name\": \"Mystic Wool\",\n\t\t\t\t\t\t\t\"description\": \"Transform a random enemy minion into a 1/1 Sheep.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\t\t\t\"card\": \"token_kabal_sheep\",\n\t\t\t\t\t\t\t\"randomTarget\": true\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\t\t\"name\": \"Felbloom\",\n\t\t\t\t\t\t\t\"description\": \"Deal 4 damage to all minions.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\t\t\t\t\"value\": 4\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\t\t\"name\": \"Heart of Fire\",\n\t\t\t\t\t\t\t\"description\": \"Deal 5 damage.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"ANY\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"value\": 5\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"MultiTargetSpell\",\n\t\t\t\t\t\t\t\"name\": \"Icecap\",\n\t\t\t\t\t\t\t\"description\": \"Freeze 2 random enemy minions.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\t\t\t\"value\": 2,\n\t\t\t\t\t\t\t\"spell\": {\n\t\t\t\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\t\t\t\"attribute\": \"FROZEN\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"ResurrectSpell\",\n\t\t\t\t\t\t\t\"name\": \"Ichor of Undeath\",\n\t\t\t\t\t\t\t\"description\": \"Summon 2 friendly minions that died this game.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"value\": 2\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\t\t\"name\": \"Netherbloom\",\n\t\t\t\t\t\t\t\"description\": \"Summon a 5/5 Demon.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"card\": \"token_greater_demon\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\t\t\"name\": \"Goldthorn\",\n\t\t\t\t\t\t\t\"description\": \"Give your minions +4 Health.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\t\t\"hpBonus\": 4\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"BuffHeroSpell\",\n\t\t\t\t\t\t\t\"name\": \"Stonescale Oil\",\n\t\t\t\t\t\t\t\"description\": \"Gain 7 Armor.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\t\t\t\"armorBonus\": 7\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\t\t\t\"name\": \"Kingsblood\",\n\t\t\t\t\t\t\t\"description\": \"Draw 2 cards.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"value\": 2\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\t\t\t\"name\": \"Shadow Oil\",\n\t\t\t\t\t\t\t\"description\": \"Add 2 random Demons to your hand.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"value\": 2,\n\t\t\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\t\t\t\t\"race\": \"DEMON\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\t\"exclusive\": true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"CreateCardSpell\",\n\t\t\t\t\t\"name\": \"Superior Potion\",\n\t\t\t\t\t\"secondaryName\": \"Kazakus Potion\",\n\t\t\t\t\t\"description\": \"Create a 10-Cost Spell.\",\n\t\t\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\t\t\"mana\": 10,\n\t\t\t\t\t\"spells\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\t\t\t\t\"name\": \"Mystic Wool\",\n\t\t\t\t\t\t\t\"description\": \"Transform all minions into 1/1 Sheep.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\t\t\t\t\"card\": \"token_sheep\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\t\t\"name\": \"Felbloom\",\n\t\t\t\t\t\t\t\"description\": \"Deal 6 damage to all minions.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\t\t\t\t\"value\": 6\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\t\t\"name\": \"Heart of Fire\",\n\t\t\t\t\t\t\t\"description\": \"Deal 8 damage.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"ANY\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"value\": 8\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"MultiTargetSpell\",\n\t\t\t\t\t\t\t\"name\": \"Icecap\",\n\t\t\t\t\t\t\t\"description\": \"Freeze 3 random enemy minions.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\t\t\t\"value\": 3,\n\t\t\t\t\t\t\t\"spell\": {\n\t\t\t\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\t\t\t\"attribute\": \"FROZEN\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"ResurrectSpell\",\n\t\t\t\t\t\t\t\"name\": \"Ichor of Undeath\",\n\t\t\t\t\t\t\t\"description\": \"Summon 3 friendly minions that died this game.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"value\": 3\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\t\t\"name\": \"Netherbloom\",\n\t\t\t\t\t\t\t\"description\": \"Summon an 8/8 Demon.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"card\": \"token_superior_demon\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\t\t\"name\": \"Goldthorn\",\n\t\t\t\t\t\t\t\"description\": \"Give your minions +6 Health.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\t\t\"hpBonus\": 6\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"BuffHeroSpell\",\n\t\t\t\t\t\t\t\"name\": \"Stonescale Oil\",\n\t\t\t\t\t\t\t\"description\": \"Gain 10 Armor.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\t\t\t\"armorBonus\": 10\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\t\t\t\"name\": \"Kingsblood\",\n\t\t\t\t\t\t\t\"description\": \"Draw 3 cards.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"value\": 3\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\t\t\t\"name\": \"Shadow Oil\",\n\t\t\t\t\t\t\t\"description\": \"Add 3 random Demons to your hand.\",\n\t\t\t\t\t\t\t\"targetSelection\": \"NONE\",\n\t\t\t\t\t\t\t\"cardDescType\": \"SPELL\",\n\t\t\t\t\t\t\t\"value\": 3,\n\t\t\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\t\t\t\t\"race\": \"DEMON\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\t\"exclusive\": true\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HighlanderDeckCondition\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/kabal/token_greater_demon.json",
    "content": "{\n\t\"name\": \"Kabal Demon\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/kabal/token_kabal_sheep.json",
    "content": "{\n\t\"name\": \"Sheep\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/kabal/token_lesser_demon.json",
    "content": "{\n\t\"name\": \"Kabal Demon\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/kabal/token_superior_demon.json",
    "content": "{\n\t\"name\": \"Kabal Demon\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/mage/hero_baaraxxus.json",
    "content": "{\n\t\"name\": \"Baaraxxus\",\n\t\"baseManaCost\": 1,\n\t\"heroPower\": \"hero_power_inferno\",\n\t\"type\": \"HERO\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"attributes\": {\n\t\t\"HP\": 1,\n\t\t\"MAX_HP\": 1\n\t},\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/mage/minion_cryomancer.json",
    "content": "{\n\t\"name\": \"Cryomancer\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Gain +2/+2 if an enemy is Frozen.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"value\": 2,\n\t\t\t\"target\": \"SELF\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HasEntityCondition\",\n\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"operation\": \"HAS\",\n\t\t\t\t\"attribute\": \"FROZEN\"\n\t\t\t} \n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/mage/minion_inkmaster_solia.json",
    "content": "{\n\t\"name\": \"Inkmaster Solia\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: If your deck has no duplicates, the next Spell you cast this turn costs (0).\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"OneTurnCostModifier\",\n\t\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\t\"operation\": \"SET\",\n\t\t\t\t\"value\": 0,\n\t\t\t\t\"expirationTrigger\": {\n\t\t\t\t\t\"class\": \"CardPlayedTrigger\",\n\t\t\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HighlanderDeckCondition\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/mage/minion_kabal_crystal_runner.json",
    "content": "{\n\t\"name\": \"Kabal Crystal Runner\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Costs (2) less for each Secret you've played this game.\",\n\t\"manaCostModifier\": {\n\t\t\"class\": \"CardsPlayedValueProvider\",\n\t\t\"filter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"operation\": \"HAS\",\n\t\t\t\"attribute\": \"SECRET\"\n\t\t},\n\t\t\"multiplier\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/mage/minion_kabal_lackey.json",
    "content": "{\n\t\"name\": \"Kabal Lackey\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: The next Secret you play this turn costs (0).\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"OneTurnCostModifier\",\n\t\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\t\"operation\": \"SET\",\n\t\t\t\t\"value\": 0,\n\t\t\t\t\"expirationTrigger\": {\n\t\t\t\t\t\"class\": \"SecretPlayedTrigger\"\n\t\t\t\t},\n\t\t\t\t\"requiredAttribute\": \"SECRET\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/mage/minion_manic_soulcaster.json",
    "content": "{\n\t\"name\": \"Manic Soulcaster\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Choose a friendly minion. Shuffle a copy into your deck.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ShuffleToDeckSpell\",\n\t\t\t\"howMany\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/mage/secret_potion_of_polymorph.json",
    "content": "{\n\t\"name\": \"Potion of Polymorph\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Secret: When your opponent plays a minion, transform it into a 1/1 Sheep.\",\n\t\"trigger\": {\n\t\t\"class\": \"MinionPlayedTrigger\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"queueCondition\": {\n\t\t\t\"class\": \"IsDeadCondition\",\n\t\t\t\"invert\": true\n\t\t},\n\t\t\"fireCondition\": {\n\t\t\t\"class\": \"CardPropertyCondition\",\n\t\t\t\"cardType\": \"HERO\",\n\t\t\t\"invert\": true\n\t\t}\n\t},\n\t\"spell\": {\n\t\t\"class\": \"TransformMinionSpell\",\n\t\t\"target\": \"EVENT_TARGET\",\n\t\t\"card\": \"token_kabal_sheep\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/mage/spell_freezing_potion.json",
    "content": "{\n\t\"name\": \"Freezing Potion\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Freeze a character.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"attribute\": \"FROZEN\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/mage/spell_greater_arcane_missiles.json",
    "content": "{\n\t\"name\": \"Greater Arcane Missiles\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Shoot three missiles at random enemies that deal 3 damage each.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MissilesSpell\",\n\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\"howMany\": 3,\n\t\t\"value\": 3\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/mage/spell_volcanic_potion.json",
    "content": "{\n\t\"name\": \"Volcanic Potion\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 2 damage to all minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_ancient_of_blossoms.json",
    "content": "{\n\t\"name\": \"Ancient of Blossoms\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_auctionmaster_beardo.json",
    "content": "{\n\t\"name\": \"Auctionmaster Beardo\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"After you cast a spell, refresh your Hero Power.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterSpellCastedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"RefreshHeroPowerSpell\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_backroom_bouncer.json",
    "content": "{\n\t\"name\": \"Backroom Bouncer\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever a friendly minion dies, gain +1 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionDeathTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_backstreet_leper.json",
    "content": "{\n\t\"name\": \"Backstreet Leper\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Deal 2 damage to the enemy hero.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_HERO\",\n\t\t\"value\": 2\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"CLASSIC\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_big-time_racketeer.json",
    "content": "{\n\t\"name\": \"Big-Time Racketeer\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Summon a 6/6 Ogre.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_ogre\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_blowgill_sniper.json",
    "content": "{\n\t\"name\": \"Blowgill Sniper\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Battlecry: Deal 1 damage.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_blubber_baron.json",
    "content": "{\n\t\"name\": \"Blubber Baron\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Whenever you summon a Battlecry minion while this is in your hand, gain +1/+1.\",\n\t\"passiveTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionSummonedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"requiredAttribute\": \"BATTLECRY\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_bomb_squad.json",
    "content": "{\n\t\"name\": \"Bomb Squad\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Deal 5 damage to an enemy minion. Deathrattle: Deal 5 damage to your hero.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 5\n\t\t}\n\t},\n\t\"deathrattle\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"value\": 5\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_burgly_bully.json",
    "content": "{\n\t\"name\": \"Burgly Bully\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Whenever your opponent casts a spell, add a Coin to your hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"OPPONENT\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"card\": \"spell_the_coin\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_daring_reporter.json",
    "content": "{\n\t\"name\": \"Daring Reporter\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever your opponent draws a card, gain +1/+1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardDrawnTrigger\",\n\t\t\t\"targetPlayer\": \"OPPONENT\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ConditionalSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"attackBonus\": 1,\n\t\t\t\t\"hpBonus\": 1\n\t\t\t},\n\t\t\t\"condition\": {\n\t\t\t\t\"class\": \"IsDeadCondition\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"invert\": true\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_defias_cleaner.json",
    "content": "{\n\t\"name\": \"Defias Cleaner\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Silence a minion with Deathrattle.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SilenceSpell\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"DEATHRATTLES\",\n\t\t\t\t\"operation\": \"HAS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_dirty_rat.json",
    "content": "{\n\t\"name\": \"Dirty Rat\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Taunt. Battlecry: Your opponent summons a random minion from their hand.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"PutRandomMinionOnBoardSpell\",\n\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\"cardLocation\": \"HAND\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_doppelgangster.json",
    "content": "{\n\t\"name\": \"Doppelgangster\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Summon 2 copies of this minion.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonCopySpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_fel_orc_soulfiend.json",
    "content": "{\n\t\"name\": \"Fel Orc Soulfiend\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"At the start of your turn, deal 2 damage to this minion.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_fight_promoter.json",
    "content": "{\n\t\"name\": \"Fight Promoter\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: If you control a minion with 6 or more Health, draw two cards.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"value\": 2\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HasEntityCondition\",\n\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"HP\",\n\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\"value\": 6\n\t\t\t} \n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_finja_the_flying_star.json",
    "content": "{\n\t\"name\": \"Finja, the Flying Star\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Stealth. Whenever this attacks and kills a minion, summon 2 Murlocs from your deck.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterPhysicalAttackTrigger\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\",\n\t\t\t\"fireCondition\": {\n\t\t\t\t\"class\": \"IsDeadCondition\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"PutRandomMinionOnBoardSpell\",\n\t\t\t\"value\": 2,\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"cardLocation\": \"DECK\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\",\n\t\t\t\t\"race\": \"MURLOC\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_friendly_bartender.json",
    "content": "{\n\t\"name\": \"Friendly Bartender\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"At the end of your turn, restore 1 Health to your hero.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_gadgetzan_socialite.json",
    "content": "{\n\t\"name\": \"Gadgetzan Socialite\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Restore 2 Health.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_genzo_the_shark.json",
    "content": "{\n\t\"name\": \"Genzo, the Shark\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Whenever this attacks, both players draw until they have 3 cards.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\t\"actionType\": \"PHYSICAL_ATTACK\",\n\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\"value1\": 3,\n\t\t\t\t\"value2\": {\n\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\t\"playerAttribute\": \"HAND_COUNT\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_grook_fu_master.json",
    "content": "{\n\t\"name\": \"Grook Fu Master\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Windfury\",\n\t\"attributes\": {\n\t\t\"WINDFURY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_hired_gun.json",
    "content": "{\n\t\"name\": \"Hired Gun\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_hozen_healer.json",
    "content": "{\n\t\"name\": \"Hozen Healer\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Restore a minion to full Health.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"TARGET\",\n\t\t\t\t\"attribute\": \"MAX_HP\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_kooky_chemist.json",
    "content": "{\n\t\"name\": \"Kooky Chemist\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Swap the Attack and Health of a minion.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SwapAttackAndHpSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_leatherclad_hogleader.json",
    "content": "{\n\t\"name\": \"Leatherclad Hogleader\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: If your opponent has 6 or more cards in hand, gain Charge.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"CHARGE\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"CardCountCondition\",\n\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\"value\": 6\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_madam_goya.json",
    "content": "{\n\t\"name\": \"Madam Goya\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Choose a friendly minion. Swap it with a minion in your deck.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"custom.MadamGoyaSpell\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"DeckContainsCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_mayor_noggenfogger.json",
    "content": "{\n\t\"name\": \"Mayor Noggenfogger\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"All targets are chosen randomly.\",\n\t\"triggers\": [\n\t\t{\n\t\t\t\"eventTrigger\": {\n\t\t\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\t\t\"actionType\": \"PHYSICAL_ATTACK\",\n\t\t\t\t\"sourcePlayer\": \"BOTH\",\n\t\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\t\"sourceEntityType\": \"MINION\"\n\t\t\t},\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"RandomAttackTargetSpell\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"eventTrigger\": {\n\t\t\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\t\t\"actionType\": \"SPELL\",\n\t\t\t\t\"sourcePlayer\": \"BOTH\",\n\t\t\t\t\"targetPlayer\": \"BOTH\"\n\t\t\t},\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"RandomSpellTargetSpell\",\n\t\t\t\t\"target\": \"PENDING_CARD\"\n\t\t\t}\n\t\t}\n\t],\n\t\"attributes\": {\n\t\t\"ALL_RANDOM_FINAL_DESTINATION\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_mistress_of_mixtures.json",
    "content": "{\n\t\"name\": \"Mistress of Mixtures\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Restore 4 Health to both players.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"value\": 4\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"target\": \"ENEMY_HERO\",\n\t\t\t\t\"value\": 4\n\t\t\t}\n\t\t]\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_naga_corsair.json",
    "content": "{\n\t\"name\": \"Naga Corsair\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Battlecry: Give your weapon +1 Attack.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_patches_the_pirate.json",
    "content": "{\n\t\"name\": \"Patches the Pirate\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Charge. After you play a pirate, summon this minion from your deck.\",\n\t\"deckTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterMinionPlayedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"RaceCondition\",\n\t\t\t\t\"race\": \"PIRATE\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"PutMinionOnBoardFromDeckSpell\",\n\t\t\t\"target\": \"SELF\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_red_mana_wyrm.json",
    "content": "{\n\t\"name\": \"Red Mana Wyrm\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever you cast a spell, gain +2 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_second-rate_bruiser.json",
    "content": "{\n\t\"name\": \"Second-Rate Bruiser\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Taunt. Costs (2) less if your opponent has at least three minions.\",\n\t\"manaCostModifier\": {\n\t\t\"class\": \"ConditionalValueProvider\",\n\t\t\"ifTrue\": 2,\n\t\t\"ifFalse\": 0,\n\t\t\"condition\": {\n\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\"value\": 3\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_sergeant_sally.json",
    "content": "{\n\t\"name\": \"Sergeant Sally\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Deathrattle: Deal damage equal to this minion's Attack to all enemy minions.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\"value\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"ATTACK\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_small-time_buccaneer.json",
    "content": "{\n\t\"name\": \"Small-Time Buccaneer\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Has +2 Attack while you have a weapon equipped.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"BoardChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ConditionalAttackBonusSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"ConditionalValueProvider\",\n\t\t\t\t\"ifTrue\": 2,\n\t\t\t\t\"ifFalse\": 0,\n\t\t\t\t\"condition\": {\n\t\t\t\t\t\"class\": \"HasWeaponCondition\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_spiked_hogrider.json",
    "content": "{\n\t\"name\": \"Spiked Hogrider\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: If an enemy minion has Taunt, gain Charge.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"CHARGE\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HasEntityCondition\",\n\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"TAUNT\",\n\t\t\t\t\"operation\": \"HAS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_street_trickster.json",
    "content": "{\n\t\"name\": \"Street Trickster\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Spell Damage +1\",\n\t\"attributes\": {\n\t\t\"SPELL_DAMAGE\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_streetwise_investigator.json",
    "content": "{\n\t\"name\": \"Streetwise Investigator\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Enemy minions lose Stealth.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"RemoveAttributeSpell\",\n\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\"attribute\": \"STEALTH\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_tanaris_hogchopper.json",
    "content": "{\n\t\"name\": \"Tanaris Hogchopper\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: If your opponent's hand is empty, gain Charge.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"CHARGE\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"CardCountCondition\",\n\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\"operation\": \"EQUAL\",\n\t\t\t\"value\": 0\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_toxic_sewer_ooze.json",
    "content": "{\n\t\"name\": \"Toxic Sewer Ooze\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Remove 1 Durability from your opponent's weapon.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyDurabilitySpell\",\n\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\"value\": -1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_weasel_tunneler.json",
    "content": "{\n\t\"name\": \"Weasel Tunneler\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Deathrattle: Shuffle this minion into your opponent's deck.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ShuffleMinionToDeckSpell\",\n\t\t\"target\": \"SELF\",\n\t\t\"targetPlayer\": \"OPPONENT\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_wind-up_burglebot.json",
    "content": "{\n\t\"name\": \"Wind-up Burglebot\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Whenever this attacks a minion and survives, draw a card.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterPhysicalAttackTrigger\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_worgen_greaser.json",
    "content": "{\n\t\"name\": \"Worgen Greaser\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/minion_wrathion.json",
    "content": "{\n\t\"name\": \"Wrathion\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Taunt. Battlecry: Draw cards until you draw one that isn't a Dragon.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardUntilConditionSpell\",\n\t\t\t\"value\": 60,\n\t\t\t\"condition\": {\n\t\t\t\t\"class\": \"RaceCondition\",\n\t\t\t\t\"race\": \"DRAGON\",\n\t\t\t\t\"invert\": true\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/neutral/token_ogre.json",
    "content": "{\n\t\"name\": \"\\\"Little Friend\\\"\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/paladin/minion_grimestreet_enforcer.json",
    "content": "{\n\t\"name\": \"Grimestreet Enforcer\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"At the end of your turn, give all minions in your hand +1/+1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/paladin/minion_grimestreet_outfitter.json",
    "content": "{\n\t\"name\": \"Grimestreet Outfitter\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give all minions in your hand +1/+1.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/paladin/minion_grimestreet_protector.json",
    "content": "{\n\t\"name\": \"Grimestreet Protector\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Taunt. Battlecry: Give adjacent minions Divine Shield.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"ADJACENT_MINIONS\",\n\t\t\t\"attribute\": \"DIVINE_SHIELD\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/paladin/minion_grimscale_chum.json",
    "content": "{\n\t\"name\": \"Grimscale Chum\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Battlecry: Give a random Murloc in your hand +1/+1.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\",\n\t\t\t\t\"race\": \"MURLOC\"\n\t\t\t},\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/paladin/minion_meanstreet_marshal.json",
    "content": "{\n\t\"name\": \"Meanstreet Marshal\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deathrattle: If this minion has 2 or more Attack, draw a card.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ConditionalSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"value\": 1\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"AttributeCondition\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/paladin/minion_wickerflame_burnbirstle.json",
    "content": "{\n\t\"name\": \"Wickerflame Burnbristle\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Divine Shield. Taunt. Damage dealt by this minion also heals your hero.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageCausedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\"attribute\": \"LAST_HIT\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DIVINE_SHIELD\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/paladin/secret_getaway_kodo.json",
    "content": "{\n\t\"name\": \"Getaway Kodo\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Secret: When a friendly minion dies, return it to your hand.\",\n\t\"trigger\": {\n\t\t\"class\": \"MinionDeathTrigger\",\n\t\t\"targetPlayer\": \"SELF\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"ReturnMinionToHandSpell\",\n\t\t\"target\": \"EVENT_TARGET\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/paladin/spell_small-time_recruits.json",
    "content": "{\n\t\"name\": \"Small-Time Recruits\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Draw three 1-Cost minions from your deck.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"FromDeckToHandSpell\",\n\t\t\"value\": 3,\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"cardType\": \"MINION\",\n\t\t\t\"manaCost\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/paladin/spell_smugglers_run.json",
    "content": "{\n\t\"name\": \"Smuggler's Run\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"All minions in your hand gain +1/+1.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\"attackBonus\": 1,\n\t\t\"hpBonus\": 1,\n\t\t\"filter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"cardType\": \"MINION\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/priest/minion_drakonid_operative.json",
    "content": "{\n\t\"name\": \"Drakonid Operative\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Battlecry: If you're holding a Dragon, Discover a card from your opponent's deck.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverFilteredCardSpell\",\n\t\t\t\"cardSource\": {\n\t\t\t\t\"class\": \"DeckSource\",\n\t\t\t\t\"targetPlayer\": \"OPPONENT\"\n\t\t\t},\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t}\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HoldsCardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/priest/minion_kabal_songstealer.json",
    "content": "{\n\t\"name\": \"Kabal Songstealer\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Silence a minion.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SilenceSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/priest/minion_kabal_talonpriest.json",
    "content": "{\n\t\"name\": \"Kabal Talonpriest\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give a friendly minion +3 Health.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"hpBonus\": 3\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/priest/minion_mana_geode.json",
    "content": "{\n\t\"name\": \"Mana Geode\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Whenever this minion is healed, summon a 2/2 Crystal.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"HealingTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_crystal\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/priest/minion_raza_the_chained.json",
    "content": "{\n\t\"name\": \"Raza the Chained\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: If your deck has no duplicates, your Hero Power costs (0) this game.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"cardType\": \"HERO_POWER\",\n\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\"operation\": \"SET\",\n\t\t\t\t\"value\": 0\n\t\t\t}\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HighlanderDeckCondition\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/priest/spell_dragonfire_potion.json",
    "content": "{\n\t\"name\": \"Dragonfire Potion\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deal 5 damage to all minions except Dragons.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": 5,\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"DRAGON\",\n\t\t\t\"invert\": true\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/priest/spell_greater_healing_potion.json",
    "content": "{\n\t\"name\": \"Greater Healing Potion\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Restore 12 Health to a friendly character.\",\n\t\"targetSelection\": \"FRIENDLY_CHARACTERS\",\n\t\"spell\": {\n\t\t\"class\": \"HealSpell\",\n\t\t\"value\": 12\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/priest/spell_pint-size_potion.json",
    "content": "{\n\t\"name\": \"Pint-Size Potion\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Give all enemy minions -3 Attack this turn only.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"TemporaryAttackSpell\",\n\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\"value\": -3\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/priest/spell_potion_of_madness.json",
    "content": "{\n\t\"name\": \"Potion of Madness\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Gain control of an enemy minion with 2 or less Attack until end of turn.\",\n\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"custom.ShadowMadnessSpell\",\n\t\t\"filter\": {\n\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/priest/token_crystal.json",
    "content": "{\n\t\"name\": \"Crystal\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/rogue/minion_gadgetzan_ferryman.json",
    "content": "{\n\t\"name\": \"Gadgetzan Ferryman\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Combo: Return a friendly minion to your hand.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReturnMinionToHandSpell\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"ComboCondition\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/rogue/minion_jade_swarmer.json",
    "content": "{\n\t\"name\": \"Jade Swarmer\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Stealth. Deathrattle: Summon a Jade Golem.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"CreateSummonSpell\",\n\t\t\"name\": \"Jade Golem\",\n\t\t\"attackBonus\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\"offset\": 1\n\t\t},\n\t\t\"hpBonus\": {\n\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\"offset\": 1\n\t\t},\n\t\t\"mana\": {\n\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\"operation\": \"MAXIMUM\",\n\t\t\t\"value1\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"value2\": 10\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true,\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/rogue/minion_lotus_assassin.json",
    "content": "{\n\t\"name\": \"Lotus Assassin\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Stealth. Whenever this minion attacks and kills a minion, gain Stealth.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterPhysicalAttackTrigger\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\",\n\t\t\t\"fireCondition\": {\n\t\t\t\t\"class\": \"IsDeadCondition\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"STEALTH\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/rogue/minion_luckydo_buccaneer.json",
    "content": "{\n\t\"name\": \"Luckydo Buccaneer\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Battlecry: If your weapon has at least 3 Attack, gain +4/+4.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": 4\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HasEntityCondition\",\n\t\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\"value\": 3,\n\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/rogue/minion_shadow_rager.json",
    "content": "{\n\t\"name\": \"Shadow Rager\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Stealth\",\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/rogue/minion_shadow_sensei.json",
    "content": "{\n\t\"name\": \"Shadow Sensei\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Give a Stealthed minion +2/+2.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"value\": 2,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"STEALTH\",\n\t\t\t\t\"operation\": \"HAS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/rogue/minion_shaku_the_collector.json",
    "content": "{\n\t\"name\": \"Shaku, the Collector\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Stealth. Whenever this minion attacks, add a random card to your hand (from your opponent's class).\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterPhysicalAttackTrigger\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"value\": 1,\n\t\t\t\"card\": \"spell_the_coin\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"heroClass\": \"OPPONENT\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/rogue/spell_counterfeit_coin.json",
    "content": "{\n\t\"name\": \"Counterfeit Coin\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Gain 1 Mana this turn only.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"GainManaSpell\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/rogue/spell_jade_shuriken.json",
    "content": "{\n\t\"name\": \"Jade Shuriken\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 2 damage. Combo: Summon a Jade Golem.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"ComboSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 2\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"CreateSummonSpell\",\n\t\t\t\"name\": \"Jade Golem\",\n\t\t\t\"attackBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"hpBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"mana\": {\n\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\"operation\": \"MAXIMUM\",\n\t\t\t\t\"value1\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"offset\": 1\n\t\t\t\t},\n\t\t\t\t\"value2\": 10\n\t\t\t},\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/shaman/minion_jade_chieftain.json",
    "content": "{\n\t\"name\": \"Jade Chieftain\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Summon a Jade Golem. Give it Taunt.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"CreateSummonSpell\",\n\t\t\t\"name\": \"Jade Golem\",\n\t\t\t\"attackBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"hpBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"mana\": {\n\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\"operation\": \"MAXIMUM\",\n\t\t\t\t\"value1\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"offset\": 1\n\t\t\t\t},\n\t\t\t\t\"value2\": 10\n\t\t\t},\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"value\": 1\n\t\t\t},\n\t\t\t\"spell1\": {\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/shaman/minion_jinyu_waterspeaker.json",
    "content": "{\n\t\"name\": \"Jinyu Waterspeaker\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Restore 6 Health. Overload: (1)\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"value\": 6\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"OVERLOAD\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/shaman/minion_lotus_illusionist.json",
    "content": "{\n\t\"name\": \"Lotus Illusionist\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"After this minion attacks a hero, transform it into a random 6-Cost minion.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterPhysicalAttackTrigger\",\n\t\t\t\"targetEntityType\": \"HERO\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"TransformToRandomMinionSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"manaCost\": 6\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/shaman/minion_white_eyes.json",
    "content": "{\n\t\"name\": \"White Eyes\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Taunt. Deathrattle: Shuffle 'The Storm Guardian' into your deck.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ShuffleToDeckSpell\",\n\t\t\"card\": \"token_the_storm_guardian\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/shaman/spell_call_in_the_finishers.json",
    "content": "{\n\t\"name\": \"Call in the Finishers\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Summon 4 1/1 Murlocs.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"value\": 4,\n\t\t\"card\": \"token_murloc_razorgill\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/shaman/spell_devolve.json",
    "content": "{\n\t\"name\": \"Devolve\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Transform all enemy minions into random ones that cost (1) less.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"TransformToRandomMinionSpell\",\n\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"manaCost\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"SPELL_TARGET\",\n\t\t\t\t\"attribute\": \"BASE_MANA_COST\",\n\t\t\t\t\"offset\": -1\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/shaman/spell_finders_keepers.json",
    "content": "{\n\t\"name\": \"Finders Keepers\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Discover a card with Overload. Overload: (1)\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"attribute\": \"OVERLOAD\",\n\t\t\t\"operation\": \"HAS\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/shaman/spell_jade_lightning.json",
    "content": "{\n\t\"name\": \"Jade Lightning\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 4 damage. Summon a Jade Golem.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 4\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"CreateSummonSpell\",\n\t\t\t\t\"name\": \"Jade Golem\",\n\t\t\t\t\"attackBonus\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"offset\": 1\n\t\t\t\t},\n\t\t\t\t\"hpBonus\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"offset\": 1\n\t\t\t\t},\n\t\t\t\t\"mana\": {\n\t\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\t\"operation\": \"MAXIMUM\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\t\"offset\": 1\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 10\n\t\t\t\t},\n\t\t\t\t\"spell\": {\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/shaman/token_murloc_razorgill.json",
    "content": "{\n\t\"name\": \"Murloc Razorgill\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/shaman/token_the_storm_guardian.json",
    "content": "{\n\t\"name\": \"The Storm Guardian\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 10,\n\t\"baseHp\": 10,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Taunt.\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/shaman/weapon_jade_claws.json",
    "content": "{\n\t\"name\": \"Jade Claws\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 2,\n\t\"durability\": 2,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Summon a Jade Golem. Overload: (1)\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"CreateSummonSpell\",\n\t\t\t\"name\": \"Jade Golem\",\n\t\t\t\"attackBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"hpBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"offset\": 1\n\t\t\t},\n\t\t\t\"mana\": {\n\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\"operation\": \"MAXIMUM\",\n\t\t\t\t\"value1\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\t\"offset\": 1\n\t\t\t\t},\n\t\t\t\t\"value2\": 10\n\t\t\t},\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\"attribute\": \"JADE_BUFF\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"OVERLOAD\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warlock/minion_abyssal_enforcer.json",
    "content": "{\n\t\"name\": \"Abyssal Enforcer\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Battlecry: Deal 3 damage to all other characters.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ALL_OTHER_CHARACTERS\",\n\t\t\t\"value\": 3\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warlock/minion_crystalweaver.json",
    "content": "{\n\t\"name\": \"Crystalweaver\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give your Demons +1/+1.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\"value\": 1,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"DEMON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warlock/minion_kabal_trafficker.json",
    "content": "{\n\t\"name\": \"Kabal Trafficker\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"At the end of your turn, add a random Demon to your hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"DEMON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warlock/minion_krul_the_unshackled.json",
    "content": "{\n\t\"name\": \"Krul the Unshackled\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 9,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: If your deck contains no duplicates, summon all Demons from your hand.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"PutRandomMinionOnBoardSpell\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"value\": 7,\n\t\t\t\"cardLocation\": \"HAND\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\",\n\t\t\t\t\"race\": \"DEMON\"\n\t\t\t}\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HighlanderDeckCondition\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warlock/minion_seadevil_stinger.json",
    "content": "{\n\t\"name\": \"Seadevil Stinger\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Battlecry: The next Murloc you play this turn costs Health instead of Mana.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"attribute\": \"MURLOCS_COST_HEALTH\",\n\t\t\t\"revertTrigger\": {\n\t\t\t\t\"class\": \"TurnEndTrigger\"\n\t\t\t},\n\t\t\t\"secondRevertTrigger\": {\n\t\t\t\t\"class\": \"CardPlayedTrigger\",\n\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\"race\": \"MURLOC\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warlock/minion_unlicensed_apothecary.json",
    "content": "{\n\t\"name\": \"Unlicensed Apothecary\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Whenever you summon a minion, deal 5 damage to your Hero.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionSummonedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"requiredAttribute\": \"DEATHRATTLES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": 5\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warlock/spell_blastcrystal_potion.json",
    "content": "{\n\t\"name\": \"Blastcrystal Potion\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Destroy a minion and one of your Mana Crystals.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DestroySpell\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\t\t\"value\": -1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warlock/spell_bloodfury_potion.json",
    "content": "{\n\t\"name\": \"Bloodfury Potion\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Give a minion +3 Attack. If it's a Demon, also give it +3 Health.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"ConditionalEffectSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"attackBonus\": 3\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"hpBonus\": 3\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"RaceCondition\",\n\t\t\t\"race\": \"DEMON\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warlock/spell_felfire_potion.json",
    "content": "{\n\t\"name\": \"Felfire Potion\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 5 damage to ALL characters.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_CHARACTERS\",\n\t\t\"value\": 5\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warrior/minion_alley_armorsmith.json",
    "content": "{\n\t\"name\": \"Alley Armorsmith\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Taunt. Whenever this minion deals damage, gain that much Armor.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageCausedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffHeroSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"armorBonus\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\"attribute\": \"LAST_HIT\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warrior/minion_grimestreet_pawnbroker.json",
    "content": "{\n\t\"name\": \"Grimestreet Pawnbroker\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Give a random weapon in your hand +1/+1.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"WEAPON\"\n\t\t\t},\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warrior/minion_grimy_gadgeteer.json",
    "content": "{\n\t\"name\": \"Grimy Gadgeteer\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"At the end of your turn, give a random minion in your hand +2/+2.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"hpBonus\": 2,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t},\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warrior/minion_hobart_grapplehammer.json",
    "content": "{\n\t\"name\": \"Hobart Grapplehammer\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Give all weapons in your hand and deck +1 Attack.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"attackBonus\": 1,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\"cardType\": \"WEAPON\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddSpellTriggerSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"trigger\": {\n\t\t\t\t\t\t\"eventTrigger\": {\n\t\t\t\t\t\t\t\"class\": \"CardDrawnTrigger\",\n\t\t\t\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\t\t\t\"queueCondition\": {\n\t\t\t\t\t\t\t\t\"class\": \"CardPropertyCondition\",\n\t\t\t\t\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\t\t\t\t\"cardType\": \"WEAPON\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"spell\": {\n\t\t\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\t\t\t\"attackBonus\": 1\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warrior/minion_public_defender.json",
    "content": "{\n\t\"name\": \"Public Defender\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt.\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warrior/spell_i_know_a_guy.json",
    "content": "{\n\t\"name\": \"I Know a Guy\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Discover a Taunt minion.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"cardType\": \"MINION\",\n\t\t\t\"attribute\": \"TAUNT\",\n\t\t\t\"operation\": \"HAS\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warrior/spell_sleep_with_the_fishes.json",
    "content": "{\n\t\"name\": \"Sleep with the Fishes\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deal 3 damage to all damaged minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": 3,\n\t\t\"filter\": {\n\t\t\t\"class\": \"DamagedFilter\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warrior/spell_stolen_goods.json",
    "content": "{\n\t\"name\": \"Stolen Goods\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Give a random Taunt minion in your hand +3/+3.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\"attackBonus\": 3,\n\t\t\"hpBonus\": 3,\n\t\t\"filter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"cardType\": \"MINION\",\n\t\t\t\"attribute\": \"TAUNT\",\n\t\t\t\"operation\": \"HAS\"\n\t\t},\n\t\t\"randomTarget\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/mean_streets_of_gadgetzan/warrior/weapon_brass_knuckles.json",
    "content": "{\n\t\"name\": \"Brass Knuckles\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 2,\n\t\"durability\": 3,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"After your Hero attacks, give a random minion in your hand +1/+1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterPhysicalAttackTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\",\n\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\"sourceEntityType\": \"HERO\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t},\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"MEAN_STREETS_OF_GADGETZAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_anubar_ambusher.json",
    "content": "{\n\t\"name\": \"Anub'ar Ambusher\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Return a friendly minion to your hand.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ReturnMinionToHandSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"randomTarget\": true\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_baron_rivendare.json",
    "content": "{\n\t\"name\": \"Baron Rivendare\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Your minions trigger their Deathrattles twice.\",\n\t\"attributes\": {\n\t\t\"DOUBLE_DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_dancing_swords.json",
    "content": "{\n\t\"name\": \"Dancing Swords\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Your opponent draws a card.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"value\": 1\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_dark_cultist.json",
    "content": "{\n\t\"name\": \"Dark Cultist\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Give a random friendly minion +3 Health.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"hpBonus\": 3,\n\t\t\"randomTarget\": true\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_deathlord.json",
    "content": "{\n\t\"name\": \"Deathlord\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Taunt. Deathrattle: Your opponent puts a minion from their deck into the battlefield.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"PutRandomMinionOnBoardSpell\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"cardLocation\": \"DECK\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_echoing_ooze.json",
    "content": "{\n\t\"name\": \"Echoing Ooze\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Summon an exact copy of this minion at the end of the turn.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddSpellTriggerSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"trigger\": {\n\t\t\t\t\"eventTrigger\": {\n\t\t\t\t\t\"class\": \"TurnEndTrigger\"\n\t\t\t\t},\n\t\t\t\t\"spell\": {\n\t\t\t\t\t\"class\": \"SummonCopySpell\",\n\t\t\t\t\t\"target\": \"SELF\"\n\t\t\t\t},\n\t\t\t\t\"oneTurn\": true\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_feugen.json",
    "content": "{\n\t\"name\": \"Feugen\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Deathrattle: If Stalagg also died this game, summon Thaddius.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ConditionalSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_thaddius\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"GraveyardContainsCondition\",\n\t\t\t\"cardId\": \"minion_stalagg\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_haunted_creeper.json",
    "content": "{\n\t\"name\": \"Haunted Creeper\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Deathrattle: Summon two 1/1 Spectral Spiders.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_spectral_spider\",\n\t\t\t\"token_spectral_spider\"\n\t\t]\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_kelthuzad.json",
    "content": "{\n\t\"name\": \"Kel'Thuzad\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"At the end of the turn, summon all friendly minions that died this turn.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"BOTH\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"custom.KelThuzadSpell\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_loatheb.json",
    "content": "{\n\t\"name\": \"Loatheb\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Enemy spells cost (5) more next turn.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\t\"operation\": \"ADD\",\n\t\t\t\t\"value\": 5,\n\t\t\t\t\"expirationTrigger\": {\n\t\t\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\t\t\"targetPlayer\": \"OPPONENT\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_mad_scientist.json",
    "content": "{\n\t\"name\": \"Mad Scientist\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Put a Secret from your deck into the battlefield.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"PutRandomSecretIntoPlaySpell\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_maexxna.json",
    "content": "{\n\t\"name\": \"Maexxna\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Destroy any minion damaged by this minion.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageCausedTrigger\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_nerubar_weblord.json",
    "content": "{\n\t\"name\": \"Nerub'ar Weblord\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Minions with Battlecry cost (2) more.\",\n\t\"cardCostModifier\": {\n\t\t\"class\": \"CardCostModifier\",\n\t\t\"cardType\": \"MINION\",\n\t\t\"targetPlayer\": \"BOTH\",\n\t\t\"value\": 2,\n\t\t\"requiredAttribute\": \"BATTLECRY\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_nerubian_egg.json",
    "content": "{\n\t\"name\": \"Nerubian Egg\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deathrattle: Summon a 4/4 Nerubian.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_nerubian\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_shade_of_naxxramas.json",
    "content": "{\n\t\"name\": \"Shade of Naxxramas\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Stealth. At the start of your turn, gain +1/+1\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_sludge_belcher.json",
    "content": "{\n\t\"name\": \"Sludge Belcher\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Taunt. Deathrattle: Summon a 1/2 Slime with Taunt.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_slime\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_spectral_knight.json",
    "content": "{\n\t\"name\": \"Spectral Knight\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Can't be targeted by spells or Hero Powers.\",\n\t\"attributes\": {\n\t\t\"UNTARGETABLE_BY_SPELLS\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_stalagg.json",
    "content": "{\n\t\"name\": \"Stalagg\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Deathrattle: If Feugen also died this game, summon Thaddius.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ConditionalSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_thaddius\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"GraveyardContainsCondition\",\n\t\t\t\"cardId\": \"minion_feugen\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_stoneskin_gargoyle.json",
    "content": "{\n\t\"name\": \"Stoneskin Gargoyle\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"At the start of your turn, restore this minion to full Health.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"attribute\": \"MAX_HP\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_undertaker.json",
    "content": "{\n\t\"name\": \"Undertaker\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever you summon a minion with Deathrattle, gain +1 Attack\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionSummonedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"requiredAttribute\": \"DEATHRATTLES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_unstable_ghoul.json",
    "content": "{\n\t\"name\": \"Unstable Ghoul\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt. Deathrattle: Deal 1 damage to all minions.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": 1\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_voidcaller.json",
    "content": "{\n\t\"name\": \"Voidcaller\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Deathrattle: Put a random Demon from your hand into the battlefield.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"PutRandomMinionOnBoardSpell\",\n\t\t\"cardLocation\": \"HAND\",\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"DEMON\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_wailing_soul.json",
    "content": "{\n\t\"name\": \"Wailing Soul\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Silence your other minions.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SilenceSpell\",\n\t\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_webspinner.json",
    "content": "{\n\t\"name\": \"Webspinner\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Deathrattle: Add a random Beast card to your hand.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"BEAST\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/minion_zombie_chow.json",
    "content": "{\n\t\"name\": \"Zombie Chow\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Restore 5 Health to the enemy hero.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"HealSpell\",\n\t\t\"target\": \"ENEMY_HERO\",\n\t\t\"value\": 5\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/secret_avenge.json",
    "content": "{\n\t\"name\": \"Avenge\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Secret: When one of your minions dies, give a random friendly minion +3/+2\",\n\t\"targetSelection\": \"NONE\",\n\t\"trigger\": {\n\t\t\"class\": \"MinionDeathTrigger\",\n\t\t\"queueCondition\": {\n\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\"operation\": \"GREATER\",\n\t\t\t\"value\": 0\n\t\t},\n\t\t\"fireCondition\": {\n\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\"operation\": \"GREATER\",\n\t\t\t\"value\": 0\n\t\t}\n\t},\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 3,\n\t\t\"hpBonus\": 2,\n\t\t\"randomTarget\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/secret_duplicate.json",
    "content": "{\n\t\"name\": \"Duplicate\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Secret: When a friendly minion dies, put 2 copies of it in your hand.\",\n\t\"trigger\": {\n\t\t\"class\": \"MinionDeathTrigger\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"fireCondition\": {\n\t\t\t\"class\": \"CardCountCondition\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"operation\": \"LESS\",\n\t\t\t\"value\": 10\n\t\t}\n\t},\n\t\"spell\": {\n\t\t\"class\": \"PutCopyInHandSpell\",\n\t\t\"target\": \"EVENT_TARGET\",\n\t\t\"howMany\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/spell_poison_seeds.json",
    "content": "{\n\t\"name\": \"Poison Seeds\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Destroy all minions and summon 2/2 treants to replace them\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"custom.PoisonSeedsSpell\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/spell_reincarnate.json",
    "content": "{\n\t\"name\": \"Reincarnate\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Destroy a minion, then return it to life with full Health.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DestroySpell\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"ReviveMinionSpell\",\n\t\t\t\t\"targetPlayer\": \"OWNER\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/token_nerubian.json",
    "content": "{\n\t\"name\": \"Nerubian\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/token_slime.json",
    "content": "{\n\t\"name\": \"Slime\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/token_spectral_spider.json",
    "content": "{\n\t\"name\": \"Spectral Spider\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/token_thaddius.json",
    "content": "{\n\t\"name\": \"Thaddius\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 11,\n\t\"baseHp\": 11,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/naxxramas/weapon_deaths_bite.json",
    "content": "{\n\t\"name\": \"Death's Bite\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 4,\n\t\"durability\": 2,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Deal 1 damage to all minions.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": 1\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"NAXXRAMAS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_arcane_anomaly.json",
    "content": "{\n\t\"name\": \"Arcane Anomaly\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever you cast a spell, give this minion +1 Health.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_arcane_giant.json",
    "content": "{\n\t\"name\": \"Arcane Giant\",\n\t\"baseManaCost\": 12,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Costs (1) less for each spell you've cast this game.\",\n\t\"manaCostModifier\": {\n\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\"playerAttribute\": \"SPELLS_CAST\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_arcanosmith.json",
    "content": "{\n\t\"name\": \"Arcanosmith\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Summon a 0/5 minion with Taunt.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_animated_shield\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_avian_watcher.json",
    "content": "{\n\t\"name\": \"Avian Watcher\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: If you control a Secret, gain +1/+1 and Taunt.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"attackBonus\": 1,\n\t\t\t\t\t\"hpBonus\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"ControlsSecretCondition\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_babbling_book.json",
    "content": "{\n\t\"name\": \"Babbling Book\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Add a random Mage spell to your hand.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\t\"heroClass\": \"MAGE\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_barnes.json",
    "content": "{\n\t\"name\": \"Barnes\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Summon a 1/1 copy of a random minion in your deck.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonOneOneCopySpell\",\n\t\t\t\"cardSource\": {\n\t\t\t\t\"class\": \"DeckSource\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t},\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_book_wyrm.json",
    "content": "{\n\t\"name\": \"Book Wyrm\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Battlecry: If you're holding a Dragon, destroy an enemy minion with 3 or less Attack.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\t\"value\": 3\n\t\t\t}\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HoldsCardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_cloaked_huntress.json",
    "content": "{\n\t\"name\": \"Cloaked Huntress\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Your Secrets cost (0).\",\n\t\"cardCostModifier\": {\n\t\t\"class\": \"CardCostModifier\",\n\t\t\"cardType\": \"SPELL\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"operation\": \"SET\",\n\t\t\"value\": 0,\n\t\t\"requiredAttribute\": \"SECRET\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_deadly_fork.json",
    "content": "{\n\t\"name\": \"Deadly Fork\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Add a 3/2 weapon to your hand.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\"card\": \"weapon_sharp_fork\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_enchanted_raven.json",
    "content": "{\n\t\"name\": \"Enchanted Raven\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_ethereal_peddler.json",
    "content": "{\n\t\"name\": \"Ethereal Peddler\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: If you're holding any non-Rogue class cards, reduce their cost by (2).\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"heroClasses\": [\n\t\t\t\t\t\"ROGUE\",\n\t\t\t\t\t\"ANY\"\n\t\t\t\t],\n\t\t\t\t\"invert\": true\n\t\t\t},\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\"value\": 2\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_ivory_knight.json",
    "content": "{\n\t\"name\": \"Ivory Knight\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Discover a spell. Restore health equal to its Cost.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ReceiveCardAndDoSomethingSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\"spell\": {\n\t\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\t\"value\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"EVENT_CARD\",\n\t\t\t\t\t\t\"attribute\": \"BASE_MANA_COST\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"SPELL\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_kindly_grandmother.json",
    "content": "{\n\t\"name\": \"Kindly Grandmother\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Deathrattle: Summon a 3/2 Big Bad Wolf.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_big_bad_wolf\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_malchezaars_imp.json",
    "content": "{\n\t\"name\": \"Malchezaar's Imp\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Whenever you discard a card, draw a card.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DiscardTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_medivh_the_guardian.json",
    "content": "{\n\t\"name\": \"Medivh, the Guardian\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Equip Atiesh, Greatstaff of the Guardian.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"EquipWeaponSpell\",\n\t\t\t\"card\": \"weapon_atiesh\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_medivhs_valet.json",
    "content": "{\n\t\"name\": \"Medivh's Valet\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: If you control a Secret, deal 3 damage.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 3\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"ControlsSecretCondition\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_menagerie_magician.json",
    "content": "{\n\t\"name\": \"Menagerie Magician\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give a random friendly Beast, Dragon, and Murloc +2/+2.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"attackBonus\": 2,\n\t\t\t\t\t\"hpBonus\": 2,\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"randomTarget\": true,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\"race\": \"BEAST\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"attackBonus\": 2,\n\t\t\t\t\t\"hpBonus\": 2,\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"randomTarget\": true,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"attackBonus\": 2,\n\t\t\t\t\t\"hpBonus\": 2,\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"randomTarget\": true,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\"race\": \"MURLOC\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_menagerie_warden.json",
    "content": "{\n\t\"name\": \"Menagerie Warden\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Choose a friendly Beast. Summon a copy of it.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonCopySpell\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"BEAST\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_moat_lurker.json",
    "content": "{\n\t\"name\": \"Moat Lurker\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Destroy a minion. Deathrattle: Resummon it.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"custom.MoatLurkerSpell\"\n\t\t}\n\t},\n\t\"deathrattle\": {\n\t\t\"class\": \"NullSpell\"\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_moroes.json",
    "content": "{\n\t\"name\": \"Moroes\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Stealth. At the end of your turn, summon a 1/1 Steward.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_steward\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_netherspite_historian.json",
    "content": "{\n\t\"name\": \"Netherspite Historian\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: If you're holding a Dragon, Discover a Dragon.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t},\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t}\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HoldsCardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_nightbane_templar.json",
    "content": "{\n\t\"name\": \"Nightbane Templar\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: If you're holding a Dragon, summon two 1/1 Whelps.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_whelp\",\n\t\t\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_whelp\",\n\t\t\t\t\t\"boardPositionRelative\": \"LEFT\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HoldsCardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_onyx_bishop.json",
    "content": "{\n\t\"name\": \"Onyx Bishop\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Summon a friendly minion that died this game.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ResurrectSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_pantry_spider.json",
    "content": "{\n\t\"name\": \"Pantry Spider\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Summon a 1/3 Spider.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_cellar_spider\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_pompous_thespian.json",
    "content": "{\n\t\"name\": \"Pompous Thespian\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_priest_of_the_feast.json",
    "content": "{\n\t\"name\": \"Priest of the Feast\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever you cast a spell, restore 3 Health to your hero.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": 3\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_prince_malchezaar.json",
    "content": "{\n\t\"name\": \"Prince Malchezaar\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"When the game starts, add 5 extra Legendary minions to your deck.\",\n\t\"deckTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"GameStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"CastRepeatedlySpell\",\n\t\t\t\"howMany\": 5,\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ShuffleToDeckSpell\",\n\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\"class\": \"AndFilter\",\n\t\t\t\t\t\"filters\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"CardFilter\",\t\n\t\t\t\t\t\t\t\"cardType\": \"MINION\",\n\t\t\t\t\t\t\t\"rarity\": \"LEGENDARY\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"OrFilter\",\n\t\t\t\t\t\t\t\"invert\": true,\n\t\t\t\t\t\t\t\"filters\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"class\": \"InDeckFilter\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"class\": \"InHandFilter\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"OrFilter\",\n\t\t\t\t\t\t\t\"filters\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\t\t\t\"heroClass\": \"ANY\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\t\t\t\"heroClass\": \"SELF\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_runic_egg.json",
    "content": "{\n\t\"name\": \"Runic Egg\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Draw a card.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"value\": 1\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_silverware_golem.json",
    "content": "{\n\t\"name\": \"Silverware Golem\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"If you discard this minion, summon it.\",\n\t\"passiveTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DiscardTrigger\",\n\t\t\t\"target\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"minion_silverware_golem\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_swashburglar.json",
    "content": "{\n\t\"name\": \"Swashburglar\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Battlecry: Add a random card class card to your hand (from your opponent's class).\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"value\": 1,\n\t\t\t\"card\": \"spell_the_coin\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"heroClass\": \"OPPONENT\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_the_curator.json",
    "content": "{\n\t\"name\": \"The Curator\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Taunt. Battlecry: Draw a Beast, Dragon, and Murloc from your deck.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"FromDeckToHandSpell\",\n\t\t\t\t\t\"value\": 1,\n\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\"race\": \"BEAST\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"FromDeckToHandSpell\",\n\t\t\t\t\t\"value\": 1,\n\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"FromDeckToHandSpell\",\n\t\t\t\t\t\"value\": 1,\n\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\"race\": \"MURLOC\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_violet_illusionist.json",
    "content": "{\n\t\"name\": \"Violet Illusionist\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"During your turn, your Hero is immune.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"BoardChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"EitherOrSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"spell1\": {\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"IMMUNE_HERO\"\n\t\t\t},\n\t\t\t\"spell2\": {\n\t\t\t\t\"class\": \"RemoveAttributeSpell\",\n\t\t\t\t\"attribute\": \"IMMUNE_HERO\"\n\t\t\t},\n\t\t\t\"condition\": {\n\t\t\t\t\"class\": \"OwnedByPlayerCondition\",\n\t\t\t\t\"targetPlayer\": \"ACTIVE\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_wicked_witchdoctor.json",
    "content": "{\n\t\"name\": \"Wicked Witchdoctor\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever you cast a spell, summon a random basic Totem.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonRandomSpell\",\n\t\t\t\"cards\": [\n\t\t\t\t\"token_searing_totem\",\n\t\t\t\t\"token_healing_totem\",\n\t\t\t\t\"token_wrath_of_air_totem\",\n\t\t\t\t\"token_stoneclaw_totem\"\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/minion_zoobot.json",
    "content": "{\n\t\"name\": \"Zoobot\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Battlecry: Give a random friendly Beast, Dragon, and Murloc +1/+1.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"attackBonus\": 1,\n\t\t\t\t\t\"hpBonus\": 1,\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"randomTarget\": true,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\"race\": \"BEAST\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"attackBonus\": 1,\n\t\t\t\t\t\"hpBonus\": 1,\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"randomTarget\": true,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"attackBonus\": 1,\n\t\t\t\t\t\"hpBonus\": 1,\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"randomTarget\": true,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\"race\": \"MURLOC\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/secret_cat_trick.json",
    "content": "{\n\t\"name\": \"Cat Trick\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Secret: After your opponent plays a spell, summon a 4/2 Panther with Stealth.\",\n\t\"trigger\": {\n\t\t\"class\": \"AfterSpellCastedTrigger\",\n\t\t\"sourcePlayer\": \"OPPONENT\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_cat_in_a_hat\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/spell_firelands_portal.json",
    "content": "{\n\t\"name\": \"Firelands Portal\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 5 damage. Summon a random 5-Cost minion.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 5\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"manaCost\": 5\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/spell_ironforge_portal.json",
    "content": "{\n\t\"name\": \"Ironforge Portal\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Gain +4 Armor. Summon a random 4-Cost minion.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"BuffHeroSpell\",\n\t\t\t\t\"armorBonus\": 4\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"manaCost\": 4\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/spell_kara_kazham.json",
    "content": "{\n\t\"name\": \"Kara Kazham!\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Summon a 1/1 Candle, 2/2 Broom, and 3/3 Teapot.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\"card\": \"token_candle\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\"card\": \"token_broom\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\"card\": \"token_teapot\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/spell_maelstrom_portal.json",
    "content": "{\n\t\"name\": \"Maelstrom Portal\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 1 damage to all enemy minions. Summon a random 1-Cost minion.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\"value\": 1\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"manaCost\": 1\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/spell_moonglade_portal.json",
    "content": "{\n\t\"name\": \"Moonglade Portal\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Restore 6 Health. Summon a random 6-Cost minion.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"value\": 6\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"manaCost\": 6\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/spell_protect_the_king.json",
    "content": "{\n\t\"name\": \"Protect the King!\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"For each enemy minion, summon a 1/1 Pawn with Taunt.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"value\": {\n\t\t\t\"class\": \"EntityCounter\",\n\t\t\t\"target\": \"ENEMY_MINIONS\"\n\t\t},\n\t\t\"card\": \"token_pawn\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/spell_purify.json",
    "content": "{\n\t\"name\": \"Purify\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Silence a friendly minion. Draw a card.\",\n\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"SilenceSpell\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/spell_silvermoon_portal.json",
    "content": "{\n\t\"name\": \"Silvermoon Portal\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a minion +2/+2. Summon a random 2-Cost minion.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"value\": 2\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"manaCost\": 2\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/token_animated_shield.json",
    "content": "{\n\t\"name\": \"Animated Shield\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/token_big_bad_wolf.json",
    "content": "{\n\t\"name\": \"Big Bad Wolf\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/token_broom.json",
    "content": "{\n\t\"name\": \"Broom\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/token_candle.json",
    "content": "{\n\t\"name\": \"Candle\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/token_cat_in_a_hat.json",
    "content": "{\n\t\"name\": \"Cat in a Hat\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Stealth\",\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/token_cellar_spider.json",
    "content": "{\n\t\"name\": \"Cellar Spider\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/token_pawn.json",
    "content": "{\n\t\"name\": \"Pawn\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/token_steward.json",
    "content": "{\n\t\"name\": \"Steward\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/token_teapot.json",
    "content": "{\n\t\"name\": \"Teapot\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/weapon_atiesh.json",
    "content": "{\n\t\"name\": \"Atiesh\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 1,\n\t\"durability\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"After you cast a spell, summon a random minion of the same Cost. Lose 1 Durability.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterSpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\",\n\t\t\t\"fireCondition\": {\n\t\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\"operation\": \"LESS\",\n\t\t\t\t\"value\": 7\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\"manaCost\": {\n\t\t\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\t\t\"playerAttribute\": \"LAST_MANA_COST\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffWeaponSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\t\t\t\"hpBonus\": -1\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/weapon_fools_bane.json",
    "content": "{\n\t\"name\": \"Fool's Bane\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 3,\n\t\"durability\": 4,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Unlimited attacks each turn. Can't attack heroes.\",\n\t\"onEquip\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"attribute\": \"CANNOT_ATTACK_HEROES\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"attribute\": \"UNLIMITED_ATTACKS\"\n\t\t\t}\n\t\t]\n\t},\n\t\"onUnequip\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"RemoveAttributeSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"attribute\": \"CANNOT_ATTACK_HEROES\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"RemoveAttributeSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"attribute\": \"UNLIMITED_ATTACKS\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/weapon_sharp_fork.json",
    "content": "{\n\t\"name\": \"Sharp Fork\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 3,\n\t\"durability\": 2,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/one_night_in_karazhan/weapon_spirit_claws.json",
    "content": "{\n\t\"name\": \"Spirit Claws\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 1,\n\t\"durability\": 3,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Has +2 Attack while you have Spell Damage.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"BoardChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ConditionalAttackBonusSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"ConditionalValueProvider\",\n\t\t\t\t\"ifTrue\": 2,\n\t\t\t\t\"ifFalse\": 0,\n\t\t\t\t\"condition\": {\n\t\t\t\t\t\"class\": \"ComparisonCondition\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"AttributeCounter\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_CHARACTERS\",\n\t\t\t\t\t\t\"attribute\": \"SPELL_DAMAGE\"\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 1,\n\t\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"ONE_NIGHT_IN_KARAZHAN\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/promo/spell_i_am_murloc.json",
    "content": "{\n\t\"name\": \"I Am Murloc\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Summon three, four, or five 1/1 Murlocs.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"value\": {\n\t\t\t\"class\": \"RandomValueProvider\",\n\t\t\t\"min\": 3,\n\t\t\t\"max\": 5\n\t\t},\n\t\t\"card\": \"token_murloc\"\n\t},\n\t\"collectible\": false,\n\t\"set\": \"PROMO\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/promo/spell_power_of_the_horde.json",
    "content": "{\n\t\"name\": \"Power of the Horde\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Summon a random Horde Warrior.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonRandomSpell\",\n\t\t\"cards\": [\n\t\t\t\"minion_frostwolf_grunt\",\n\t\t\t\"minion_tauren_warrior\",\n\t\t\t\"minion_senjin_shieldmasta\",\n\t\t\t\"minion_thrallmar_farseer\",\n\t\t\t\"minion_silvermoon_guardian\",\n\t\t\t\"minion_cairne_bloodhoof\"\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"PROMO\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/promo/spell_rogues_do_it.json",
    "content": "{\n\t\"name\": \"Rogues Do it...\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 4 damage. Draw a card.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 4\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"PROMO\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/promo/token_emboldener_3000.json",
    "content": "{\n\t\"name\": \"Emboldener 3000\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"At the end of your turn, give a random minion +1/+1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1,\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"PROMO\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/promo/token_homing_chicken.json",
    "content": "{\n\t\"name\": \"Homing Chicken\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"At the start of your turn, destroy this minion and draw 3 cards.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\t\"value\": 3\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\t\t\"target\": \"SELF\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"PROMO\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/promo/token_poultryizer.json",
    "content": "{\n\t\"name\": \"Poultryizer\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"At the start of your turn, transform a random minion into a 1/1 Chicken.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\"card\": \"token_chicken\",\n\t\t\t\"randomTarget\": true\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"PROMO\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/promo/token_repair_bot.json",
    "content": "{\n\t\"name\": \"Repair Bot\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"At the end of your turn, restore 6 Health to a damaged character.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"ALL_CHARACTERS\",\n\t\t\t\"value\": 6,\n\t\t\t\"randomTarget\": true,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"DamagedFilter\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"PROMO\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/hero_power_dire_shapeshift.json",
    "content": "{\n\t\"name\": \"Dire Shapeshift\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Gain 2 Armor and +2 Attack this turn.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffHeroSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"attackBonus\": 2,\n\t\t\"armorBonus\": 2\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/minion_aviana.json",
    "content": "{\n\t\"name\": \"Aviana\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Your minions cost (1).\",\n\t\"cardCostModifier\": {\n\t\t\"class\": \"CardCostModifier\",\n\t\t\"cardType\": \"MINION\",\n\t\t\"operation\": \"SET\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/minion_darnassus_aspirant.json",
    "content": "{\n\t\"name\": \"Darnassus Aspirant\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Gain an empty Mana Crystal.Deathrattle: Lose a Mana Crystal.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"deathrattle\": {\n\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\"value\": -1\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/minion_druid_of_the_saber.json",
    "content": "{\n\t\"name\": \"Druid of the Saber\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Choose One - Transform to gain Charge; or +1/+1 and Stealth.\",\n\t\"options\": [\n\t\t{\n\t\t\t\"description\": \"Charge\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"card\": \"token_sabertooth_lion\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"description\": \"Stealth +1/+1\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"card\": \"token_sabertooth_panther\"\n\t\t\t}\n\t\t}\n\t],\n\t\"bothOptions\": {\n\t\t\"description\": \"Charge, Stealth, +1/+1\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"TransformMinionSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"card\": \"token_sabertooth_tiger\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/minion_knight_of_the_wild.json",
    "content": "{\n\t\"name\": \"Knight of the Wild\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever you summon a Beast, reduce the Cost of this card by (1).\",\n\t\"passiveTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionSummonedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"race\": \"BEAST\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/minion_savage_combatant.json",
    "content": "{\n\t\"name\": \"Savage Combatant\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Inspire: Give your hero +2 Attack this turn.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffHeroSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"attackBonus\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/minion_wildwalker.json",
    "content": "{\n\t\"name\": \"Wildwalker\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give a friendly Beast +3 Health.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"hpBonus\": 3,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"BEAST\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/spell_astral_communion.json",
    "content": "{\n\t\"name\": \"Astral Communion\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Gain 10 Mana Crystals. Discard your hand.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DiscardSpell\",\n\t\t\t\t\"value\": -1\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"EitherOrSpell\",\n\t\t\t\t\"spell1\": {\n\t\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\t\"card\": \"spell_excess_mana\"\n\t\t\t\t},\n\t\t\t\t\"spell2\": {\n\t\t\t\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\t\t\t\"fullManaCrystals\": true,\n\t\t\t\t\t\"value\": 10\n\t\t\t\t},\n\t\t\t\t\"condition\": {\n\t\t\t\t\t\"class\": \"ManaMaxedCondition\"\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/spell_living_roots.json",
    "content": "{\n\t\"name\": \"Living Roots\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"CHOOSE_ONE\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Choose One - Deal 2 damage; or Summon two 1/1 Saplings.\",\n\t\"targetSelection\": \"NONE\",\n\t\"options\": [\n\t\t\"spell_living_roots_1\",\n\t\t\"spell_living_roots_2\"\n\t],\n\t\"bothOptions\": \"spell_living_roots_3\",\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/spell_living_roots_1.json",
    "content": "{\n\t\"name\": \"2 damage\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 2 damage\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/spell_living_roots_2.json",
    "content": "{\n\t\"name\": \"Summon two 1/1\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Summon two 1/1 Saplings\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_sapling\",\n\t\t\t\"token_sapling\"\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/spell_living_roots_3.json",
    "content": "{\n\t\"name\": \"Deal 2 damage and summon two 1/1\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 2 damage. Summon two 1/1 Saplings.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 2\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\"cards\": [\n\t\t\t\t\t\"token_sapling\",\n\t\t\t\t\t\"token_sapling\"\n\t\t\t\t]\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/spell_mulch.json",
    "content": "{\n\t\"name\": \"Mulch\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Destroy a minion. Add a random minion to your opponent's hand.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DestroySpell\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/token_sabertooth_lion.json",
    "content": "{\n\t\"name\": \"Sabertooth Lion\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Charge\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/token_sabertooth_panther.json",
    "content": "{\n\t\"name\": \"Sabertooth Panther\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Stealth\",\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/token_sabertooth_tiger.json",
    "content": "{\n\t\"name\": \"Sabertooth Tiger\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Charge, Stealth\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true,\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/druid/token_sapling.json",
    "content": "{\n\t\"name\": \"Sapling\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/hunter/hero_power_ballista_shot.json",
    "content": "{\n\t\"name\": \"Ballista Shot\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 3 damage to the enemy hero.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ENEMY_HERO\",\n\t\t\"value\": 3\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/hunter/minion_acidmaw.json",
    "content": "{\n\t\"name\": \"Acidmaw\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Whenever another minion takes damage, destroy it.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_AS_TARGET\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/hunter/minion_brave_archer.json",
    "content": "{\n\t\"name\": \"Brave Archer\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Inspire: If your hand is empty, deal 2 damage to the enemy hero.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"fireCondition\": {\n\t\t\t\t\"class\": \"CardCountCondition\",\n\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\t\"value\": 0\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ENEMY_HERO\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/hunter/minion_dreadscale.json",
    "content": "{\n\t\"name\": \"Dreadscale\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"At the end of your turn, deal 1 damage to all other minions.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ALL_OTHER_MINIONS\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/hunter/minion_kings_elekk.json",
    "content": "{\n\t\"name\": \"King's Elekk\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Reveal a minion in each deck. If yours costs more, draw it.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"JoustSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"FromDeckToHandSpell\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/hunter/minion_ram_wrangler.json",
    "content": "{\n\t\"name\": \"Ram Wrangler\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: If you have a Beast, summon a random Beast.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"BEAST\"\n\t\t\t}\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"BEAST\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/hunter/minion_stablemaster.json",
    "content": "{\n\t\"name\": \"Stablemaster\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Give a friendly Beast Immune this turn.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"attribute\": \"IMMUNE\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"BEAST\"\n\t\t\t},\n\t\t\t\"revertTrigger\": {\n\t\t\t\t\"class\": \"TurnEndTrigger\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/hunter/secret_bear_trap.json",
    "content": "{\n\t\"name\": \"Bear Trap\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Secret: After your hero is attacked, summon a 3/3 Bear with Taunt.\",\n\t\"trigger\": {\n\t\t\"class\": \"AfterPhysicalAttackTrigger\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"targetEntityType\": \"HERO\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"minion_ironfur_grizzly\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/hunter/spell_ball_of_spiders.json",
    "content": "{\n\t\"name\": \"Ball of Spiders\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Summon three 1/1 Webspinners.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"minion_webspinner\",\n\t\t\t\"minion_webspinner\",\n\t\t\t\"minion_webspinner\"\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/hunter/spell_lock_and_load.json",
    "content": "{\n\t\"name\": \"Lock and Load\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Each time you cast a spell this turn, add a random Hunter card to your hand.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"AddSpellTriggerSpell\",\n\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\"trigger\": {\n\t\t\t\"eventTrigger\": {\n\t\t\t\t\"class\": \"SpellCastedTrigger\",\n\t\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t\t},\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"heroClass\": \"HUNTER\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"oneTurn\": true\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/hunter/spell_powershot.json",
    "content": "{\n\t\"name\": \"Powershot\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 2 damage to a minion and the minions next to it.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"AdjacentEffectSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 2\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/mage/hero_power_fireblast_rank_2.json",
    "content": "{\n\t\"name\": \"Fireblast Rank 2\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 2 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/mage/minion_coldarra_drake.json",
    "content": "{\n\t\"name\": \"Coldarra Drake\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"You can use your Hero Power any number of times.\",\n\t\"race\": \"DRAGON\",\n\t\"attributes\": {\n\t\t\"HERO_POWER_USAGES\": -1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/mage/minion_dalaran_aspirant.json",
    "content": "{\n\t\"name\": \"Dalaran Aspirant\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Inspire: Gain Spell Damage +1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"SPELL_DAMAGE\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/mage/minion_fallen_hero.json",
    "content": "{\n\t\"name\": \"Fallen Hero\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Your Hero Power deals 1 extra damage.\",\n\t\"attributes\": {\n\t\t\"HERO_POWER_DAMAGE\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/mage/minion_rhonin.json",
    "content": "{\n\t\"name\": \"Rhonin\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Deathrattle: Add 3 copies of Arcane Missiles to your hand.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\"cards\": [\n\t\t\t\"spell_arcane_missiles\",\n\t\t\t\"spell_arcane_missiles\",\n\t\t\t\"spell_arcane_missiles\"\n\t\t]\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/mage/minion_spellslinger.json",
    "content": "{\n\t\"name\": \"Spellslinger\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Add a random spell to each player's hand.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"targetPlayer\": \"BOTH\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"SPELL\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/mage/secret_effigy.json",
    "content": "{\n\t\"name\": \"Effigy\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Secret: When a friendly minion dies, summon a random minion with the same Cost.\",\n\t\"trigger\": {\n\t\t\"class\": \"MinionDeathTrigger\",\n\t\t\"targetPlayer\": \"SELF\"\n\t},\n\t\"spell\": {\n\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"manaCost\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\"attribute\": \"BASE_MANA_COST\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/mage/spell_arcane_blast.json",
    "content": "{\n\t\"name\": \"Arcane Blast\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deal 2 damage to a minion. This spell gets double bonus from Spell Damage.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 2\n\t},\n\t\"attributes\": {\n\t\t\"SPELL_DAMAGE_MULTIPLIER\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/mage/spell_flame_lance.json",
    "content": "{\n\t\"name\": \"Flame Lance\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 8 damage to a minion.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 8\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/mage/spell_polymorph_boar.json",
    "content": "{\n\t\"name\": \"Polymorph: Boar\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Transform a minion into a 4/2 Boar with Charge.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"TransformMinionSpell\",\n\t\t\"card\": \"token_mage_huffer\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/mage/token_mage_huffer.json",
    "content": "{\n\t\"name\": \"Boar\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Charge\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_argent_horserider.json",
    "content": "{\n\t\"name\": \"Argent Horserider\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Divine Shield. Charge.\",\n\t\"attributes\": {\n\t\t\"CHARGE\": true,\n\t\t\"DIVINE_SHIELD\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_argent_watchman.json",
    "content": "{\n\t\"name\": \"Argent Watchman\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Can't attack. Inspire: Can attack as normal this turn.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"RemoveAttributeSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"CANNOT_ATTACK\",\n\t\t\t\"revertTrigger\": {\n\t\t\t\t\"class\": \"TurnEndTrigger\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"CANNOT_ATTACK\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_armored_warhorse.json",
    "content": "{\n\t\"name\": \"Armored Warhorse\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Reveal a minion in each deck.If yours costs more, gain Charge.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"JoustSpell\",\n\t\t\t\"target\": \"NONE\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"attribute\": \"CHARGE\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_bolf_ramshield.json",
    "content": "{\n\t\"name\": \"Bolf Ramshield\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 9,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Whenever your hero takes damage, this minion takes it instead.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"PreDamageTrigger\",\n\t\t\t\"sourcePlayer\": \"BOTH\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"targetEntityType\": \"HERO\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\"target\": \"SELF\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyDamageSpell\",\n\t\t\t\t\t\"operation\": \"SET\",\n\t\t\t\t\t\"value\": 0\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_boneguard_lieutenant.json",
    "content": "{\n\t\"name\": \"Boneguard Lieutenant\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Inspire: Gain +1 Health.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_captured_jormungar.json",
    "content": "{\n\t\"name\": \"Captured Jormungar\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 9,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_chillmaw.json",
    "content": "{\n\t\"name\": \"Chillmaw\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Taunt. Deathrattle: If you're holding a Dragon, deal 3 damage to all minions.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ConditionalSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\"value\": 3\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HoldsCardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_clockwork_knight.json",
    "content": "{\n\t\"name\": \"Clockwork Knight\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Battlecry: Give a friendly Mech +1/+1.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"MECH\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_coliseum_manager.json",
    "content": "{\n\t\"name\": \"Coliseum Manager\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Inspire: Return this minion to your hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReturnMinionToHandSpell\",\n\t\t\t\"target\": \"SELF\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_crowd_favorite.json",
    "content": "{\n\t\"name\": \"Crowd Favorite\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Whenever you play a card with Battlecry, gain +1/+1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardPlayedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"AttributeCondition\",\n\t\t\t\t\"attribute\": \"BATTLECRY\",\n\t\t\t\t\"operation\": \"HAS\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_dragonhawk_rider.json",
    "content": "{\n\t\"name\": \"Dragonhawk Rider\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Inspire: Gain Windfury this turn.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"WINDFURY\",\n\t\t\t\"revertTrigger\": {\n\t\t\t\t\"class\": \"TurnEndTrigger\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_evil_heckler.json",
    "content": "{\n\t\"name\": \"Evil Heckler\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_eydis_darkbane.json",
    "content": "{\n\t\"name\": \"Eydis Darkbane\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Whenever you target this minion with a spell, deal 3 damage to a random enemy.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\t\"actionType\": \"SPELL\",\n\t\t\t\"sourcePlayer\": \"SELF\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\"value\": 3,\n\t\t\t\"howMany\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_fencing_coach.json",
    "content": "{\n\t\"name\": \"Fencing Coach\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: The next time you use your Hero Power, it costs (2) less.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"cardType\": \"HERO_POWER\",\n\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\"value\": \"2\",\n\t\t\t\t\"expirationTrigger\": {\n\t\t\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_fjola_lightbane.json",
    "content": "{\n\t\"name\": \"Fjola Lightbane\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Whenever you target this minion with a spell, gain Divine Shield.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\t\"actionType\": \"SPELL\",\n\t\t\t\"sourcePlayer\": \"SELF\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"DIVINE_SHIELD\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_flame_juggler.json",
    "content": "{\n\t\"name\": \"Flame Juggler\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Deal 1 damage to a random enemy.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\"value\": 1,\n\t\t\t\"howMany\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_frigid_snobold.json",
    "content": "{\n\t\"name\": \"Frigid Snobold\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Spell Damage +1\",\n\t\"attributes\": {\n\t\t\"SPELL_DAMAGE\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_frost_giant.json",
    "content": "{\n\t\"name\": \"Frost Giant\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Costs (1) less for each time you used your Hero Power this game.\",\n\t\"manaCostModifier\": {\n\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\"playerAttribute\": \"HERO_POWER_USED\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_gadgetzan_jouster.json",
    "content": "{\n\t\"name\": \"Gadgetzan Jouster\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Reveal a minion in each deck. If yours costs more, gain +1/+1\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"JoustSpell\",\n\t\t\t\"target\": \"NONE\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"attackBonus\": 1,\n\t\t\t\t\"hpBonus\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_garrison_commander.json",
    "content": "{\n\t\"name\": \"Garrison Commander\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"You can use your Hero Power twice a turn.\",\n\t\"attributes\": {\n\t\t\"HERO_POWER_USAGES\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_gormok_the_impaler.json",
    "content": "{\n\t\"name\": \"Gormok the Impaler\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: If you have at least 4 other minions, deal 4 damage.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 4\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"operation\": \"GREATER\",\n\t\t\t\"value\": 4\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_grand_crusader.json",
    "content": "{\n\t\"name\": \"Grand Crusader\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Add a random Paladin card to your hand.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"heroClass\": \"PALADIN\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_ice_rager.json",
    "content": "{\n\t\"name\": \"Ice Rager\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_icehowl.json",
    "content": "{\n\t\"name\": \"Icehowl\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 10,\n\t\"baseHp\": 10,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Charge. Can't attack heroes.\",\n\t\"attributes\": {\n\t\t\"CANNOT_ATTACK_HEROES\": true,\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_injured_kvaldir.json",
    "content": "{\n\t\"name\": \"Injured Kvaldir\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Deal 3 damage to this minion\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": 3\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_justicar_trueheart.json",
    "content": "{\n\t\"name\": \"Justicar Trueheart\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Replace your starting Hero Power with a better one.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"ConditionalSpell\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ChangeHeroPowerSpell\",\n\t\t\t\t\t\"card\": \"hero_power_dire_shapeshift\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ChangeHeroPowerSpell\",\n\t\t\t\t\t\"card\": \"hero_power_ballista_shot\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ChangeHeroPowerSpell\",\n\t\t\t\t\t\"card\": \"hero_power_fireblast_rank_2\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ChangeHeroPowerSpell\",\n\t\t\t\t\t\"card\": \"hero_power_the_silver_hand\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ChangeHeroPowerSpell\",\n\t\t\t\t\t\"card\": \"hero_power_heal\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ChangeHeroPowerSpell\",\n\t\t\t\t\t\"card\": \"hero_power_poisoned_dagger\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ChangeHeroPowerSpell\",\n\t\t\t\t\t\"card\": \"hero_power_totemic_slam\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ChangeHeroPowerSpell\",\n\t\t\t\t\t\"card\": \"hero_power_soul_tap\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ChangeHeroPowerSpell\",\n\t\t\t\t\t\"card\": \"hero_power_tank_up\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"conditions\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"HasHeroPowerCondition\",\n\t\t\t\t\t\"cardId\": \"hero_power_shapeshift\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"HasHeroPowerCondition\",\n\t\t\t\t\t\"cardId\": \"hero_power_steady_shot\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"HasHeroPowerCondition\",\n\t\t\t\t\t\"cardId\": \"hero_power_fireblast\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"HasHeroPowerCondition\",\n\t\t\t\t\t\"cardId\": \"hero_power_reinforce\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"HasHeroPowerCondition\",\n\t\t\t\t\t\"cardId\": \"hero_power_lesser_heal\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"HasHeroPowerCondition\",\n\t\t\t\t\t\"cardId\": \"hero_power_dagger_mastery\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"HasHeroPowerCondition\",\n\t\t\t\t\t\"cardId\": \"hero_power_totemic_call\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"HasHeroPowerCondition\",\n\t\t\t\t\t\"cardId\": \"hero_power_life_tap\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"HasHeroPowerCondition\",\n\t\t\t\t\t\"cardId\": \"hero_power_armor_up\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_kodorider.json",
    "content": "{\n\t\"name\": \"Kodorider\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Inspire: Summon a 3/5 War Kodo.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_war_kodo\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_kvaldir_raider.json",
    "content": "{\n\t\"name\": \"Kvaldir Raider\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Inspire: Gain +2/+2.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"hpBonus\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_lance_carrier.json",
    "content": "{\n\t\"name\": \"Lance Carrier\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give a friendly minion +2 Attack.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"attackBonus\": 2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_lights_champion.json",
    "content": "{\n\t\"name\": \"Light's Champion\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Silence a Demon.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"SilenceSpell\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"DEMON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_lowly_squire.json",
    "content": "{\n\t\"name\": \"Lowly Squire\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Inspire: Gain +1 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_maiden_of_the_lake.json",
    "content": "{\n\t\"name\": \"Maiden of the Lake\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Your Hero Power costs (1).\",\n\t\"cardCostModifier\": {\n\t\t\"class\": \"CardCostModifier\",\n\t\t\"cardType\": \"HERO_POWER\",\n\t\t\"operation\": \"SET\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_master_jouster.json",
    "content": "{\n\t\"name\": \"Master Jouster\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry : Reveal a minion in each deck. If yours costs more, gain Taunt and Divine Shield.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"JoustSpell\",\n\t\t\t\"target\": \"NONE\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"spells\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\t\"attribute\": \"DIVINE_SHIELD\"\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_master_of_ceremonies.json",
    "content": "{\n\t\"name\": \"Master of Ceremonies\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: If you have a minion with Spell Damage, gain +2/+2.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"hpBonus\": 2\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HasEntityCondition\",\n\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"SPELL_DAMAGE\",\n\t\t\t\t\"operation\": \"HAS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_mogors_champion.json",
    "content": "{\n\t\"name\": \"Mogor's Champion\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"50% chance to attack the wrong enemy.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TargetAcquisitionTrigger\",\n\t\t\t\"actionType\": \"PHYSICAL_ATTACK\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"RandomCondition\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"FumbleSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_muklas_champion.json",
    "content": "{\n\t\"name\": \"Mukla's Champion\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Inspire: Give your other minions +1/+1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_nexus-champion_saraad.json",
    "content": "{\n\t\"name\": \"Nexus-Champion Saraad\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Inspire: Add a random spell to your hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"SPELL\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_north_sea_kraken.json",
    "content": "{\n\t\"name\": \"North Sea Kraken\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 9,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Deal 4 damage.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 4\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_pit_fighter.json",
    "content": "{\n\t\"name\": \"Pit Fighter\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_recruiter.json",
    "content": "{\n\t\"name\": \"Recruiter\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Inspire: Add a 2/2 Squire to your hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"card\": \"token_squire\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_refreshment_vendor.json",
    "content": "{\n\t\"name\": \"Refreshment Vendor\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Restore 4 Health to each hero.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\t\"value\": 4\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\t\"target\": \"ENEMY_HERO\",\n\t\t\t\t\t\"value\": 4\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_saboteur.json",
    "content": "{\n\t\"name\": \"Saboteur\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Your opponent's Hero Power costs (5) more next turn.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"cardType\": \"HERO_POWER\",\n\t\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\t\"operation\": \"ADD\",\n\t\t\t\t\"value\": 5,\n\t\t\t\t\"expirationTrigger\": {\n\t\t\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\t\t\"targetPlayer\": \"OPPONENT\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_sideshow_spelleater.json",
    "content": "{\n\t\"name\": \"Sideshow Spelleater\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Copy your opponent's Hero Power.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"CopyHeroPower\",\n\t\t\t\"target\": \"FRIENDLY_HERO\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_silent_knight.json",
    "content": "{\n\t\"name\": \"Silent Knight\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Stealth. Divine Shield.\",\n\t\"attributes\": {\n\t\t\"DIVINE_SHIELD\": true,\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_silver_hand_regent.json",
    "content": "{\n\t\"name\": \"Silver Hand Regent\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Inspire: Summon a 1/1 Silver Hand Recruit.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_silver_hand_recruit\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_skycapn_kragg.json",
    "content": "{\n\t\"name\": \"Skycap'n Kragg\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Charrrrrge. Costs (1) less for each friendly Pirate.\",\n\t\"manaCostModifier\": {\n\t\t\"class\": \"EntityCounter\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"PIRATE\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"CHARGE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_the_skeleton_knight.json",
    "content": "{\n\t\"name\": \"The Skeleton Knight\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Deathrattle: Reveal a minion in each deck. If yours costs more, return this to your hand.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"JoustSpell\",\n\t\t\"target\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReturnMinionToHandSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"condition\": {\n\t\t\t\t\"class\": \"CardCountCondition\",\n\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\"operation\": \"LESS\",\n\t\t\t\t\"value\": 10\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_tournament_attendee.json",
    "content": "{\n\t\"name\": \"Tournament Attendee\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_tournament_medic.json",
    "content": "{\n\t\"name\": \"Tournament Medic\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Inspire: Restore 2 Health to your Hero.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/minion_twilight_guardian.json",
    "content": "{\n\t\"name\": \"Twilight Guardian\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Battlecry: If you're holding a Dragon, gain +1 Attack and Taunt.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"attackBonus\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HoldsCardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/neutral/token_war_kodo.json",
    "content": "{\n\t\"name\": \"War Kodo\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/paladin/hero_power_the_silver_hand.json",
    "content": "{\n\t\"name\": \"The Silver Hand\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Summon two 1/1 Silver Hand Recruits.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_silver_hand_recruit\",\n\t\t\t\"token_silver_hand_recruit\"\n\t\t]\n\t},\n\t\"condition\": {\n\t\t\"class\": \"MinionCountCondition\",\n\t\t\"operation\": \"LESS\",\n\t\t\"value\": 7\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/paladin/minion_eadric_the_pure.json",
    "content": "{\n\t\"name\": \"Eadric the Pure\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Change all enemy minions' Attack to 1.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SetAttackSpell\",\n\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/paladin/minion_murloc_knight.json",
    "content": "{\n\t\"name\": \"Murloc Knight\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Inspire: Summon a random Murloc.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"MURLOC\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/paladin/minion_mysterious_challenger.json",
    "content": "{\n\t\"name\": \"Mysterious Challenger\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Put one of each Secret from your deck into the battlefield.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"PutRandomSecretIntoPlaySpell\",\n\t\t\t\"howMany\": 99\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/paladin/minion_tuskarr_jouster.json",
    "content": "{\n\t\"name\": \"Tuskarr Jouster\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Reveal a minion in each deck. If yours costs more, restore 7 Health to your hero.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"JoustSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"value\": 7\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/paladin/minion_warhorse_trainer.json",
    "content": "{\n\t\"name\": \"Warhorse Trainer\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Your Silver Hand Recruits have +1 Attack.\",\n\t\"aura\": {\n\t\t\"class\": \"BuffAura\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 1,\n\t\t\"filter\": {\n\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\"cardId\": \"token_silver_hand_recruit\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/paladin/secret_competitive_spirit.json",
    "content": "{\n\t\"name\": \"Competitive Spirit\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Secret: When your turn starts, give your minions +1/+1.\",\n\t\"targetSelection\": \"NONE\",\n\t\"trigger\": {\n\t\t\"class\": \"TurnEndTrigger\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"fireCondition\": {\n\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\"operation\": \"GREATER\",\n\t\t\t\"value\": 0\n\t\t}\n\t},\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 1,\n\t\t\"hpBonus\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/paladin/spell_enter_the_coliseum.json",
    "content": "{\n\t\"name\": \"Enter the Coliseum\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Destroy all minions except each player's highest Attack minion.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DestroyAllExceptOneSpell\",\n\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\"filter\": {\n\t\t\t\t\t\"class\": \"HighestAttributeFilter\",\n\t\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DestroyAllExceptOneSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\"filter\": {\n\t\t\t\t\t\"class\": \"HighestAttributeFilter\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/paladin/spell_seal_of_champions.json",
    "content": "{\n\t\"name\": \"Seal of Champions\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a minion +3 Attack and Divine Shield.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"attackBonus\": 3\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"DIVINE_SHIELD\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/paladin/weapon_argent_lance.json",
    "content": "{\n\t\"name\": \"Argent Lance\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 2,\n\t\"durability\": 2,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry : Reveal a minion in each deck. If yours costs more, gain +1 durability.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"JoustSpell\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ModifyDurabilitySpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/priest/hero_power_heal.json",
    "content": "{\n\t\"name\": \"Heal\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Restore 4 Health.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"HealSpell\",\n\t\t\"value\": 4\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/priest/minion_confessor_paletress.json",
    "content": "{\n\t\"name\": \"Confessor Paletress\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Inspire: Summon a random Legendary minion.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"rarity\": \"LEGENDARY\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/priest/minion_holy_champion.json",
    "content": "{\n\t\"name\": \"Holy Champion\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever a character is healed, gain +2 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"HealingTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/priest/minion_shadowfiend.json",
    "content": "{\n\t\"name\": \"Shadowfiend\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Whenever you draw a card, reduce its Cost by (1).\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardDrawnTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/priest/minion_spawn_of_shadows.json",
    "content": "{\n\t\"name\": \"Spawn of Shadows\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Inspire: Deal 4 damage to each hero.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\"target\": \"ENEMY_HERO\",\n\t\t\t\t\t\"value\": 4\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\t\"value\": 4\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/priest/minion_wyrmrest_agent.json",
    "content": "{\n\t\"name\": \"Wyrmrest Agent\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: If you're holding a Dragon, gain +1 Attack and Taunt.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"attackBonus\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HoldsCardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/priest/spell_confuse.json",
    "content": "{\n\t\"name\": \"Confuse\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Swap the Attack and Health of all minions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SwapAttackAndHpSpell\",\n\t\t\"target\": \"ALL_MINIONS\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/priest/spell_convert.json",
    "content": "{\n\t\"name\": \"Convert\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Put a copy of an enemy minion into your hand.\",\n\t\"targetSelection\": \"ENEMY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"CopyCardSpell\"\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/priest/spell_flash_heal.json",
    "content": "{\n\t\"name\": \"Flash Heal\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Restore 5 Health.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"HealSpell\",\n\t\t\"value\": 5\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/priest/spell_power_word_glory.json",
    "content": "{\n\t\"name\": \"Power Word: Glory\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Choose a minion. Whenever it attacks, restore 4 Health to your hero.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"AddSpellTriggerSpell\",\n\t\t\"trigger\": {\n\t\t\t\"eventTrigger\": {\n\t\t\t\t\"class\": \"PhysicalAttackTrigger\",\n\t\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t\t},\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"value\": 4\n\t\t\t},\n\t\t\t\"persistentOwner\": true\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/rogue/hero_power_poisoned_dagger.json",
    "content": "{\n\t\"name\": \"Poisoned Dagger\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Equip a 2/2 Dagger.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ConditionalSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"EquipWeaponSpell\",\n\t\t\t\"card\": \"weapon_poisoned_dagger\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HasWeaponCondition\",\n\t\t\t\"cardId\": \"weapon_poisoned_blade\",\n\t\t\t\"invert\": \"true\"\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/rogue/minion_anubarak.json",
    "content": "{\n\t\"name\": \"Anub'arak\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Deathrattle: Return this to your hand and summon a 4/4 Nerubian.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"target\": \"NONE\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"ReturnMinionToHandSpell\",\n\t\t\t\t\"target\": \"SELF\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\"card\": \"token_nerubian\"\n\t\t\t}\n\t\t]\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/rogue/minion_buccaneer.json",
    "content": "{\n\t\"name\": \"Buccaneer\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Whenever you equip a weapon, give it +1 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"WeaponEquippedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffWeaponSpell\",\n\t\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/rogue/minion_cutpurse.json",
    "content": "{\n\t\"name\": \"Cutpurse\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever this minion attacks a hero, add the Coin to your hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"PhysicalAttackTrigger\",\n\t\t\t\"targetEntityType\": \"HERO\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"card\": \"spell_the_coin\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/rogue/minion_shado-pan_rider.json",
    "content": "{\n\t\"name\": \"Shado-Pan Rider\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Combo: Gain +3 Attack.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 3\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"ComboCondition\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/rogue/minion_shady_dealer.json",
    "content": "{\n\t\"name\": \"Shady Dealer\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: If you have a Pirate, gain +1/+1.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"PIRATE\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/rogue/minion_undercity_valiant.json",
    "content": "{\n\t\"name\": \"Undercity Valiant\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Combo: Deal 1 damage.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 1\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"ComboCondition\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/rogue/spell_ambush.json",
    "content": "{\n\t\"name\": \"Ambush!\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"When you draw this, summon a 4/4 Nerubian for your opponent. Draw a card.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"NullSpell\"\n\t},\n\t\"passiveTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardDrawnTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\t\t\"card\": \"token_nerubian\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"RemoveCardSpell\",\n\t\t\t\t\t\"target\": \"EVENT_TARGET\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DrawCardSpell\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/rogue/spell_beneath_the_ground.json",
    "content": "{\n\t\"name\": \"Beneath the Grounds\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Shuffle 3 Ambushes into your opponent's deck. When drawn, you summon a 4/4 Nerubian.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ShuffleToDeckSpell\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"card\": \"spell_ambush\",\n\t\t\"howMany\": 3\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/rogue/spell_burgle.json",
    "content": "{\n\t\"name\": \"Burgle\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Add 2 random class cards to your hand (from your opponent's class).\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\"value\": 2,\n\t\t\"card\": \"spell_the_coin\",\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"heroClass\": \"OPPONENT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/rogue/weapon_poisoned_blade.json",
    "content": "{\n\t\"name\": \"Poisoned Blade\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 1,\n\t\"durability\": 3,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Your Hero Power gives this weapon +1 Attack instead of replacing it.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffWeaponSpell\",\n\t\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/rogue/weapon_poisoned_dagger.json",
    "content": "{\n\t\"name\": \"Poisoned Dagger\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 2,\n\t\"durability\": 2,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/hero_power_lightning_jolt.json",
    "content": "{\n\t\"name\": \"Lightning Jolt\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 2 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/hero_power_totemic_slam.json",
    "content": "{\n\t\"name\": \"Totemic Slam\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Summon a Totem of your choice.\",\n\t\"targetSelection\": \"NONE\",\n\t\"options\": [\n\t\t\"spell_summon_searing_totem\",\n\t\t\"spell_summon_healing_totem\",\n\t\t\"spell_summon_wrath_of_air_totem\",\n\t\t\"spell_summon_stoneclaw_totem\"\n\t],\n\t\"condition\": {\n\t\t\"class\": \"MinionCountCondition\",\n\t\t\"operation\": \"LESS\",\n\t\t\"value\": 7\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/minion_draenei_totemcarver.json",
    "content": "{\n\t\"name\": \"Draenei Totemcarver\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Gain +1/+1 for each friendly Totem.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"EntityCounter\",\n\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\"filter\": {\n\t\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\t\"race\": \"TOTEM\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/minion_the_mistcaller.json",
    "content": "{\n\t\"name\": \"The Mistcaller\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Give all minions in your hand and deck +1/+1.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"attackBonus\": 1,\n\t\t\t\t\t\"hpBonus\": 1,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddSpellTriggerSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"trigger\": {\n\t\t\t\t\t\t\"eventTrigger\": {\n\t\t\t\t\t\t\t\"class\": \"CardDrawnTrigger\",\n\t\t\t\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\t\t\t\"queueCondition\": {\n\t\t\t\t\t\t\t\t\"class\": \"CardPropertyCondition\",\n\t\t\t\t\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"spell\": {\n\t\t\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\t\t\t\"attackBonus\": 1,\n\t\t\t\t\t\t\t\"hpBonus\": 1\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/minion_thunder_bluff_valiant.json",
    "content": "{\n\t\"name\": \"Thunder Bluff Valiant\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Inspire: Give your Totems +2 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"TOTEM\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/minion_totem_golem.json",
    "content": "{\n\t\"name\": \"Totem Golem\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"TOTEM\",\n\t\"description\": \"Overload: (1)\",\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/minion_tuskarr_totemic.json",
    "content": "{\n\t\"name\": \"Tuskarr Totemic\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Summon a random basic Totem.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonRandomSpell\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\",\n\t\t\t\"cards\": [\n\t\t\t\t\"token_searing_totem\",\n\t\t\t\t\"token_healing_totem\",\n\t\t\t\t\"token_wrath_of_air_totem\",\n\t\t\t\t\"token_stoneclaw_totem\"\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/spell_ancestral_knowledge.json",
    "content": "{\n\t\"name\": \"Ancestral Knowledge\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Draw 2 cards. Overload: (2)\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"value\": 2\n\t},\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/spell_elemental_destruction.json",
    "content": "{\n\t\"name\": \"Elemental Destruction\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deal 4-5 damage to all minions. Overload: (5)\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": {\n\t\t\t\"class\": \"RandomValueProvider\",\n\t\t\t\"min\": 4,\n\t\t\t\"max\": 5\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 5\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/spell_healing_wave.json",
    "content": "{\n\t\"name\": \"Healing Wave\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Restore 7 Health. Reveal a minion in each deck. If yours costs more, Restore 14 instead.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"JoustSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"value\": 7\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"value\": 14\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/spell_summon_healing_totem.json",
    "content": "{\n\t\"name\": \"Healing Totem\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Summon a Healing Totem.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_healing_totem\"\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/spell_summon_searing_totem.json",
    "content": "{\n\t\"name\": \"Searing Totem\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Summon a Searing Totem.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_searing_totem\"\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/spell_summon_stoneclaw_totem.json",
    "content": "{\n\t\"name\": \"Stoneclaw Totem\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Summon a Stoneclaw Totem.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_stoneclaw_totem\"\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/spell_summon_wrath_of_air_totem.json",
    "content": "{\n\t\"name\": \"Wrath of Air Totem\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Summon a Wrath of Air Totem.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_wrath_of_air_totem\"\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/shaman/weapon_charged_hammer.json",
    "content": "{\n\t\"name\": \"Charged Hammer\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 2,\n\t\"durability\": 4,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deathrattle: Your Hero Power becomes 'Deal 2 damage.'\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ChangeHeroPowerSpell\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"card\": \"hero_power_lightning_jolt\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warlock/hero_power_soul_tap.json",
    "content": "{\n\t\"name\": \"Soul Tap\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Draw a card.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warlock/minion_dreadsteed.json",
    "content": "{\n\t\"name\": \"Dreadsteed\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Deathrattle: Summon a Dreadsteed.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"minion_dreadsteed\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warlock/minion_fearsome_doomguard.json",
    "content": "{\n\t\"name\": \"Fearsome Doomguard\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warlock/minion_tiny_knight_of_evil.json",
    "content": "{\n\t\"name\": \"Tiny Knight of Evil\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Whenever you discard a card, gain +1/+1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DiscardTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warlock/minion_void_crusher.json",
    "content": "{\n\t\"name\": \"Void Crusher\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Inspire: Destroy a random minion for each player.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"randomTarget\": true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\t\"randomTarget\": true\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warlock/minion_wilfred_fizzlebang.json",
    "content": "{\n\t\"name\": \"Wilfred Fizzlebang\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Cards you draw from your Hero Power cost (0).\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardDrawnTrigger\",\n\t\t\t\"sourceType\": \"HERO_POWER\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\"operation\": \"SET\",\n\t\t\t\t\"value\": 0\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warlock/minion_wrathguard.json",
    "content": "{\n\t\"name\": \"Wrathguard\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"DEMON\",\n\t\"description\": \"Whenever this minion takes damage, also deal that amount to your hero.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"attribute\": \"LAST_HIT\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warlock/spell_dark_bargain.json",
    "content": "{\n\t\"name\": \"Dark Bargain\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Destroy 2 random enemy minions. Discard 2 random cards.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"MultiTargetSpell\",\n\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\"value\": 2,\n\t\t\t\t\"spell\": {\n\t\t\t\t\t\"class\": \"DestroySpell\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DiscardSpell\",\n\t\t\t\t\"value\": 2\n\t\t\t}\n\t\t]\n\t},\n\t\"condition\": {\n\t\t\"class\": \"MinionCountCondition\",\n\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\"operation\": \"GREATER\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warlock/spell_demonfuse.json",
    "content": "{\n\t\"name\": \"Demonfuse\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a Demon +3/+3. Give your opponent a Mana Crystal.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"attackBonus\": 3,\n\t\t\t\t\"hpBonus\": 3\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t],\n\t\t\"filter\": {\n\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\"race\": \"DEMON\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warlock/spell_fist_of_jaraxxus.json",
    "content": "{\n\t\"name\": \"Fist of Jaraxxus\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"When you play or discard this, deal 4 damage to a random enemy.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MissilesSpell\",\n\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\"value\": 4,\n\t\t\"howMany\": 1\n\t},\n\t\"passiveTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DiscardTrigger\",\n\t\t\t\"target\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\"value\": 4,\n\t\t\t\"howMany\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warrior/hero_power_tank_up.json",
    "content": "{\n\t\"name\": \"Tank Up!\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Gain 4 Armor.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffHeroSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"armorBonus\": 4\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warrior/minion_alexstraszas_champion.json",
    "content": "{\n\t\"name\": \"Alexstrasza's Champion\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: If you're holding a Dragon, gain +1 Attack and Charge.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"attackBonus\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"attribute\": \"CHARGE\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HoldsCardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"race\": \"DRAGON\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warrior/minion_magnataur_alpha.json",
    "content": "{\n\t\"name\": \"Magnataur Alpha\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Also damages the minions next to whomever he attacks.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterPhysicalAttackTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"AdjacentEffectSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\"spell2\": {\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warrior/minion_orgrimmar_aspirant.json",
    "content": "{\n\t\"name\": \"Orgrimmar Aspirant\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Inspire: Give your weapon +1 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"InspireTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffWeaponSpell\",\n\t\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warrior/minion_sea_reaver.json",
    "content": "{\n\t\"name\": \"Sea Reaver\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"When you draw this, deal 1 damage to your minions.\",\n\t\"passiveTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardDrawnTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warrior/minion_sparring_partner.json",
    "content": "{\n\t\"name\": \"Sparring Partner\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Taunt. Battlecry: Give a minion Taunt.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"attribute\": \"TAUNT\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warrior/minion_varian_wrynn.json",
    "content": "{\n\t\"name\": \"Varian Wrynn\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Draw 3 cards. Put any minions you drew directly into the battlefield.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardAndDoSomethingSpell\",\n\t\t\t\"value\": 3,\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ConditionalSpell\",\n\t\t\t\t\"spell\": {\n\t\t\t\t\t\"class\": \"PutMinionOnBoardSpell\"\n\t\t\t\t},\n\t\t\t\t\"condition\": {\n\t\t\t\t\t\"class\": \"AndCondition\",\n\t\t\t\t\t\"conditions\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"CardPropertyCondition\",\n\t\t\t\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"class\": \"MinionCountCondition\",\n\t\t\t\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\t\t\t\"operation\": \"LESS\",\n\t\t\t\t\t\t\t\"value\": 7\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warrior/spell_bash.json",
    "content": "{\n\t\"name\": \"Bash\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 3 damage. Gain 3 Armor.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 3\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"BuffHeroSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\t\"armorBonus\": 3\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warrior/spell_bolster.json",
    "content": "{\n\t\"name\": \"Bolster\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give your Taunt minions +2/+2.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 2,\n\t\t\"hpBonus\": 2,\n\t\t\"filter\": {\n\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\"attribute\": \"TAUNT\",\n\t\t\t\"operation\": \"HAS\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_grand_tournament/warrior/weapon_kings_defender.json",
    "content": "{\n\t\"name\": \"King's Defender\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 3,\n\t\"durability\": 2,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: If you have a minion with Taunt, gain +1 Durability.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyDurabilitySpell\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"value\": 1\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"HasEntityCondition\",\n\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"TAUNT\",\n\t\t\t\t\"operation\": \"HAS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_GRAND_TOURNAMENT\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/minion_addled_grizzly.json",
    "content": "{\n\t\"name\": \"Addled Grizzly\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Whenever you summon a minion, give it +1/+1.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionSummonedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/minion_dark_arakkoa.json",
    "content": "{\n\t\"name\": \"Dark Arakkoa\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give your C'Thun +3/+3 (wherever it is).\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"value\": 3,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"value\": 3,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\",\n\t\t\t\t\t\"value\": 3\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_HEALTH_BUFF\",\n\t\t\t\t\t\"value\": 3\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/minion_fandral_staghelm.json",
    "content": "{\n\t\"name\": \"Fandral Staghelm\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Your Choose One cards have both effects combined.\",\n\t\"attributes\": {\n\t\t\"BOTH_CHOOSE_ONE_OPTIONS\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/minion_forbidden_ancient.json",
    "content": "{\n\t\"name\": \"Forbidden Ancient\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Spend all your Mana. Gain +1/+1 for each Mana spent.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"value\": {\n\t\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\t\"playerAttribute\": \"MANA\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"GainManaSpell\",\n\t\t\t\t\t\"value\": {\n\t\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\t\"playerAttribute\": \"MANA\",\n\t\t\t\t\t\t\"multiplier\": -1\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/minion_klaxxi_amber-weaver.json",
    "content": "{\n\t\"name\": \"Klaxxi Amber-Weaver\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: If your C'Thun has 10 or more Attack, gain +5 Health.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"hpBonus\": 5\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"OrCondition\",\n\t\t\t\"conditions\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ComparisonCondition\",\n\t\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"HighestAttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 10\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ComparisonCondition\",\n\t\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"HighestAttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 10\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ComparisonCondition\",\n\t\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\"\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 4\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/minion_mire_keeper.json",
    "content": "{\n\t\"name\": \"Mire Keeper\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Choose One - Summon a 2/2 Slime; or Gain an empty Mana Crystal.\",\n\t\"options\": [\n\t\t{\n\t\t\t\"description\": \"Summon 2/2 Slime\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\"card\": \"token_tauren_slime\",\n\t\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"description\": \"Mana Crystal\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t}\n\t],\n\t\"bothOptions\": {\n\t\t\"description\": \"Summon 2/2 Slime and Mana Crystal.\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\t\"card\": \"token_tauren_slime\",\n\t\t\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyMaxManaSpell\",\n\t\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/spell_feral_rage.json",
    "content": "{\n\t\"name\": \"Feral Rage\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"CHOOSE_ONE\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Choose One - Gain +4 Attack this turn; or Gain 8 Armor.\",\n\t\"targetSelection\": \"NONE\",\n\t\"options\": [\n\t\t\"spell_feral_rage_1\",\n\t\t\"spell_feral_rage_2\"\n\t],\n\t\"bothOptions\": \"spell_feral_rage_3\",\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/spell_feral_rage_1.json",
    "content": "{\n\t\"name\": \"Evolve Spines\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Gain +4 Attack this turn.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffHeroSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"attackBonus\": 4\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/spell_feral_rage_2.json",
    "content": "{\n\t\"name\": \"Evolve Scales\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Gain 8 Armor.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffHeroSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"armorBonus\": 8\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/spell_feral_rage_3.json",
    "content": "{\n\t\"name\": \"Evolve Spines and Scales\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Gain 4 Attack and 8 Armor.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffHeroSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"attackBonus\": 4,\n\t\t\"armorBonus\": 8\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/spell_mark_of_yshaarj.json",
    "content": "{\n\t\"name\": \"Mark of Y'Shaarj\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a minion +2/+2. If it's a Beast, draw a card.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"ConditionalEffectSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"TARGET\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"hpBonus\": 2\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": 1\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"RaceCondition\",\n\t\t\t\"race\": \"BEAST\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/spell_wisps_of_the_old_gods.json",
    "content": "{\n\t\"name\": \"Wisps of the Old Gods\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"CHOOSE_ONE\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Choose One - Summon 7 Wisps; or Give your minions +2/+2.\",\n\t\"targetSelection\": \"NONE\",\n\t\"options\": [\n\t\t\"spell_wisps_of_the_old_gods_1\",\n\t\t\"spell_wisps_of_the_old_gods_2\"\n\t],\n\t\"bothOptions\": \"spell_wisps_of_the_old_gods_3\",\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/spell_wisps_of_the_old_gods_1.json",
    "content": "{\n\t\"name\": \"Summon 7 Wisps\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Summon 7 Wisps\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"minion_wisp\",\n\t\t\t\"minion_wisp\",\n\t\t\t\"minion_wisp\",\n\t\t\t\"minion_wisp\",\n\t\t\t\"minion_wisp\",\n\t\t\t\"minion_wisp\",\n\t\t\t\"minion_wisp\"\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/spell_wisps_of_the_old_gods_2.json",
    "content": "{\n\t\"name\": \"Give your minions +2/2\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Your minions get +2/2\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 2,\n\t\t\"hpBonus\": 2\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/druid/spell_wisps_of_the_old_gods_3.json",
    "content": "{\n\t\"name\": \"Summon 7 Wisps and give your minions +2/+2\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"DRUID\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Summon 7 Wisps. Give your minions +2/+2.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\"cards\": [\n\t\t\t\t\t\"minion_wisp\",\n\t\t\t\t\t\"minion_wisp\",\n\t\t\t\t\t\"minion_wisp\",\n\t\t\t\t\t\"minion_wisp\",\n\t\t\t\t\t\"minion_wisp\",\n\t\t\t\t\t\"minion_wisp\",\n\t\t\t\t\t\"minion_wisp\"\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\"attackBonus\": 2,\n\t\t\t\t\"hpBonus\": 2\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/hunter/minion_carrion_grub.json",
    "content": "{\n\t\"name\": \"Carrion Grub\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/hunter/minion_fiery_bat.json",
    "content": "{\n\t\"name\": \"Fiery Bat\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Deathrattle: Deal 1 damage to a random enemy.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"MissilesSpell\",\n\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\"value\": 1,\n\t\t\"howMany\": 1\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/hunter/minion_forlorn_stalker.json",
    "content": "{\n\t\"name\": \"Forlorn Stalker\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Give all minions with Deathrattle in your hand +1/+1.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\",\n\t\t\t\t\"attribute\": \"DEATHRATTLES\",\n\t\t\t\t\"operation\": \"HAS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/hunter/minion_giant_sand_worm.json",
    "content": "{\n\t\"name\": \"Giant Sand Worm\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Whenever this minion kills another minion, it may attack again.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterPhysicalAttackTrigger\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\",\n\t\t\t\"fireCondition\": {\n\t\t\t\t\"class\": \"IsDeadCondition\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attribute\": \"EXTRA_ATTACKS\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/hunter/minion_infested_wolf.json",
    "content": "{\n\t\"name\": \"Infested Wolf\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Deathrattle: Summon two 1/1 Spiders.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_spider\",\n\t\t\t\"token_spider\"\n\t\t]\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/hunter/minion_princess_huhuran.json",
    "content": "{\n\t\"name\": \"Princess Huhuran\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Trigger a friendly minion's Deathrattle effect immediately.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"TriggerDeathrattleSpell\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"DEATHRATTLES\",\n\t\t\t\t\"operation\": \"HAS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/hunter/spell_call_of_the_wild.json",
    "content": "{\n\t\"name\": \"Call of the Wild\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Summon all 3 Animal Companions.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_huffer\",\n\t\t\t\"token_misha\",\n\t\t\t\"token_leokk\"\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/hunter/spell_infest.json",
    "content": "{\n\t\"name\": \"Infest\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Give your minions \\\"Deathrattle: Add a random Beast card to your hand.\\\"\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"AddDeathrattleSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"BEAST\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/hunter/spell_on_the_hunt.json",
    "content": "{\n\t\"name\": \"On the Hunt\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 1 damage. Summon a 1/1 Mastiff.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\"card\": \"token_mastiff\"\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/hunter/token_mastiff.json",
    "content": "{\n\t\"name\": \"Mastiff\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/hunter/token_spider.json",
    "content": "{\n\t\"name\": \"Spider\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"HUNTER\",\n\t\"rarity\": \"FREE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/mage/minion_anomalus.json",
    "content": "{\n\t\"name\": \"Anomalus\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Deathrattle: Deal 8 damage to all minions.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": 8\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/mage/minion_cult_sorcerer.json",
    "content": "{\n\t\"name\": \"Cult Sorcerer\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Spell Damage +1. After you cast a spell, give your C'Thun +1/+1 (wherever it is).\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterSpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"value\": 1,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"value\": 1,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_HEALTH_BUFF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"SPELL_DAMAGE\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/mage/minion_demented_frostcaller.json",
    "content": "{\n\t\"name\": \"Demented Frostcaller\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"After you cast a spell, Freeze a random enemy.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterSpellCastedTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\"attribute\": \"FROZEN\",\n\t\t\t\"randomTarget\": true,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"FROZEN\",\n\t\t\t\t\"operation\": \"HAS\",\n\t\t\t\t\"invert\": true\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/mage/minion_faceless_summoner.json",
    "content": "{\n\t\"name\": \"Faceless Summoner\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Summon a random 3-Cost minion.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"manaCost\": 3\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/mage/minion_servant_of_yogg_saron.json",
    "content": "{\n\t\"name\": \"Servant of Yogg-Saron\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Cast a random spell that costs (5) or less (targets chosen randomly).\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"CastRandomSpellSpell\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"attribute\": \"BASE_MANA_COST\",\n\t\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\t\"value\": 5\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/mage/minion_twilight_flamecaller.json",
    "content": "{\n\t\"name\": \"Twilight Flamecaller\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Deal 1 damage to all enemy minions.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/mage/spell_cabalists_tome.json",
    "content": "{\n\t\"name\": \"Cabalist's Tome\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Add 3 random Mage spells to your hand.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\"value\": 3,\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\"heroClass\": \"MAGE\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/mage/spell_forbidden_flame.json",
    "content": "{\n\t\"name\": \"Forbidden Flame\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Spend all your mana. Deal that much damage to a minion.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\"playerAttribute\": \"MANA\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"GainManaSpell\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\t\"operation\": \"NEGATE\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\t\"playerAttribute\": \"MANA\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/mage/spell_shatter.json",
    "content": "{\n\t\"name\": \"Shatter\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"MAGE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Destroy a Frozen minion.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DestroySpell\",\n\t\t\"filter\": {\n\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\"attribute\": \"FROZEN\",\n\t\t\t\"operation\": \"HAS\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_aberrant_berserker.json",
    "content": "{\n\t\"name\": \"Aberrant Berserker\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Enrage: +2 Attack\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"EnrageChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"EnrageSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 2\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"ENRAGABLE\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_amgam_rager.json",
    "content": "{\n\t\"name\": \"Am'gam Rager\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_ancient_harbinger.json",
    "content": "{\n\t\"name\": \"Ancient Harbinger\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"At the start of your turn, put a 10-Cost minion from your deck into your hand.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"FromDeckToHandSpell\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"value\": 1,\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\",\n\t\t\t\t\"manaCost\": 10\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_beckoner_of_evil.json",
    "content": "{\n\t\"name\": \"Beckoner of Evil\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Give your C'Thun +2/+2 (wherever it is).\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"value\": 2,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"value\": 2,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\",\n\t\t\t\t\t\"value\": 2\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_HEALTH_BUFF\",\n\t\t\t\t\t\"value\": 2\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_bilefin_tidehunter.json",
    "content": "{\n\t\"name\": \"Bilefin Tidehunter\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Battlecry: Summon a 1/1 Ooze with Taunt.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"card\": \"token_ooze\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_blackwater_pirate.json",
    "content": "{\n\t\"name\": \"Blackwater Pirate\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Your Weapons cost (2) less.\",\n\t\"cardCostModifier\": {\n\t\t\"class\": \"CardCostModifier\",\n\t\t\"cardType\": \"WEAPON\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"value\": -2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_blood_of_the_ancient_one.json",
    "content": "{\n\t\"name\": \"Blood of The Ancient One\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 9,\n\t\"baseHp\": 9,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"If you control two of these at the end of your turn, merge them into 'The Ancient One'.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"fireCondition\": {\n\t\t\t\t\"class\": \"HasEntitiesOnBoardCondition\",\n\t\t\t\t\"cardIds\": [\n\t\t\t\t\t\"minion_blood_of_the_ancient_one\",\n\t\t\t\t\t\"minion_blood_of_the_ancient_one\"\n\t\t\t\t]\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"custom.MergeSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"card\": \"minion_blood_of_the_ancient_one\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\t\"card\": \"token_the_ancient_one\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_bog_creeper.json",
    "content": "{\n\t\"name\": \"Bog Creeper\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_corrupted_healbot.json",
    "content": "{\n\t\"name\": \"Corrupted Healbot\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Deathrattle: Restore 8 Health to the enemy hero.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"HealSpell\",\n\t\t\"target\": \"ENEMY_HERO\",\n\t\t\"value\": 8\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_corrupted_seer.json",
    "content": "{\n\t\"name\": \"Corrupted Seer\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Battlecry: Deal 2 damage to all non-Murloc minions.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ALL_MINIONS\",\n\t\t\t\"value\": 2,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"MURLOC\",\n\t\t\t\t\"invert\": true\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_crazed_worshipper.json",
    "content": "{\n\t\"name\": \"Crazed Worshipper\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Taunt. Whenever this minion takes damage, give your C'Thun +1/+1 (wherever it is).\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"value\": 1,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"value\": 1,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_HEALTH_BUFF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_cthun.json",
    "content": "{\n\t\"name\": \"C'Thun\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Deal damage equal to this minion's Attack randomly split among all enemies.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MissilesSpell\",\n\t\t\t\"target\": \"ENEMY_CHARACTERS\",\n\t\t\t\"howMany\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t}\n\t\t}\n\t},\n\t\"passiveTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"CardReceivedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"attackBonus\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\"\n\t\t\t\t\t},\n\t\t\t\t\t\"hpBonus\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\t\"attribute\": \"CTHUN_HEALTH_BUFF\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ConditionalSpell\",\n\t\t\t\t\t\"spell\": {\n\t\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\t\"attribute\": \"TAUNT\"\n\t\t\t\t\t},\n\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\"class\": \"AttributeCondition\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\t\"attribute\": \"CTHUN_TAUNT\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_cthuns_chosen.json",
    "content": "{\n\t\"name\": \"C'Thun's Chosen\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Divine Shield. Battlecry: Give your C'Thun +2/+2 (wherever it is).\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"value\": 2,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"value\": 2,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\",\n\t\t\t\t\t\"value\": 2\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_HEALTH_BUFF\",\n\t\t\t\t\t\"value\": 2\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"DIVINE_SHIELD\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_cult_apothecary.json",
    "content": "{\n\t\"name\": \"Cult Apothecary\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: For each enemy minion, restore 2 Health to your Hero.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"EntityCounter\",\n\t\t\t\t\"target\": \"ENEMY_MINIONS\",\n\t\t\t\t\"multiplier\": 2\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_cyclopian_horror.json",
    "content": "{\n\t\"name\": \"Cyclopian Horror\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Taunt. Battlecry: Gain +1 Health for each enemy minion.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"hpBonus\": {\n\t\t\t\t\"class\": \"EntityCounter\",\n\t\t\t\t\"target\": \"ENEMY_MINIONS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_darkspeaker.json",
    "content": "{\n\t\"name\": \"Darkspeaker\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Swap stats with a friendly minion.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SwapAttackSpell\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SwapHpSpell\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_deathwing_dragonlord.json",
    "content": "{\n\t\"name\": \"Deathwing, Dragonlord\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 12,\n\t\"baseHp\": 12,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Deathrattle: Put all Dragons in your hand into the battlefield.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"PutRandomMinionOnBoardSpell\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"value\": 7,\n\t\t\"cardLocation\": \"HAND\",\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"cardType\": \"MINION\",\n\t\t\t\"race\": \"DRAGON\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_disciple_of_cthun.json",
    "content": "{\n\t\"name\": \"Disciple of C'Thun\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Deal 2 damage. Give your C'Thun +2/+2 (wherever it is).\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\t\t\"value\": 2\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"value\": 2,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"value\": 2,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\",\n\t\t\t\t\t\"value\": 2\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_HEALTH_BUFF\",\n\t\t\t\t\t\"value\": 2\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_doomcaller.json",
    "content": "{\n\t\"name\": \"Doomcaller\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 9,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Give your C'Thun +2/+2 (wherever it is). If it's dead, shuffle it into your deck.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"value\": 2,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"value\": 2,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\",\n\t\t\t\t\t\"value\": 2\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_HEALTH_BUFF\",\n\t\t\t\t\t\"value\": 2\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ConditionalSpell\",\n\t\t\t\t\t\"spell\": {\n\t\t\t\t\t\t\"class\": \"ShuffleToDeckSpell\",\n\t\t\t\t\t\t\"card\": \"minion_cthun\"\n\t\t\t\t\t},\n\t\t\t\t\t\"condition\": {\n\t\t\t\t\t\t\"class\": \"GraveyardContainsCondition\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_duskboar.json",
    "content": "{\n\t\"name\": \"Duskboar\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_eater_of_secrets.json",
    "content": "{\n\t\"name\": \"Eater of Secrets\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Destroy all enemy Secrets. Gain +1/+1 for each.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"value\": {\n\t\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\t\t\t\"playerAttribute\": \"SECRET_COUNT\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DestroySecretsSpell\",\n\t\t\t\t\t\"targetPlayer\": \"OPPONENT\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_eldritch_horror.json",
    "content": "{\n\t\"name\": \"Eldritch Horror\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 10,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_evolved_kobold.json",
    "content": "{\n\t\"name\": \"Evolved Kobold\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Spell Damage +2\",\n\t\"attributes\": {\n\t\t\"SPELL_DAMAGE\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_faceless_behemoth.json",
    "content": "{\n\t\"name\": \"Faceless Behemoth\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 10,\n\t\"baseHp\": 10,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_faceless_shambler.json",
    "content": "{\n\t\"name\": \"Faceless Shambler\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Taunt. Battlecry: Copy a friendly minion's Attack and Health.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SetAttackSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"value\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"TARGET\",\n\t\t\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"SetHpSpell\",\n\t\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\t\"value\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"TARGET\",\n\t\t\t\t\t\t\"attribute\": \"HP\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_grotesque_dragonhawk.json",
    "content": "{\n\t\"name\": \"Grotesque Dragonhawk\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Windfury\",\n\t\"attributes\": {\n\t\t\"WINDFURY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_hogger_doom_of_elwynn.json",
    "content": "{\n\t\"name\": \"Hogger, Doom of Elwynn\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Whenever this minion takes damage, summon a 2/2 Gnoll with Taunt.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageReceivedTrigger\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_TARGETS\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_gnoll\",\n\t\t\t\"boardPositionRelative\": \"RIGHT\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_infested_tauren.json",
    "content": "{\n\t\"name\": \"Infested Tauren\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt. Deathrattle: Summon a 2/2 Slime.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_tauren_slime\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_midnight_drake.json",
    "content": "{\n\t\"name\": \"Midnight Drake\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"Battlecry: Gain +1 Attack for each other card in your hand.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": {\n\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\t\"playerAttribute\": \"HAND_COUNT\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_mukla_tyrant_of_the_vale.json",
    "content": "{\n\t\"name\": \"Mukla, Tyrant of the Vale\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Battlecry: Add 2 Bananas to your hand.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"cards\": [\n\t\t\t\t\"spell_bananas\",\n\t\t\t\t\"spell_bananas\"\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_nat_the_darkfisher.json",
    "content": "{\n\t\"name\": \"Nat, the Darkfisher\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"At the start of your opponent's turn, they have a 50% chance to draw an extra card.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"OPPONENT\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\"targetPlayer\": \"OPPONENT\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"ConditionalValueProvider\",\n\t\t\t\t\"ifTrue\": 1,\n\t\t\t\t\"ifFalse\": 0,\n\t\t\t\t\"condition\": {\n\t\t\t\t\t\"class\": \"RandomCondition\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_nerubian_prophet.json",
    "content": "{\n\t\"name\": \"Nerubian Prophet\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"At the start of your turn, reduce this card's Cost by (1).\",\n\t\"passiveTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"target\": \"SELF\",\n\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_nzoth_the_corruptor.json",
    "content": "{\n\t\"name\": \"N'Zoth, the Corruptor\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Summon your friendly Deathrattle minions that died this game.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"ResurrectSpell\",\n\t\t\t\"value\": 7,\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"attribute\": \"DEATHRATTLES\",\n\t\t\t\t\"operation\": \"HAS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_polluted_hoarder.json",
    "content": "{\n\t\"name\": \"Polluted Hoarder\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Draw a card.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"value\": 1\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_psych-o-tron.json",
    "content": "{\n\t\"name\": \"Psych-o-Tron\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MECH\",\n\t\"description\": \"Taunt. Divine Shield\",\n\t\"attributes\": {\n\t\t\"DIVINE_SHIELD\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_scaled_nightmare.json",
    "content": "{\n\t\"name\": \"Scaled Nightmare\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"DRAGON\",\n\t\"description\": \"At the start of your turn, double this minion's Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"DoubleAttackSpell\",\n\t\t\t\"target\": \"SELF\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_shifter_zerus.json",
    "content": "{\n\t\"name\": \"Shifter Zerus\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Each turn this is in your hand, transform it into a random minion.\",\n\t\"passiveTrigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"custom.ShifterZerusSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"cardType\": \"MINION\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_silithid_swarmer.json",
    "content": "{\n\t\"name\": \"Silithid Swarmer\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"BEAST\",\n\t\"description\": \"Can only attack if your Hero attacked this turn.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"BoardChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"EitherOrSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"spell1\": {\n\t\t\t\t\"class\": \"RemoveAttributeSpell\",\n\t\t\t\t\"attribute\": \"CANNOT_ATTACK\"\n\t\t\t},\n\t\t\t\"spell2\": {\n\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\"attribute\": \"CANNOT_ATTACK\"\n\t\t\t},\n\t\t\t\"condition\": {\n\t\t\t\t\"class\": \"HasAttackedCondition\",\n\t\t\t\t\"target\": \"FRIENDLY_HERO\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_skeram_cultist.json",
    "content": "{\n\t\"name\": \"Skeram Cultist\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Give your C'Thun +2/+2 (wherever it is).\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"value\": 2,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"value\": 2,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\",\n\t\t\t\t\t\"value\": 2\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_HEALTH_BUFF\",\n\t\t\t\t\t\"value\": 2\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_soggoth_the_slitherer.json",
    "content": "{\n\t\"name\": \"Soggoth the Slitherer\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 9,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Taunt. Can't be targeted by Spells or Hero Powers.\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true,\n\t\t\"UNTARGETABLE_BY_SPELLS\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_spawn_of_nzoth.json",
    "content": "{\n\t\"name\": \"Spawn of N'Zoth\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Give your minions +1/+1.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"target\": \"OTHER_FRIENDLY_MINIONS\",\n\t\t\"attackBonus\": 1,\n\t\t\"hpBonus\": 1\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_squirming_tentacle.json",
    "content": "{\n\t\"name\": \"Squirming Tentacle\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt.\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_tentacle_of_nzoth.json",
    "content": "{\n\t\"name\": \"Tentacle of N'Zoth\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Deal 1 damage to all minions.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"value\": 1\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_the_boogeymonster.json",
    "content": "{\n\t\"name\": \"The Boogeymonster\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"When this attacks and kills a minion, give it +2/+2.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterPhysicalAttackTrigger\",\n\t\t\t\"targetEntityType\": \"MINION\",\n\t\t\t\"hostTargetType\": \"IGNORE_OTHER_SOURCES\",\n\t\t\t\"fireCondition\": {\n\t\t\t\t\"class\": \"IsDeadCondition\"\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 2,\n\t\t\t\"hpBonus\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_twilight_elder.json",
    "content": "{\n\t\"name\": \"Twilight Elder\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"At the end of your turn, give your C'Thun +1/+1 (wherever it is).\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"value\": 1,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"value\": 1,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_HEALTH_BUFF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_twilight_geomancer.json",
    "content": "{\n\t\"name\": \"Twilight Geomancer\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt. Battlecry: Give your C'Thun Taunt (wherever it is).\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"attribute\": \"TAUNT\",\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"attribute\": \"TAUNT\",\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_TAUNT\"\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_twilight_summoner.json",
    "content": "{\n\t\"name\": \"Twilight Summoner\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deathrattle: Summon a 5/5 Faceless Destroyer.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_faceless_destroyer\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_twin_emperor_veklor.json",
    "content": "{\n\t\"name\": \"Twin Emperor Vek'lor\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Taunt. Battlecry: If your C'Thun has 10 or more Attack, summon another Emperor.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_twin_emperor_veknilash\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"OrCondition\",\n\t\t\t\"conditions\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ComparisonCondition\",\n\t\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"HighestAttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 10\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ComparisonCondition\",\n\t\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"HighestAttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 10\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ComparisonCondition\",\n\t\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\"\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 4\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_twisted_worgen.json",
    "content": "{\n\t\"name\": \"Twisted Worgen\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Stealth\",\n\t\"attributes\": {\n\t\t\"STEALTH\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_validated_doomsayer.json",
    "content": "{\n\t\"name\": \"Validated Doomsayer\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 0,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"At the start of your turn, set this minion's Attack to 7.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"SetAttackSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"value\": 7\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_yogg_saron_hopes_end.json",
    "content": "{\n\t\"name\": \"Yogg-Saron, Hope's End\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Cast a random spell for each spell you've cast this game (targets chosen randomly).\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"CastRandomSpellSpell\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\"playerAttribute\": \"SPELLS_CAST\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_yshaarj_rage_unbound.json",
    "content": "{\n\t\"name\": \"Y'Shaarj, Rage Unbound\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 10,\n\t\"baseHp\": 10,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"At the end of your turn, put a minion from your deck into the battlefield.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"PutRandomMinionOnBoardSpell\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"cardLocation\": \"DECK\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/minion_zealous_initiate.json",
    "content": "{\n\t\"name\": \"Zealous Initiate\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Give a random friendly minion +1/+1.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"attackBonus\": 1,\n\t\t\"hpBonus\": 1,\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"randomTarget\": true\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/token_faceless_destroyer.json",
    "content": "{\n\t\"name\": \"Faceless Destroyer\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/token_ooze.json",
    "content": "{\n\t\"name\": \"Ooze\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt.\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/token_tauren_slime.json",
    "content": "{\n\t\"name\": \"Slime\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/token_the_ancient_one.json",
    "content": "{\n\t\"name\": \"The Ancient One\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 30,\n\t\"baseHp\": 30,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/neutral/token_twin_emperor_veknilash.json",
    "content": "{\n\t\"name\": \"Twin Emperor Vek'nilash\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"ANY\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Taunt.\",\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/paladin/hero_power_the_tidal_hand.json",
    "content": "{\n\t\"name\": \"The Tidal Hand\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"HERO_POWER\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Summon a 1/1 Silver Hand Murloc.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_silver_hand_murloc\"\n\t},\n\t\"condition\": {\n\t\t\"class\": \"MinionCountCondition\",\n\t\t\"operation\": \"LESS\",\n\t\t\"value\": 7\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/paladin/minion_ragnaros_lightlord.json",
    "content": "{\n\t\"name\": \"Ragnaros, Lightlord\",\n\t\"baseManaCost\": 8,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 8,\n\t\"baseHp\": 8,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"At the end of your turn, restore 8 Health to a damaged friendly character.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_CHARACTERS\",\n\t\t\t\"value\": 8,\n\t\t\t\"randomTarget\": true,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"DamagedFilter\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/paladin/minion_selfless_hero.json",
    "content": "{\n\t\"name\": \"Selfless Hero\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deathrattle: Give a random friendly minion Divine Shield.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"attribute\": \"DIVINE_SHIELD\",\n\t\t\"randomTarget\": true\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/paladin/minion_steward_of_darkshire.json",
    "content": "{\n\t\"name\": \"Steward of Darkshire\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Whenever you summon a minion with 1 health, give it Divine Shield.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"BeforeMinionSummonedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"queueCondition\": {\n\t\t\t\t\"class\": \"AttributeCondition\",\n\t\t\t\t\"attribute\": \"HP\",\n\t\t\t\t\"operation\": \"EQUAL\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\"attribute\": \"DIVINE_SHIELD\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/paladin/minion_vilefin_inquisitor.json",
    "content": "{\n\t\"name\": \"Vilefin Inquisitor\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"EPIC\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"Battlecry: Your Hero Power becomes 'Summon a 1/1 Murloc.'\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"ChangeHeroPowerSpell\",\n\t\t\t\"card\": \"hero_power_the_tidal_hand\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/paladin/spell_a_light_in_the_darkness.json",
    "content": "{\n\t\"name\": \"A Light in the Darkness\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Discover a minion. Give it +1/+1.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardAndDoSomethingSpell\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"spell\": {\n\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\"value\": 1\n\t\t\t}\n\t\t},\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"cardType\": \"MINION\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/paladin/spell_divine_strength.json",
    "content": "{\n\t\"name\": \"Divine Strength\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a minion +1/+2.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"attackBonus\": 1,\n\t\t\"hpBonus\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/paladin/spell_forbidden_healing.json",
    "content": "{\n\t\"name\": \"Forbidden Healing\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Spend all your mana. Heal for double the mana spent.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"HealSpell\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\"playerAttribute\": \"MANA\",\n\t\t\t\t\t\"multiplier\": 2\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"GainManaSpell\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\"playerAttribute\": \"MANA\",\n\t\t\t\t\t\"multiplier\": -1\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/paladin/spell_stand_against_darkness.json",
    "content": "{\n\t\"name\": \"Stand Against Darkness\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Summon five 1/1 Silver Hand Recruits.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"cards\": [\n\t\t\t\"token_silver_hand_recruit\",\n\t\t\t\"token_silver_hand_recruit\",\n\t\t\t\"token_silver_hand_recruit\",\n\t\t\t\"token_silver_hand_recruit\",\n\t\t\t\"token_silver_hand_recruit\"\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/paladin/token_silver_hand_murloc.json",
    "content": "{\n\t\"name\": \"Silver Hand Murloc\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"MURLOC\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/paladin/weapon_rallying_blade.json",
    "content": "{\n\t\"name\": \"Rallying Blade\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 3,\n\t\"durability\": 2,\n\t\"heroClass\": \"PALADIN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Give +1/+1 to your minions with Divine Shield.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\"value\": 1,\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\t\"attribute\": \"DIVINE_SHIELD\",\n\t\t\t\t\"operation\": \"HAS\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/priest/minion_darkshire_alchemist.json",
    "content": "{\n\t\"name\": \"Darkshire Alchemist\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Restore 5 Health.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"ANY\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"value\": 5\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/priest/minion_herald_volazj.json",
    "content": "{\n\t\"name\": \"Herald Volazj\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Summon a 1/1 copy of each of your other minions.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"custom.HeraldVolajzSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/priest/minion_hooded_acolyte.json",
    "content": "{\n\t\"name\": \"Hooded Acolyte\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever a character is healed, give your C'Thun +1/+1 (wherever it is).\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"HealingTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"value\": 1,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"value\": 1,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_HEALTH_BUFF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/priest/minion_shifting_shade.json",
    "content": "{\n\t\"name\": \"Shifting Shade\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deathrattle: Copy a card from your opponent's deck and add it to your hand.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"CopyCardSpell\",\n\t\t\"cardLocation\": \"DECK\",\n\t\t\"value\": 1\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/priest/minion_twilight_darkmender.json",
    "content": "{\n\t\"name\": \"Twilight Darkmender\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: If your C'Thun has at least 10 Attack, restore 10 Health to your Hero.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": 10\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"OrCondition\",\n\t\t\t\"conditions\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ComparisonCondition\",\n\t\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"HighestAttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 10\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ComparisonCondition\",\n\t\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"HighestAttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 10\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ComparisonCondition\",\n\t\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\"\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 4\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/priest/spell_embrace_the_shadow.json",
    "content": "{\n\t\"name\": \"Embrace the Shadow\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Your healing effects deal damage instead this turn.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\"attribute\": \"INVERT_HEALING\",\n\t\t\"revertTrigger\": {\n\t\t\t\"class\": \"TurnEndTrigger\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/priest/spell_forbidden_shaping.json",
    "content": "{\n\t\"name\": \"Forbidden Shaping\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Spend all your mana. Summon a random minion that costs that much.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"SummonRandomMinionFilteredSpell\",\n\t\t\t\t\"cardFilter\": {\n\t\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\t\"manaCost\": {\n\t\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\t\"playerAttribute\": \"MANA\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"GainManaSpell\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"AlgebraicValueProvider\",\n\t\t\t\t\t\"operation\": \"NEGATE\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\t\"playerAttribute\": \"MANA\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/priest/spell_power_word_tentacles.json",
    "content": "{\n\t\"name\": \"Power Word: Tentacles\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a minion +2/+6.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"attackBonus\": 2,\n\t\t\"hpBonus\": 6\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/priest/spell_shadow_word_horror.json",
    "content": "{\n\t\"name\": \"Shadow Word: Horror\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"PRIEST\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Destroy all minions with 2 or less Attack.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DestroySpell\",\n\t\t\"target\": \"ALL_MINIONS\",\n\t\t\"filter\": {\n\t\t\t\"class\": \"AttributeFilter\",\n\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\"operation\": \"LESS_OR_EQUAL\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/rogue/minion_blade_of_cthun.json",
    "content": "{\n\t\"name\": \"Blade of C'Thun\",\n\t\"baseManaCost\": 9,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Destroy a minion. Add its Attack and Health to C'Thun's (wherever it is).\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"DestroySpell\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"attackBonus\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"TARGET\",\n\t\t\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t\t\t},\n\t\t\t\t\t\"hpBonus\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"TARGET\",\n\t\t\t\t\t\t\"attribute\": \"HP\"\n\t\t\t\t\t},\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"attackBonus\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"TARGET\",\n\t\t\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t\t\t},\n\t\t\t\t\t\"hpBonus\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"TARGET\",\n\t\t\t\t\t\t\"attribute\": \"HP\"\n\t\t\t\t\t},\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\",\n\t\t\t\t\t\"value\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"TARGET\",\n\t\t\t\t\t\t\"attribute\": \"ATTACK\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_HEALTH_BUFF\",\n\t\t\t\t\t\"value\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"TARGET\",\n\t\t\t\t\t\t\"attribute\": \"HP\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/rogue/minion_bladed_cultist.json",
    "content": "{\n\t\"name\": \"Bladed Cultist\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Combo: Gain +1/+1.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"ComboCondition\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"COMBO\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/rogue/minion_shadowcaster.json",
    "content": "{\n\t\"name\": \"Shadowcaster\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Choose a friendly minion. Add a 1/1 copy to your hand that costs (1).\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"custom.PutMiniCopyInHandSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/rogue/minion_southsea_squidface.json",
    "content": "{\n\t\"name\": \"Southsea Squidface\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Deathrattle: Give your weapon +2 Attack.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"BuffWeaponSpell\",\n\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\"attackBonus\": 2\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/rogue/minion_undercity_huckster.json",
    "content": "{\n\t\"name\": \"Undercity Huckster\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deathrattle: Add a random class card to your hand (from your opponent's class).\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\"value\": 1,\n\t\t\"card\": \"spell_the_coin\",\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"heroClass\": \"OPPONENT\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/rogue/minion_xaril_poisoned_mind.json",
    "content": "{\n\t\"name\": \"Xaril, Poisoned Mind\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry and Deathrattle: Add a random Toxin to your hand.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveRandomCardSpell\",\n\t\t\t\"cards\": [\n\t\t\t\t\"spell_bloodthistle_toxin\",\n\t\t\t\t\"spell_briarthorn_toxin\",\n\t\t\t\t\"spell_fadeleaf_toxin\",\n\t\t\t\t\"spell_firebloom_toxin\",\n\t\t\t\t\"spell_kingsblood_toxin\"\n\t\t\t]\n\t\t}\n\t},\n\t\"deathrattle\": {\n\t\t\"class\": \"ReceiveRandomCardSpell\",\n\t\t\"cards\": [\n\t\t\t\"spell_bloodthistle_toxin\",\n\t\t\t\"spell_briarthorn_toxin\",\n\t\t\t\"spell_fadeleaf_toxin\",\n\t\t\t\"spell_firebloom_toxin\",\n\t\t\t\"spell_kingsblood_toxin\"\n\t\t]\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/rogue/spell_bloodthistle_toxin.json",
    "content": "{\n\t\"name\": \"Bloodthistle Toxin\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Return a friendly minion to your hand. It costs (2) less.\",\n\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"ReturnMinionToHandSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"CardCostModifierSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"cardCostModifier\": {\n\t\t\t\t\"class\": \"CardCostModifier\",\n\t\t\t\t\"target\": \"EVENT_CARD\",\n\t\t\t\t\"operation\": \"SUBTRACT\",\n\t\t\t\t\"value\": 2\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/rogue/spell_briarthorn_toxin.json",
    "content": "{\n\t\"name\": \"Briarthorn Toxin\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a minion +3 Attack.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"attackBonus\": 3\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/rogue/spell_fadeleaf_toxin.json",
    "content": "{\n\t\"name\": \"Fadeleaf Toxin\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Give a friendly minion Stealth until your next turn.\",\n\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"AddAttributeSpell\",\n\t\t\"attribute\": \"STEALTH\",\n\t\t\"revertTrigger\": {\n\t\t\t\"class\": \"TurnStartTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t}\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/rogue/spell_firebloom_toxin.json",
    "content": "{\n\t\"name\": \"Firebloom Toxin\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Deal 2 damage.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 2\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/rogue/spell_journey_below.json",
    "content": "{\n\t\"name\": \"Journey Below\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Discover a Deathrattle card.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DiscoverRandomCardSpell\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t},\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"attribute\": \"DEATHRATTLES\",\n\t\t\t\"operation\": \"HAS\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/rogue/spell_kingsblood_toxin.json",
    "content": "{\n\t\"name\": \"Kingsblood Toxin\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"Draw a card.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"value\": 1\n\t},\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/rogue/spell_shadow_strike.json",
    "content": "{\n\t\"name\": \"Shadow Strike\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 5 damage to an undamaged character.\",\n\t\"targetSelection\": \"ANY\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 5,\n\t\t\"filter\": {\n\t\t\t\"class\": \"DamagedFilter\",\n\t\t\t\"invert\": true\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/rogue/spell_thistle_tea.json",
    "content": "{\n\t\"name\": \"Thistle Tea\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"ROGUE\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Draw a card. Add two extra copies of it to your hand.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"DrawCardAndDoSomethingSpell\",\n\t\t\"value\": 1,\n\t\t\"spell\": {\n\t\t\t\"class\": \"CopyCardSpell\",\n\t\t\t\"value\": 2\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/shaman/minion_eternal_sentinel.json",
    "content": "{\n\t\"name\": \"Eternal Sentinel\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Battlecry: Unlock your Overloaded Mana Crystals.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"ClearOverloadSpell\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/shaman/minion_flamewreathed_faceless.json",
    "content": "{\n\t\"name\": \"Flamewreathed Faceless\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Overload: (2)\",\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 2\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/shaman/minion_hallazeal_the_ascended.json",
    "content": "{\n\t\"name\": \"Hallazeal the Ascended\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"When your spells deal damage, restore that much Health to your hero.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"DamageCausedTrigger\",\n\t\t\t\"cardType\": \"SPELL\",\n\t\t\t\"sourcePlayer\": \"SELF\",\n\t\t\t\"targetPlayer\": \"BOTH\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"HealSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"value\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"EVENT_TARGET\",\n\t\t\t\t\"attribute\": \"LAST_HIT\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/shaman/minion_master_of_evolution.json",
    "content": "{\n\t\"name\": \"Master of Evolution\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Transform a friendly minion into a random one that costs (1) more.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"FRIENDLY_MINIONS\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"TransformToRandomMinionSpell\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"CardFilter\",\n\t\t\t\t\"manaCost\": {\n\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\"target\": \"TARGET\",\n\t\t\t\t\t\"attribute\": \"BASE_MANA_COST\",\n\t\t\t\t\t\"offset\": 1\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/shaman/minion_thing_from_below.json",
    "content": "{\n\t\"name\": \"Thing from Below\",\n\t\"baseManaCost\": 6,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Taunt. Costs (1) less for each Totem you've summoned this game.\",\n\t\"manaCostModifier\": {\n\t\t\"class\": \"MinionSummonValueProvider\",\n\t\t\"filter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"race\": \"TOTEM\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/shaman/spell_evolve.json",
    "content": "{\n\t\"name\": \"Evolve\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Transform your minions into random minions that cost (1) more.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"TransformToRandomMinionSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"manaCost\": {\n\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\"target\": \"SPELL_TARGET\",\n\t\t\t\t\"attribute\": \"BASE_MANA_COST\",\n\t\t\t\t\"offset\": 1\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/shaman/spell_primal_fusion.json",
    "content": "{\n\t\"name\": \"Primal Fusion\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Give a minion +1/+1 for each of your Totems.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"BuffSpell\",\n\t\t\"value\": {\n\t\t\t\"class\": \"EntityCounter\",\n\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\"filter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"TOTEM\"\n\t\t\t}\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/shaman/spell_stormcrack.json",
    "content": "{\n\t\"name\": \"Stormcrack\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deal 4 damage to a minion. Overload: (1)\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"DamageSpell\",\n\t\t\"value\": 4\n\t},\n\t\"attributes\": {\n\t\t\"OVERLOAD\": 1\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/shaman/token_twilight_elemental.json",
    "content": "{\n\t\"name\": \"Twilight Elemental\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 4,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/shaman/weapon_hammer_of_twilight.json",
    "content": "{\n\t\"name\": \"Hammer of Twilight\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 4,\n\t\"durability\": 2,\n\t\"heroClass\": \"SHAMAN\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deathrattle: Summon a 4/2 Elemental.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"targetPlayer\": \"SELF\",\n\t\t\"card\": \"token_twilight_elemental\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warlock/minion_chogall.json",
    "content": "{\n\t\"name\": \"Cho'gall\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 7,\n\t\"baseHp\": 7,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Your next spell this turn costs Health instead.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"AddAttributeSpell\",\n\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\"attribute\": \"SPELLS_COST_HEALTH\",\n\t\t\t\"revertTrigger\": {\n\t\t\t\t\"class\": \"TurnEndTrigger\"\n\t\t\t},\n\t\t\t\"secondRevertTrigger\": {\n\t\t\t\t\"class\": \"SpellCastedTrigger\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warlock/minion_darkshire_councilman.json",
    "content": "{\n\t\"name\": \"Darkshire Councilman\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"After you summon a minion, gain +1 Attack.\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"AfterMinionSummonedTrigger\",\n\t\t\t\"targetPlayer\": \"SELF\",\n\t\t\t\"hostTargetType\": \"IGNORE_AS_TARGET\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 1\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warlock/minion_darkshire_librarian.json",
    "content": "{\n\t\"name\": \"Darkshire Librarian\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 2,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: Discard a random card. Deathrattle: Draw a card.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DiscardSpell\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"deathrattle\": {\n\t\t\"class\": \"DrawCardSpell\",\n\t\t\"value\": 1\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true,\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warlock/minion_possessed_villager.json",
    "content": "{\n\t\"name\": \"Possessed Villager\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Deathrattle: Summon a 1/1 Shadowbeast.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"SummonSpell\",\n\t\t\"card\": \"token_shadowbeast\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warlock/minion_usher_of_souls.json",
    "content": "{\n\t\"name\": \"Usher of Souls\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 5,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Whenever a friendly minion dies, give your C'Thun +1/+1 (wherever it is).\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"MinionDeathTrigger\",\n\t\t\t\"sourcePlayer\": \"SELF\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"MetaSpell\",\n\t\t\t\"spells\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\"value\": 1,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"BuffSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\"value\": 1,\n\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ModifyAttributeSpell\",\n\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\"attribute\": \"CTHUN_HEALTH_BUFF\",\n\t\t\t\t\t\"value\": 1\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warlock/spell_doom.json",
    "content": "{\n\t\"name\": \"DOOM!\",\n\t\"baseManaCost\": 10,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Destroy all minions. Draw a card for each.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"DestroySpell\",\n\t\t\t\t\"target\": \"ALL_MINIONS\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"DrawCardSpell\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"EntityCounter\",\n\t\t\t\t\t\"target\": \"ALL_MINIONS\"\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warlock/spell_forbidden_ritual.json",
    "content": "{\n\t\"name\": \"Forbidden Ritual\",\n\t\"baseManaCost\": 0,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Spend all your mana. Summon that many 1/1 Tentacles.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MetaSpell\",\n\t\t\"spells\": [\n\t\t\t{\n\t\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\"playerAttribute\": \"MANA\"\n\t\t\t\t},\n\t\t\t\t\"card\": \"token_icky_tentacle\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"class\": \"GainManaSpell\",\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"class\": \"PlayerAttributeValueProvider\",\n\t\t\t\t\t\"playerAttribute\": \"MANA\",\n\t\t\t\t\t\"multiplier\": -1\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warlock/spell_renounce_darkness.json",
    "content": "{\n\t\"name\": \"Renounce Darkness\",\n\t\"baseManaCost\": 2,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Replace your Hero Power and Warlock Cards with another class's. The cards cost (1) less.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"RenounceClassSpell\",\n\t\t\"manaModifier\": -1,\n\t\t\"cardFilter\": {\n\t\t\t\"class\": \"CardFilter\",\n\t\t\t\"heroClass\": \"WARLOCK\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warlock/spell_spreading_madness.json",
    "content": "{\n\t\"name\": \"Spreading Madness\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 9 damage randomly split among all characters.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"MissilesSpell\",\n\t\t\"target\": \"ALL_CHARACTERS\",\n\t\t\"value\": 1,\n\t\t\"howMany\": 9\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warlock/token_icky_tentacle.json",
    "content": "{\n\t\"name\": \"Icky Tentacle\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warlock/token_shadowbeast.json",
    "content": "{\n\t\"name\": \"Shadowbeast\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"WARLOCK\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warrior/minion_ancient_shieldbearer.json",
    "content": "{\n\t\"name\": \"Ancient Shieldbearer\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Battlecry: If your C'Thun has 10 or more Attack, gain 10 Armor.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffHeroSpell\",\n\t\t\t\"target\": \"FRIENDLY_HERO\",\n\t\t\t\"armorBonus\": 10\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"OrCondition\",\n\t\t\t\"conditions\": [\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ComparisonCondition\",\n\t\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"HighestAttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 10\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ComparisonCondition\",\n\t\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"HighestAttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_HAND\",\n\t\t\t\t\t\t\"attribute\": \"ATTACK\",\n\t\t\t\t\t\t\"filter\": {\n\t\t\t\t\t\t\t\"class\": \"SpecificCardFilter\",\n\t\t\t\t\t\t\t\"cardId\": \"minion_cthun\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 10\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"class\": \"ComparisonCondition\",\n\t\t\t\t\t\"operation\": \"GREATER_OR_EQUAL\",\n\t\t\t\t\t\"value1\": {\n\t\t\t\t\t\t\"class\": \"AttributeValueProvider\",\n\t\t\t\t\t\t\"target\": \"FRIENDLY_PLAYER\",\n\t\t\t\t\t\t\"attribute\": \"CTHUN_ATTACK_BUFF\"\n\t\t\t\t\t},\n\t\t\t\t\t\"value2\": 4\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warrior/minion_bloodhoof_brave.json",
    "content": "{\n\t\"name\": \"Bloodhoof Brave\",\n\t\"baseManaCost\": 4,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 2,\n\t\"baseHp\": 6,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Taunt. Enrage: +3 Attack\",\n\t\"trigger\": {\n\t\t\"eventTrigger\": {\n\t\t\t\"class\": \"EnrageChangedTrigger\"\n\t\t},\n\t\t\"spell\": {\n\t\t\t\"class\": \"EnrageSpell\",\n\t\t\t\"target\": \"SELF\",\n\t\t\t\"attackBonus\": 3\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"ENRAGABLE\": true,\n\t\t\"TAUNT\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warrior/minion_bloodsail_cultist.json",
    "content": "{\n\t\"name\": \"Bloodsail Cultist\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 4,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Battlecry: If you control a Pirate, give your weapon +1/+1.\",\n\t\"battlecry\": {\n\t\t\"targetSelection\": \"NONE\",\n\t\t\"spell\": {\n\t\t\t\"class\": \"BuffWeaponSpell\",\n\t\t\t\"target\": \"FRIENDLY_WEAPON\",\n\t\t\t\"attackBonus\": 1,\n\t\t\t\"hpBonus\": 1\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"MinionOnBoardCondition\",\n\t\t\t\"cardFilter\": {\n\t\t\t\t\"class\": \"RaceFilter\",\n\t\t\t\t\"race\": \"PIRATE\"\n\t\t\t}\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warrior/minion_malkorok.json",
    "content": "{\n\t\"name\": \"Malkorok\",\n\t\"baseManaCost\": 7,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 6,\n\t\"baseHp\": 5,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"LEGENDARY\",\n\t\"description\": \"Battlecry: Equip a random weapon.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"EquipRandomWeaponSpell\",\n\t\t\t\"targetPlayer\": \"SELF\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warrior/minion_nzoths_first_mate.json",
    "content": "{\n\t\"name\": \"N'Zoth's First Mate\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 1,\n\t\"baseHp\": 1,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"race\": \"PIRATE\",\n\t\"description\": \"Battlecry: Equip a 1/3 Rusty Hook.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"EquipWeaponSpell\",\n\t\t\t\"card\": \"weapon_rusty_hook\"\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warrior/minion_ravaging_ghoul.json",
    "content": "{\n\t\"name\": \"Ravaging Ghoul\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"MINION\",\n\t\"baseAttack\": 3,\n\t\"baseHp\": 3,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"COMMON\",\n\t\"description\": \"Battlecry: Deal 1 damage to all other minions.\",\n\t\"battlecry\": {\n\t\t\"spell\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"target\": \"ALL_OTHER_MINIONS\",\n\t\t\t\"value\": 1\n\t\t}\n\t},\n\t\"attributes\": {\n\t\t\"BATTLECRY\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warrior/spell_blood_to_ichor.json",
    "content": "{\n\t\"name\": \"Blood To Ichor\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"RARE\",\n\t\"description\": \"Deal 1 damage to a minion. If it survives, summon a 2/2 Slime.\",\n\t\"targetSelection\": \"MINIONS\",\n\t\"spell\": {\n\t\t\"class\": \"ConditionalEffectSpell\",\n\t\t\"spell1\": {\n\t\t\t\"class\": \"DamageSpell\",\n\t\t\t\"value\": 1\n\t\t},\n\t\t\"spell2\": {\n\t\t\t\"class\": \"SummonSpell\",\n\t\t\t\"card\": \"token_tauren_slime\"\n\t\t},\n\t\t\"condition\": {\n\t\t\t\"class\": \"IsDeadCondition\",\n\t\t\t\"invert\": true\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warrior/spell_blood_warriors.json",
    "content": "{\n\t\"name\": \"Blood Warriors\",\n\t\"baseManaCost\": 3,\n\t\"type\": \"SPELL\",\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Add a copy of each damaged friendly minion to your hand.\",\n\t\"targetSelection\": \"NONE\",\n\t\"spell\": {\n\t\t\"class\": \"PutCopyInHandSpell\",\n\t\t\"target\": \"FRIENDLY_MINIONS\",\n\t\t\"filter\": {\n\t\t\t\"class\": \"DamagedFilter\"\n\t\t}\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warrior/weapon_rusty_hook.json",
    "content": "{\n\t\"name\": \"Rusty Hook\",\n\t\"baseManaCost\": 1,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 1,\n\t\"durability\": 3,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"FREE\",\n\t\"description\": \"\",\n\t\"collectible\": false,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/cards/the_old_gods/warrior/weapon_tentacles_for_arms.json",
    "content": "{\n\t\"name\": \"Tentacles for Arms\",\n\t\"baseManaCost\": 5,\n\t\"type\": \"WEAPON\",\n\t\"damage\": 2,\n\t\"durability\": 2,\n\t\"heroClass\": \"WARRIOR\",\n\t\"rarity\": \"EPIC\",\n\t\"description\": \"Deathrattle: Return this to your hand.\",\n\t\"deathrattle\": {\n\t\t\"class\": \"ReceiveCardSpell\",\n\t\t\"card\": \"weapon_tentacles_for_arms\"\n\t},\n\t\"attributes\": {\n\t\t\"DEATHRATTLES\": true\n\t},\n\t\"collectible\": true,\n\t\"set\": \"THE_OLD_GODS\",\n\t\"fileFormatVersion\": 1\n}"
  },
  {
    "path": "cards/src/main/resources/decks/aggro_shaman.json",
    "content": "{\n  \"cards\": [\n    \"minion_tunnel_trogg\",\n    \"minion_tunnel_trogg\",\n    \"spell_finders_keepers\",\n    \"spell_finders_keepers\",\n    \"spell_lightning_bolt\",\n    \"spell_lightning_bolt\",\n    \"minion_southsea_deckhand\",\n    \"minion_southsea_deckhand\",\n    \"minion_patches_the_pirate\",\n    \"spell_ancestral_knowledge\",\n    \"spell_ancestral_knowledge\",\n    \"minion_totem_golem\",\n    \"minion_totem_golem\",\n    \"spell_lava_shock\",\n    \"spell_lava_shock\",\n    \"weapon_jade_claws\",\n    \"spell_rockbiter_weapon\",\n    \"spell_rockbiter_weapon\",\n    \"minion_bloodmage_thalnos\",\n    \"spell_lava_burst\",\n    \"spell_lava_burst\",\n    \"spell_feral_spirit\",\n    \"spell_feral_spirit\",\n    \"spell_lightning_storm\",\n    \"spell_lightning_storm\",\n    \"minion_argent_horserider\",\n    \"minion_argent_horserider\",\n    \"weapon_doomhammer\",\n    \"weapon_doomhammer\",\n    \"minion_leeroy_jenkins\"\n  ],\n  \"name\": \"Aggro Shaman\",\n  \"heroClass\": \"SHAMAN\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/aggrodin.json",
    "content": "{\n  \"cards\": [\n    \"minion_argent_squire\",\n    \"minion_argent_squire\",\n    \"minion_meanstreet_marshal\",\n    \"minion_meanstreet_marshal\",\n    \"minion_selfless_hero\",\n    \"minion_selfless_hero\",\n    \"minion_small-time_buccaneer\",\n    \"minion_small-time_buccaneer\",\n    \"minion_southsea_deckhand\",\n    \"minion_southsea_deckhand\",\n    \"spell_smugglers_run\",\n    \"spell_smugglers_run\",\n    \"minion_loot_hoarder\",\n    \"minion_loot_hoarder\",\n    \"minion_grimestreet_outfitter\",\n    \"minion_grimestreet_outfitter\",\n    \"spell_small-time_recruits\",\n    \"spell_small-time_recruits\",\n    \"spell_divine_favor\",\n    \"spell_divine_favor\",\n    \"weapon_rallying_blade\",\n    \"weapon_rallying_blade\",\n    \"minion_argent_horserider\",\n    \"minion_argent_horserider\",\n    \"minion_stormwind_knight\",\n    \"minion_stormwind_knight\",\n    \"minion_leeroy_jenkins\",\n    \"minion_argent_commander\",\n    \"minion_argent_commander\",\n    \"minion_tirion_fordring\"\n  ],\n  \"name\": \"Aggrodin\",\n  \"heroClass\": \"PALADIN\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/beastrattle_hunter.json",
    "content": "{\n  \"cards\": [\n    \"minion_fiery_bat\",\n    \"minion_fiery_bat\",\n    \"minion_alleycat\",\n    \"minion_alleycat\",\n    \"minion_huge_toad\",\n    \"minion_huge_toad\",\n    \"minion_kindly_grandmother\",\n    \"minion_kindly_grandmother\",\n    \"minion_loot_hoarder\",\n    \"minion_loot_hoarder\",\n    \"spell_quick_shot\",\n    \"spell_quick_shot\",\n    \"minion_rat_pack\",\n    \"minion_rat_pack\",\n    \"minion_forlorn_stalker\",\n    \"minion_forlorn_stalker\",\n    \"minion_shaky_zipgunner\",\n    \"minion_shaky_zipgunner\",\n    \"spell_animal_companion\",\n    \"spell_animal_companion\",\n    \"spell_kill_command\",\n    \"spell_kill_command\",\n    \"minion_infested_wolf\",\n    \"minion_infested_wolf\",\n    \"minion_dispatch_kodo\",\n    \"minion_dispatch_kodo\",\n    \"minion_princess_huhuran\",\n    \"minion_savannah_highmane\",\n    \"minion_savannah_highmane\",\n    \"minion_sylvanas_windrunner\"\n  ],\n  \"name\": \"Beastrattle Hunter\",\n  \"heroClass\": \"HUNTER\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/burgle_rogue.json",
    "content": "{\n  \"cards\": [\n    \"spell_backstab\",\n    \"spell_backstab\",\n    \"spell_preparation\",\n    \"spell_preparation\",\n    \"minion_swashburglar\",\n    \"minion_swashburglar\",\n    \"spell_deadly_poison\",\n    \"spell_deadly_poison\",\n    \"minion_undercity_huckster\",\n    \"minion_undercity_huckster\",\n    \"minion_bloodmage_thalnos\",\n    \"spell_eviscerate\",\n    \"spell_eviscerate\",\n    \"spell_sap\",\n    \"spell_sap\",\n    \"spell_burgle\",\n    \"spell_burgle\",\n    \"minion_shaku_the_collector\",\n    \"minion_brann_bronzebeard\",\n    \"spell_fan_of_knives\",\n    \"spell_fan_of_knives\",\n    \"minion_si7_agent\",\n    \"minion_si7_agent\",\n    \"spell_shadow_strike\",\n    \"minion_lotus_agents\",\n    \"minion_lotus_agents\",\n    \"minion_ethereal_peddler\",\n    \"minion_ethereal_peddler\",\n    \"minion_emperor_thaurissan\",\n    \"minion_ragnaros_the_firelord\"\n  ],\n  \"name\": \"Burgle Rogue\",\n  \"heroClass\": \"ROGUE\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/face_hunter.json",
    "content": "{\n  \"cards\": [\n    \"minion_southsea_deckhand\",\n    \"minion_southsea_deckhand\",\n    \"minion_patches_the_pirate\",\n    \"minion_fiery_bat\",\n    \"minion_fiery_bat\",\n    \"secret_explosive_trap\",\n    \"secret_explosive_trap\",\n    \"minion_dire_wolf_alpha\",\n    \"minion_dire_wolf_alpha\",\n    \"minion_flame_juggler\",\n    \"minion_flame_juggler\",\n    \"minion_huge_toad\",\n    \"minion_huge_toad\",\n    \"minion_knife_juggler\",\n    \"minion_knife_juggler\",\n    \"spell_quick_shot\",\n    \"spell_quick_shot\",\n    \"minion_argent_horserider\",\n    \"minion_argent_horserider\",\n    \"spell_unleash_the_hounds\",\n    \"spell_unleash_the_hounds\",\n    \"minion_wolfrider\",\n    \"minion_wolfrider\",\n    \"weapon_eaglehorn_bow\",\n    \"weapon_eaglehorn_bow\",\n    \"spell_animal_companion\",\n    \"spell_animal_companion\",\n    \"spell_kill_command\",\n    \"spell_kill_command\",\n    \"minion_leeroy_jenkins\"\n  ],\n  \"name\": \"Face Hunter\",\n  \"heroClass\": \"HUNTER\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/freeze_mage.json",
    "content": "{\n  \"cards\": [\n    \"spell_ice_lance\",\n    \"spell_ice_lance\",\n    \"minion_bloodmage_thalnos\",\n    \"spell_frostbolt\",\n    \"spell_frostbolt\",\n    \"minion_doomsayer\",\n    \"minion_doomsayer\",\n    \"minion_loot_hoarder\",\n    \"minion_loot_hoarder\",\n    \"minion_novice_engineer\",\n    \"minion_novice_engineer\",\n    \"spell_arcane_intellect\",\n    \"spell_arcane_intellect\",\n    \"spell_forgotten_torch\",\n    \"spell_forgotten_torch\",\n    \"spell_frost_nova\",\n    \"spell_frost_nova\",\n    \"secret_ice_barrier\",\n    \"secret_ice_block\",\n    \"secret_ice_block\",\n    \"minion_acolyte_of_pain\",\n    \"minion_acolyte_of_pain\",\n    \"spell_fireball\",\n    \"spell_fireball\",\n    \"spell_blizzard\",\n    \"spell_blizzard\",\n    \"minion_emperor_thaurissan\",\n    \"spell_flamestrike\",\n    \"minion_alexstrasza\",\n    \"spell_pyroblast\"\n  ],\n  \"name\": \"Freeze Mage\",\n  \"heroClass\": \"MAGE\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/jade_druid.json",
    "content": "{\n  \"cards\": [\n    \"spell_jade_idol\",\n    \"spell_jade_idol\",\n    \"spell_raven_idol\",\n    \"spell_raven_idol\",\n    \"spell_wild_growth\",\n    \"spell_wild_growth\",\n    \"spell_wrath\",\n    \"spell_wrath\",\n    \"spell_jade_blossom\",\n    \"spell_jade_blossom\",\n    \"minion_brann_bronzebeard\",\n    \"spell_feral_rage\",\n    \"spell_feral_rage\",\n    \"minion_jade_spirit\",\n    \"minion_jade_spirit\",\n    \"minion_fandral_staghelm\",\n    \"minion_mire_keeper\",\n    \"minion_mire_keeper\",\n    \"spell_swipe\",\n    \"spell_swipe\",\n    \"spell_nourish\",\n    \"spell_nourish\",\n    \"minion_druid_of_the_claw\",\n    \"minion_druid_of_the_claw\",\n    \"minion_aya_blackpaw\",\n    \"minion_jade_behemoth\",\n    \"spell_moonglade_portal\",\n    \"spell_moonglade_portal\",\n    \"minion_cenarius\",\n    \"minion_kun_the_forgotten_king\"\n  ],\n  \"name\": \"Jade Druid\",\n  \"heroClass\": \"DRUID\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/jade_miracle_druid.json",
    "content": "{\n  \"cards\": [\n    \"spell_innervate\",\n    \"spell_innervate\",\n    \"spell_raven_idol\",\n    \"spell_raven_idol\",\n    \"spell_jade_idol\",\n    \"spell_jade_idol\",\n    \"spell_living_roots\",\n    \"spell_living_roots\",\n    \"spell_wild_growth\",\n    \"spell_wild_growth\",\n    \"minion_bloodmage_thalnos\",\n    \"spell_wrath\",\n    \"spell_wrath\",\n    \"spell_feral_rage\",\n    \"spell_feral_rage\",\n    \"spell_swipe\",\n    \"spell_swipe\",\n    \"minion_mire_keeper\",\n    \"minion_mire_keeper\",\n    \"minion_fandral_staghelm\",\n    \"minion_azure_drake\",\n    \"minion_azure_drake\",\n    \"spell_nourish\",\n    \"spell_nourish\",\n    \"minion_gadgetzan_auctioneer\",\n    \"minion_gadgetzan_auctioneer\",\n    \"minion_jade_behemoth\",\n    \"minion_jade_behemoth\",\n    \"minion_arcane_giant\",\n    \"minion_arcane_giant\"\n  ],\n  \"name\": \"Jade Miracle Druid\",\n  \"heroClass\": \"DRUID\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/jade_rogue.json",
    "content": "{\n  \"cards\": [\n    \"spell_backstab\",\n    \"spell_backstab\",\n    \"spell_preparation\",\n    \"spell_preparation\",\n    \"minion_swashburglar\",\n    \"minion_swashburglar\",\n    \"spell_deadly_poison\",\n    \"spell_deadly_poison\",\n    \"spell_jade_shuriken\",\n    \"spell_jade_shuriken\",\n    \"minion_jade_swarmer\",\n    \"minion_jade_swarmer\",\n    \"spell_gang_up\",\n    \"spell_gang_up\",\n    \"minion_bloodmage_thalnos\",\n    \"spell_shiv\",\n    \"spell_shiv\",\n    \"minion_brann_bronzebeard\",\n    \"spell_fan_of_knives\",\n    \"spell_fan_of_knives\",\n    \"minion_edwin_vancleef\",\n    \"minion_jade_spirit\",\n    \"minion_jade_spirit\",\n    \"minion_shadowcaster\",\n    \"minion_shadowcaster\",\n    \"minion_azure_drake\",\n    \"minion_azure_drake\",\n    \"minion_dark_iron_skulker\",\n    \"minion_aya_blackpaw\",\n    \"spell_sprint\"\n  ],\n  \"name\": \"Jade Rogue\",\n  \"heroClass\": \"ROGUE\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/midrange_shaman.json",
    "content": "{\n  \"cards\": [\n    \"spell_lightning_bolt\",\n    \"minion_tunnel_trogg\",\n    \"minion_tunnel_trogg\",\n    \"weapon_spirit_claws\",\n    \"weapon_spirit_claws\",\n    \"minion_totem_golem\",\n    \"minion_totem_golem\",\n    \"spell_maelstrom_portal\",\n    \"spell_maelstrom_portal\",\n    \"minion_bloodmage_thalnos\",\n    \"minion_flametongue_totem\",\n    \"minion_flametongue_totem\",\n    \"spell_lightning_storm\",\n    \"spell_lightning_storm\",\n    \"spell_feral_spirit\",\n    \"spell_feral_spirit\",\n    \"minion_mana_tide_totem\",\n    \"minion_mana_tide_totem\",\n    \"spell_hex\",\n    \"spell_hex\",\n    \"minion_azure_drake\",\n    \"minion_azure_drake\",\n    \"minion_thunder_bluff_valiant\",\n    \"minion_thunder_bluff_valiant\",\n    \"minion_harrison_jones\",\n    \"minion_fire_elemental\",\n    \"minion_fire_elemental\",\n    \"minion_thing_from_below\",\n    \"minion_thing_from_below\",\n    \"minion_ragnaros_the_firelord\"\n  ],\n  \"name\": \"Midrange Shaman\",\n  \"heroClass\": \"SHAMAN\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/miracle_rogue.json",
    "content": "{\n  \"cards\": [\n    \"spell_preparation\",\n    \"spell_preparation\",\n    \"spell_backstab\",\n    \"spell_backstab\",\n    \"spell_counterfeit_coin\",\n    \"spell_counterfeit_coin\",\n    \"spell_conceal\",\n    \"spell_conceal\",\n    \"spell_cold_blood\",\n    \"spell_cold_blood\",\n    \"spell_eviscerate\",\n    \"spell_eviscerate\",\n    \"minion_bloodmage_thalnos\",\n    \"spell_sap\",\n    \"spell_sap\",\n    \"spell_shiv\",\n    \"minion_questing_adventurer\",\n    \"spell_fan_of_knives\",\n    \"spell_fan_of_knives\",\n    \"spell_shadow_strike\",\n    \"minion_edwin_vancleef\",\n    \"minion_si7_agent\",\n    \"minion_si7_agent\",\n    \"minion_tomb_pillager\",\n    \"minion_tomb_pillager\",\n    \"minion_red_mana_wyrm\",\n    \"minion_azure_drake\",\n    \"minion_azure_drake\",\n    \"minion_gadgetzan_auctioneer\",\n    \"minion_gadgetzan_auctioneer\"\n  ],\n  \"name\": \"Miracle Rogue\",\n  \"heroClass\": \"ROGUE\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/pirate_warrior.json",
    "content": "{\n  \"cards\": [\n    \"minion_southsea_deckhand\",\n    \"minion_southsea_deckhand\",\n    \"spell_upgrade\",\n    \"spell_upgrade\",\n    \"minion_sir_finley_mrrgglton\",\n    \"minion_nzoths_first_mate\",\n    \"minion_nzoths_first_mate\",\n    \"minion_patches_the_pirate\",\n    \"minion_small-time_buccaneer\",\n    \"minion_small-time_buccaneer\",\n    \"spell_heroic_strike\",\n    \"spell_heroic_strike\",\n    \"minion_acidic_swamp_ooze\",\n    \"weapon_fiery_war_axe\",\n    \"weapon_fiery_war_axe\",\n    \"minion_bloodsail_raider\",\n    \"minion_bloodsail_raider\",\n    \"minion_frothing_berserker\",\n    \"minion_frothing_berserker\",\n    \"minion_argent_horserider\",\n    \"minion_argent_horserider\",\n    \"minion_bloodsail_cultist\",\n    \"minion_bloodsail_cultist\",\n    \"minion_korkron_elite\",\n    \"minion_korkron_elite\",\n    \"spell_mortal_strike\",\n    \"spell_mortal_strike\",\n    \"weapon_arcanite_reaper\",\n    \"weapon_arcanite_reaper\",\n    \"minion_leeroy_jenkins\"\n  ],\n  \"name\": \"Pirate Warrior\",\n  \"heroClass\": \"WARRIOR\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/reno_mage.json",
    "content": "{\n  \"cards\": [\n    \"spell_arcane_blast\",\n    \"minion_babbling_book\",\n    \"minion_mistress_of_mixtures\",\n    \"minion_bloodmage_thalnos\",\n    \"spell_frostbolt\",\n    \"minion_doomsayer\",\n    \"minion_acidic_swamp_ooze\",\n    \"secret_ice_block\",\n    \"secret_ice_barrier\",\n    \"spell_arcane_intellect\",\n    \"spell_forgotten_torch\",\n    \"spell_frost_nova\",\n    \"spell_volcanic_potion\",\n    \"minion_manic_soulcaster\",\n    \"minion_kazakus\",\n    \"spell_fireball\",\n    \"minion_water_elemental\",\n    \"spell_polymorph\",\n    \"minion_azure_drake\",\n    \"spell_cabalists_tome\",\n    \"minion_reno_jackson\",\n    \"spell_blizzard\",\n    \"minion_emperor_thaurissan\",\n    \"minion_sylvanas_windrunner\",\n    \"minion_inkmaster_solia\",\n    \"spell_firelands_portal\",\n    \"spell_flamestrike\",\n    \"minion_archmage_antonidas\",\n    \"minion_ragnaros_the_firelord\",\n    \"minion_alexstrasza\"\n  ],\n  \"name\": \"Reno Mage\",\n  \"heroClass\": \"MAGE\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/reno_priest.json",
    "content": "{\n  \"cards\": [\n    \"spell_power_word_shield\",\n    \"minion_northshire_cleric\",\n    \"minion_twilight_whelp\",\n    \"spell_pint-size_potion\",\n    \"minion_acidic_swamp_ooze\",\n    \"spell_shadow_word_pain\",\n    \"minion_doomsayer\",\n    \"minion_wyrmrest_agent\",\n    \"minion_netherspite_historian\",\n    \"minion_dirty_rat\",\n    \"minion_mind_control_tech\",\n    \"spell_shadow_word_death\",\n    \"minion_brann_bronzebeard\",\n    \"minion_kabal_talonpriest\",\n    \"minion_kabal_courier\",\n    \"minion_twilight_guardian\",\n    \"minion_kazakus\",\n    \"minion_azure_drake\",\n    \"spell_holy_nova\",\n    \"minion_blackwing_corruptor\",\n    \"minion_drakonid_operative\",\n    \"minion_raza_the_chained\",\n    \"minion_kabal_songstealer\",\n    \"minion_cabal_shadow_priest\",\n    \"minion_reno_jackson\",\n    \"spell_entomb\",\n    \"minion_book_wyrm\",\n    \"spell_dragonfire_potion\",\n    \"minion_chillmaw\",\n    \"minion_ysera\"\n  ],\n  \"name\": \"Reno Priest\",\n  \"heroClass\": \"PRIEST\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/renolock.json",
    "content": "{\n  \"cards\": [\n    \"spell_mortal_coil\",\n    \"spell_power_overwhelming\",\n    \"minion_dark_peddler\",\n    \"minion_acidic_swamp_ooze\",\n    \"minion_dirty_rat\",\n    \"minion_doomsayer\",\n    \"minion_sunfury_protector\",\n    \"spell_demonwrath\",\n    \"spell_shadow_bolt\",\n    \"minion_imp_gang_boss\",\n    \"minion_brann_bronzebeard\",\n    \"minion_earthen_ring_farseer\",\n    \"minion_mind_control_tech\",\n    \"spell_hellfire\",\n    \"spell_shadowflame\",\n    \"minion_kazakus\",\n    \"minion_faceless_shambler\",\n    \"minion_refreshment_vendor\",\n    \"minion_twilight_drake\",\n    \"minion_faceless_manipulator\",\n    \"minion_leeroy_jenkins\",\n    \"minion_second-rate_bruiser\",\n    \"spell_siphon_soul\",\n    \"minion_emperor_thaurissan\",\n    \"minion_reno_jackson\",\n    \"minion_sylvanas_windrunner\",\n    \"minion_abyssal_enforcer\",\n    \"spell_twisting_nether\",\n    \"minion_lord_jaraxxus\",\n    \"minion_mountain_giant\"\n  ],\n  \"name\": \"Renolock\",\n  \"heroClass\": \"WARLOCK\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/decks/wild_pirate_warrior.json",
    "content": "{\n  \"cards\": [\n    \"minion_southsea_deckhand\",\n    \"minion_southsea_deckhand\",\n    \"spell_upgrade\",\n    \"spell_upgrade\",\n    \"minion_sir_finley_mrrgglton\",\n    \"minion_nzoths_first_mate\",\n    \"minion_nzoths_first_mate\",\n    \"minion_patches_the_pirate\",\n    \"minion_small-time_buccaneer\",\n    \"minion_small-time_buccaneer\",\n    \"spell_heroic_strike\",\n    \"spell_heroic_strike\",\n    \"weapon_fiery_war_axe\",\n    \"weapon_fiery_war_axe\",\n    \"minion_bloodsail_raider\",\n    \"minion_bloodsail_raider\",\n    \"minion_ships_cannon\",\n    \"minion_ships_cannon\",\n    \"minion_bloodsail_cultist\",\n    \"minion_bloodsail_cultist\",\n    \"minion_korkron_elite\",\n    \"minion_korkron_elite\",\n    \"minion_dread_corsair\",\n    \"minion_dread_corsair\",\n    \"spell_mortal_strike\",\n    \"spell_mortal_strike\",\n    \"weapon_deaths_bite\",\n    \"weapon_deaths_bite\",\n    \"weapon_arcanite_reaper\",\n    \"minion_leeroy_jenkins\"\n  ],\n  \"name\": \"Wild Pirate Warrior\",\n  \"heroClass\": \"WARRIOR\",\n  \"arbitrary\": false\n}"
  },
  {
    "path": "cards/src/main/resources/formats/all.json",
    "content": "{\r\n\t\"name\": \"All\",\r\n\t\"sets\": [\r\n\t\t\"BASIC\",\r\n\t\t\"CLASSIC\",\r\n\t\t\"REWARD\",\r\n\t\t\"PROMO\",\r\n\t\t\"HALL_OF_FAME\",\r\n\t\t\"NAXXRAMAS\",\r\n\t\t\"GOBLINS_VS_GNOMES\",\r\n\t\t\"BLACKROCK_MOUNTAIN\",\r\n\t\t\"THE_GRAND_TOURNAMENT\",\r\n\t\t\"LEAGUE_OF_EXPLORERS\",\r\n\t\t\"THE_OLD_GODS\",\r\n\t\t\"ONE_NIGHT_IN_KARAZHAN\",\r\n\t\t\"MEAN_STREETS_OF_GADGETZAN\",\r\n\t\t\"CUSTOM\"\r\n\t]\r\n}"
  },
  {
    "path": "cards/src/main/resources/formats/standard.json",
    "content": "{\r\n\t\"name\": \"Standard\",\r\n\t\"sets\": [\r\n\t\t\"BASIC\",\r\n\t\t\"CLASSIC\",\r\n\t\t\"THE_OLD_GODS\",\r\n\t\t\"ONE_NIGHT_IN_KARAZHAN\",\r\n\t\t\"MEAN_STREETS_OF_GADGETZAN\"\r\n\t]\r\n}\r\n"
  },
  {
    "path": "cards/src/main/resources/formats/wild.json",
    "content": "{\r\n\t\"name\": \"Wild\",\r\n\t\"sets\": [\r\n\t\t\"BASIC\",\r\n\t\t\"CLASSIC\",\r\n\t\t\"REWARD\",\r\n\t\t\"PROMO\",\r\n\t\t\"HALL_OF_FAME\",\r\n\t\t\"NAXXRAMAS\",\r\n\t\t\"GOBLINS_VS_GNOMES\",\r\n\t\t\"BLACKROCK_MOUNTAIN\",\r\n\t\t\"THE_GRAND_TOURNAMENT\",\r\n\t\t\"LEAGUE_OF_EXPLORERS\",\r\n\t\t\"THE_OLD_GODS\",\r\n\t\t\"ONE_NIGHT_IN_KARAZHAN\",\r\n\t\t\"MEAN_STREETS_OF_GADGETZAN\"\r\n\t]\r\n}"
  },
  {
    "path": "cards/src/main/resources/training/budeget_effective_gvg_rogue_tempo_mech_synergy.json",
    "content": "{\n    \"deck\": \"*Budeget* Effective GvG Rogue Tempo (Mech Synergy)!\",\n\t\"RED_MODIFIER\": -48.803558713282236,\n\t\"YELLOW_MODIFIER\": -11.224674467097728,\n\t\"OWN_HP_FACTOR\": 0.14996902907278864,\n\t\"OPPONENT_HP_FACTOR\": -2.0230072985287704,\n\t\"OWN_CARD_COUNT\": 4.132855301498971,\n\t\"OPPONENT_CARD_COUNT\": -4.577058921869457,\n\t\"MINION_INTRINSIC_VALUE\": 2.285553015021578,\n\t\"MINION_ATTACK_FACTOR\": 2.793246134001451,\n\t\"MINION_HP_FACTOR\": 1.5,\n\t\"MINION_RED_TAUNT_MODIFIER\": 10.711545515875244,\n\t\"MINION_YELLOW_TAUNT_MODIFIER\": 3.8732942739305773,\n\t\"MINION_DEFAULT_TAUNT_MODIFIER\": 2.1757223355543553,\n\t\"MINION_WINDFURY_MODIFIER\": 2.994253358356527,\n\t\"MINION_DIVINE_SHIELD_MODIFIER\": 0.38538298903310375,\n\t\"MINION_SPELL_POWER_MODIFIER\": 1.0,\n\t\"MINION_STEALTHED_MODIFIER\": 2.660502416907415,\n\t\"MINION_UNTARGETABLE_BY_SPELLS_MODIFIER\": 2.7616036350041426\n}"
  },
  {
    "path": "cards/src/main/resources/training/gvg_face_hunter_season_9_legend_24_na.json",
    "content": "{\n    \"deck\": \"(GvG) Face Hunter Season 9 Legend #24 NA\",\n\t\"RED_MODIFIER\": -50.74442817563185,\n\t\"YELLOW_MODIFIER\": -45.4008353210228,\n\t\"OWN_HP_FACTOR\": 0.7508193696415226,\n\t\"OPPONENT_HP_FACTOR\": -4.975683193134163,\n\t\"OWN_CARD_COUNT\": -11.46299348412484,\n\t\"OPPONENT_CARD_COUNT\": 0.22641703440126815,\n\t\"MINION_INTRINSIC_VALUE\": 0.6481875719621666,\n\t\"MINION_ATTACK_FACTOR\": 5.652364118008309,\n\t\"MINION_HP_FACTOR\": 1.3324129443737278,\n\t\"MINION_RED_TAUNT_MODIFIER\": 10.524204678372168,\n\t\"MINION_YELLOW_TAUNT_MODIFIER\": 23.542877265462998,\n\t\"MINION_DEFAULT_TAUNT_MODIFIER\": 4.2453137253795425,\n\t\"MINION_WINDFURY_MODIFIER\": 2.0671218820194692,\n\t\"MINION_DIVINE_SHIELD_MODIFIER\": 1.1507303795144685,\n\t\"MINION_SPELL_POWER_MODIFIER\": 3.7213036522499343,\n\t\"MINION_STEALTHED_MODIFIER\": 0.9710135999168554,\n\t\"MINION_UNTARGETABLE_BY_SPELLS_MODIFIER\": 1.0\n}"
  },
  {
    "path": "cards/src/main/resources/training/handlock_mechanization_____.json",
    "content": "{\n  \"MINION_DEFAULT_TAUNT_MODIFIER\": 0.8828493421917007,\n  \"deck\": \"Handlock Mechanization!   (?_? )\",\n  \"MINION_UNTARGETABLE_BY_SPELLS_MODIFIER\": 0.0,\n  \"MINION_HP_FACTOR\": 0.0,\n  \"RED_MODIFIER\": -51.11188853763535,\n  \"OPPONENT_HP_FACTOR\": -2.1014486603663967,\n  \"MINION_INTRINSIC_VALUE\": 2.4803740783056663,\n  \"MINION_WINDFURY_MODIFIER\": 3.5721633351020885,\n  \"OPPONENT_CARD_COUNT\": -1.8715261412724835,\n  \"MINION_STEALTHED_MODIFIER\": 2.1718690181391915,\n  \"OWN_CARD_COUNT\": 4.229052445352634,\n  \"MINION_RED_TAUNT_MODIFIER\": 9.479147671156035,\n  \"YELLOW_MODIFIER\": -8.102225668324532,\n  \"MINION_ATTACK_FACTOR\": 2.4645867260898786,\n  \"HARD_REMOVAL_VALUE\": 3.1955233658244744,\n  \"MINION_DIVINE_SHIELD_MODIFIER\": 0.456232936251036,\n  \"MINION_SPELL_POWER_MODIFIER\": 2.053100854673998,\n  \"OWN_HP_FACTOR\": 2.0605171321700464,\n  \"MINION_YELLOW_TAUNT_MODIFIER\": 2.8916046211292725\n}"
  },
  {
    "path": "cards/src/test/java/net/demilich/metastone/tests/ValidateCards.java",
    "content": "package net.demilich.metastone.tests;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.lang.reflect.Array;\nimport java.util.List;\n\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.io.filefilter.DirectoryFileFilter;\nimport org.apache.commons.io.filefilter.RegexFileFilter;\nimport org.testng.Assert;\nimport org.testng.annotations.DataProvider;\nimport org.testng.annotations.Test;\n\nimport net.demilich.metastone.game.cards.CardParser;\nimport net.demilich.metastone.utils.ResourceInputStream;\nimport net.demilich.metastone.utils.UserHomeMetastone;\n\n/**\n * This test will iterate through all the cards in the cards resources dir\n * and invoke the CardParser.parseCard(cardFile) method to ensure that\n * each card is well formed and can be parsed.\n */\npublic class ValidateCards {\n\n    private static final String CARDS_DIR = \"src/main/resources/cards/\"; // relative path from module root\n    private static final CardParser CARD_PARSER = new CardParser();\n    private static final List<File> ALL_CARD_FILES;\n\n    static {\n\n        // recursively crawl the cards dir and pull out all the files\n        ALL_CARD_FILES = (List<File>)FileUtils.listFiles(\n                new File(CARDS_DIR),\n                new RegexFileFilter(\"^(.*json)\"),\n                DirectoryFileFilter.DIRECTORY);\n        // also pull in the user's custom cards dir\n        if (new File(UserHomeMetastone.getPath() + File.separator + \"cards\").exists()) {\n            ALL_CARD_FILES.addAll(\n                    FileUtils.listFiles(\n                        new File(UserHomeMetastone.getPath() + File.separator + \"cards\"),\n                        new RegexFileFilter(\"^(.*json)\"),\n                        DirectoryFileFilter.DIRECTORY)\n            );\n        }\n    }\n\n    @DataProvider(name = \"CardProvider\")\n    public static Object[][] getCardFiles() {\n\n        int size =  ALL_CARD_FILES.size();\n        File file;\n        Object [][] matrix = (Object[][]) Array.newInstance(Object.class, size, 1);\n        for (int i = 0; i < size; i++) {\n            file = ALL_CARD_FILES.get(i);\n            matrix[i][0] = file;\n        };\n\n        return matrix;\n    }\n\n    @Test(dataProvider = \"CardProvider\")\n    public void validateCard(File cardFile) throws FileNotFoundException {\n        try {\n            CARD_PARSER.parseCard(new ResourceInputStream(cardFile.getName(), new FileInputStream(cardFile), true));\n        } catch (Exception ex) {\n            System.err.println(ex);\n            Assert.fail(cardFile.getName(), ex);\n        }\n    }\n}\n"
  },
  {
    "path": "documentation/attributes.txt",
    "content": "Attributes\r\n\r\nBASE_MANA_COST\r\nThe base Mana Cost of a card.\r\n\r\nHP\r\nThe current HP or Durability of an Entity.\r\n\r\nATTACK\r\nThe current Attack of an Entity.\r\n\r\nATTACK_BONUS\r\nThe current bonus Attack of an Entity.\r\n\r\nMAX_HP\r\nThe maximum HP or Durability of an Entity.\r\n\r\nARMOR\r\nThe amount of Armor on an Entity.\r\n\r\nTEMPORARY_ATTACK_BONUS\r\nThe current temporary bonus Attack (lasts for one turn) of an Entity.\r\n\r\nHP_BONUS\r\nThe current bonus maximum HP or Durability of an Entity.\r\n\r\nAURA_ATTACK_BONUS\r\nThe bonus attack gained through auras.\r\n\r\nAURA_HP_BONUS\r\nThe bonus maximum HP gained through auras.\r\n\r\nBASE_HP\r\nThe base HP or Durability of an entity’s source card.\r\n\r\nBASE_ATTACK\r\nThe base Attack of an entity’s source card.\r\n\r\nCONDITIONAL_ATTACK_BONUS\r\nThe bonus Attack gained through conditional effects, such as Enrage.\r\n\r\nRACE\r\nThe Race of an Entity.\r\n\r\nDESTROYED\r\nDestroys the target, regardless of other Attributes.\r\n\r\nPENDING_DESTROY\r\nInternal tracker to determine death ordering.\r\n\r\nFATIGUE\r\nInternal tracker for the amount of Fatigue a player has.\r\n\r\nFROZEN\r\nFreezes the target Actor. Frozen Actors cannot attack, and Unfreeze at the end of their controller’s turn, provided they have not attacked that turn.\r\n\r\nENRAGABLE\r\nDetermines if the Minion has an Enrage effect.\r\n\r\nWINDFURY\r\nGrants the Actor Windfury. Windfuried Actors have two attacks per turn.\r\n\r\nMEGA_WINDFURY\r\nGrants the Actor Mega Windfury. Mega Windfuried Actors have four attacks per turn.\r\n\r\nUNLIMITED_ATTACKS\r\nGrants the Actor Unlimited Attacks. Actors with Unlimited Attacks may attack an infinite number of times per turn, limited only by the turn timer.\r\n\r\nTAUNT\r\nGrants the Actor Taunt. Taunted Actors must be attacked before any non-Taunted Actor.\r\n\r\nSPELL_DAMAGE: X\r\nGrants the target Spell Damage +X\r\n\r\nOPPONENT_SPELL_DAMAGE: X\r\nGrants the target Spell Damage +X for the opposing player.\r\n\r\nCHARGE\r\nGrants the target Charge. Charge minions can attack the turn they are summoned and do not get Summoning Sickness. See Summoning Sickness.\r\n\r\nBATTLECRY\r\nDetermines if a card has a Battlecry.\r\nAdditionally, holds the Battlecry of the source card in an Entity after play.\r\n\r\nDOUBLE_BATTLECRIES\r\nEntities with this Attribute have “Battlecries happen twice.”\r\n\r\nDEATHRATTLES\r\nDetermines if a card has a Deathrattle.\r\nAdditionally, holds the Deathrattle(s) of the source card in an Entity after play.\r\n\r\nDOUBLE_DEATHRATTLES\r\nEntities with this Attribute have “Deathrattles happen twice.”\r\n\r\nIMMUNE\r\nGrants the Entity Immune. Immune Entities cannot be damaged or targeted directly by the opposing player.\r\n\r\nIMMUNE_WHILE_ATTACKING\r\nGrants the Entity Immune while attacking. See Immune.\r\n\r\nDIVINE_SHIELD\r\nGrants the Actor Divine Shield. Actors with Divine Shield negate the next instance of damage taken.\r\n\r\nSTEALTH\r\nGrants the Actor Stealth. Actors with Stealth cannot be targeted by the opposing player directly. Dealing damage while Stealthed removes Stealth.\r\n\r\nSECRET\r\nDetermines if a card is a Secret.\r\n\r\nCOMBO\r\nDetermines if a card has Combo.\r\n\r\nOVERLOAD: X\r\nWhen the card is played, the player overloads for X.\r\n\r\nCHOOSE_ONE\r\nDetermines if a Card has Choose One.\r\n\r\nBOTH_CHOOSE_ONE_OPTIONS\r\nEntities with this attribute have \"Your Choose One cards have both effects combined.\"\r\n\r\nSUMMONING_SICKNESS\r\nGrants a minion Summoning Sickness. Minions with Summoning Sickness cannot attack. Minions automatically get Summoning Sickness the turn they are summoned. Summoning Sickness is removed at the end of each player’s turn.\r\n\r\nUNTARGETABLE_BY_SPELLS\r\nGrants an Actor Untargetable by Spells. Actors with Untargetable by Spells cannot be directly targeted by Spells or Hero Powers.\r\n\r\nSPELL_DAMAGE_MULTIPLIER: X\r\nGrants a Spell Spell Damage Multiplier X. Spells casts with Spell Damage Multiplier have their additional Spell Damage multiplied by X. See Spell Damage.\r\n\r\nSPELL_AMPLIFY_MULTIPLIER: X\r\nGrants an Entity Spell Amplify Multiplier X. Damage dealt by Spells or Hero Powers played by the Entity’s owner are multiplied by X.\r\n\r\nHEAL_AMPLIFY_MULTIPLIER: X\r\nGrants an Entity Heal Amplify Multiplier X. Healing dealt by Spells or Hero Powers played by the Entity’s owner are multiplied by X.\r\n\r\nMANA_COST_MODIFIER\r\nGrants a card a modified Mana Cost.\r\n\r\nATTACK_EQUALS_HP\r\nGrants a minion “This minion has Attack equal to its health.”\r\n\r\nCANNOT_ATTACK\r\nGrants an Actor “Cannot Attack.” Actors with “Cannot Attack” cannot attack, regardless of the number of attacks the Actor has.\r\n\r\nCANNOT_ATTACK_HEROES\r\nGrants an Actor “Cannot attack Heroes.” Actors with this Attribute cannot attack Heroes, regardless of the minions the opposing player owns.\r\n\r\nINVERT_HEALING\r\nGrants an Actor “Any healing by cards you own deals damage instead.” Actors with this Attribute cause all cards or Entities you own or play to deal damage instead of healing.\r\n\r\nCANNOT_REDUCE_HP_BELOW_1\r\nGrants an Actor “Cannot reduce HP below 1.” Actors with this Attribute cannot have their HP reduced below 1 through damage effects, and any damage received past that is negated.\r\n\r\nCOUNTERED\r\nDetermines if a card has been Countered. Countered cards are removed from play before the card’s effect or minion enters play.\r\n\r\nHERO_POWER_CAN_TARGET_MINIONS\r\nGrants an Actor “Your Hero Power can target minions.” This only affects the Hunter’s Hero Power.\r\n\r\nLAST_HIT\r\nDetermines the amount of damage this Actor received last.\r\n\r\nPASSIVE_TRIGGER\r\nDetermines if an Entity has a Trigger effect.\r\n\r\nHERO_POWER_DAMAGE: X\r\nGrants an Entity “Your Hero Power deals X additional damage.” Entities with this Attribute give your Hero Power X additional damage.\r\n\r\nCTHUN_ATTACK_BUFF: X\r\nAdds X to the Attack of owner's C'Thun\r\n\r\nCTHUN_HEALTH_BUFF: X\r\nAdds X to the Health of owner's C'Thun\r\n\r\nCTHUN_TAUNT\r\nGives owner's C'thun Taunt\r\n\r\nSPELLS_COST_HEALTH\r\nGrants an Entity \"Your spells cost Health instead of Mana.\" Whenever the owner of an entity with this attribute plays a spell, their hero will lose Health equal to the cost of the spell instead of mana.\r\n\r\nIMMUNE_HERO\r\nGrants an Entity \"Your hero has Immune.\" Immune heroes cannot take damage.\r\n\r\nTAKE_DOUBLE_DAMAGE\r\nGrants an Entity “This {Actor} takes double damage.” Entities with this Attribute take double damage.\r\n\r\n\r\n\r\nInternal Attributes\r\n\r\nDIED_ON_TURN\r\nInternal use. Holds the turn a minion died on.\r\n\r\nHERO_POWER_USAGES\r\nInternal use. Holds the number of Hero Powers that have been used during this turn.\r\n\r\nSHADOWFORM\r\nInternal tracker for when your Hero enters Shadowform.\r\n\r\nHERO_POWER_USED\r\nInternal tracker for the number of Hero Powers that have been used during the game.\r\n\r\nNUMBER_OF_ATTACKS: X\r\nInternal tracker for the number of attacks an Actor can perform each turn. \r\n\r\nEXTRA_ATTACKS: X\r\nInternal tracker for the number of extra attacks an Actor can perform each turn.\r\n\r\nENRAGED\r\nInternal flag for whether a minion is currently Enraged.\r\n\r\n"
  },
  {
    "path": "documentation/card.txt",
    "content": "General Information\r\n\r\nThis information is used on all card types.\r\n\r\n\"id\": String. The internal identifier for the card. This must be unique, so that\r\n\tcards can be distinguished from another card.\r\n\"name\": String. The name of the card. This is the displayed name on the card,\r\n\tand does not have to be unique.\r\n\"baseManaCost\": Integer. This is the base mana cost for the card before any mana\r\n\tmodification.\r\n\"type\": CardType Enum. The type of card the card is. There are several different\r\n\ttypes of cards:\r\n\t\t\"MINION\"\r\n\t\t\"SPELL\"\r\n\t\t\"CHOOSE_ONE\"\r\n\t\t\"WEAPON\"\r\n\t\t\"HERO_POWER\"\r\n\t\t\"HERO\"\r\n\"heroClass\": HeroClass Enum. The class that the card belongs to or ANY if the\r\n\tcard is neutral. The hero classes are:\r\n\t\t\"ANY\"\r\n\t\t\"DRUID\"\r\n\t\t\"HUNTER\"\r\n\t\t\"MAGE\"\r\n\t\t\"PALADIN\"\r\n\t\t\"PRIEST\"\r\n\t\t\"ROGUE\"\r\n\t\t\"SHAMAN\"\r\n\t\t\"WARLOCK\"\r\n\t\t\"WARRIOR\"\r\n\tIn addition, the following hero classes are used for cards:\r\n\t\t\"OPPONENT\" - selects the opponent's class.\r\n\t\t\"BOSS\" - the class of the Hero Ragnaros the Firelord.\r\n\"rarity\": Rarity Enum. What rarity the card is. These rarities are:\r\n\t\t\"FREE\" - This is the same as Basic.\r\n\t\t\"COMMON\"\r\n\t\t\"RARE\"\r\n\t\t\"EPIC\"\r\n\t\t\"LEGENDARY\"\r\n\"description\": String. The card text that is displayed on the card.\r\n\"manaCostModifier\": Integer. The mana cost modifier that changes how much the\r\n\tcard costs to play.\r\n\"collectible\": Boolean. Whether the card is collectible or not.\r\n\"set\": CardSet Enum. Which card set the card belongs to. Currently, these are:\r\n\t\t\"CLASSIC\"\r\n\t\t\"NAXXRAMAS\"\r\n\t\t\"GOBLINS_VS_GNOMES\"\r\n\t\t\"BLACKROCK_MOUNTAIN\"\r\n\t\t\"THE_GRAND_TOURNAMENT\"\r\n\t\t\"LEAGUE_OF_EXPLORERS\"\r\n\tIn addition, the following card sets have been added:\r\n\t\t\"CUSTOM\" - For use with formats to sort custom cards.\r\n\r\n\r\n\r\nMinion Information\r\n\r\nThis information is used only on minion cards.\r\n\r\n\"baseAttack\": Integer. The base amount of attack the minion has.\r\n\"baseHp\": Integer. The base amount of health the minion has.\r\n\"race\": Race Enum. The race of the minion. Currently, these are:\r\n\t\t\"BEAST\"\r\n\t\t\"DEMON\"\r\n\t\t\"DRAGON\"\r\n\t\t\"MECH\"\r\n\t\t\"MURLOC\"\r\n\t\t\"NONE\" - This does not have to be set if the minion has no race.\r\n\t\t\"PIRATE\"\r\n\t\t\"TOTEM\"\r\n\"battlecry\": Battlecry. The battlecry that is cast when this minion is played.\r\n\"deathrattle\": Spell. The spell that is cast when this minion is destroyed.\r\n\"aura\": Aura. The aura spell that is granted by this minion.\r\n\"trigger\": Trigger. The spell that is casted when the appropriate trigger\r\n\toccurs. See triggers.txt for a list of triggers.\r\n\"options\": Custom. The choose one options available for the minion when played.\r\n\"attributes\": Attribute Array. The list of attributes that a minion has. See\r\n\tattributes.txt for a list of attributes.\r\n\r\n\r\n\r\nSpell Information\r\n\r\nThis information is used only on spell cards.\r\n\r\n\"targetSelection\": TargetSelection Enum. The targets selectable for the spell\r\n\twhen played. The current selectable targets are:\r\n\t\t\"NONE\" - No direct targeting occurs.\r\n\t\t\"ENEMY_CHARACTERS\"\r\n\t\t\"ENEMY_MINIONS\"\r\n\t\t\"FRIENDLY_CHARACTERS\"\r\n\t\t\"FRIENDLY_MINIONS\"\r\n\t\t\"MINIONS\"\r\n\t\t\"HEROES\"\r\n\t\t\"ADJACENT_MINIONS\"\r\n\t\t\"ANY\"\r\n\"spell\": Spell. The spell that is cast when the card is played.\r\n\"condition\": Condition. The condition that must be satisfied in order to play the card.\r\n\r\nChoose One Spell cards have the following keys instead of \"spell\".\r\n\r\n\"options\": Spell Array. The available options to choose from when the card is\r\n\tplayed.\r\n\r\nSecret spell cards have the following keys instead of \"spell\".\r\n\r\n\"trigger\": Trigger. The spell that is casted when the appropriate trigger\r\n\toccurs. See triggers.txt for a list of triggers.\r\n\r\n\r\n\r\nWeapon Information\r\n\r\nThis information is used only on weapon cards.\r\n\r\n\"damage\": Integer. The base attack of the weapon.\r\n\"durability\": Integer. The base durability of the weapon.\r\n\"battlecry\": Battlecry. The battlecry that is cast when this weapon is played.\r\n\"deathrattle\": Spell. The spell that is cast when this weapon is destroyed.\r\n\"aura\": Aura. The aura spell that is granted by this weapon.\r\n\"trigger\": Trigger. The spell that is casted when the appropriate trigger\r\n\toccurs. See triggers.txt for a list of triggers.\r\n\"onEquip\": Spell. The spell that is cast when you equip the weapon.\r\n\"onUnequip\": Spell. The spell that is cast when you unequip the weapon.\r\n\r\n\r\n\r\nHero Power Information\r\n\r\nHero Power cards follow the same format as Spells."
  },
  {
    "path": "documentation/conditions.txt",
    "content": "Conditions\r\n\r\nAndCondition\r\n\r\nAttributeCondition\r\n\r\nCardCountCondition\r\n\r\nCardPropertyCondition\r\n\r\nComboCondition\r\n\r\nComparisonCondition\r\n\r\nControlsSecretCondition\r\n\r\nDeckContainsCondition\r\n\r\nGraveyardContainsCondition\r\n\r\nHasAttackedCondition\r\n\r\nHasEntitiesOnBoardCondition\r\n\r\nHasEntityCondition\r\n\r\nHasHeroPowerCondition\r\n\r\nHasSpecificMinionOnBoardCondition\r\n\r\nHasWeaponCondition\r\n\r\nHighlanderDeckCondition\r\n\r\nHoldsCardCondition\r\n\r\nIsDamagedCondition\r\n\r\nIsDeadCondition\r\n\r\nManaCostCondition\r\n\r\nManaMaxedCondition\r\n\r\nMinionCountCondition\r\n\r\nOrCondition\r\n\r\nOwnedByPlayerCondition\r\n\r\nRaceCondition\r\n\r\nRaceOnBoardCondition\r\n\r\nRandomCondition\r\n"
  },
  {
    "path": "documentation/filters.txt",
    "content": "Filters\r\n\r\nAndFilter\r\nCombines multiple filters.\r\n\r\nAttributeFilter\r\nFilters based on the specific attribute given.\r\n\r\nCardFilter\r\nFilters based on a multitude of card attributes.\r\n\r\nDamagedFilter\r\nFilters entities if they are damaged or not.\r\n\r\nHighestAttributeFilter\r\nFilters entities based on which entity has the highest value of the attribute given.\r\n\r\nInDeckFilter\r\nFilters based on whether it belongs to the player's deck.\r\n\r\nRaceFilter\r\nFilters based on the Race given.\r\n\r\nSpecificCardFilter\r\nFilters based on the specific card name provided."
  },
  {
    "path": "documentation/knowledge.txt",
    "content": "==============================================\r\nBLUE POST 02.05.2014\r\n==============================================\r\n\r\nBlade Fury is doing extra damage!\r\nBlade Fury is a Rogue ability that destroys the player's weapon and deals the weapons damage to all enemies. Even though the weapon has a set amount, Blade Fury is still a spell and can be affected by Spell Power.\r\n\r\nAll my decks and/or cards vanished!\r\nMajority of the time this issue is seen it is due to the players region being changed. Double check that the region you are currently in is the one you regularly play in.\r\n\r\nMy opponent stole my minion.\r\nWhen I bounced it to the owners hand, it went to their hand! If you control a minion, you own that minion. Ownership is based off which side of the battlefield a minion is on. When returning a minion to its owners hand, it will return to the hand of the player who controls that minion on their side of the battlefield at the time the effect is played.\r\n\r\nMy Noble Sacrifice didn't trigger when my opponent attacked!\r\nDid you have 7 minions on the battlefield? Playing cards that create minions act the exact same way as trying to play a minion. If you don't have room, the card will not trigger.\r\n\r\nMinions healed when I killed/silenced Stormwind Champion (or other buffs)!\r\nAt the time you Silence a minion who is effected by a stat modifier, if their health is higher than their printed value (the value the minion displays with no stat modifiers), it will return the minion to its original health that is displayed on the card. If at the time Silence is cast, the minions current health is less than the printed value on the card, the minion will remain on the battlefield with that much damage.\r\nBen Brode gives an explanation here: http://us.battle.net/hearthstone/en/forum/topic/9793229858?page=2#21\r\n\r\nGorehowl was destroyed after one use!\r\nThe mistake here is usually in understanding the card. If a player attacks a hero, the durability is reduced. If a player attacks a minion, the attack is reduced.\r\n\r\nIce Block did not prevent my opponents secret from killing me!\r\nIf you are attacking and in a position to trigger your opponent's secret, it is most likely your turn. As secret's cannot trigger on the owning player's turn, there is no chance for Ice Block to become active.\r\n\r\nMy opponent was able to attack my Faerie Dragon with their hero power!\r\nEven though you are not able to target the Faerie dragon with spells, you are able to attack the Faerie Dragon. So Rogues and Druids are able to use their hero powers to deal damage to the minion.\r\n\r\n\r\n\r\n==============================================\r\nLIQUIDHEARTH FORUMS POST (last update 20.03.2014)\r\n==============================================\r\n\r\n#1 Of course you may know you can silence a taunt so you can get to your opponent, but did you know you can silence your own frozen minion to unfreeze them?\r\n\r\n#2 More silence goodness! #Thingssilencedoes, not only does it remove buffs from enemy minions it can remove debuffs on friendly: humility, hunter's mark, corruption, power overwhelming, etc... You can not silence a silence. You can buff minions that have been silenced. Silence does not negate the \"minion-type\" of the card (ie. murloc, beast, pirate, etc.)\r\n\r\n#3 Due to the way buffs effecting health work, that is, adding 0/+1 increases both maximum and current health by 1 and subtracting 0/-1 only decreases maximum health by 1. Damage amounts equal to or less than the buff amount essentially are not applied if you then remove the buff. \r\nExamples:\r\nA) Silencing a (toughness) buffed minion that has taken damge, but not below its base health will not carry damage over to its unbuffed health. For instance, say I have a 4/4 that has blessing of kings on it and it becomes an 8/8. Say that 8/8 takes 2 damage, becomes 8/6, then I silence it. It will go back to being a 4/4 with no damage on it.\r\nB) Your opponent has a stormwind champion(6/6 +1/+1 to your other minions) and a bloodfin raptor (3/2) now buffed to a (4/3). You are playing a mage and have 6 mana. You anticipate killing the stormwind champion with a fireball and ping the raptor with your champion ability for 1 bringing it to 4/2 assuming you'll be able to remove it with your ability next turn. But you fireball the stormwind champion and the raptor becomes a 3/2. (Obviously you should fireball then ping, just trying to create an example.)\r\n\r\n#4 AoE and random target spells hit stealthed minions and Faerie dragons.\r\n\r\n#5 Battlecry affects can target faerie dragons.\r\n\r\n#6 Raging Worgen can attack a second time the first turn he enrages.\r\n\r\n#7 If an enraged minion is healed to full they lose their enraged abilities. \r\n\r\n#8 Use straight removal spells(cards that outright remove a minion, rather than deal damage) to bypass things like divine shield and Acolyte of Pain's card draw.\r\n\r\n#9 Returning a minion to its owner's hand isn't entirely accurate, it actually returns it to it's controller's hand. You can return tokens to controller's hands. Returning a minion to your hand under shadow madness (priest spell lets you take control of an enemy minion one turn) will return it to your hand, effectively keeping it away from your opponent.\r\n\r\n#10 Hexed 0/1 Frogs cost 0 mana to play if returned to hand.\r\n\r\n#11 Due to the \"choose one\" mechanic Keeper of the Grove 's silence and 2 damage abilities count as a spell so it will trigger things like Spellbender or Questing Adventurer or could even be counterspelled by counterspell. \r\n\r\n#12 Every time you \"draw a card\" while in fatigue raises fatigue by one. If you can force your opponent to draw cards while in fatigue you can use this to your advantage. \r\n\r\n#13 The Auchenai Soulpriest turns all healing into damage, regardless of source (hero power, minions, or spells).\r\nThe Soulpriest turns Voodoo Doctor and Earthen Ring Shaman into *devastating* nukers.\r\n\r\n#14 You can use Blessing of Wisdom on an opponent's minion and it'll draw for you. Good for when you are in a bind or maybe can't remove a minion you'd like an opponent to trade with. \r\n\r\n#15 While a minion is controlled by Shadow Madness, you gain the benefit of any special abilities on the card.\r\nExamples:Harvest Golem's deathrattle will summon a damaged golem for you.\r\nLoot Hoarder's deathrattle gives you a card.\r\nAlpha Wolves, Raid Leaders, and Flametongue Totems buff your minions.\r\nPint Sized Summoner lets you summon a creature cheaply.\r\nNorthshire Cleric lets you heal and get a card.\r\nBlessing of Wisdom is still considered your opponents card though so you do not get the card draw. \r\n\r\n#16 Shadow Madness ignores summoning sickness. Mind Control and Cabal Shadow Priest controlled cards do have summoning sickness. However, they are able to attack the turn you take control if they have charge. \r\n\r\n#17 Resurrecting a minion (With Redemption or Ancestral Spirit) will not restore it's battlecry effects or external buffs but all other effects will be. A Steath minion will go back into stealth, A Divine Shield minion will get it's shield back and a charge minion will be able to attack once more during that turn, Injured Blademaster comes back as a 4/7(with ancestral spirit). Exceptionally powerful combo with Argent Commander since he has both charge and divine shield. \r\n\r\n#18 May be an old one, but all new players may not know yet. The coin counts as a spell. It can be countered, comboed, levels Questing Adventurer, etc. \r\n\r\n#19 Snakes count as beasts, use the snake trap secret in combination with the starving buzzard to draw 3 cards.\r\n\r\n#20 Humility nor Aldor Peacekeeper bring a light elemental's power to 1(It stays equal to it's health).\r\n\r\n#21 You can use Dark Iron Dwarf to boost the power of an enemy minion (unlike the Shattered Sun Cleric) to put it into range of Shadow Word: Death if you need a 4 power minion removed. Great way to get rid of Ysera if you don't have a MC.\r\n\r\n#22 Spells that summon a minion, such as the hunter's Animal Companion, do not trigger secret's such as the mage's Mirror Entity, hunter's Snipe or paladin's Repentance. \r\n\r\n#23 When you have minions in your deck that buff adjacent minions its generally a good rule of thumb to put new minions to the left, instead of your usual instinct to the right. The reason is: your totems, spirit wolves, squires, tokens spawn automatically to the right, they die more often, and you need to have a balanced board to put Flametongue totems, dire wolves, doa's,etc in the middle of.\r\n\r\n#24 The druid card Wild Growth will give you a free card to draw a card when played at 10 mana crystals. \r\n\r\n#25 Casting Betrayal on a Cobra destroys both adjacent minions.\r\n\r\n#26 As a mage using a Leper Gnome you can ping it with your hero ability to kill it and deal the two damage to your opponent. It may help you squeeze in that last couple of damage, can be done the same turn you summon it or you can attack with it and then ping it during other turns. \r\n\r\n#27 Noble Sacrifice tricks - Sword of Justice will buff the defender to 3/2, leaving the board empty to the right of a dire wolf will make the defender spawn as a 3/1, having a raid leader on the board will make the defender spawn as a 3/1, Stormwind Champion will make it enter play as a 3/2, a knife jugglar on your board will throw a knife, etc. \r\n\r\n#28 Mind gaming your opponent: Try not to mouse over / select things when you don't need to ie if you mouse a spell over a minion and decide on a different play your opponent knows you have some form of buff/removal in your hand, make plays in your head. Bringing minions health down to an aoe threshhold may trick them into thinking you have said aoe spell in your hand. Having only 1 copy of a card in your deck may trick your opponent into playing around the other. - submitted by tOb4ccO\r\n\r\n#29 Crazed Alchemist tricks - It will kill a minion with 0 attack. If you crazed alchemist a damaged minion and then silence it, it will return to the cards original stats. Temporary stat boosts like the abusive sergeant's +2 attack will swap over permanently. Global stat boosts such as the stormwind champion's +1/+1 will be reapplied after the swap, ie a 4/5 yeti buffed to 5/6 will swap to 6/5 and then get the +1/+1 buff from stormwind champion again, making it a 7/6. \r\n\r\n#30 If you use the Priest card: Mindgames and you happen to copy a Twilight Drake, it will show up as a 4/1.\r\n\r\n#31 Alexstrasza can also be used to heal you back up to 15 health.\r\n\r\n#32 If a priest uses 'shadow madness' to gain control of a minion for a turn and then uses faceless manipulator to duplicate it, at the end of the turn you get both minions back (if they survive).\r\n\r\n#33 Equality removes Enrage. Pretty useful in many situations. The logic behind it is that the creature's max health is changed to 1, so it's no longer enraged - it's at max health.\r\n\r\n#34 If you summon Faceless Manipulator vs a mage, he won't get the pre-battlecry 3/3; he'll get the post-battlecry creature that it turned into.\r\n\r\n#35 Molten giant does not take into consideration all damage you have taken, it is actually cost = 20 - x, x being equal to your total health minus your current health. Example if you are at 15 health 30 - 15 = 15. 20 - 15 = it will cost 5 to play; however if you play earthen ring farseer and heal yourself for 3 the molten giant will cost 8 again. If you play Jaraxxus and have a molten giant, the molten giant wont cost only 5 (since jaraxxus initial health is 15), it will cost 20, cause it considers the damage taken by the hero, and jaraxxus is at full health at 15 life... So it is impossible to play a molten giant for less then 6 mana after playing Jaraxxus.\r\n\r\n#36 Don't forget! You should always attack with minions before you plan on returning them to your hand (brewmaster ie) or before you play a wipe like deathwing.\r\n\r\n#37 Shaman's totem hero ability summons a random totem you do not already have on the board. If your opponent silences one of your totems other than seering you can not get another one unless you can manage to kill it, like give it rockbiter to attack. \r\n\r\n#38 Humility or Aldor Peacekeeper + Stampeding Kodo is pretty good yo.\r\n\r\n#39 If you use Shadow Madness to gain control of a Wild Pyromancer it's ability will still trigger from the Shadow Madness. I would assume this works with Mind Control too, though I have not tested it.\r\n\r\n#40 If you cast a spell with a wild pyromancer on the board and remove an opponent's sylvanas and she steals your pyromancer, the pyromancer's effect will not trigger.\r\n\r\n#41 +Spell Damage also gives extra damage to Shadowflame (warlock) and Blade Flurry (rogue).\r\n\r\n#42 When a spellbender secret is activated, the minion will be affected by spells that otherwise will be unable to target it or have different result. for example:\r\nIt will die to an execute or buffed by rampage, even though it's at full health.\r\nIt will get buffed by a demon fire that was casted on a demon, even though it is not a demon.\r\n\r\n#43 You cannot play Lord Jaraxxus when your board is full of minions already, as he's technically a minion with battlecry. Lord Jaraxxus can be killed by sacrificial pact.\r\n\r\n#44 If you silence something that was hit with Equality it goes back to max health.\r\n\r\n#45 Freezing rules: Freeze causes you to lose your next attack. If you have a minion attack a water elemental, even though it may kill it, it will not be able to attack next turn, if you attack a water elemental, you will not be able to attack next turn. Water elemental must deal damage to freeze so will not freeze if hitting a divine shield. However, spells such as blizzard and frostbolt which specifically state freeze or damage and freeze will still freeze.\r\n\r\n#46 Druid of the claw and ancient of war work differently,\r\n\r\nWhen you return the druid of the claw to your hand, the card is fixed to the form you chose. if you went with bear form, and you used the panda to return the card back, it will be a bear form card.\r\n\r\nWith ancients, the form is reset. if you play the ancient of war, and you choose the uprooted form, and return it to your hand with the panda, the form is reset, so you get to choose the form again.\r\n\r\n#47 If your opponent plays a Millhouse Manastorm; then when it's your turn and you play Lord Jaraxxus, since you become a new character, the Millhouse Manastorm effect will lose it's validity. I just learned this the hard way in a game when I had lethal with Hellfire.\r\n\r\n#48 Don't equality/wild pyromancer against a stormwind champion on the board to remove multiple minions. All other minions will be reduced to 1 health and then buffed back to 2 health allowing them to live. \r\n\r\n#49 You can not go over 10 mana. If you would like to play a combination of cards costing more than 10 mana using Innervate you must spend mana before using innervate otherwise it does nothing.\r\n\r\n#50 Minions can have multiple death rattles.\r\n\r\n#51 You must cast a minion for its battlecry to occur. For example, Almaro-o-bot will not cause a minions battlecry to activate when swapped.\r\n\r\n#52 If you play commanding shout on a minion with 1 health and attack a minion, their won't be any triggers for \"damage dealt\" like water elemental's frost or emperor cobra's poison or frothing berserker's +1Attack and so on. (I assume this works any time the minion would have otherwise died.)\r\n\r\n#53 You can use the priest's hero power twice in the same turn mana permitting with shadowform. Heal two damage, cast the spell and then deal two damage. Same thing applies with the second shadowform, you can deal two damage, shadowform deal three damage.\r\n\r\n==============================================\r\nIHEATHU ARTICLE (16.12.2013)\r\n==============================================\r\n\r\nHearthstone is made to be a simple game to pick up. However, because of the wide variety of effects \r\nin the game, many of which have unwritten rules, the game is in practice very complex, to the point \r\nof having some truly bizarre interactions. The following is by no means a complete list, but should \r\nserve to whet your appetite for how crazy this game can get. As a note, some of these are most likely \r\nbugs, and may be fixed before the game is officially released.\r\n\r\nIf you play Equality with an Amani Berserker in play, he will be a 2/1. If an Amani revives from \r\nRedemption, he will be a 5/1.\r\n\r\nA 1/1 minion is standing next to a Dire Wolf Alpha, making it a 2/1. If you play Crazed Alchemist on \r\nthe token, it will become a 2/2. If instead you play Blessed Champion on the token, it will become a \r\n3/1.\r\n\r\nA minion attacks a Hunter who has Misdirection Trap set. If the minion attacks your character \r\ninstead, the minion will take damage from your weapon. However, you wont lose a weapon durability.\r\n\r\nMany spells have an effect even if the printed text says they do nothing. At 10 mana, Wild Growth \r\ngives you an Excess Mana spell that costs 0 and draws a card. If you have no demons in your deck, \r\nSense Demons gives you 2 Worthless Imps, which are 1/1s for 1 (not worthless!). If you play Mindgames \r\nwith no creatures in your opponents deck, you get a 0/1 for 0 called Shadow of Nothing. Captains \r\nParrot with no pirates in your deck does nothing. Innervate at 10 mana does nothing. Thoughtsteal and \r\nMind Vision also do nothing if the opposing player has no deck or no hand.\r\n\r\nIf a Warlock plays Lord Jaraxxus into a Repentance secret, youll have 1 HP when he replaces your \r\ncharacter. Molten Giant also becomes uncastable until youre at 5 life if you are Jaraxxus. If you \r\nplay Lord Jaraxxus with a Blood Imp in play, youll have 15 HP, even though the Jaraxxus minion on \r\nthe board had 16 HP. You can use Alarm-o-bot or Mindgames to cheat Jaraxxus in play, but in these \r\ncases his battlecry does not activate, and hes simply a 3/15.\r\n\r\nA Chillwind Yeti has 1 damage, making him a 4/4. If you cast Divine Spirit and heal him, he will be a \r\n4/9, not a 4/10.\r\n\r\nBane of Doom summons all common demons, Felguard (a rare), but not Doomguard (a rare).\r\n\r\nKilling an opponent with Bane of Doom summons an irrelevant demon.\r\n\r\nShadow Madness can be silenced. Mind Control cannot.\r\n\r\nIf stealthed, Knife Juggler, Imp Master and Ragnaros The Firelord immediately break stealth, since \r\ntheir abilities count as dealing damage.\r\n\r\nMirror Entity does not typically copy the Battlecry effect of a minion. For instance, if your \r\nopponent plays a Novice Engineer, you dont get to draw a card. An exception to this is Twilight \r\nDrake, which copies based on your opponents hand (formerly: your hand), or any Divine Shield which \r\nalso gets applied again (its not actually a Battle Cry). A copied Void Terror will also have a buff \r\nif the original ate a minion.\r\n\r\nHoly Nova deals damage before it heals. How is this relevant? Well, if the opponent has an Eye for an \r\nEye, and you cast it at 2 HP, you die.\r\n\r\nThe Battlecry of Aldor Peacekeeper and Humility do not affect Lightspawn at all.  The attack of Dire \r\nWolf Alpha buff also does not affect Lightspawn.\r\n\r\nPower of the Wild  3/2 panther does not trigger Mirror Entity, because it counts as a spell.\r\n\r\nIf a Blood Imp buffs a minion from 2 to 3 HP, and you cast 2 points of area damage, the minion goes \r\nto 1 HP but survives.\r\n\r\nWarrior Armor is strictly better than HP, so you should always hit with your weapon, *then* armor up. \r\nThe two examples you might know about are Molten Giant not counting armor as HP for the purposes of \r\nits cost, and of course Shield Slam. The one you might not know about is that Water Elemental does \r\nnot freeze you if it only takes off armor. As of the most recent patch, Alexstrasza lets you keep all \r\narmor you have when she sets your HP to 15.\r\n\r\nTraps trigger in the order you cast them. If you play a Counterspell, and then play a Spellbender, \r\nthe first spell your opponent plays will get countered (even if it targeted a minion), and the \r\nSpellbender wont trigger at all. If you play the Spellbender first and the Counterspell second, the \r\nspell will get redirected to your 1/3, then get countered.\r\n\r\nEnd of turn minion abilities activate in the order you summon them. For instance, if you play an Imp \r\nMaster, and then create a Healing Totem that turn, the Imp Master will damage itself and then heal \r\nback to 5 HP. If you summon them in the reverse order, the Healing Totem will activate first, and \r\nyoull only have a 4 HP Imp Master.\r\n\r\nIf you have 7 minions (the max number of minions allowed on one side of the board), your Noble \r\nSacrifice does not trigger at all.\r\n\r\nYou have Violet Teacher and 5 other minions on your side of the board. If you Mind Control, the \r\nteacher token will fill your board, leaving you no room. In this case, the mind controlled minion \r\ndies instead of coming to your side of the board.\r\n\r\nMost triggers will always resolve before the spell that triggered them. For instance, Soulfire + \r\nGadgetzan Auctioneer will draw a card before discarding it, and Power of the Wild buff + Violet \r\nTeacher will make the token, then pump it. An exception to this is Wild Pyromancer, which explicitly \r\ntriggers after you cast a spell.\r\n\r\nShamans hero power can only create a totem if none of that type are on the board. If you have 3 \r\ntotems out, you will get the 4th totem you dont have with 100% probability. If you have 4 totems \r\nout, youll get an error when you try to use your ability.\r\n\r\nIf you have 1 charge left on your Sword of Justice, and play a Silver Hand Knight, the sword will \r\npump the 2/2 Squire, not the Knight. A Snipe works the opposite way, and will kill the 4/4.\r\n\r\nSilencing Ancient of War Rooted form removes the health buff, leaving a 5/5 with no taunt. \r\nSilencing Druid of the Claw in taunt mode does not remove the health buff, leaving a 4/6 with no taunt.\r\n\r\nThe end is coming! After Doomsayer utters these fateful words, he almost always dies, either by his \r\nown hand or by your opponents. However, if you silence him, he will live to see your next turn. If \r\nyou then pump his attack above 0 with and attack with him, hell say, Did I miss it?\r\n\r\nShadowflame and Blade Flurry both counterintuitively get buffed by +Spell Damage minions such as \r\nKobold Geomancer. Betrayal does not.\r\n\r\nIf you cast Betrayal on an Emperor Cobra, it will instantly kill everything it damages.\r\n\r\nIf you Mind Control a minion that your opponent cast Blessing of Wisdom on, attacking with the minion \r\nwill still draw cards for your opponent.\r\n\r\nIf a full health Amani Berserker attacks a Hunter who has set an Explosive Trap, the enrage will \r\ninstantly activate and hell take 5 damage, not 2.\r\n\r\nYour board is a lone Sylvanas Windrunner, while your opponent has a 1/1 and a Sylvanas of his own. \r\nLets say you trade your Sylvanas for his. If your Sylvanas came into play first, youll gain control \r\nof his token, but then immediately lose control of it. If yours came into play second, his \r\ndeathrattle will trigger first, and youll get to keep the token."
  },
  {
    "path": "documentation/known_issues.txt",
    "content": "It is impossible to place a minion at the leftmost position when there are 6 minions on the board, since the leftmost minion and the leftmost selection arrow are off the screen.\n\nClicking \"Stop\" when selecting a Battlecry in Sandbox Mode crashes the program.\n\nFeature Request: There is currently no way to go back when in the Deck Creation screen without saving the current deck.\n\nSuggestions:\nMilled cards displayed somewhere\nMethod of conceding or ending match\n\nCheck if it is possible to remove CARD_LOCATION from SpellArg and introduce EntityReference.OPPONENT_HAND, etc (could also be used to target all cards in hand for example)\n"
  },
  {
    "path": "documentation/spells.txt",
    "content": "Spells\r\n“class”: The Spell name that you wish to cast.\r\n“target”: \r\n\r\nAddAttributeSpell\r\nAdds an Attribute to the target. See Attributes.\r\nExamples: Ice Block, Frost Elemental, Conceal\r\n“attribute”: Attribute. The Attribute you want to add to the target.\r\n“revertTrigger”: Optional Trigger. The trigger to revert the added Attribute.\r\n\r\nAddDeathrattleSpell\r\nAdds a Deathrattle to the target.\r\nExamples: Explorer’s Hat, Ancestral Spirit, Soul of the Forest\r\n“spell”: Spell. The Deathrattle you want to add to the target.\r\n\r\nAddSecretSpell\r\nDO NOT USE. Internal Spell for casting Secrets.\r\n\r\nAddSpellTriggerSpell\r\nAdds a Trigger to the target.\r\nExamples: Blessing of Wisdom, Corruption, Lock and Load\r\n“trigger”: Trigger. The Trigger you want to add to the target.\r\n\r\nAdjacentEffectSpell\r\nCasts a spell on the main target and the adjacent targets.\r\nExamples: Explosive Shot, Cone of Cold, Foe Reaper 4000\r\n“spell1”: Spell. The spell that affects the targeted minion.\r\n“spell2”: Optional Spell. The spell that affects the adjacent minions to the target. If this is blank, uses “spell1”\r\n\r\nAuraBuffSpell\r\nDO NOT USE. Internal Spell for applying and removing Auras. See AuraSpell.\r\n\r\nBuffHeroSpell\r\nBuffs the target Hero. This is used to modify a Hero’s Armor or Attack.\r\nExamples: Shapeshift (Hero Power), Ice Barrier, Armor Up (Hero Power)\r\n“attackBonus”: Optional Integer. The amount of Attack to give the target Hero.\r\n“hpBonus”: Optional Integer. The amount of Armor to give the target Hero.\r\n\r\nBuffSpell\r\nBuffs a minion or group of minions.\r\nExamples: Shattered Sun Cleric, Everyfin is Awesome\r\n\"attackBonus\": The amount of attack to give the minion(s).\r\n\"hpBonus\": The amount of health to give the minion(s)\r\n\r\nBuffWeaponSpell\r\nBuffs the target Weapon. This is used to modify a Weapon’s Attack or Durability.\r\nExamples: Captain Greenskin, Deadly Poison, Upgrade!\r\n“attackBonus”: Optional Integer. The amount of Attack to give the target Weapon.\r\n“hpBonus”: Optional Integer. The amount of Durability to give the target Weapon.\r\n\r\nCardCostModifierSpell\r\nGives a mana cost modifier to the target card. This should target card types or specific cards.\r\nExamples: Loatheb, Millhouse Manastorm, Dragon Consort, Wilfred Fizzlebang\r\n\"cardCostModifier\": CardCostModifier. The CardCostModifier to add to the target.\r\n\r\nCastRandomSpellSpell\r\nCasts a random spell\r\nExamples: Yogg-Saron, Servant of Yogg-Saron\r\n\"value\": Number of random spells to cast.\r\n\r\nCastRepeatedlySpell\r\n[]\r\n\r\nChangeHeroPowerSpell\r\nChanges the target Player’s Hero Power.\r\nExamples: Justicar Trueheart, Charged Hammer, Shadowform\r\n“card”: Card ID. The card ID of the Hero Power to change the target’s Hero Power to.\r\n\r\nChangeHeroSpell\r\nReplaces the target Player's hero with a different hero.\r\nExamples: Majordomo Executus, Jaraxxus\r\n\"card\": Card ID to change hero to. Must be a hero card.\r\n\r\nCheckDeadSpell\r\n[]\r\n\r\nClearOverloadSpell\r\nRemoves the Overloaded Mana Crystals of the target Player.\r\nExamples: Lava Shock\r\n\r\nCloneMinionSpell\r\n[]\r\n\r\nComboSpell\r\nUsed for spells with a combo effect. The first effect is the normal action, and the second activates only as a combo.\r\nExamples: Sabotage, Eviscerate, SI:7 Agent\r\n\"spell1\": Normal spell.\r\n\"spell2\": Combo effect.\r\n\r\nConditionalAttackBonusSpell\r\nGives a target or targets extra attack if a condition is met.\r\nExamples: Old Murk-Eye, Cogmaster, Goblin Sapper.\r\n\"target\": The entity to give the attack bonus to.\r\n\"ifTrue\": How much attack to grant if the condition is met.\r\n\"ifFalse\": How much attack to grant if it is not.\r\n\r\nConditionalEffectSpell\r\nHas an effect only when a condition is met, similar to a ComboSpell.\r\nExamples: Mortal Coil, Quick Shot, Southsea Deckhand\r\n\"spell1\": Normal spell.\r\n\"spell2\": Spell to cast if condition is met.\r\n\"condition\": Condition to check for.\r\n\r\nConditionalSpell\r\n[]\r\n\r\nCopyCardSpell\r\nPuts a copy of a card into your hand.\r\nExamples: Mind Vision, Convert, Chromaggus\r\n\"value\": Number of copies to make\r\n\"cardLocation\": Where to find the source of the copy. \"HAND\" refers to the opponent's hand, \"DECK\" refers to the opponent's deck, and so on.\r\n\r\nCopyDeathrattleSpell\r\nCopies the target’s Deathrattles.\r\nExamples: Unearthed Raptor\r\n\r\nCopyHeroPower\r\nCopies the target Player’s Hero Power.\r\nExamples: Sideshow Spelleater\r\n\r\nDamageSpell\r\nDamages the target.\r\nExamples: Fireball, Frostbolt, Hellfire, Lightning Storm, Steady Shot (Hero Power)\r\n“value”: Integer. The amount of damage to deal.\r\n\r\nDestroyAllExceptOneSpell\r\nDestroys all minions except one. \r\nExamples: Enter the Coliseum(leaves one minion for each player), Brawl\r\n\"target\": The pool of minions to destroy.\r\n\"filter\": The condition to choose which minion survives.\r\n\r\nDestroySecretsSpell\r\nDestroys all secrets of target player.\r\nExamples: Flare, Eater of Secrets\r\n\"targetPlayer\": The player whose secrets are destroyed.\r\n\r\nDestroySpell\r\nDestroys the target.\r\nExamples: Sacrificial Pact, Big Game Hunter, Execute\r\n\r\nDiscardCardsFromDeckSpell {}: Combine with DiscardSpell (Use DeckLocation)\r\nMills cards off the top of your deck. Works identically to overdraw.\r\nExamples: Fel Reaver\r\n\"value\": Number of cards to discard.\r\n\r\n\r\nDiscardSpell\r\nDiscards a number of target Player’s cards.\r\nExamples: Doomguard, Soulfire, Deathwing, Astral Communion\r\n“value”: Integer. The number of cards to discard.\r\n“targetPlayer”: Optional Player. Default is SELF. The player to discard cards.\r\n\r\nDiscoverCardSpell\r\nDiscovers a card from a list.\r\nExamples: Sir Finley Mrrgglton, Arch-Thief Rafaam\r\n“cards”: Card ID Array. The list of cards to choose from.\r\n“spell”: Spell. The Spell to use after Discovering the Card.\r\n“cannotReceiveOwned”: Optional Boolean. Set to true to disallow receiving a Card from the list that you own, including Hero Powers.\r\n“howMany”: Optional Integer. Default is 3. The number of Card options.\r\n\r\nDiscoverDrawSpell\r\nDiscovers a Card from your Deck to draw and discards the other options.\r\nExamples: Tracking\r\n“howMany”: Optional Integer. Default is 3. The number of Card options.\r\n\r\nDiscoverRandomCardSpell {}: Consider merging with DiscoverCardSpell\r\nDiscovers a random Card from a filter.\r\nExamples: Museum Curator, Raven Idol, Dark Peddler\r\n“spell”: Spell. The Spell to use after Discovering the Card.\r\n“cardFilter”: Card Filter. Filters the Discoverable Cards.\r\n“howMany”: Optional Integer. Default is 3. The number of Card options.\r\n\r\nDoubleAttackSpell\r\nDoubles the Attack of the target. Also doubles temporary Attack.\r\nExamples: Blessed Champion, Gahz’rilla\r\n\r\nDrawCardAndDoSomethingSpell {}: Consider merging with DrawCardSpell?\r\n[] \r\n\r\nDrawCardSpell\r\nTarget player draws a Card from their Deck.\r\nExamples: Coldlight Oracle, Gnomish Inventor, Cult Master\r\n“value”: Integer. The number of Cards to draw.\r\n“targetPlayer”: Optional Player. Default is SELF. The player to draw Cards.\r\n\r\nEitherOrSpell\r\nSimilar to ConditionalEffectSpell, has 1 effect if the spell meets a condition and another if it does not.\r\nExamples: Wild Growth(Excess Mana),Ice Lance, Upgrade!\r\n\"spell1\": Spell to cast if condition is met.\r\n\"spell2\": Spell to cast if condition is not met.\r\n\"condition\": Condition to check for.\r\n\r\n\r\nEnrageSpell\r\nGives a minion a buff when damage is first dealt to it.\r\nExamples: Amani Berserker, Raging Worgen, Angry Chicken\r\n\"attackBonus\": Attack to gain when enraged.\r\n\"attribute\": Attribute to gain when enraged.\r\n\r\nEquipRandomWeaponSpell\r\nEquips a random weapon for each target player.\r\nExamples: Blingtron\r\n\"targetPlayer\": Player to equip the random weapon for.\r\n\r\nEquipWeaponSpell\r\nTarget player equips a Weapon.\r\nExamples: Arathi Weaponsmith, Upgrade!, Dagger Mastery (Hero Power)\r\n“card”: Card ID. The card ID of the weapon to equip.\r\n\r\nFromDeckToHandSpell {}: Can this be merged with Draw Card?\r\n[]\r\n\r\nFumbleSpell\r\nTarget attacks the wrong enemy.\r\nExamples: Ogre Ninja, Mogor the Ogre, Ogre Brute, Ogre Warmaul\r\n\r\nGainManaSpell\r\nGives target Player additional Mana this turn.\r\nExamples: Innervate, The Coin\r\n“value”: Integer. The amount of Mana to gain.\r\n\r\nHealSpell\r\nHeals the target.\r\nExamples: Lesser Heal (Hero Power), Flash Heal, Voodoo Doctor, Circle of Healing\r\n“value”: Integer. The amount to heal for.\r\n\r\nJoustSpell {}: Rewrite JoustSpell\r\nCreates a Joust Event.\r\nExamples: Tuskarr Jouster, Healing Wave\r\n“spell1”: Spell. Spell to cast if the Joust succeeds.\r\n“spell2”: Optional Spell. Spell to cast if the Joust fails.\r\n\r\nMetaSpell\r\nA Spell that casts multiple Spells in succession. These do not act as separate Spells.\r\nExamples: Frostbolt, Earth Shock, Lord Jaraxxus\r\n“spells”: Spell Array. Array of multiple spells you wish to cast.\r\n\r\nMindControlSpell\r\n[]\r\n\r\nMisdirectSpell\r\nTarget attacks a random character from a list instead of its intended target.\r\nExamples: Misdirection\r\n\"target\": The target to misdirect.\r\n\r\nMissilesSpell {}: Consider Rewriting\r\nDeals 1 damage missiles to randomly selected targets.\r\nExamples: Mad Bomber, Arcane Missiles, Flamewaker\r\n“howMany”: The number of missiles to fire.\r\n\r\nModifyAttributeSpell\r\nAdd or remove attributes from a target.\r\nExamples: Bolvar Fordragon, Ancient Mage, Shadowfiend.\r\n\"target\": The target of the spell.\r\n\"attribute\": The attribute to change.\r\n\"value\": The amount to modify the attribute by. Only used for health and attack.\r\n\r\nModifyDamageSpell\r\nPerforms an operation on damage being processed by the game.\r\nExamples: Ice Block, Animated Armor\r\n\"operation\": The operation to perform on target damage. Can be ADD, SUBTRACT, MODULO, MULTIPLY, DIVIDE, NEGATE, or SET.\r\n\"value\": The value to operate by.\r\n\r\nModifyDurabilitySpell {}: Combine with Attribute Spell? (Only applies to Weapons, so might as well…)\r\n[]\r\n\r\nModifyMaxManaSpell {}: Is this possible to combine with GainManaSpell?\r\nTarget hero gains or loses mana crystals.\r\nExamples: Wild Growth, Darnassus Aspirant, Felguard\r\n\"value\": Number of crystals to gain. Negative values destroy crystals instead.\r\n\"targetPlayer\": Player whose mana will be modified. Default is \"SELF\".\r\n\r\nMultiTargetSpell\r\nA Spell that targets multiple random Entities with the given Spell.\r\n\"value\": Maximum number of targets to acquire. If there are less, will target all.\r\n\"spell\": The Spell to cast on the targeted Entities.\r\n\r\nNullSpell\r\nA Spell that does nothing. Used on cards that passively activate.\r\nExamples: Burrowing Mine, Ambushed!, Cursed!\r\n\r\nPutCopyInHandSpell\r\nCopies a minion and puts it into your hand. Works the same as CopyCardSpell, except is used for minions on board, not cards.\r\nExamples: Echo of Medivh, Duplicate\r\n\"target\": Minion(s) to put into your hand.\r\n\"howMany\": Number of copies of each minion to put into your hand.\r\n\r\nPutMinionOnBoardSpell\r\nPuts minions fom your deck directly into the battlefield.\r\nExamples: Varian Wrynn\r\n\r\nPutRandomMinionOnBoardSpell\r\nWorks identical to PutMinionOnBoardSpell, except a random minion instead of a specific one.\r\nExamples: Voidcaller, Deathlord, Desert Camel\r\n\"targetPlayer\": Player to put the minion into play for.\r\n\"cardLocation\": Where to find the card to play.\r\n\r\nPutRandomSecretIntoPlaySpell\r\nPuts the specified number of secrets directly from your deck into the battlefield.\r\nExamples: Mysterious Challenger, Mad Scientist\r\n\"howMany\": Number of secrets to put into play.\r\n\r\nRandomlyCastSpell\r\nCasts a random spell from a set of spells on a specific target.\r\nExamples: Enhance-O-Mechano\r\n\"target\": Target to cast spell on.\r\n\"spells\": Array of spells to choose from.\r\n\r\nRecastSpell\r\nReplays a specified Card.\r\nExamples: Djinni of Zephyrs\r\n“card”: Card ID. The card to be cast.\r\n\r\nReceiveCardSpell {}: Combine with ShuffleToDeck? (DeckLocation)\r\nThe target Player receives a specified Card.\r\nExamples: Archmage Antonidas, Rhonin, Curse of Rafaam\r\n“cards”: Card ID Array. The array of Card IDs to receive.\r\n\r\nReceiveRandomCardSpell {}: Combine with Receive Card? (randomTarget)\r\nExamples: Mechanical Yeti, Tinkertown Technician, Ysera\r\n[]\r\n\r\nReceiveRandomCollectibleCardSpell {}: Combine with Receive Card? (cardFilter)\r\nPut a random card into your hand from a set.\r\nExamples: Grand Crusader, Neptulon, Burgle\r\n\"cardFilter\": Filter the set of possible cards.\r\n\r\nRemoveAttributeSpell\r\nRemove an attribute from a character.\r\nExamples: Flare, Blood Knight\r\n\"target\": Target to remove attribute from.\r\n\"attribute\": Attribute to remove.\r\n\r\nRemoveCardSpell\r\nUsed for token cards that have actions when drawn, such as Burrowing Mine. Destroys the card.\r\nExamples: Ambush!, Curse of Rafaam, Iron Juggernaut\r\n\"target\": Card to destroy.\r\n\r\nReplaceCardLocationSpell\r\nReplaces cards in a location with other cards from a set.\r\nExamples: Golden Monkey\r\n\"cardFilter\": Filters the set of possible cards to replace with.\r\n\r\nResurrectFromBothSpell {}: Mindgames-esque Combination (DeckLocation Array?)\r\nSummons a set number of minions that have died this game.\r\nExamples: Anyfin Can Happen\r\n\"value\": Number of minions to resummon.\r\n\"cardFilter\": Filters the list of possible cards that can be resummoned.\r\n\r\nResurrectSpell\r\nSummons a random friendly minion that died this game.\r\nExamples: Resurrect\r\n\r\nReturnMinionToHandSpell {}: Combine with ShuffleMinionToDeck (DeckLocation)\r\nReturns a minion to its owners hand.\r\nExamples: Dream(Ysera), Sap, Anub'Arak\r\n\"target\": The target of the spell.\r\n\r\nRevertableSpell\r\n[]\r\n\r\nReviveMinionSpell\r\nResummons a minion immediately upon death.\r\nExamples: Redemption, Ancestral Spirit, Reincarnate\r\n\"hpBonus\": The health of the target when it revives.\r\n\r\n\r\nSetAttackSpell\r\nSets the attack of a minion to a specified value.\r\nExamples: Humility, Aldor Peacekeeper, Inner Fire\r\n\"value\": New attack value of the target.\r\n\r\n\r\nSetHeroHpSpell\r\nSets the health of a target hero to a specified amount.\r\nExamples: Alexstraza\r\n\"value\": The amount of health to set\r\n\r\nSetHpSpell {}: Combine with SetHeroHpSpell?\r\nSet the Hp of an Entity.\r\nExamples: Repentance, Equality, Keeper of Uldaman\r\n\r\nShuffleMinionToDeckSpell\r\nShuffles a minion into a players deck.\r\nExamples: Malorne, Entomb, Recycle\r\n\"target\": Minion to shuffle.\r\n\r\nShuffleToDeckSpell {}: Rename to ShuffleCopyToDeck\r\n[]\r\n\r\nSilenceSpell\r\nSilences the target.\r\nExamples: Silence, Ironbeak Owl, Spellbreaker\r\n\r\nStealRandomSecretSpell\r\nTakes a random secret from your opponent and gives it to you.\r\nExamples: Kezan Mystic\r\n\r\nSummonCopySpell\r\nSummons a copy of the target.\r\nExamples: Echoing Ooze.\r\n\r\nSummonNewAttackTargetSpell {}: Separate into two different spells?\r\nSummons a new minion and redirects the current action to the new minion.\r\nExamples: Spellbender, Noble Sacrifice\r\n“card”: Card ID. The Card ID of the minion to summon.\r\n\r\nSummonRandomMinionFilteredSpell\r\nSummons a random minion from a filtered set.\r\nExamples: Piloted Shredder, Sneed's Old Shredder, Murloc Knight, Bane of Doom\r\n\"cardFilter\": The set of minions to summon from.\r\n\r\nSummonRandomMinionFromSpell {}: Rename to SummonRandomMinionFromSourceSpell\r\n[]\r\n\r\nSummonRandomNotOnBoardSpell\r\nSummons a random minion from a set that cannot be a friendly minion on the board.\r\nExamples: Totemic Call(hero power)\r\n\"cards\": Array of cards to summon from.\r\n\r\nSummonRandomSpell\r\nSummons a random minion from an array.\r\nExamples: Animal Companion, Gelbin Mekkatorque, Elite Tauren Chieftain\r\n\"cards\": Array of cards to summon from.\r\n\r\nSummonSpell\r\nSummon a minion.\r\n[]\r\n\r\nSwapAttackAndHpSpell\r\nSwaps the Attack and HP of the target.\r\nExamples: Crazed Alchemist, Confuse\r\n\r\nSwapAttackSpell\r\nSwaps the calling Actor's attack with that of another Actor(only minions?)\r\nExamples: Darkspeaker\r\n\r\nSwapHpSpell\r\nSwaps the calling Actor's health with that of another Actor(only minions?)\r\nExamples: Darkspeaker\r\n\r\nTemporaryAttackSpell {}: Combine with Modify Attribute?\r\nModifies the Temporary Attack of an Actor. This Attack is removed at the end of the turn.\r\nExamples: Savage Roar, Abusive Sergeant, Shrinkmeister\r\n“value”: The amount of Attack to add to the target.\r\n\r\nTransformCardSpell\r\nTransforms a Card into another Card.\r\nExamples: Gnomish Experimenter\r\n“card”: Card ID. The \r\n[]\r\n\r\nTransformMinionSpell\r\nTransforms a minion into another minion.\r\nExamples: Druid of the Claw, Faceless Manipulator, Recombobulator, Tinkmaster Overspark\r\n“card”: Optional Card ID. The Card ID to transform into.\r\n[]\r\n\r\nTransformToMinionWithManaCostSpell {}: Combine with Transform Minion (Filter)\r\n[]\r\n\r\nTransformToRandomMinionSpell\r\nTransforms a minion into another random minion.\r\nExamples: Recombobulator, Evolve, Master of Evolution\r\n\r\nTriggerDeathrattleSpell\r\nTriggers all Deathrattles for the target Player.\r\nExamples: Feign Death\r\n"
  },
  {
    "path": "documentation/triggers.txt",
    "content": "Triggers\r\n\r\nAfterSpellCastedTrigger\r\nTriggers immediately after a spell is cast.\r\n\r\nArmorGainedTrigger\r\nTriggers whenever target player gains Armor.\r\n\r\nBoardChangedTrigger\r\nTriggers whenever there is a change in the minions/weapons in play. Used for things like checking if Southsea Deckhand should have charge\r\n\r\nCardDrawnTrigger\r\nTriggers whenever target player draws a card.\r\n\r\nCardPlayedTrigger\r\nTriggers whenever target player plays a card.\r\n\r\nCardReceivedTrigger\r\nTriggers whenever target player receives a copy of target card.\r\n\r\nDamageCausedTrigger\r\nTriggers whenever target causes damage.\r\n\r\nDamageReceivedTrigger\r\nTriggers whenever target receives damage.\r\n\r\nDiscardTrigger\r\nTriggers whenever target player discards a card.\r\n\r\nEnrageChangedTrigger\r\nTriggers whenever the internal ENRAGED attribute of a target is changed.\r\n\r\nFatalDamageTrigger\r\nTriggers whenever target takes fatal damage.\r\n\r\nGameStartTrigger\r\nTriggers at the beginning of the game.\r\n\r\nHealingTrigger\r\nTriggers whenever target is healed.\r\n\r\nInspireTrigger\r\nTriggers your hero power is used.\r\n\r\nMinionDeathTrigger\r\nTriggers whenever a minion dies.\r\n\r\nMinionPlayedTrigger\r\nTriggers whenever a minion is played.\r\n\r\nMinionSummonedTrigger\r\nTriggers whenever a minion is summoned.\r\n\r\nOverloadTrigger\r\nTriggers whenever target player overloads.\r\n\r\nPhysicalAttackTrigger\r\nTriggers whenever target is attacked by a minion or hero.\r\n\r\nPreDamageTrigger\r\nTriggers before target is damaged.\r\n\r\nSecretPlayedTrigger\r\nTriggers whenever a Secret for target player is played.\r\n\r\nSecretRevealedTrigger\r\nTriggers whenever a Secret for target player is revealed.\r\n\r\nSilenceTrigger\r\nTriggers whenever target is silenced.\r\n\r\nSpellCastedTrigger\r\nTriggers whenever target player casts a Spell.\r\n\r\nTargetAcquisitionTrigger\r\nTriggers whenever target acquires a target. This can be a spell as well as a minion or hero.\r\n\r\nTurnEndTrigger\r\nTriggers whenever target player's turn ends.\r\n\r\nTurnStartTrigger\r\nTriggers whenever target player's turn begins.\r\n\r\nWeaponDestroyedTrigger\r\nTriggers whenever target player's weapon is destroyed.\r\n\r\nWeaponEquippedTrigger\r\nTriggers whenever target player equips a weapon.\r\n"
  },
  {
    "path": "documentation/valueproviders.txt",
    "content": "Value Providers\r\nValue Providers are values that are not static, modifying themselves based on the state of the game.\r\n\r\nAlgebraicValueProvider\r\nProvides a value following an algebraic expressions i.e. Molten Giant (25 SUBTRACT (MaxHP SUBTRACT HP))\r\n\r\nAttributeCounter\r\nProvides a value equal to the number of specified Entities with a specific Attribute.\r\n\r\nAttributeValueProvider\r\nProvides a value equal to the Attribute value of the target Entity.\r\n\r\nCardCounter\r\nProvides a value equal to the number of cards being held by the target Player.\r\n\r\nConditionalValueProvider\r\nProvides a value based on a conditional statement.\r\n\r\nDeadMinionsThisTurn\r\nProvides a value equal to the number of minions that were killed this turn.\r\n\r\nEntityCounter\r\nProvides a value equal to the number of entities in play that meet the CardFilter criteria.\r\n\r\nHighestAttributeValueProvider\r\nProvides a value based on the highest value of the attribute found among the target entities.\r\n\r\nMinionSummonValueProvider\r\nProvides a value based on the number of minions summoned that meet the CardFilter criteria.\r\n\r\nPlayerAttributeValueProvider\r\nProvides a value equal to the player attribute given.\r\n\r\nRandomValueProvider\r\nProvides a random value between the min and max given, inclusively."
  },
  {
    "path": "game/build.gradle",
    "content": "dependencies {\n    compile project(':shared')\n    compile group: 'com.google.code.gson', name: 'gson', version: '2.3.1'\n    compile group: 'commons-io', name: 'commons-io', version: '2.4'\n\tcompile 'org.jsoup:jsoup:1.10.2'\n\n    testCompile project(':cards')\n    testCompile group: 'org.testng', name: 'testng', version: '6.+'\n}\n\ntest {\n    // enable TestNG support (default is JUnit)\n    useTestNG()\n    testLogging {\n        events \"standardError\"\n    }\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/Attribute.java",
    "content": "package net.demilich.metastone.game;\n\npublic enum Attribute {\n\tDEBUG,\n\tBASE_MANA_COST,\n\tHP,\n\tATTACK,\n\tATTACK_BONUS,\n\tMAX_HP,\n\tARMOR,\n\tTEMPORARY_ATTACK_BONUS,\n\tHP_BONUS,\n\tAURA_ATTACK_BONUS,\n\tAURA_HP_BONUS,\n\tBASE_HP,\n\tBASE_ATTACK,\n\tCONDITIONAL_ATTACK_BONUS,\n\tRACE,\n\tDESTROYED,\n\tPENDING_DESTROY,\n\tFATIGUE,\n\tFROZEN,\n\tENRAGABLE,\n\tSILENCED,\n\tWINDFURY,\n\tMEGA_WINDFURY,\n\tUNLIMITED_ATTACKS,\n\tTAUNT,\n\tSPELL_DAMAGE,\n\tOPPONENT_SPELL_DAMAGE,\n\tCHARGE,\n\tNUMBER_OF_ATTACKS,\n\tEXTRA_ATTACKS,\n\tENRAGED,\n\tBATTLECRY,\n\tDOUBLE_BATTLECRIES,\n\tDEATHRATTLES,\n\tDOUBLE_DEATHRATTLES,\n\tIMMUNE,\n\tIMMUNE_WHILE_ATTACKING,\n\tDIVINE_SHIELD,\n\tSTEALTH,\n\tQUEST,\n\tSECRET,\n\tCOMBO,\n\tOVERLOAD,\n\tCHOOSE_ONE,\n\tBOTH_CHOOSE_ONE_OPTIONS,\n\tSUMMONING_SICKNESS,\n\tUNTARGETABLE_BY_SPELLS,\n\tAURA_UNTARGETABLE_BY_SPELLS,\n\tSPELL_DAMAGE_MULTIPLIER,\n\tSPELL_AMPLIFY_MULTIPLIER,\n\tHEAL_AMPLIFY_MULTIPLIER,\n\tMANA_COST_MODIFIER,\n\tATTACK_EQUALS_HP,\n\tCANNOT_ATTACK,\n\tCANNOT_ATTACK_HEROES,\n\tINVERT_HEALING,\n\tCANNOT_REDUCE_HP_BELOW_1,\n\tCOUNTERED,\n\tDIED_ON_TURN,\n\tHERO_POWER_CAN_TARGET_MINIONS,\n\tLAST_HIT,\n\tPASSIVE_TRIGGER,\n\tDECK_TRIGGER,\n\tHERO_POWER_USAGES,\n\tHERO_POWER_DAMAGE,\n\tSHADOWFORM,\n\tCTHUN_ATTACK_BUFF,\n\tCTHUN_HEALTH_BUFF,\n\tCTHUN_TAUNT,\n\tSPELLS_COST_HEALTH,\n\tMURLOCS_COST_HEALTH,\n\tIMMUNE_HERO,\n\tTAKE_DOUBLE_DAMAGE,\n\tCANNOT_ATTACK_HERO_ON_SUMMON,\n\tJADE_BUFF,\n\tALL_RANDOM_FINAL_DESTINATION,\n\tALL_RANDOM_YOGG_ONLY_FINAL_DESTINATION,\n\n\t// AI flags\n\tMARKED_FOR_DEATH, \n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/Environment.java",
    "content": "package net.demilich.metastone.game;\n\npublic enum Environment {\n\tSUMMON_REFERENCE_STACK,\n\tKILLED_MINION,\n\tTARGET_OVERRIDE,\n\tATTACKER_REFERENCE,\n\tEVENT_TARGET_REFERENCE_STACK,\n\tTARGET,\n\tSPELL_TARGET,\n\tTRANSFORM_REFERENCE,\n\tDAMAGE_STACK,\n\tDEBUG,\n\tPENDING_CARD,\n\tEVENT_CARD,\n\tCHOOSE_ONE_CARD,\n\tLAST_MANA_COST,\n\t;\n\t\n\tpublic boolean customClone() {\n\t\treturn (this == SUMMON_REFERENCE_STACK ||\n\t\t\t\tthis == EVENT_TARGET_REFERENCE_STACK ||\n\t\t\t\tthis == DAMAGE_STACK);\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/GameContext.java",
    "content": "package net.demilich.metastone.game;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Stack;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.actions.ActionType;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.cards.costmodifier.CardCostModifier;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Summon;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.game.logic.MatchResult;\nimport net.demilich.metastone.game.logic.TargetLogic;\nimport net.demilich.metastone.game.spells.trigger.IGameEventListener;\nimport net.demilich.metastone.game.spells.trigger.TriggerManager;\nimport net.demilich.metastone.game.targeting.CardReference;\nimport net.demilich.metastone.game.targeting.EntityReference;\nimport net.demilich.metastone.utils.IDisposable;\n\npublic class GameContext implements Cloneable, IDisposable {\n\tpublic static final int PLAYER_1 = 0;\n\tpublic static final int PLAYER_2 = 1;\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(GameContext.class);\n\n\tprivate final Player[] players = new Player[2];\n\tprivate final GameLogic logic;\n\tprivate final DeckFormat deckFormat;\n\tprivate final TargetLogic targetLogic = new TargetLogic();\n\tprivate TriggerManager triggerManager = new TriggerManager();\n\tprivate final HashMap<Environment, Object> environment = new HashMap<>();\n\tprivate final List<CardCostModifier> cardCostModifiers = new ArrayList<>();\n\n\tprotected int activePlayer = -1;\n\tprivate Player winner;\n\tprivate MatchResult result;\n\tprivate TurnState turnState = TurnState.TURN_ENDED;\n\n\tprivate int turn;\n\tprivate int actionsThisTurn;\n\n\tprivate boolean ignoreEvents;\n\n\tprivate CardCollection tempCards = new CardCollection();\n\n\tpublic GameContext(Player player1, Player player2, GameLogic logic, DeckFormat deckFormat) {\n\t\tthis.getPlayers()[PLAYER_1] = player1;\n\t\tplayer1.setId(PLAYER_1);\n\t\tthis.getPlayers()[PLAYER_2] = player2;\n\t\tplayer2.setId(PLAYER_2);\n\t\tthis.logic = logic;\n\t\tthis.deckFormat = deckFormat;\n\t\tthis.logic.setContext(this);\n\t\ttempCards.removeAll();\n\t}\n\n\tprotected boolean acceptAction(GameAction nextAction) {\n\t\treturn true;\n\t}\n\n\tpublic void addCardCostModifier(CardCostModifier cardCostModifier) {\n\t\tgetCardCostModifiers().add(cardCostModifier);\n\t}\n\n\tpublic void addTempCard(Card card) {\n\t\ttempCards.add(card);\n\t}\n\n\tpublic void addTrigger(IGameEventListener trigger) {\n\t\ttriggerManager.addTrigger(trigger);\n\t}\n\n\t@Override\n\tpublic GameContext clone() {\n\t\tGameLogic logicClone = getLogic().clone();\n\t\tPlayer player1Clone = getPlayer1().clone();\n\t\t// player1Clone.getDeck().shuffle();\n\t\tPlayer player2Clone = getPlayer2().clone();\n\t\t// player2Clone.getDeck().shuffle();\n\t\tGameContext clone = new GameContext(player1Clone, player2Clone, logicClone, deckFormat);\n\t\tclone.tempCards = tempCards.clone();\n\t\tclone.triggerManager = triggerManager.clone();\n\t\tclone.activePlayer = activePlayer;\n\t\tclone.turn = turn;\n\t\tclone.actionsThisTurn = actionsThisTurn;\n\t\tclone.result = result;\n\t\tclone.turnState = turnState;\n\t\tclone.winner = logicClone.getWinner(player1Clone, player2Clone);\n\t\tclone.cardCostModifiers.clear();\n\t\tfor (CardCostModifier cardCostModifier : cardCostModifiers) {\n\t\t\tclone.cardCostModifiers.add(cardCostModifier.clone());\n\t\t}\n\t\t\n\t\tStack<Integer> damageStack = new Stack<Integer>();\n\t\tdamageStack.addAll(getDamageStack());\n\t\tclone.getEnvironment().put(Environment.DAMAGE_STACK, damageStack);\n\t\tStack<EntityReference> summonReferenceStack = new Stack<EntityReference>();\n\t\tsummonReferenceStack.addAll(getSummonReferenceStack());\n\t\tclone.getEnvironment().put(Environment.SUMMON_REFERENCE_STACK, summonReferenceStack);\n\t\tStack<EntityReference> eventTargetReferenceStack = new Stack<EntityReference>();\n\t\teventTargetReferenceStack.addAll(getEventTargetStack());\n\t\tclone.getEnvironment().put(Environment.EVENT_TARGET_REFERENCE_STACK, eventTargetReferenceStack);\n\t\t\n\t\tfor (Environment key : getEnvironment().keySet()) {\n\t\t\tif (!key.customClone()) {\n\t\t\t\tclone.getEnvironment().put(key, getEnvironment().get(key));\n\t\t\t}\n\t\t}\n\t\tclone.getLogic().setLoggingEnabled(false);\n\t\treturn clone;\n\t}\n\n\t@Override\n\tpublic void dispose() {\n\t\tfor (int i = 0; i < players.length; i++) {\n\t\t\tplayers[i] = null;\n\t\t}\n\t\tgetCardCostModifiers().clear();\n\t\ttriggerManager.dispose();\n\t\tenvironment.clear();\n\t}\n\n\tprivate void endGame() {\n\t\twinner = logic.getWinner(getActivePlayer(), getOpponent(getActivePlayer()));\n\t\tfor (Player player : getPlayers()) {\n\t\t\tplayer.getBehaviour().onGameOver(this, player.getId(), winner != null ? winner.getId() : -1);\n\t\t}\n\n\t\tif (winner != null) {\n\t\t\tlogger.debug(\"Game finished after \" + turn + \" turns, the winner is: \" + winner.getName());\n\t\t\twinner.getStatistics().gameWon();\n\t\t\tPlayer looser = getOpponent(winner);\n\t\t\tlooser.getStatistics().gameLost();\n\t\t} else {\n\t\t\tlogger.debug(\"Game finished after \" + turn + \" turns, DRAW\");\n\t\t\tgetPlayer1().getStatistics().gameLost();\n\t\t\tgetPlayer2().getStatistics().gameLost();\n\t\t}\n\t}\n\n\tpublic void endTurn() {\n\t\tlogic.endTurn(activePlayer);\n\t\tactivePlayer = activePlayer == PLAYER_1 ? PLAYER_2 : PLAYER_1;\n\t\tonGameStateChanged();\n\t\tturnState = TurnState.TURN_ENDED;\n\t}\n\n\tprivate Card findCardinCollection(CardCollection cardCollection, int cardId) {\n\t\tfor (Card card : cardCollection) {\n\t\t\tif (card.getId() == cardId) {\n\t\t\t\treturn card;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic void fireGameEvent(GameEvent gameEvent) {\n\t\tif (ignoreEvents()) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\ttriggerManager.fireGameEvent(gameEvent);\t\n\t\t} catch(Exception e) {\n\t\t\tlogger.error(\"Error while processing gameEvent {}\", gameEvent);\n\t\t\tlogic.panicDump();\n\t\t\tthrow e;\n\t\t}\n\t\t\n\t}\n\n\tpublic boolean gameDecided() {\n\t\tresult = logic.getMatchResult(getActivePlayer(), getOpponent(getActivePlayer()));\n\t\twinner = logic.getWinner(getActivePlayer(), getOpponent(getActivePlayer()));\n\t\treturn result != MatchResult.RUNNING;\n\t}\n\n\tpublic Player getActivePlayer() {\n\t\treturn getPlayer(activePlayer);\n\t}\n\n\tpublic int getActivePlayerId() {\n\t\treturn activePlayer;\n\t}\n\n\tpublic List<Summon> getAdjacentSummons(Player player, EntityReference minionReference) {\n\t\tList<Summon> adjacentSummons = new ArrayList<>();\n\t\tSummon summon = (Summon) resolveSingleTarget(minionReference);\n\t\tList<Summon> summons = getPlayer(summon.getOwner()).getSummons();\n\t\tint index = summons.indexOf(summon);\n\t\tif (index == -1) {\n\t\t\treturn adjacentSummons;\n\t\t}\n\t\tint left = index - 1;\n\t\tint right = index + 1;\n\t\tif (left > -1 && left < summons.size()) {\n\t\t\tadjacentSummons.add(summons.get(left));\n\t\t}\n\t\tif (right > -1 && right < summons.size()) {\n\t\t\tadjacentSummons.add(summons.get(right));\n\t\t}\n\t\treturn adjacentSummons;\n\t}\n\n\tpublic GameAction getAutoHeroPowerAction() {\n\t\treturn logic.getAutoHeroPowerAction(activePlayer);\n\t}\n\n\tpublic int getBoardPosition(Summon summon) {\n\t\tfor (Player player : getPlayers()) {\n\t\t\tList<Summon> summons = player.getSummons();\n\t\t\tfor (int i = 0; i < summons.size(); i++) {\n\t\t\t\tif (summons.get(i) == summon) {\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t}\n\n\tpublic Card getCardById(String cardId) {\n\t\tCard card = CardCatalogue.getCardById(cardId);\n\t\tif (card == null) {\n\t\t\tfor (Card tempCard : tempCards) {\n\t\t\t\tif (tempCard.getCardId().equalsIgnoreCase(cardId)) {\n\t\t\t\t\treturn tempCard.clone();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn card;\n\t}\n\n\tpublic List<CardCostModifier> getCardCostModifiers() {\n\t\treturn cardCostModifiers;\n\t}\n\t\n\t@SuppressWarnings(\"unchecked\")\n\tpublic Stack<Integer> getDamageStack() {\n\t\tif (!environment.containsKey(Environment.DAMAGE_STACK)) {\n\t\t\tenvironment.put(Environment.DAMAGE_STACK, new Stack<Integer>());\n\t\t}\n\t\treturn (Stack<Integer>) environment.get(Environment.DAMAGE_STACK);\n\t}\n\n\tpublic DeckFormat getDeckFormat() {\n\t\treturn deckFormat;\n\t}\n\n\tpublic HashMap<Environment, Object> getEnvironment() {\n\t\treturn environment;\n\t}\n\t\n\tpublic Card getEventCard() {\n\t\treturn (Card) resolveSingleTarget((EntityReference) getEnvironment().get(Environment.EVENT_CARD));\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic Stack<EntityReference> getEventTargetStack() {\n\t\tif (!environment.containsKey(Environment.EVENT_TARGET_REFERENCE_STACK)) {\n\t\t\tenvironment.put(Environment.EVENT_TARGET_REFERENCE_STACK, new Stack<EntityReference>());\n\t\t}\n\t\treturn (Stack<EntityReference>) environment.get(Environment.EVENT_TARGET_REFERENCE_STACK);\n\t}\n\n\tpublic List<Summon> getLeftSummons(Player player, EntityReference minionReference) {\n\t\tList<Summon> leftSummons = new ArrayList<>();\n\t\tSummon summon = (Summon) resolveSingleTarget(minionReference);\n\t\tList<Summon> summons = getPlayer(summon.getOwner()).getSummons();\n\t\tint index = summons.indexOf(summon);\n\t\tif (index == -1) {\n\t\t\treturn leftSummons;\n\t\t}\n\t\tfor (int i = 0; i < index; i++) {\n\t\t\tleftSummons.add(summons.get(i));\n\t\t}\n\t\treturn leftSummons;\n\t}\n\n\tpublic GameLogic getLogic() {\n\t\treturn logic;\n\t}\n\n\tpublic int getMinionCount(Player player) {\n\t\treturn player.getMinions().size();\n\t}\n\n\tpublic int getSummonCount(Player player) {\n\t\treturn player.getSummons().size();\n\t}\n\n\tpublic Player getOpponent(Player player) {\n\t\treturn player.getId() == PLAYER_1 ? getPlayer2() : getPlayer1();\n\t}\n\n\tpublic List<Summon> getOppositeSummons(Player player, EntityReference minionReference) {\n\t\tList<Summon> oppositeSummons = new ArrayList<>();\n\t\tSummon summon = (Summon) resolveSingleTarget(minionReference);\n\t\tPlayer owner = getPlayer(summon.getOwner());\n\t\tPlayer opposingPlayer = getOpponent(owner);\n\t\tint index = owner.getSummons().indexOf(summon);\n\t\tif (opposingPlayer.getSummons().size() == 0 || owner.getSummons().size() == 0 || index == -1) {\n\t\t\treturn oppositeSummons;\n\t\t}\n\t\tList<Summon> opposingSummons = opposingPlayer.getSummons();\n\t\tint delta = opposingPlayer.getSummons().size() - owner.getSummons().size();\n\t\tif (delta % 2 == 0) {\n\t\t\tdelta /= 2;\n\t\t\tint epsilon = delta + index;\n\t\t\tif (epsilon > -1 && epsilon < opposingSummons.size()) {\n\t\t\t\toppositeSummons.add(opposingSummons.get(epsilon));\n\t\t\t}\n\t\t} else {\n\t\t\tdelta = (delta - 1) / 2;\n\t\t\tint epsilon = delta + index;\n\t\t\tif (epsilon > -1 && epsilon < opposingSummons.size()) {\n\t\t\t\toppositeSummons.add(opposingSummons.get(epsilon));\n\t\t\t}\n\t\t\tif (epsilon + 1 > -1 && epsilon + 1 < opposingSummons.size()) {\n\t\t\t\toppositeSummons.add(opposingSummons.get(epsilon + 1));\n\t\t\t}\n\t\t}\n\t\treturn oppositeSummons;\n\t}\n\t\n\tpublic Card getPendingCard() {\n\t\treturn (Card) resolveSingleTarget((EntityReference) getEnvironment().get(Environment.PENDING_CARD));\n\t}\n\n\tpublic Player getPlayer(int index) {\n\t\treturn players[index];\n\t}\n\n\tpublic Player getPlayer1() {\n\t\treturn getPlayers()[PLAYER_1];\n\t}\n\n\tpublic Player getPlayer2() {\n\t\treturn getPlayers()[PLAYER_2];\n\t}\n\n\tpublic Player[] getPlayers() {\n\t\treturn players;\n\t}\n\n\tpublic List<Summon> getRightSummons(Player player, EntityReference minionReference) {\n\t\tList<Summon> rightSummons = new ArrayList<>();\n\t\tSummon summon = (Summon) resolveSingleTarget(minionReference);\n\t\tList<Summon> summons = getPlayer(summon.getOwner()).getSummons();\n\t\tint index = summons.indexOf(summon);\n\t\tif (index == -1) {\n\t\t\treturn rightSummons;\n\t\t}\n\t\tfor (int i = index + 1; i < player.getSummons().size(); i++) {\n\t\t\trightSummons.add(summons.get(i));\n\t\t}\n\t\treturn rightSummons;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic Stack<EntityReference> getSummonReferenceStack() {\n\t\tif (!environment.containsKey(Environment.SUMMON_REFERENCE_STACK)) {\n\t\t\tenvironment.put(Environment.SUMMON_REFERENCE_STACK, new Stack<EntityReference>());\n\t\t}\n\t\treturn (Stack<EntityReference>) environment.get(Environment.SUMMON_REFERENCE_STACK);\n\t}\n\n\tpublic CardCollection getTempCards() {\n\t\treturn tempCards;\n\t}\n\n\tpublic int getTotalMinionCount() {\n\t\tint totalMinionCount = 0;\n\t\tfor (int i = 0; i < players.length; i++) {\n\t\t\ttotalMinionCount += getMinionCount(players[i]);\n\t\t}\n\t\treturn totalMinionCount;\n\t}\n\n\tpublic int getTotalSummonCount() {\n\t\tint totalSummonCount = 0;\n\t\tfor (int i = 0; i < players.length; i++) {\n\t\t\ttotalSummonCount += getSummonCount(players[i]);\n\t\t}\n\t\treturn totalSummonCount;\n\t}\n\n\tpublic List<IGameEventListener> getTriggersAssociatedWith(EntityReference entityReference) {\n\t\treturn triggerManager.getTriggersAssociatedWith(entityReference);\n\t}\n\n\tpublic int getTurn() {\n\t\treturn turn;\n\t}\n\n\tpublic TurnState getTurnState() {\n\t\treturn turnState;\n\t}\n\n\tpublic List<GameAction> getValidActions() {\n\t\tif (gameDecided()) {\n\t\t\treturn new ArrayList<>();\n\t\t}\n\t\treturn logic.getValidActions(activePlayer);\n\t}\n\n\tpublic int getWinningPlayerId() {\n\t\treturn winner == null ? -1 : winner.getId();\n\t}\n\n\tpublic boolean hasAutoHeroPower() {\n\t\tif (gameDecided()) {\n\t\t\treturn false;\n\t\t}\n\t\treturn logic.hasAutoHeroPower(activePlayer);\n\t}\n\n\tpublic boolean ignoreEvents() {\n\t\treturn ignoreEvents;\n\t}\n\n\tpublic void init() {\n\t\tint startingPlayerId = logic.determineBeginner(PLAYER_1, PLAYER_2);\n\t\tactivePlayer = getPlayer(startingPlayerId).getId();\n\t\tlogger.debug(getActivePlayer().getName() + \" begins\");\n\t\tlogic.init(activePlayer, true);\n\t\tlogic.init(getOpponent(getActivePlayer()).getId(), false);\n\t}\n\n\tprotected void onGameStateChanged() {\n\t}\n\n\tprivate void performAction(int playerId, GameAction gameAction) {\n\t\tlogic.performGameAction(playerId, gameAction);\n\t\tonGameStateChanged();\n\t}\n\n\tpublic void play() {\n\t\tlogger.debug(\"Game starts: \" + getPlayer1().getName() + \" VS. \" + getPlayer2().getName());\n\t\tinit();\n\t\twhile (!gameDecided()) {\n\t\t\tstartTurn(activePlayer);\n\t\t\twhile (playTurn()) {}\n\t\t\tif (getTurn() > GameLogic.TURN_LIMIT) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tendGame();\n\n\t}\n\n\tpublic void playFromState(){\n\t\t//Play the whole game starting from any turn\n\t\twhile (!gameDecided()) {\n\t\t\tstartTurn(getActivePlayer().getId());\n\t\t\twhile (playTurn()) {}\n\t\t\tif (getTurn() > GameLogic.TURN_LIMIT) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tendGame();\n\n\t}\n\n\tpublic boolean playTurn() {\n\t\tif (++actionsThisTurn > 99) {\n\t\t\tlogger.warn(\"Turn has been forcefully ended after {} actions\", actionsThisTurn);\n\t\t\tendTurn();\n\t\t\treturn false;\n\t\t}\n\t\tif (logic.hasAutoHeroPower(activePlayer)) {\n\t\t\tperformAction(activePlayer, getAutoHeroPowerAction());\n\t\t\treturn true;\n\t\t}\n\n\t\tList<GameAction> validActions = getValidActions();\n\t\tif (validActions.size() == 0) {\n\t\t\t//endTurn();\n\t\t\treturn false;\n\t\t}\n\n\t\tGameAction nextAction = getActivePlayer().getBehaviour().requestAction(this, getActivePlayer(), getValidActions());\n\t\twhile (!acceptAction(nextAction)) {\n\t\t\tnextAction = getActivePlayer().getBehaviour().requestAction(this, getActivePlayer(), getValidActions());\n\t\t}\n\t\tif (nextAction == null) {\n\t\t\tthrow new RuntimeException(\"Behaviour \" + getActivePlayer().getBehaviour().getName() + \" selected NULL action while \"\n\t\t\t\t\t+ getValidActions().size() + \" actions were available\");\n\t\t}\n\t\tperformAction(activePlayer, nextAction);\n\n\t\treturn nextAction.getActionType() != ActionType.END_TURN;\n\t}\n\n\tpublic void printCurrentTriggers() {\n\t\tlogger.info(\"Active spelltriggers:\");\n\t\ttriggerManager.printCurrentTriggers();\n\t}\n\t\n\tpublic void removeTrigger(IGameEventListener trigger) {\n\t\ttriggerManager.removeTrigger(trigger);\n\t}\n\n\tpublic void removeTriggersAssociatedWith(EntityReference entityReference, boolean removeAuras) {\n\t\ttriggerManager.removeTriggersAssociatedWith(entityReference, removeAuras);\n\t}\n\n\tpublic Card resolveCardReference(CardReference cardReference) {\n\t\tPlayer player = getPlayer(cardReference.getPlayerId());\n\t\tif (getPendingCard() != null && getPendingCard().getCardReference().equals(cardReference)) {\n\t\t\treturn getPendingCard();\n\t\t}\n\t\tswitch (cardReference.getLocation()) {\n\t\tcase DECK:\n\t\t\treturn findCardinCollection(player.getDeck(), cardReference.getCardId());\n\t\tcase HAND:\n\t\t\treturn findCardinCollection(player.getHand(), cardReference.getCardId());\n\t\tcase PENDING:\n\t\t\treturn getPendingCard();\n\t\tcase HERO_POWER:\n\t\t\treturn player.getHero().getHeroPower();\n\t\tdefault:\n\t\t\tbreak;\n\n\t\t}\n\t\tlogger.error(\"Could not resolve cardReference {}\", cardReference);\n\t\tnew RuntimeException().printStackTrace();\n\t\treturn null;\n\t}\n\n\tpublic Entity resolveSingleTarget(EntityReference targetKey) {\n\t\tif (targetKey == null) {\n\t\t\treturn null;\n\t\t}\n\t\treturn targetLogic.findEntity(this, targetKey);\n\t}\n\n\tpublic List<Entity> resolveTarget(Player player, Entity source, EntityReference targetKey) {\n\t\treturn targetLogic.resolveTargetKey(this, player, source, targetKey);\n\t}\n\t\n\tpublic void setEventCard(Card eventCard) {\n\t\tif (eventCard != null) {\n\t\t\tgetEnvironment().put(Environment.EVENT_CARD, eventCard.getReference());\n\t\t} else {\n\t\t\tgetEnvironment().put(Environment.EVENT_CARD, null);\n\t\t}\n\t}\n\n\tpublic void setIgnoreEvents(boolean ignoreEvents) {\n\t\tthis.ignoreEvents = ignoreEvents;\n\t}\n\t\n\tpublic void setPendingCard(Card pendingCard) {\n\t\tif (pendingCard != null) {\n\t\t\tgetEnvironment().put(Environment.PENDING_CARD, pendingCard.getReference());\n\t\t} else {\n\t\t\tgetEnvironment().put(Environment.PENDING_CARD, null);\n\t\t}\n\t}\n\n\tprotected void startTurn(int playerId) {\n\t\tturn++;\n\t\tlogic.startTurn(playerId);\n\t\tonGameStateChanged();\n\t\tactionsThisTurn = 0;\n\t\tturnState = TurnState.TURN_IN_PROGRESS;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\tStringBuilder builder = new StringBuilder(\"GameContext hashCode: \" + hashCode() + \"\\nPlayer: \");\n\t\tfor (Player player : players) {\n\t\t\tbuilder.append(player.getName());\n\t\t\tbuilder.append(\" Mana: \");\n\t\t\tbuilder.append(player.getMana());\n\t\t\tbuilder.append('/');\n\t\t\tbuilder.append(player.getMaxMana());\n\t\t\tbuilder.append(\" HP: \");\n\t\t\tbuilder.append(player.getHero().getHp() + \"(\" + player.getHero().getArmor() + \")\");\n\t\t\tbuilder.append('\\n');\n\t\t\tbuilder.append(\"Behaviour: \" + player.getBehaviour().getName() + \"\\n\");\n\t\t\tbuilder.append(\"Minions:\\n\");\n\t\t\tfor (Summon summon : player.getSummons()) {\n\t\t\t\tbuilder.append('\\t');\n\t\t\t\tbuilder.append(summon);\n\t\t\t\tbuilder.append('\\n');\n\t\t\t}\n\t\t\tbuilder.append(\"Cards (hand):\\n\");\n\t\t\tfor (Card card : player.getHand()) {\n\t\t\t\tbuilder.append('\\t');\n\t\t\t\tbuilder.append(card);\n\t\t\t\tbuilder.append('\\n');\n\t\t\t}\n\t\t\tbuilder.append(\"Secrets:\\n\");\n\t\t\tfor (String secretId : player.getSecrets()) {\n\t\t\t\tbuilder.append('\\t');\n\t\t\t\tbuilder.append(secretId);\n\t\t\t\tbuilder.append('\\n');\n\t\t\t}\n\t\t}\n\t\tbuilder.append(\"Turn: \" + getTurn() + \"\\n\");\n\t\tbuilder.append(\"Result: \" + result + \"\\n\");\n\t\tbuilder.append(\"Winner: \" + (winner == null ? \"tbd\" : winner.getName()));\n\n\t\treturn builder.toString();\n\t}\n\n\tpublic Entity tryFind(EntityReference targetKey) {\n\t\ttry {\n\t\t\treturn resolveSingleTarget(targetKey);\n\t\t} catch (Exception e) {\n\t\t}\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/Player.java",
    "content": "package net.demilich.metastone.game;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.HashSet;\r\nimport java.util.List;\r\nimport java.util.stream.Collectors;\r\n\r\nimport net.demilich.metastone.game.behaviour.IBehaviour;\r\nimport net.demilich.metastone.game.behaviour.human.HumanBehaviour;\r\nimport net.demilich.metastone.game.cards.CardCollection;\r\nimport net.demilich.metastone.game.decks.Deck;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.EntityType;\r\nimport net.demilich.metastone.game.entities.heroes.Hero;\r\nimport net.demilich.metastone.game.entities.minions.Minion;\r\nimport net.demilich.metastone.game.entities.minions.Summon;\r\nimport net.demilich.metastone.game.statistics.GameStatistics;\r\nimport net.demilich.metastone.game.gameconfig.PlayerConfig;\r\n\r\npublic class Player extends Entity {\r\n\r\n\tprivate Hero hero;\r\n\tprivate final String deckName;\r\n\r\n\tprivate final CardCollection deck;\r\n\tprivate final CardCollection hand = new CardCollection();\r\n\tprivate final List<Entity> setAsideZone = new ArrayList<>();\r\n\tprivate final List<Entity> graveyard = new ArrayList<>();\r\n\tprivate final List<Summon> summons = new ArrayList<>();\r\n\tprivate final HashSet<String> secrets = new HashSet<>();\r\n\tprivate final HashSet<String> quests = new HashSet<>();\r\n\r\n\tprivate final GameStatistics statistics = new GameStatistics();\r\n\r\n\tprivate int mana;\r\n\tprivate int maxMana;\r\n\tprivate int lockedMana;\r\n\r\n\tprivate boolean hideCards;\r\n\r\n\tprivate IBehaviour behaviour;\r\n\r\n\tprivate Player(Player otherPlayer) {\r\n\t\tthis.setName(otherPlayer.getName());\r\n\t\tthis.deckName = otherPlayer.getDeckName();\r\n\t\tthis.setHero(otherPlayer.getHero().clone());\r\n\t\tthis.deck = otherPlayer.getDeck().clone();\r\n\t\tthis.attributes.putAll(otherPlayer.getAttributes());\r\n\t\tthis.hand.addAll(otherPlayer.getHand().clone());\r\n\t\tthis.summons.addAll(otherPlayer.getSummons().stream().map(Summon::clone).collect(Collectors.toList()));\r\n\t\tthis.graveyard.addAll(otherPlayer.getGraveyard().stream().map(Entity::clone).collect(Collectors.toList()));\r\n\t\tthis.setAsideZone.addAll(otherPlayer.getSetAsideZone().stream().map(Entity::clone).collect(Collectors.toList()));\r\n\t\tthis.secrets.addAll(otherPlayer.secrets);\r\n\t\tthis.quests.addAll(otherPlayer.quests);\r\n\t\tthis.setId(otherPlayer.getId());\r\n\t\tthis.mana = otherPlayer.mana;\r\n\t\tthis.maxMana = otherPlayer.maxMana;\r\n\t\tthis.lockedMana = otherPlayer.lockedMana;\r\n\t\tthis.behaviour = otherPlayer.behaviour;\r\n\t\tthis.getStatistics().merge(otherPlayer.getStatistics());\r\n\t}\r\n\r\n\tpublic Player(PlayerConfig config) {\r\n\t\tconfig.build();\r\n\t\tDeck selectedDeck = config.getDeckForPlay();\r\n\t\tthis.deck = selectedDeck.getCardsCopy();\r\n\t\tthis.setHero(config.getHeroForPlay().createHero());\r\n\t\tthis.setName(config.getName() + \" - \" + hero.getName());\r\n\t\tthis.deckName = selectedDeck.getName();\r\n\t\tsetBehaviour(config.getBehaviour().clone());\r\n\t\tsetHideCards(config.hideCards());\r\n\t}\r\n\r\n\t@Override\r\n\tpublic Player clone() {\r\n\t\treturn new Player(this);\r\n\t}\r\n\r\n\tpublic IBehaviour getBehaviour() {\r\n\t\treturn behaviour;\r\n\t}\r\n\r\n\tpublic List<Actor> getCharacters() {\r\n\t\tList<Actor> characters = new ArrayList<Actor>();\r\n\t\tcharacters.add(getHero());\r\n\t\tcharacters.addAll(getMinions());\r\n\t\treturn characters;\r\n\t}\r\n\r\n\tpublic CardCollection getDeck() {\r\n\t\treturn deck;\r\n\t}\r\n\r\n\tpublic String getDeckName() {\r\n\t\treturn deckName;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic EntityType getEntityType() {\r\n\t\treturn EntityType.PLAYER;\r\n\t}\r\n\r\n\tpublic List<Entity> getGraveyard() {\r\n\t\treturn graveyard;\r\n\t}\r\n\r\n\tpublic CardCollection getHand() {\r\n\t\treturn hand;\r\n\t}\r\n\r\n\tpublic Hero getHero() {\r\n\t\treturn hero;\r\n\t}\r\n\r\n\tpublic int getLockedMana() {\r\n\t\treturn lockedMana;\r\n\t}\r\n\r\n\tpublic int getMana() {\r\n\t\treturn mana;\r\n\t}\r\n\r\n\tpublic int getMaxMana() {\r\n\t\treturn maxMana;\r\n\t}\r\n\r\n\tpublic List<Minion> getMinions() {\r\n\t\tList<Minion> minions = new ArrayList<Minion>();\r\n\t\tfor (Summon summon : getSummons()) {\r\n\t\t\tif (summon instanceof Minion) {\r\n\t\t\t\tminions.add((Minion) summon);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn minions;\r\n\t}\r\n\r\n\tpublic HashSet<String> getQuests() {\r\n\t\treturn quests;\r\n\t}\r\n\r\n\tpublic List<Summon> getSummons() {\r\n\t\treturn summons;\r\n\t}\r\n\r\n\tpublic HashSet<String> getSecrets() {\r\n\t\treturn secrets;\r\n\t}\r\n\t\r\n\tpublic List<Entity> getSetAsideZone() {\r\n\t\treturn setAsideZone;\r\n\t}\r\n\r\n\tpublic GameStatistics getStatistics() {\r\n\t\treturn statistics;\r\n\t}\r\n\r\n\tpublic boolean hideCards() {\r\n\t\treturn hideCards && !(behaviour instanceof HumanBehaviour);\r\n\t}\r\n\r\n\tpublic void setBehaviour(IBehaviour behaviour) {\r\n\t\tthis.behaviour = behaviour;\r\n\t}\r\n\r\n\tpublic void setHero(Hero hero) {\r\n\t\tthis.hero = hero;\r\n\t}\r\n\r\n\tpublic void setHideCards(boolean hideCards) {\r\n\t\tthis.hideCards = hideCards;\r\n\t}\r\n\r\n\tpublic void setLockedMana(int lockedMana) {\r\n\t\tthis.lockedMana = lockedMana;\r\n\t}\r\n\r\n\tpublic void setMana(int mana) {\r\n\t\tthis.mana = mana;\r\n\t}\r\n\r\n\tpublic void setMaxMana(int maxMana) {\r\n\t\tthis.maxMana = maxMana;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String toString() {\r\n\t\treturn \"[PLAYER \" + \"id: \" + getId() + \", name: \" + getName() + \", hero: \" + getHero() + \"]\";\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/PlayerAttribute.java",
    "content": "package net.demilich.metastone.game;\n\npublic enum PlayerAttribute {\n\tMANA,\n\tMAX_MANA,\n\tHAND_COUNT,\n\tHERO_POWER_USED,\n\tDECK_COUNT,\n\tLAST_MANA_COST,\n\tSECRET_COUNT,\n\tSPELLS_CAST,\n\t\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/TurnState.java",
    "content": "package net.demilich.metastone.game;\n\npublic enum TurnState {\n\tTURN_ENDED, TURN_IN_PROGRESS\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/ActionType.java",
    "content": "package net.demilich.metastone.game.actions;\n\npublic enum ActionType {\n\tSYSTEM,\n\tEND_TURN,\n\tPHYSICAL_ATTACK,\n\tSPELL,\n\tSUMMON,\n\tHERO_POWER,\n\tBATTLECRY,\n\tEQUIP_WEAPON,\n\tDISCOVER,\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/BattlecryAction.java",
    "content": "package net.demilich.metastone.game.actions;\n\nimport java.util.function.Predicate;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.condition.Condition;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.targeting.EntityReference;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class BattlecryAction extends GameAction {\n\n\tpublic static BattlecryAction createBattlecry(SpellDesc spell) {\n\t\treturn createBattlecry(spell, TargetSelection.NONE);\n\t}\n\n\tpublic static BattlecryAction createBattlecry(SpellDesc spell, TargetSelection targetSelection) {\n\t\tBattlecryAction battlecry = new BattlecryAction(spell);\n\t\tbattlecry.setTargetRequirement(targetSelection);\n\t\treturn battlecry;\n\t}\n\n\tprivate final SpellDesc spell;\n\tprivate Condition condition;\n\n\tprotected BattlecryAction(SpellDesc spell) {\n\t\tthis.spell = spell;\n\t\tsetActionType(ActionType.BATTLECRY);\n\t}\n\n\tpublic boolean canBeExecuted(GameContext context, Player player) {\n\t\tif (getCondition() == null) {\n\t\t\treturn true;\n\t\t}\n\t\treturn getCondition().isFulfilled(context, player, null, null);\n\t}\n\n\t@Override\n\tpublic final boolean canBeExecutedOn(GameContext context, Player player, Entity entity) {\n\t\tif (!super.canBeExecutedOn(context, player, entity)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (getSource().getId() == entity.getId()) {\n\t\t\treturn false;\n\t\t}\n\t\tif (getEntityFilter() == null) {\n\t\t\treturn true;\n\t\t}\n\t\treturn getEntityFilter().matches(context, player, entity);\n\t}\n\n\t@Override\n\tpublic BattlecryAction clone() {\n\t\tBattlecryAction clone = BattlecryAction.createBattlecry(getSpell(), getTargetRequirement());\n\t\tclone.setActionSuffix(getActionSuffix());\n\t\tclone.setSource(getSource());\n\t\treturn clone;\n\t}\n\n\t@Override\n\tpublic void execute(GameContext context, int playerId) {\n\t\tEntityReference target = getSpell().hasPredefinedTarget() ? getSpell().getTarget() : getTargetKey();\n\t\tcontext.getLogic().castSpell(playerId, getSpell(), getSource(), target, getTargetRequirement(), false);\n\t}\n\n\tprivate Condition getCondition() {\n\t\treturn condition;\n\t}\n\n\tpublic EntityFilter getEntityFilter() {\n\t\treturn spell.getEntityFilter();\n\t}\n\n\t@Override\n\tpublic String getPromptText() {\n\t\treturn \"[Battlecry]\";\n\t}\n\n\tpublic SpellDesc getSpell() {\n\t\treturn spell;\n\t}\n\n\t@Override\n\tpublic boolean isSameActionGroup(GameAction anotherAction) {\n\t\treturn anotherAction.getActionType() == getActionType();\n\t}\n\n\tpublic void setCondition(Condition condition) {\n\t\tthis.condition = condition;\n\t}\n\n\tpublic void setEntityFilter(Predicate<Entity> entityFilter) {\n\t\t// this.entityFilter = entityFilter;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn String.format(\"[%s '%s']\", getActionType(), getSpell().getSpellClass().getSimpleName());\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/DiscoverAction.java",
    "content": "package net.demilich.metastone.game.actions;\n\nimport java.util.function.Predicate;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.condition.Condition;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.targeting.EntityReference;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class DiscoverAction extends GameAction {\n\tprivate int groupIndex;\n\n\tpublic static DiscoverAction createDiscover(SpellDesc spell) {\n\t\tDiscoverAction discover = new DiscoverAction(spell);\n\t\tdiscover.setTargetRequirement(TargetSelection.NONE);\n\t\treturn discover;\n\t}\n\n\tprivate final SpellDesc spell;\n\tprivate Condition condition;\n\tprivate Card card;\n\tprivate String name = \"\";\n\tprivate String description = \"\";\n\n\tprotected DiscoverAction(SpellDesc spell) {\n\t\tthis.spell = spell;\n\t\tsetActionType(ActionType.DISCOVER);\n\t}\n\n\tpublic boolean canBeExecuted(GameContext context, Player player) {\n\t\tif (getCondition() == null) {\n\t\t\treturn true;\n\t\t}\n\t\treturn getCondition().isFulfilled(context, player, null, null);\n\t}\n\n\t@Override\n\tpublic final boolean canBeExecutedOn(GameContext context, Player player, Entity entity) {\n\t\tif (!super.canBeExecutedOn(context, player, entity)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (getSource().getId() == entity.getId()) {\n\t\t\treturn false;\n\t\t}\n\t\tif (getEntityFilter() == null) {\n\t\t\treturn true;\n\t\t}\n\t\treturn getEntityFilter().matches(context, player, entity);\n\t}\n\n\t@Override\n\tpublic DiscoverAction clone() {\n\t\tDiscoverAction clone = DiscoverAction.createDiscover(getSpell().clone());\n\t\tclone.setActionSuffix(getActionSuffix());\n\t\tclone.setSource(getSource());\n\t\treturn clone;\n\t}\n\n\t@Override\n\tpublic void execute(GameContext context, int playerId) {\n\t\tEntityReference target = getSpell().hasPredefinedTarget() ? getSpell().getTarget() : getTargetKey();\n\t\tcontext.getLogic().castSpell(playerId, getSpell(), getSource(), target, false);\n\t}\n\n\tpublic Card getCard() {\n\t\treturn card;\n\t}\n\n\tprivate Condition getCondition() {\n\t\treturn condition;\n\t}\n\n\tpublic String getDescription() {\n\t\treturn description;\n\t}\n\n\tpublic EntityFilter getEntityFilter() {\n\t\treturn spell.getEntityFilter();\n\t}\n\n\tpublic int getGroupIndex() {\n\t\treturn groupIndex;\n\t}\n\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t@Override\n\tpublic String getPromptText() {\n\t\treturn \"[Discover]\";\n\t}\n\n\tpublic SpellDesc getSpell() {\n\t\treturn spell;\n\t}\n\n\t@Override\n\tpublic boolean isSameActionGroup(GameAction anotherAction) {\n\t\treturn false;\n\t}\n\n\tpublic void setCard(Card card) {\n\t\tthis.card = card;\n\t}\n\n\tpublic void setCondition(Condition condition) {\n\t\tthis.condition = condition;\n\t}\n\n\tpublic void setDescription(String description) {\n\t\tthis.description = description;\n\t}\n\n\tpublic void setEntityFilter(Predicate<Entity> entityFilter) {\n\t\t// this.entityFilter = entityFilter;\n\t}\n\n\tpublic void setName(String name) {\n\t\tthis.name = name;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn String.format(\"[%s '%s' %s]\", getActionType(), getSpell().getSpellClass().getSimpleName(), \"Test\");\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/EndTurnAction.java",
    "content": "package net.demilich.metastone.game.actions;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class EndTurnAction extends GameAction {\n\n\tpublic EndTurnAction() {\n\t\tsetActionType(ActionType.END_TURN);\n\t\tsetTargetRequirement(TargetSelection.NONE);\n\t}\n\n\t@Override\n\tpublic void execute(GameContext context, int playerId) {\n\t\tcontext.endTurn();\n\t}\n\n\t@Override\n\tpublic String getPromptText() {\n\t\treturn \"[End turn]\";\n\t}\n\n\t@Override\n\tpublic boolean isSameActionGroup(GameAction anotherAction) {\n\t\treturn anotherAction.getActionType() == getActionType();\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn String.format(\"[%s]\", getActionType());\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/GameAction.java",
    "content": "package net.demilich.metastone.game.actions;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.targeting.EntityReference;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic abstract class GameAction implements Cloneable {\n\n\tprivate TargetSelection targetRequirement = TargetSelection.NONE;\n\tprivate ActionType actionType = ActionType.SYSTEM;\n\tprivate EntityReference source;\n\tprivate EntityReference targetKey;\n\tprivate String actionSuffix;\n\n\tpublic boolean canBeExecutedOn(GameContext gameContext, Player player, Entity entity) {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameAction clone() {\n\t\ttry {\n\t\t\treturn (GameAction) super.clone();\n\t\t} catch (CloneNotSupportedException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic abstract void execute(GameContext context, int playerId);\n\n\tpublic String getActionSuffix() {\n\t\treturn actionSuffix;\n\t}\n\n\tpublic ActionType getActionType() {\n\t\treturn actionType;\n\t}\n\n\tpublic abstract String getPromptText();\n\n\tpublic EntityReference getSource() {\n\t\treturn source;\n\t}\n\n\tpublic EntityReference getTargetKey() {\n\t\treturn targetKey;\n\t}\n\n\tpublic TargetSelection getTargetRequirement() {\n\t\treturn targetRequirement;\n\t}\n\n\tpublic abstract boolean isSameActionGroup(GameAction anotherAction);\n\n\tpublic void setActionSuffix(String actionSuffix) {\n\t\tthis.actionSuffix = actionSuffix;\n\t}\n\n\tprotected void setActionType(ActionType actionType) {\n\t\tthis.actionType = actionType;\n\t}\n\n\tpublic void setSource(EntityReference source) {\n\t\tthis.source = source;\n\t}\n\n\tpublic void setTarget(Entity target) {\n\t\tthis.targetKey = EntityReference.pointTo(target);\n\t}\n\n\tpublic void setTargetKey(EntityReference targetKey) {\n\t\tthis.targetKey = targetKey;\n\t}\n\n\tpublic void setTargetRequirement(TargetSelection targetRequirement) {\n\t\tthis.targetRequirement = targetRequirement;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"Action \" + actionType.toString() + \" \" + actionSuffix + \" from \" + source.toString();\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/HeroPowerAction.java",
    "content": "package net.demilich.metastone.game.actions;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class HeroPowerAction extends PlaySpellCardAction {\n\n\tpublic HeroPowerAction(SpellDesc spell, Card card, TargetSelection targetSelection) {\n\t\tsuper(spell, card, targetSelection);\n\t\tsetActionType(ActionType.HERO_POWER);\n\t}\n\n\t@Override\n\tpublic void execute(GameContext context, int playerId) {\n\t\tplay(context, playerId);\n\t\tcontext.getLogic().useHeroPower(playerId);\n\t}\n\n\t@Override\n\tpublic String getPromptText() {\n\t\treturn \"[Use hero power]\";\n\t}\n\n\t@Override\n\tpublic void play(GameContext context, int playerId) {\n\t\tcontext.getLogic().castSpell(playerId, getSpell(), cardReference, getTargetKey(), getTargetRequirement(), false);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/IActionSelectionListener.java",
    "content": "package net.demilich.metastone.game.actions;\n\npublic interface IActionSelectionListener {\n\n\tvoid onActionSelected(GameAction action);\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/IBattlecryCondition.java",
    "content": "package net.demilich.metastone.game.actions;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\n\npublic interface IBattlecryCondition {\n\n\tboolean isFulfilled(GameContext context, Player player);\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/PhysicalAttackAction.java",
    "content": "package net.demilich.metastone.game.actions;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.targeting.EntityReference;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class PhysicalAttackAction extends GameAction {\n\n\tprivate final EntityReference attackerReference;\n\n\tpublic PhysicalAttackAction(EntityReference attackerReference) {\n\t\tsetTargetRequirement(TargetSelection.ENEMY_CHARACTERS);\n\t\tsetActionType(ActionType.PHYSICAL_ATTACK);\n\t\tthis.attackerReference = attackerReference;\n\t}\n\t\n\t@Override\n\tpublic boolean canBeExecutedOn(GameContext context, Player player, Entity entity) {\n\t\tif (!super.canBeExecutedOn(context, player, entity)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (entity.getEntityType() != EntityType.HERO) {\n\t\t\treturn true;\n\t\t}\n\t\tActor attacker = (Actor) context.resolveSingleTarget(attackerReference);\n\t\tif (attacker.hasAttribute(Attribute.CANNOT_ATTACK_HEROES) ||\n\t\t\t\t(attacker.hasAttribute(Attribute.CANNOT_ATTACK_HERO_ON_SUMMON)\n\t\t\t\t\t\t&& attacker.hasAttribute(Attribute.SUMMONING_SICKNESS))) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic void execute(GameContext context, int playerId) {\n\t\tActor defender = (Actor) context.resolveSingleTarget(getTargetKey());\n\t\tActor attacker = (Actor) context.resolveSingleTarget(attackerReference);\n\n\t\tcontext.getLogic().fight(context.getPlayer(playerId), attacker, defender);\n\t}\n\n\tpublic EntityReference getAttackerReference() {\n\t\treturn attackerReference;\n\t}\n\n\t@Override\n\tpublic String getPromptText() {\n\t\treturn \"[Physical Attack]\";\n\t}\n\n\t@Override\n\tpublic boolean isSameActionGroup(GameAction anotherAction) {\n\t\tif (anotherAction.getActionType() != getActionType()) {\n\t\t\treturn false;\n\t\t}\n\t\tPhysicalAttackAction physicalAttackAction = (PhysicalAttackAction) anotherAction;\n\n\t\treturn this.getAttackerReference().equals(physicalAttackAction.getAttackerReference());\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn String.format(\"%s Attacker: %s Defender: %s\", getActionType(), attackerReference, getTargetKey());\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/PlayCardAction.java",
    "content": "package net.demilich.metastone.game.actions;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.SpellCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.targeting.CardReference;\n\npublic abstract class PlayCardAction extends GameAction {\n\n\tpublic static Logger logger = LoggerFactory.getLogger(PlayCardAction.class);\n\n\tprivate final CardReference cardReference;\n\tprivate int groupIndex;\n\n\tpublic PlayCardAction(CardReference cardReference) {\n\t\tthis.cardReference = cardReference;\n\t}\n\n\t@Override\n\tpublic boolean canBeExecutedOn(GameContext context, Player player, Entity entity) {\n\t\tCard card = context.resolveCardReference(getCardReference());\n\t\tif (card instanceof SpellCard) {\n\t\t\tSpellCard spellCard = (SpellCard) card;\n\t\t\treturn spellCard.canBeCastOn(context, player, entity);\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic void execute(GameContext context, int playerId) {\n\t\tCard card = context.resolveCardReference(getCardReference());\n\t\tcontext.setPendingCard(card);\n\t\ttry {\n\t\t\tcontext.getLogic().playCard(playerId, getCardReference());\n\t\t\t// card was countered, do not actually resolve its effects\n\t\t\tif (!card.hasAttribute(Attribute.COUNTERED)) {\n\t\t\t\tplay(context, playerId);\n\t\t\t}\n\n\t\t} catch (Exception e) {\n\t\t\tlogger.error(\"ERROR while playing card \" + card + \" reference: \" + cardReference);\n\t\t\tcontext.getLogic().panicDump();\n\t\t\te.printStackTrace();\n\t\t\tSystem.exit(-1);\n\t\t\tthrow e;\n\t\t}\n\n\t\tcontext.getLogic().afterCardPlayed(playerId, getCardReference());\n\t\tcontext.setPendingCard(null);\n\t}\n\n\tpublic CardReference getCardReference() {\n\t\treturn cardReference;\n\t}\n\n\tpublic int getGroupIndex() {\n\t\treturn groupIndex;\n\t}\n\n\t@Override\n\tpublic String getPromptText() {\n\t\treturn \"[Play card]\";\n\t}\n\n\t@Override\n\tpublic boolean isSameActionGroup(GameAction anotherAction) {\n\t\tif (anotherAction.getActionType() != getActionType()) {\n\t\t\treturn false;\n\t\t}\n\t\tPlayCardAction playCardAction = (PlayCardAction) anotherAction;\n\t\treturn playCardAction.getGroupIndex() == getGroupIndex() && this.cardReference.equals(playCardAction.cardReference);\n\t}\n\n\tprotected abstract void play(GameContext context, int playerId);\n\n\tpublic void setGroupIndex(int groupIndex) {\n\t\tthis.groupIndex = groupIndex;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn String.format(\"%s Card: %s Target: %s\", getActionType(), cardReference, getTargetKey());\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/PlayChooseOneCardAction.java",
    "content": "package net.demilich.metastone.game.actions;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\nimport net.demilich.metastone.game.targeting.TargetSelection;\r\n\r\npublic class PlayChooseOneCardAction extends PlayCardAction {\r\n\r\n\tprivate SpellDesc spell;\r\n\tprotected final EntityReference cardReference;\r\n\tprotected final String chosenCard;\r\n\r\n\tpublic PlayChooseOneCardAction(SpellDesc spell, Card chooseOneCard, String chosenCard, TargetSelection targetSelection) {\r\n\t\tsuper(chooseOneCard.getCardReference());\r\n\t\tsetActionType(ActionType.SPELL);\r\n\t\tsetTargetRequirement(targetSelection);\r\n\t\tthis.setSpell(spell);\r\n\t\tthis.cardReference = chooseOneCard.getReference();\r\n\t\tthis.chosenCard = chosenCard;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void play(GameContext context, int playerId) {\r\n\t\tcontext.getLogic().castChooseOneSpell(playerId, spell, cardReference, getTargetKey(), chosenCard);\r\n\t}\r\n\r\n\tpublic SpellDesc getSpell() {\r\n\t\treturn spell;\r\n\t}\r\n\r\n\tpublic void setSpell(SpellDesc spell) {\r\n\t\tthis.spell = spell;\r\n\t}\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/PlayMinionCardAction.java",
    "content": "package net.demilich.metastone.game.actions;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.targeting.CardReference;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class PlayMinionCardAction extends PlayCardAction {\n\n\tprivate final BattlecryAction battlecry;\n\n\tpublic PlayMinionCardAction(CardReference cardReference) {\n\t\tthis(cardReference, null);\n\t}\n\n\tpublic PlayMinionCardAction(CardReference cardReference, BattlecryAction battlecry) {\n\t\tsuper(cardReference);\n\t\tthis.battlecry = battlecry;\n\t\tsetTargetRequirement(TargetSelection.FRIENDLY_MINIONS);\n\t\tsetActionType(ActionType.SUMMON);\n\t}\n\n\t@Override\n\tpublic String getPromptText() {\n\t\treturn \"[Summon minion]\";\n\t}\n\n\t@Override\n\tprotected void play(GameContext context, int playerId) {\n\t\tMinionCard minionCard = (MinionCard) context.getPendingCard();\n\t\tActor nextTo = (Actor) (getTargetKey() != null ? context.resolveSingleTarget(getTargetKey()) : null);\n\t\tMinion minion = minionCard.summon();\n\t\tif (battlecry != null) {\n\t\t\tminion.setBattlecry(battlecry);\n\t\t}\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tint index = player.getSummons().indexOf(nextTo);\n\t\tcontext.getLogic().summon(playerId, minion, minionCard, index, true);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/PlayPermanentCardAction.java",
    "content": "package net.demilich.metastone.game.actions;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.PermanentCard;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.minions.Permanent;\nimport net.demilich.metastone.game.targeting.CardReference;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class PlayPermanentCardAction extends PlayCardAction {\n\n\tprivate final BattlecryAction battlecry;\n\n\tpublic PlayPermanentCardAction(CardReference cardReference) {\n\t\tthis(cardReference, null);\n\t}\n\n\tpublic PlayPermanentCardAction(CardReference cardReference, BattlecryAction battlecry) {\n\t\tsuper(cardReference);\n\t\tthis.battlecry = battlecry;\n\t\tsetTargetRequirement(TargetSelection.FRIENDLY_MINIONS);\n\t\tsetActionType(ActionType.SUMMON);\n\t}\n\n\t@Override\n\tpublic String getPromptText() {\n\t\treturn \"[Summon permanent]\";\n\t}\n\n\t@Override\n\tprotected void play(GameContext context, int playerId) {\n\t\tPermanentCard permanentCard = (PermanentCard) context.getPendingCard();\n\t\tActor nextTo = (Actor) (getTargetKey() != null ? context.resolveSingleTarget(getTargetKey()) : null);\n\t\tPermanent permanent = permanentCard.summon();\n\t\tif (battlecry != null) {\n\t\t\tpermanent.setBattlecry(battlecry);\n\t\t}\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tint index = player.getSummons().indexOf(nextTo);\n\t\tcontext.getLogic().summon(playerId, permanent, permanentCard, index, true);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/PlaySpellCardAction.java",
    "content": "package net.demilich.metastone.game.actions;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\nimport net.demilich.metastone.game.targeting.TargetSelection;\r\n\r\npublic class PlaySpellCardAction extends PlayCardAction {\r\n\r\n\tprivate SpellDesc spell;\r\n\tprotected final EntityReference cardReference;\r\n\r\n\tpublic PlaySpellCardAction(SpellDesc spell, Card card, TargetSelection targetSelection) {\r\n\t\tsuper(card.getCardReference());\r\n\t\tsetActionType(ActionType.SPELL);\r\n\t\tsetTargetRequirement(targetSelection);\r\n\t\tthis.setSpell(spell);\r\n\t\tthis.cardReference = card.getReference();\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void play(GameContext context, int playerId) {\r\n\t\tcontext.getLogic().castSpell(playerId, spell, cardReference, getTargetKey(), getTargetRequirement(), false);\r\n\t}\r\n\r\n\tpublic SpellDesc getSpell() {\r\n\t\treturn spell;\r\n\t}\r\n\r\n\tpublic void setSpell(SpellDesc spell) {\r\n\t\tthis.spell = spell;\r\n\t}\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/actions/PlayWeaponCardAction.java",
    "content": "package net.demilich.metastone.game.actions;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.cards.WeaponCard;\r\nimport net.demilich.metastone.game.targeting.CardReference;\r\n\r\npublic class PlayWeaponCardAction extends PlayCardAction {\r\n\r\n\tpublic PlayWeaponCardAction(CardReference cardReference) {\r\n\t\tsuper(cardReference);\r\n\t\tsetActionType(ActionType.EQUIP_WEAPON);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void play(GameContext context, int playerId) {\r\n\t\tWeaponCard weaponCard = (WeaponCard) context.getPendingCard();\r\n\t\tcontext.getLogic().equipWeapon(playerId, weaponCard.getWeapon(), true);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/Behaviour.java",
    "content": "package net.demilich.metastone.game.behaviour;\n\nimport net.demilich.metastone.game.GameContext;\n\npublic abstract class Behaviour implements IBehaviour {\n\n\tpublic IBehaviour clone() {\n\t\ttry {\n\t\t\treturn (IBehaviour) super.clone();\n\t\t} catch (CloneNotSupportedException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void onGameOver(GameContext context, int playerId, int winningPlayerId) {\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/DoNothingBehaviour.java",
    "content": "package net.demilich.metastone.game.behaviour;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.cards.Card;\n\npublic class DoNothingBehaviour extends Behaviour {\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"Do Nothing\";\n\t}\n\n\t@Override\n\tpublic List<Card> mulligan(GameContext context, Player player, List<Card> cards) {\n\t\treturn new ArrayList<Card>();\n\t}\n\n\t@Override\n\tpublic GameAction requestAction(GameContext context, Player player, List<GameAction> validActions) {\n\t\treturn validActions.get(validActions.size() - 1);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/FlatMonteCarlo.java",
    "content": "package net.demilich.metastone.game.behaviour;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.cards.Card;\n\npublic class FlatMonteCarlo extends Behaviour {\n\n\tprivate final static Logger logger = LoggerFactory.getLogger(FlatMonteCarlo.class);\n\n\tprivate int iterations;\n\n\tpublic FlatMonteCarlo(int iterations) {\n\t\tthis.iterations = iterations;\n\t}\n\n\tprivate GameAction getBestAction(HashMap<GameAction, Double> actionScores) {\n\t\tGameAction bestAction = null;\n\t\tdouble bestScore = Integer.MIN_VALUE;\n\t\tfor (GameAction actionEntry : actionScores.keySet()) {\n\t\t\tdouble score = actionScores.get(actionEntry);\n\t\t\tif (score > bestScore) {\n\t\t\t\tbestAction = actionEntry;\n\t\t\t\tbestScore = score;\n\t\t\t}\n\t\t}\n\t\tlogger.debug(\"Best action determined by MonteCarlo: \" + bestAction.getActionType());\n\t\treturn bestAction;\n\t}\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"Flat Monte-Carlo \" + iterations;\n\t}\n\n\t@Override\n\tpublic List<Card> mulligan(GameContext context, Player player, List<Card> cards) {\n\t\tList<Card> discardedCards = new ArrayList<Card>();\n\t\tfor (Card card : cards) {\n\t\t\tif (card.getBaseManaCost() >= 4) {\n\t\t\t\tdiscardedCards.add(card);\n\t\t\t}\n\t\t}\n\t\treturn discardedCards;\n\t}\n\n\tprivate int playRandomUntilEnd(GameContext simulation, int playerId) {\n\t\tfor (Player player : simulation.getPlayers()) {\n\t\t\tplayer.setBehaviour(new PlayRandomBehaviour());\n\t\t}\n\t\tsimulation.playFromState();\n\t\treturn simulation.getWinningPlayerId() == playerId ? 1 : 0;\n\t}\n\n\t@Override\n\tpublic GameAction requestAction(GameContext context, Player player, List<GameAction> validActions) {\n\t\tif (validActions.size() == 1) {\n\t\t\treturn validActions.get(0);\n\t\t}\n\t\tHashMap<GameAction, Double> actionScores = new HashMap<>();\n\t\tfor (GameAction gameAction : validActions) {\n\t\t\tdouble score = simulate(context, player.getId(), gameAction);\n\t\t\tactionScores.put(gameAction, score);\n\t\t\tlogger.debug(\"Action {} gets score of {}\", gameAction.getActionType(), score);\n\n\t\t}\n\t\tGameAction bestAction = getBestAction(actionScores);\n\t\treturn bestAction;\n\t}\n\n\tprivate double simulate(GameContext context, int playerId, GameAction action) {\n\t\tGameContext simulation = context.clone();\n\t\tsimulation.getLogic().performGameAction(simulation.getActivePlayer().getId(), action);\n\t\tif (simulation.gameDecided()) {\n\t\t\treturn simulation.getWinningPlayerId() == playerId ? 1 : 0;\n\t\t}\n\t\tdouble score = 0;\n\t\tfor (int i = 0; i < iterations; i++) {\n\t\t\tscore += playRandomUntilEnd(simulation.clone(), playerId);\n\t\t}\n\t\treturn score;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/GreedyOptimizeMove.java",
    "content": "package net.demilich.metastone.game.behaviour;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.behaviour.heuristic.IGameStateHeuristic;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.logic.GameLogic;\n\npublic class GreedyOptimizeMove extends Behaviour {\n\n\tprivate final static Logger logger = LoggerFactory.getLogger(GreedyOptimizeMove.class);\n\n\tprivate final IGameStateHeuristic heuristic;\n\n\tpublic GreedyOptimizeMove(IGameStateHeuristic heuristic) {\n\t\tthis.heuristic = heuristic;\n\t}\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"Greedy Best Move\";\n\t}\n\n\t@Override\n\tpublic List<Card> mulligan(GameContext context, Player player, List<Card> cards) {\n\t\tList<Card> discardedCards = new ArrayList<Card>();\n\t\tfor (Card card : cards) {\n\t\t\tif (card.getBaseManaCost() >= 4) {\n\t\t\t\tdiscardedCards.add(card);\n\t\t\t}\n\t\t}\n\t\treturn discardedCards;\n\t}\n\n\t@Override\n\tpublic GameAction requestAction(GameContext context, Player player, List<GameAction> validActions) {\n\t\tif (validActions.size() == 1) {\n\t\t\treturn validActions.get(0);\n\t\t}\n\t\tGameAction bestAction = validActions.get(0);\n\t\tdouble bestScore = Double.NEGATIVE_INFINITY;\n\t\tlogger.debug(\"Current game state has a score of {}\", bestScore, hashCode());\n\t\tfor (GameAction gameAction : validActions) {\n\t\t\tGameContext simulationResult = simulateAction(context.clone(), player, gameAction);\n\t\t\tdouble gameStateScore = heuristic.getScore(simulationResult, player.getId());\n\t\t\tlogger.debug(\"Action {} gains score of {}\", gameAction, gameStateScore);\n\t\t\tif (gameStateScore > bestScore) {\n\t\t\t\tbestScore = gameStateScore;\n\t\t\t\tbestAction = gameAction;\n\t\t\t\tlogger.debug(\"BEST ACTION SO FAR id:{}\", bestAction.hashCode());\n\t\t\t}\n\t\t\tsimulationResult.dispose();\n\n\t\t}\n\t\tlogger.debug(\"Performing best action: {}\", bestAction);\n\n\t\treturn bestAction;\n\t}\n\n\tprivate GameContext simulateAction(GameContext simulation, Player player, GameAction action) {\n\t\tGameLogic.logger.debug(\"\");\n\t\tGameLogic.logger.debug(\"********SIMULATION starts********** \" + simulation.getLogic().hashCode());\n\t\tsimulation.getLogic().performGameAction(player.getId(), action);\n\t\tGameLogic.logger.debug(\"********SIMULATION ends**********\");\n\t\tGameLogic.logger.debug(\"\");\n\t\treturn simulation;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/GreedyOptimizeTurn.java",
    "content": "package net.demilich.metastone.game.behaviour;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.ActionType;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.behaviour.heuristic.IGameStateHeuristic;\nimport net.demilich.metastone.game.cards.Card;\n\npublic class GreedyOptimizeTurn extends Behaviour {\n\n\tprivate final Logger logger = LoggerFactory.getLogger(GreedyOptimizeTurn.class);\n\n\tprivate final IGameStateHeuristic heuristic;\n\n\tprivate int assignedGC;\n\tprivate final HashMap<ActionType, Integer> evaluatedActions = new HashMap<ActionType, Integer>();\n\tprivate final TranspositionTable table = new TranspositionTable();\n\n\tpublic GreedyOptimizeTurn(IGameStateHeuristic heuristic) {\n\t\tthis.heuristic = heuristic;\n\t}\n\n\tprivate double alphaBeta(GameContext context, int playerId, GameAction action, int depth) {\n\t\tGameContext simulation = context.clone();\n\t\tsimulation.getLogic().performGameAction(playerId, action);\n\t\tif (!evaluatedActions.containsKey(action.getActionType())) {\n\t\t\tevaluatedActions.put(action.getActionType(), 0);\n\t\t}\n\t\tevaluatedActions.put(action.getActionType(), evaluatedActions.get(action.getActionType()) + 1);\n\t\tif (depth == 0 || simulation.getActivePlayerId() != playerId || simulation.gameDecided()) {\n\t\t\treturn heuristic.getScore(simulation, playerId);\n\t\t}\n\n\t\tList<GameAction> validActions = simulation.getValidActions();\n\n\t\tdouble score = Float.NEGATIVE_INFINITY;\n\t\tif (table.known(simulation)) {\n\t\t\treturn table.getScore(simulation);\n\t\t\t// logger.info(\"GameState is known, has score of {}\", score);\n\t\t} else {\n\t\t\tfor (GameAction gameAction : validActions) {\n\t\t\t\tscore = Math.max(score, alphaBeta(simulation, playerId, gameAction, depth - 1));\n\t\t\t\tif (score >= 10000) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\ttable.save(simulation, score);\n\t\t}\n\n\t\treturn score;\n\t}\n\n\t@Override\n\tpublic IBehaviour clone() {\n\t\ttry {\n\t\t\treturn new GreedyOptimizeTurn(heuristic.getClass().newInstance());\n\t\t} catch (InstantiationException e) {\n\t\t\t// TODO Auto-generated catch block\n\t\t\te.printStackTrace();\n\t\t} catch (IllegalAccessException e) {\n\t\t\t// TODO Auto-generated catch block\n\t\t\te.printStackTrace();\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"Min-Max Turn\";\n\t}\n\n\t@Override\n\tpublic List<Card> mulligan(GameContext context, Player player, List<Card> cards) {\n\t\tList<Card> discardedCards = new ArrayList<Card>();\n\t\tfor (Card card : cards) {\n\t\t\tif (card.getBaseManaCost() >= 4) {\n\t\t\t\tdiscardedCards.add(card);\n\t\t\t}\n\t\t}\n\t\treturn discardedCards;\n\t}\n\n\t@Override\n\tpublic GameAction requestAction(GameContext context, Player player, List<GameAction> validActions) {\n\t\tif (validActions.size() == 1) {\n\t\t\theuristic.onActionSelected(context, player.getId());\n\t\t\treturn validActions.get(0);\n\t\t}\n\n\t\t// for now, do now evaluate battecry actions\n\t\tif (validActions.get(0).getActionType() == ActionType.BATTLECRY) {\n\t\t\treturn validActions.get(context.getLogic().random(validActions.size()));\n\t\t}\n\n\t\tif (assignedGC != 0 && assignedGC != context.hashCode()) {\n\t\t\tlogger.warn(\"AI behaviour was used in another context!\");\n\t\t}\n\n\t\tassignedGC = context.hashCode();\n\t\tevaluatedActions.clear();\n\t\ttable.clear();\n\n\t\tGameAction bestAction = validActions.get(0);\n\t\tdouble bestScore = Double.NEGATIVE_INFINITY;\n\n\t\tfor (GameAction gameAction : validActions) {\n\t\t\tlogger.debug(\"********************* SIMULATION STARTS *********************\");\n\t\t\tdouble score = alphaBeta(context, player.getId(), gameAction, 3);\n\t\t\tif (score > bestScore) {\n\t\t\t\tbestAction = gameAction;\n\t\t\t\tbestScore = score;\n\t\t\t}\n\t\t\tlogger.debug(\"********************* SIMULATION ENDS, Action {} achieves score {}\", gameAction, score);\n\t\t}\n\n\t\tint totalActionCount = 0;\n\t\tfor (ActionType actionType : evaluatedActions.keySet()) {\n\t\t\tint count = evaluatedActions.get(actionType);\n\t\t\tlogger.debug(\"{} actions of type {} have been evaluated this turn\", count, actionType);\n\t\t\ttotalActionCount += count;\n\t\t}\n\t\tlogger.debug(\"{} actions in total have been evaluated this turn\", totalActionCount);\n\t\tlogger.debug(\"Selecting best action {} with score {}\", bestAction, bestScore);\n\t\theuristic.onActionSelected(context, player.getId());\n\n\t\treturn bestAction;\n\t}\n\n\t/*private double simulateAction(GameContext context, int playerId, GameAction action) {\n\t\tGameContext simulation = context.clone();\n\t\tsimulation.getLogic().performGameAction(playerId, action);\n\t\tif (!evaluatedActions.containsKey(action.getActionType())) {\n\t\t\tevaluatedActions.put(action.getActionType(), 0);\n\t\t}\n\t\tevaluatedActions.put(action.getActionType(), evaluatedActions.get(action.getActionType()) + 1);\n\t\tif (simulation.getActivePlayerId() != playerId || simulation.gameDecided()) {\n\t\t\treturn heuristic.getScore(simulation, playerId);\n\t\t}\n\t\tList<GameAction> validActions = simulation.getValidActions();\n\t\tif (validActions.size() == 0) {\n\t\t\tthrow new RuntimeException(\"No more possible moves, last action was: \" + action);\n\t\t}\n\t\tdouble bestScore = Integer.MIN_VALUE;\n\t\tfor (GameAction gameAction : validActions) {\n\t\t\tbestScore = Math.max(bestScore, simulateAction(simulation, playerId, gameAction));\n\t\t}\n\t\treturn bestScore;\n\t}*/\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/IBehaviour.java",
    "content": "package net.demilich.metastone.game.behaviour;\n\nimport java.util.List;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.cards.Card;\n\npublic interface IBehaviour extends Cloneable {\n\n\tIBehaviour clone();\n\n\tString getName();\n\n\tList<Card> mulligan(GameContext context, Player player, List<Card> cards);\n\n\tvoid onGameOver(GameContext context, int playerId, int winningPlayerId);\n\n\tGameAction requestAction(GameContext context, Player player, List<GameAction> validActions);\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/NoAggressionBehaviour.java",
    "content": "package net.demilich.metastone.game.behaviour;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.ActionType;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.cards.Card;\n\npublic class NoAggressionBehaviour extends Behaviour {\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"No Aggression\";\n\t}\n\n\t@Override\n\tpublic List<Card> mulligan(GameContext context, Player player, List<Card> cards) {\n\t\treturn new ArrayList<Card>();\n\t}\n\n\t@Override\n\tpublic GameAction requestAction(GameContext context, Player player, List<GameAction> validActions) {\n\t\tif (validActions.size() == 1) {\n\t\t\treturn validActions.get(0);\n\t\t}\n\t\tfor (GameAction gameAction : validActions) {\n\t\t\tif (gameAction.getActionType() == ActionType.SUMMON) {\n\t\t\t\treturn gameAction;\n\t\t\t}\n\t\t}\n\t\treturn validActions.get(validActions.size() - 1);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/PlayRandomBehaviour.java",
    "content": "package net.demilich.metastone.game.behaviour;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Random;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.cards.Card;\n\npublic class PlayRandomBehaviour extends Behaviour {\n\n\tprivate Random random = new Random();\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"Play Random\";\n\t}\n\n\t@Override\n\tpublic List<Card> mulligan(GameContext context, Player player, List<Card> cards) {\n\t\treturn new ArrayList<>();\n\t}\n\n\t@Override\n\tpublic GameAction requestAction(GameContext context, Player player, List<GameAction> validActions) {\n\t\tif (validActions.size() == 1) {\n\t\t\treturn validActions.get(0);\n\t\t}\n\n\t\tint randomIndex = random.nextInt(validActions.size());\n\t\tGameAction randomAction = validActions.get(randomIndex);\n\t\treturn randomAction;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/TranspositionTable.java",
    "content": "package net.demilich.metastone.game.behaviour;\n\nimport java.util.HashMap;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class TranspositionTable {\n\n\tprivate static int hash(Entity entity) {\n\t\tint hash = entity.getName().hashCode();\n\t\tfor (Attribute tag : entity.getAttributes().keySet()) {\n\t\t\tObject value = entity.getAttributes().get(tag);\n\t\t\tif (!(value instanceof Integer)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\thash = mergeHashes(hash, entity.getAttribute(tag).hashCode());\n\t\t}\n\t\treturn hash;\n\t}\n\n\tprivate static int mergeHashes(int hash1, int hash2) {\n\t\tint hash = 0;\n\t\thash = hash * 33 ^ hash1;\n\t\thash = hash * 33 ^ hash2;\n\t\treturn hash;\n\t}\n\n\tprivate HashMap<Integer, Double> knownScores = new HashMap<Integer, Double>();\n\n\t// private HashMap<Integer, GameContext> debug = new HashMap<Integer,\n\t// GameContext>();\n\tprivate int cachedKey;\n\n\tprivate GameContext cachedState;\n\n\tpublic void clear() {\n\t\tknownScores.clear();\n\t}\n\n\tpublic double getScore(GameContext context) {\n\t\treturn knownScores.get(hash(context));\n\t}\n\n\tprivate int hash(GameContext context) {\n\t\tif (context == cachedState) {\n\t\t\treturn cachedKey;\n\t\t}\n\n\t\tint hash = context.getActivePlayerId();\n\n\t\tfor (Player player : context.getPlayers()) {\n\t\t\thash = mergeHashes(hash, player.getMana());\n\t\t\thash = mergeHashes(hash, player.getMaxMana());\n\t\t\thash = mergeHashes(hash, hash(player.getHero()));\n\t\t\tfor (Card handCard : player.getHand()) {\n\t\t\t\thash = mergeHashes(hash, hash(handCard));\n\t\t\t}\n\n\t\t\tfor (Card deckCard : player.getDeck()) {\n\t\t\t\thash = mergeHashes(hash, hash(deckCard));\n\t\t\t}\n\t\t\tfor (Entity minion : player.getMinions()) {\n\t\t\t\thash = mergeHashes(hash, hash(minion));\n\t\t\t\thash = mergeHashes(hash, context.getTriggersAssociatedWith(minion.getReference()).size());\n\t\t\t}\n\n\t\t}\n\t\tcachedState = context;\n\t\tcachedKey = hash;\n\t\treturn hash;\n\t}\n\n\tpublic boolean known(GameContext context) {\n\t\tint key = hash(context);\n\t\t// if (knownScores.containsKey(key)) {\n\t\t// System.out.println(\"Identical game states found!\");\n\t\t// System.out.println(\"New: \" + context);\n\t\t// System.out.println(\"Existing: \" + debug.get(key));\n\t\t// }\n\t\treturn knownScores.containsKey(key);\n\t}\n\n\tpublic void save(GameContext context, double score) {\n\t\tint key = hash(context);\n\t\tknownScores.put(key, score);\n\t\t// debug.put(key, context);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/heuristic/IGameStateHeuristic.java",
    "content": "package net.demilich.metastone.game.behaviour.heuristic;\n\nimport net.demilich.metastone.game.GameContext;\n\npublic interface IGameStateHeuristic {\n\n\tdouble getScore(GameContext context, int playerId);\n\n\tvoid onActionSelected(GameContext context, int playerId);\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/heuristic/WeightedFeature.java",
    "content": "package net.demilich.metastone.game.behaviour.heuristic;\n\npublic enum WeightedFeature {\n\tHP_DIFFERENCE, CARD_DIFFERENCE, OWN_MINION_COUNT, OPPONENT_MINION_COUNT, MINION_ATTACK, MINION_HP, MINION_TAUNT, MINION_WINDFURY, MINION_DIVINE_SHIELD, MINION_SPELL_POWER, MINION_UNTARGETABLE_BY_SPELLS, MINION_STEALTHED,\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/heuristic/WeightedHeuristic.java",
    "content": "package net.demilich.metastone.game.behaviour.heuristic;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.minions.Minion;\n\npublic class WeightedHeuristic implements IGameStateHeuristic {\n\n\tprivate float calculateMinionScore(Minion minion) {\n\t\tfloat minionScore = minion.getAttack() + minion.getHp();\n\t\tfloat baseScore = minionScore;\n\t\tif (minion.hasAttribute(Attribute.FROZEN)) {\n\t\t\treturn minion.getHp();\n\t\t}\n\t\tif (minion.hasAttribute(Attribute.TAUNT)) {\n\t\t\tminionScore += 2;\n\t\t}\n\t\tif (minion.hasAttribute(Attribute.WINDFURY)) {\n\t\t\tminionScore += minion.getAttack() * 0.5f;\n\t\t}\n\t\tif (minion.hasAttribute(Attribute.DIVINE_SHIELD)) {\n\t\t\tminionScore += 1.5f * baseScore;\n\t\t}\n\t\tif (minion.hasAttribute(Attribute.SPELL_DAMAGE)) {\n\t\t\tminionScore += minion.getAttributeValue(Attribute.SPELL_DAMAGE);\n\t\t}\n\t\tif (minion.hasAttribute(Attribute.ENRAGED)) {\n\t\t\tminionScore += 1;\n\t\t}\n\t\tif (minion.hasAttribute(Attribute.STEALTH)) {\n\t\t\tminionScore += 1;\n\t\t}\n\t\tif (minion.hasAttribute(Attribute.UNTARGETABLE_BY_SPELLS)) {\n\t\t\tminionScore += 1.5f * baseScore;\n\t\t}\n\n\t\treturn minionScore;\n\t}\n\n\t@Override\n\tpublic double getScore(GameContext context, int playerId) {\n\t\tfloat score = 0;\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tPlayer opponent = context.getOpponent(player);\n\t\tif (player.getHero().isDestroyed()) {\n\t\t\treturn Float.NEGATIVE_INFINITY;\n\t\t}\n\t\tif (opponent.getHero().isDestroyed()) {\n\t\t\treturn Float.POSITIVE_INFINITY;\n\t\t}\n\t\tint ownHp = player.getHero().getHp() + player.getHero().getArmor();\n\t\tint opponentHp = opponent.getHero().getHp() + opponent.getHero().getArmor();\n\t\tscore += ownHp - opponentHp;\n\n\t\tscore += player.getHand().getCount() * 3;\n\t\tscore -= opponent.getHand().getCount() * 3;\n\t\tscore += player.getMinions().size() * 2;\n\t\tscore -= opponent.getMinions().size() * 2;\n\t\tfor (Minion minion : player.getMinions()) {\n\t\t\tscore += calculateMinionScore(minion);\n\t\t}\n\t\tfor (Minion minion : opponent.getMinions()) {\n\t\t\tscore -= calculateMinionScore(minion);\n\t\t}\n\n\t\treturn score;\n\t}\n\n\t@Override\n\tpublic void onActionSelected(GameContext context, int playerId) {\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/human/ActionGroup.java",
    "content": "package net.demilich.metastone.game.behaviour.human;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.metastone.game.actions.GameAction;\n\npublic class ActionGroup {\n\n\tprivate final GameAction prototype;\n\tprivate final List<GameAction> actionsInGroup = new ArrayList<>();\n\n\tpublic ActionGroup(GameAction prototype) {\n\t\tthis.prototype = prototype;\n\t\tadd(prototype);\n\t}\n\n\tpublic void add(GameAction action) {\n\t\tactionsInGroup.add(action);\n\t}\n\n\tpublic List<GameAction> getActionsInGroup() {\n\t\treturn actionsInGroup;\n\t}\n\n\tpublic GameAction getPrototype() {\n\t\treturn prototype;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/human/HumanActionOptions.java",
    "content": "package net.demilich.metastone.game.behaviour.human;\n\nimport java.util.List;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\n\npublic class HumanActionOptions {\n\n\tprivate final HumanBehaviour behaviour;\n\tprivate final GameContext context;\n\tprivate final Player player;\n\tprivate final List<GameAction> validActions;\n\n\tpublic HumanActionOptions(HumanBehaviour behaviour, GameContext context, Player player, List<GameAction> validActions) {\n\t\tthis.behaviour = behaviour;\n\t\tthis.context = context;\n\t\tthis.player = player;\n\t\tthis.validActions = validActions;\n\t}\n\n\tpublic HumanBehaviour getBehaviour() {\n\t\treturn behaviour;\n\t}\n\n\tpublic GameContext getContext() {\n\t\treturn context;\n\t}\n\n\tpublic Player getPlayer() {\n\t\treturn player;\n\t}\n\n\tpublic List<GameAction> getValidActions() {\n\t\treturn validActions;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/human/HumanBehaviour.java",
    "content": "package net.demilich.metastone.game.behaviour.human;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.metastone.BuildConfig;\nimport net.demilich.metastone.GameNotification;\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.actions.IActionSelectionListener;\nimport net.demilich.metastone.game.behaviour.Behaviour;\nimport net.demilich.metastone.game.cards.Card;\n\npublic class HumanBehaviour extends Behaviour implements IActionSelectionListener {\n\n\tprivate GameAction selectedAction;\n\tprivate boolean waitingForInput;\n\tprivate List<Card> mulliganCards;\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"<Human controlled>\";\n\t}\n\n\t@Override\n\tpublic List<Card> mulligan(GameContext context, Player player, List<Card> cards) {\n\t\tif (context.ignoreEvents()) {\n\t\t\treturn new ArrayList<Card>();\n\t\t}\n\t\twaitingForInput = true;\n\t\tHumanMulliganOptions options = new HumanMulliganOptions(player, this, cards);\n\t\tNotificationProxy.sendNotification(GameNotification.HUMAN_PROMPT_FOR_MULLIGAN, options);\n\t\twhile (waitingForInput) {\n\t\t\ttry {\n\t\t\t\tThread.sleep(BuildConfig.DEFAULT_SLEEP_DELAY);\n\t\t\t} catch (InterruptedException e) {\n\t\t\t}\n\t\t}\n\t\treturn mulliganCards;\n\t}\n\n\t@Override\n\tpublic void onActionSelected(GameAction action) {\n\t\tthis.selectedAction = action;\n\t\twaitingForInput = false;\n\t}\n\n\t@Override\n\tpublic GameAction requestAction(GameContext context, Player player, List<GameAction> validActions) {\n\t\twaitingForInput = true;\n\t\tHumanActionOptions options = new HumanActionOptions(this, context, player, validActions);\n\t\tNotificationProxy.sendNotification(GameNotification.HUMAN_PROMPT_FOR_ACTION, options);\n\t\twhile (waitingForInput) {\n\t\t\ttry {\n\t\t\t\tThread.sleep(BuildConfig.DEFAULT_SLEEP_DELAY);\n\t\t\t\tif (context.ignoreEvents()) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t} catch (InterruptedException e) {\n\t\t\t}\n\t\t}\n\t\treturn selectedAction;\n\t}\n\n\tpublic void setMulliganCards(List<Card> mulliganCards) {\n\t\tthis.mulliganCards = mulliganCards;\n\t\twaitingForInput = false;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/human/HumanMulliganOptions.java",
    "content": "package net.demilich.metastone.game.behaviour.human;\n\nimport java.util.List;\n\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\n\npublic class HumanMulliganOptions {\n\n\tprivate final Player player;\n\tprivate final HumanBehaviour behaviour;\n\tprivate final List<Card> offeredCards;\n\n\tpublic HumanMulliganOptions(Player player, HumanBehaviour behaviour, List<Card> offeredCards) {\n\t\tthis.player = player;\n\t\tthis.behaviour = behaviour;\n\t\tthis.offeredCards = offeredCards;\n\t}\n\n\tpublic HumanBehaviour getBehaviour() {\n\t\treturn behaviour;\n\t}\n\n\tpublic List<Card> getOfferedCards() {\n\t\treturn offeredCards;\n\t}\n\n\tpublic Player getPlayer() {\n\t\treturn player;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/human/HumanTargetOptions.java",
    "content": "package net.demilich.metastone.game.behaviour.human;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.actions.IActionSelectionListener;\n\npublic class HumanTargetOptions {\n\n\tprivate final IActionSelectionListener actionSelectionListener;\n\tprivate final GameContext context;\n\tprivate final int playerId;\n\tprivate final ActionGroup actionGroup;\n\n\tpublic HumanTargetOptions(IActionSelectionListener actionSelectionListener, GameContext context, int playerId,\n\t\t\tActionGroup actionGroup) {\n\t\tthis.actionSelectionListener = actionSelectionListener;\n\t\tthis.context = context;\n\t\tthis.playerId = playerId;\n\t\tthis.actionGroup = actionGroup;\n\t}\n\n\tpublic ActionGroup getActionGroup() {\n\t\treturn actionGroup;\n\t}\n\n\tpublic IActionSelectionListener getActionSelectionListener() {\n\t\treturn actionSelectionListener;\n\t}\n\n\tpublic GameContext getContext() {\n\t\treturn context;\n\t}\n\n\tpublic int getPlayerId() {\n\t\treturn playerId;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/learning/Brain.java",
    "content": "package net.demilich.metastone.game.behaviour.learning;\n\nimport java.util.List;\n\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.behaviour.neutralnetwork.HiddenUnit;\nimport net.demilich.metastone.game.behaviour.neutralnetwork.NeuralNetwork;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.utils.MathUtils;\n\npublic class Brain implements IBrain {\n\n\t//private static Logger logger = LoggerFactory.getLogger(Brain.class);\n\n\tprivate static final int INPUTS = 15;\n\tprivate static final int HIDDEN_NEURONS = 40;\n\tprivate static final int OUTPUTS = 1;\n\n\tprivate static final double ALPHA = 0.1;\n\tprivate static final double BETA = 0.1;\n\tprivate static final double LAMBDA = 0.5;\n\n\tprivate boolean learning;\n\tprivate NeuralNetwork neuralNetwork;\n\tprivate double[][] ew;\n\tprivate double[][][] ev;\n\n\tpublic Brain() {\n\t\tneuralNetwork = new NeuralNetwork(INPUTS, new int[] { HIDDEN_NEURONS, OUTPUTS });\n\t\tew = new double[HIDDEN_NEURONS][OUTPUTS];\n\t\tev = new double[INPUTS][HIDDEN_NEURONS][OUTPUTS];\n\t}\n\n\tprivate void backPropagation(double[] in, double[] out, double[] expected) {\n\t\t// compute eligability traces\n\t\tfor (int j = 0; j < neuralNetwork.hidden[0].length; j++) {\n\t\t\tfor (int k = 0; k < out.length; k++) {\n\t\t\t\tew[j][k] = LAMBDA * ew[j][k] + gradient(neuralNetwork.hidden[1][k]) * neuralNetwork.hidden[0][j].getValue();\n\t\t\t\tfor (int i = 0; i < in.length; i++) {\n\t\t\t\t\tev[i][j][k] = LAMBDA * ev[i][j][k] + gradient(neuralNetwork.hidden[1][k]) * neuralNetwork.hidden[1][k].weights[j]\n\t\t\t\t\t\t\t* gradient(neuralNetwork.hidden[0][j]) * in[i];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tdouble[] error = new double[out.length];\n\t\tfor (int k = 0; k < out.length; k++) {\n\t\t\terror[k] = expected[k] - out[k];\n\t\t}\n\n\t\tfor (int j = 0; j < neuralNetwork.hidden[0].length; j++) {\n\t\t\tfor (int k = 0; k < out.length; k++) {\n\t\t\t\t// weight from j to k, shown with learning param of BETA\n\t\t\t\tneuralNetwork.hidden[1][k].weights[j] += BETA * error[k] * ew[j][k];\n\t\t\t\tfor (int i = 0; i < in.length; i++) {\n\t\t\t\t\tneuralNetwork.hidden[0][j].weights[i] += ALPHA * error[k] * ev[i][j][k];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void encodePlayer(Player player, double[] data, int offset) {\n\t\tList<Minion> minions = player.getMinions();\n\t\tint totalMinionAttack = 0;\n\t\tint totalMinionHp = 0;\n\t\tfor (int i = 0; i < 7; i++) {\n\t\t\tMinion minion = i < minions.size() ? player.getMinions().get(i) : null;\n\t\t\ttotalMinionAttack += minion != null ? minion.getAttack() : 0;\n\t\t\ttotalMinionHp += minion != null ? minion.getHp() : 0;\n\t\t}\n\t\tdata[offset++] = minions.size() / 7.0;\n\t\tdata[offset++] = MathUtils.clamp01(totalMinionAttack / 40.0);\n\t\tdata[offset++] = MathUtils.clamp01(totalMinionHp / 40.0);\n\t\tdata[offset++] = MathUtils.clamp01(player.getHero().getAttack() / 10.0);\n\t\tdata[offset++] = MathUtils.clamp01((player.getHero().getHp() + player.getHero().getArmor()) / 40.0);\n\t\tdata[offset++] = player.getHand().getCount() / 10.0;\n\t\tdata[offset++] = MathUtils.clamp01(player.getDeck().getCount() / 30.0);\n\t}\n\n\tprivate double[] gameStateToInput(GameContext context, int playerId) {\n\t\tdouble[] input = new double[INPUTS];\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tPlayer opponent = context.getOpponent(player);\n\t\tencodePlayer(player, input, 0);\n\t\tencodePlayer(opponent, input, INPUTS / 2);\n\t\tinput[INPUTS - 1] = MathUtils.clamp01(context.getTurn() / 10.0);\n\t\treturn input;\n\t}\n\n\t@Override\n\tpublic double getEstimatedUtility(double[] output) {\n\t\treturn output[0];\n\t}\n\n\t@Override\n\tpublic double[] getOutput(GameContext context, int playerId) {\n\t\tdouble[] input = gameStateToInput(context, playerId);\n\t\treturn neuralNetwork.getValue(input);\n\t}\n\n\tprivate double gradient(HiddenUnit hiddenUnit) {\n\t\treturn hiddenUnit.getValue() * (1.0 - hiddenUnit.getValue());\n\t}\n\n\t@Override\n\tpublic boolean isLearning() {\n\t\treturn learning;\n\t}\n\n\t@Override\n\tpublic void learn(GameContext originalState, int playerId, double[] nextOutput, double reward) {\n\t\tdouble[] currentInput = gameStateToInput(originalState, playerId);\n\t\tdouble[] currentOutput = getOutput(originalState, playerId);\n\t\tfor (int i = 0; i < nextOutput.length; i++) {\n\t\t\tnextOutput[i] += reward;\n\t\t}\n\t\tbackPropagation(currentInput, currentOutput, nextOutput);\n\t}\n\n\tpublic void load(String path) {\n\t\t// try {\n\t\t// neuralNetwork = NeuralNetwork.readFrom(path);\n\t\t// logger.info(\"Saved brain data loaded\");\n\t\t// } catch (ClassNotFoundException e) {\n\t\t// e.printStackTrace();\n\t\t// } catch (IOException e) {\n\t\t// logger.info(\"Brain data not found, using unlearned neural network\");\n\t\t// }\n\n\t}\n\n\t/*private void printWeights() {\n\t\tfor (int i = 0; i < neuralNetwork.hidden.length; i++) {\n\t\t\tfor (int j = 0; j < neuralNetwork.hidden[i].length; j++) {\n\t\t\t\tfor (int k = 0; k < neuralNetwork.hidden[i][j].weights.length; k++) {\n\t\t\t\t\tSystem.out.println(Arrays.toString(neuralNetwork.hidden[i][j].weights));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}*/\n\n\tpublic void save(String path) {\n\n\t\t// try {\n\t\t// neuralNetwork.writeTo(path);\n\t\t// logger.info(\"Brain data saved to: \" + path);\n\t\t// } catch (IOException e) {\n\t\t// }\n\t}\n\n\t@Override\n\tpublic void setLearning(boolean learning) {\n\t\tthis.learning = learning;\n\t}\n\n\tpublic void wipeEligabilityTraces() {\n\t\tew = new double[HIDDEN_NEURONS][OUTPUTS];\n\t\tev = new double[INPUTS][HIDDEN_NEURONS][OUTPUTS];\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/learning/IBrain.java",
    "content": "package net.demilich.metastone.game.behaviour.learning;\n\nimport net.demilich.metastone.game.GameContext;\n\npublic interface IBrain {\n\n\tpublic abstract double getEstimatedUtility(double[] output);\n\n\tpublic abstract double[] getOutput(GameContext context, int playerId);\n\n\tpublic abstract boolean isLearning();\n\n\tpublic abstract void learn(GameContext originalState, int playerId, double[] nextOutput, double reward);\n\n\tpublic abstract void load(String savePath);\n\n\tpublic abstract void save(String savePath);\n\n\tpublic abstract void setLearning(boolean learning);\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/learning/LearningBehaviour.java",
    "content": "package net.demilich.metastone.game.behaviour.learning;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.ActionType;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.behaviour.Behaviour;\nimport net.demilich.metastone.game.cards.Card;\n\npublic class LearningBehaviour extends Behaviour {\n\n\t//private static Logger logger = LoggerFactory.getLogger(LearningBehaviour.class);\n\n\tprivate static final String SAVE_PATH = \"brain.ser\";\n\n\tprivate static final IBrain brain = new Brain();\n\n\tpublic LearningBehaviour(boolean learn) {\n\t\tbrain.load(SAVE_PATH);\n\t\tbrain.setLearning(learn);\n\t}\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"Learning\";\n\t}\n\n\t@Override\n\tpublic List<Card> mulligan(GameContext context, Player player, List<Card> cards) {\n\t\tList<Card> discardedCards = new ArrayList<Card>();\n\t\tfor (Card card : cards) {\n\t\t\tif (card.getBaseManaCost() >= 4) {\n\t\t\t\tdiscardedCards.add(card);\n\t\t\t}\n\t\t}\n\t\treturn discardedCards;\n\t}\n\n\t@Override\n\tpublic void onGameOver(GameContext context, int playerId, int winningPlayerId) {\n\t\tif (!brain.isLearning()) {\n\t\t\treturn;\n\t\t}\n\n\t\tdouble reward = 0;\n\t\tdouble[] actual = new double[1];\n\t\tif (playerId == winningPlayerId) {\n\t\t\tactual[0] = reward = 1.0;\n\t\t} else {\n\t\t\tactual[0] = reward = -1.0;\n\t\t}\n\n\t\tbrain.learn(context, playerId, actual, reward);\n\t}\n\n\t@Override\n\tpublic GameAction requestAction(GameContext context, Player player, List<GameAction> validActions) {\n\n\t\t// for now, do not evaluate battecry actions\n\t\tif (validActions.get(0).getActionType() == ActionType.BATTLECRY) {\n\t\t\treturn validActions.get(context.getLogic().random(validActions.size()));\n\t\t}\n\n\t\tGameAction bestAction = null;\n\t\tdouble expectedUtility = -2.0;\n\t\tdouble[] nextOutput = null;\n\n\t\tfor (GameAction gameAction : validActions) {\n\t\t\tGameContext simulation = context.clone();\n\t\t\tsimulation.getLogic().performGameAction(player.getId(), gameAction);\n\t\t\tdouble[] output = brain.getOutput(simulation, player.getId());\n\t\t\tdouble utility = brain.getEstimatedUtility(output);\n\n\t\t\tif (utility > expectedUtility) {\n\t\t\t\tbestAction = gameAction;\n\t\t\t\texpectedUtility = utility;\n\t\t\t\tnextOutput = output;\n\t\t\t\t// logger.info(\"Action {} received utility of {}\", gameAction,\n\t\t\t\t// utility);\n\t\t\t}\n\t\t}\n\n\t\tif (brain.isLearning()) {\n\t\t\tbrain.learn(context, player.getId(), nextOutput, 0);\n\t\t}\n\n\t\treturn bestAction;\n\t}\n\n\tpublic void save() {\n\t\tbrain.save(SAVE_PATH);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/mcts/ITreePolicy.java",
    "content": "package net.demilich.metastone.game.behaviour.mcts;\n\ninterface ITreePolicy {\n\n\tNode select(Node parent);\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/mcts/MonteCarloTreeSearch.java",
    "content": "package net.demilich.metastone.game.behaviour.mcts;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.behaviour.Behaviour;\nimport net.demilich.metastone.game.cards.Card;\n\npublic class MonteCarloTreeSearch extends Behaviour {\n\n\t//private final static Logger logger = LoggerFactory.getLogger(MonteCarloTreeSearch.class);\n\n\tprivate static final int ITERATIONS = 500;\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"MCTS\";\n\t}\n\n\t@Override\n\tpublic List<Card> mulligan(GameContext context, Player player, List<Card> cards) {\n\t\tList<Card> discardedCards = new ArrayList<Card>();\n\t\tfor (Card card : cards) {\n\t\t\tif (card.getBaseManaCost() >= 4) {\n\t\t\t\tdiscardedCards.add(card);\n\t\t\t}\n\t\t}\n\t\treturn discardedCards;\n\t}\n\n\t@Override\n\tpublic GameAction requestAction(GameContext context, Player player, List<GameAction> validActions) {\n\t\tif (validActions.size() == 1) {\n\t\t\t// logger.info(\"MCTS selected best action {}\", validActions.get(0));\n\t\t\treturn validActions.get(0);\n\t\t}\n\t\tNode root = new Node(null, player.getId());\n\t\troot.initState(context, validActions);\n\t\tUctPolicy treePolicy = new UctPolicy();\n\t\tfor (int i = 0; i < ITERATIONS; i++) {\n\t\t\troot.process(treePolicy);\n\t\t}\n\t\tGameAction bestAction = root.getBestAction();\n\t\t// logger.info(\"MCTS selected best action {}\", bestAction);\n\t\treturn bestAction;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/mcts/Node.java",
    "content": "package net.demilich.metastone.game.behaviour.mcts;\n\nimport java.util.ArrayList;\nimport java.util.LinkedList;\nimport java.util.List;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.behaviour.PlayRandomBehaviour;\n\nclass Node {\n\n\tprivate GameContext state;\n\tprivate List<GameAction> validTransitions;\n\tprivate final List<Node> children = new LinkedList<>();\n\tprivate final GameAction incomingAction;\n\tprivate int visits;\n\tprivate int score;\n\tprivate final int player;\n\n\tpublic Node(GameAction incomingAction, int player) {\n\t\tthis.incomingAction = incomingAction;\n\t\tthis.player = player;\n\t}\n\n\tprivate boolean canFurtherExpanded() {\n\t\treturn !validTransitions.isEmpty();\n\t}\n\n\tprivate Node expand() {\n\t\tGameAction action = validTransitions.remove(0);\n\t\tGameContext newState = state.clone();\n\n\t\ttry {\n\t\t\tnewState.getLogic().performGameAction(newState.getActivePlayer().getId(), action);\n\t\t} catch (Exception e) {\n\t\t\tSystem.err.println(\"Exception on action: \" + action + \" state decided: \" + state.gameDecided());\n\t\t\te.printStackTrace();\n\t\t\tthrow e;\n\t\t}\n\n\t\tNode child = new Node(action, getPlayer());\n\t\tchild.initState(newState, newState.getValidActions());\n\t\tchildren.add(child);\n\t\treturn child;\n\t}\n\n\tpublic GameAction getBestAction() {\n\t\tGameAction best = null;\n\t\tint bestScore = Integer.MIN_VALUE;\n\t\tfor (Node node : children) {\n\t\t\tif (node.getScore() > bestScore) {\n\t\t\t\tbest = node.incomingAction;\n\t\t\t\tbestScore = node.getScore();\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t}\n\n\tpublic List<Node> getChildren() {\n\t\treturn children;\n\t}\n\n\tpublic int getPlayer() {\n\t\treturn player;\n\t}\n\n\tpublic int getScore() {\n\t\treturn score;\n\t}\n\n\tpublic GameContext getState() {\n\t\treturn state;\n\t}\n\n\tpublic int getVisits() {\n\t\treturn visits;\n\t}\n\n\tpublic void initState(GameContext state, List<GameAction> validActions) {\n\t\tthis.state = state.clone();\n\t\tthis.validTransitions = new ArrayList<GameAction>(validActions);\n\t}\n\n\tpublic boolean isExpandable() {\n\t\tif (validTransitions.isEmpty()) {\n\t\t\treturn false;\n\t\t}\n\t\tif (state.gameDecided()) {\n\t\t\treturn false;\n\t\t}\n\t\treturn getChildren().size() < validTransitions.size();\n\t}\n\n\tpublic boolean isLeaf() {\n\t\treturn children == null || children.isEmpty();\n\t}\n\n\tprivate boolean isTerminal() {\n\t\treturn state.gameDecided();\n\t}\n\n\tpublic void process(ITreePolicy treePolicy) {\n\t\tList<Node> visited = new LinkedList<Node>();\n\t\tNode current = this;\n\t\tvisited.add(this);\n\t\twhile (!current.isTerminal()) {\n\t\t\tif (current.canFurtherExpanded()) {\n\t\t\t\tcurrent = current.expand();\n\t\t\t\tvisited.add(current);\n\t\t\t\tbreak;\n\t\t\t} else {\n\t\t\t\tcurrent = treePolicy.select(current);\n\t\t\t\tvisited.add(current);\n\t\t\t}\n\t\t}\n\n\t\tint value = rollOut(current);\n\t\tfor (Node node : visited) {\n\t\t\tnode.updateStats(value);\n\t\t}\n\t}\n\n\tpublic int rollOut(Node node) {\n\t\tif (node.getState().gameDecided()) {\n\t\t\tGameContext state = node.getState();\n\t\t\treturn state.getWinningPlayerId() == getPlayer() ? 1 : 0;\n\t\t}\n\n\t\tGameContext simulation = node.getState().clone();\n\t\tfor (Player player : simulation.getPlayers()) {\n\t\t\tplayer.setBehaviour(new PlayRandomBehaviour());\n\t\t}\n\n\t\tsimulation.playTurn();\n\n\t\treturn simulation.getWinningPlayerId() == getPlayer() ? 1 : 0;\n\t}\n\n\tprivate void updateStats(int value) {\n\t\tvisits++;\n\t\tscore += value;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/mcts/UctPolicy.java",
    "content": "package net.demilich.metastone.game.behaviour.mcts;\n\nimport java.util.Random;\n\nclass UctPolicy implements ITreePolicy {\n\n\tprivate static final double EPSILON = 1e-5;\n\tprivate static final Random random = new Random();\n\n\tprivate static final double C = 1 / Math.sqrt(2);\n\n\t@Override\n\tpublic Node select(Node parent) {\n\t\tNode selected = null;\n\t\tdouble bestValue = Double.NEGATIVE_INFINITY;\n\t\tfor (Node child : parent.getChildren()) {\n\t\t\tdouble uctValue = child.getVisits() == 0 ? 1000000\n\t\t\t\t\t: child.getScore() / (double) child.getVisits() + C * Math.sqrt(Math.log(parent.getVisits()) / child.getVisits())\n\t\t\t\t\t\t\t+ random.nextDouble() * EPSILON;\n\n\t\t\t// small random number to break ties randomly in unexpanded nodes\n\t\t\tif (uctValue > bestValue) {\n\t\t\t\tselected = child;\n\t\t\t\tbestValue = uctValue;\n\t\t\t}\n\t\t}\n\t\treturn selected;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/neutralnetwork/HiddenUnit.java",
    "content": "package net.demilich.metastone.game.behaviour.neutralnetwork;\n\nimport java.util.Random;\n\npublic class HiddenUnit implements Unit {\n\n\t// serailver\n\tprivate static final long serialVersionUID = -6987443254721148687L;\n\n\t// the array of input weights\n\tpublic double[] weights;\n\n\t// the list of inputs to this hidden unit\n\tprotected Unit[] units;\n\n\t// the current unit value (cached)\n\tprotected double value;\n\n\t/**\n\t * Builds a hidden unit taking the provided number of inputs. Sets the\n\t * initial weights to be a copy of the provided weights\n\t *\n\t * @param units\n\t *            The input units to this unit\n\t * @param weights\n\t *            The weights to use\n\t */\n\tprotected HiddenUnit(Unit[] units, double[] weights) {\n\t\tthis.units = units;\n\t\tthis.weights = new double[weights.length];\n\n\t\tSystem.arraycopy(weights, 0, this.weights, 0, weights.length);\n\t}\n\n\t/**\n\t * Builds a hidden unit taking the provided number of inputs. Sets the\n\t * initial weights to be random values to be 0.\n\t *\n\t * @param units\n\t *            The input units to this unit\n\t * @param random\n\t *            The random number generator\n\t */\n\tprotected HiddenUnit(Unit[] units, Random random) {\n\t\tthis.units = units;\n\t\tthis.weights = new double[units.length];\n\t}\n\n\t/**\n\t * Returns the sum of all of the inputs and weights\n\t *\n\t * @return the sum\n\t */\n\tprotected double getSum() {\n\t\tdouble total = 0;\n\n\t\tfor (int i = 0; i < units.length; i++)\n\t\t\ttotal += weights[i] * units[i].getValue();\n\n\t\treturn total;\n\t}\n\n\t/**\n\t * Returns the current value of this input\n\t *\n\t * @return The current value of this input\n\t */\n\tpublic double getValue() {\n\t\treturn value;\n\t}\n\n\t/**\n\t * Generates a new weight\n\t *\n\t * @param random\n\t *            The rng\n\t * @return A new value\n\t */\n\tpublic void randomizeWeights(Random random) {\n\t\tfor (int i = 0; i < weights.length; i++)\n\t\t\tweights[i] = random.nextDouble() * 0.2;\n\t}\n\n\t/**\n\t * Recomputes the value of this hidden unit, querying it's prior inputs.\n\t */\n\tpublic void recompute() {\n\t\tvalue = sigmoid(getSum());\n\t}\n\n\t/**\n\t * Implements the sigmoid function to provide the non-linearity to this\n\t * function. Simply returns\n\t *\n\t * 1 / (1 + e^-x)\n\t *\n\t * @param x\n\t *            The value to apply the function to\n\t * @return The result\n\t */\n\tprotected double sigmoid(double x) {\n\t\treturn (1 / (1 + Math.exp(-x)));\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/neutralnetwork/InputUnit.java",
    "content": "package net.demilich.metastone.game.behaviour.neutralnetwork;\n\npublic class InputUnit implements Unit {\n\n\t/**\n\t * \n\t */\n\tprivate static final long serialVersionUID = 8699533068346744182L;\n\t// the current value of the input\n\tprotected double value;\n\n\t/**\n\t * Builds a hidden unit taking the provided number of inputs. Sets the\n\t * initial weights to be random values, using the provided RNG.\n\t *\n\t * @param units\n\t *            The input units to this unit\n\t * @param random\n\t *            The random number generator\n\t */\n\tprotected InputUnit() {\n\t}\n\n\t/**\n\t * Returns the current value of this input\n\t *\n\t * @return The current value of this input\n\t */\n\tpublic double getValue() {\n\t\treturn value;\n\t}\n\n\t/**\n\t * Recomputes the value of this hidden unit, querying it's prior inputs.\n\t */\n\tpublic void recompute() {\n\t}\n\n\t/**\n\t * Sets the value of this input unit\n\t *\n\t * @return the value\n\t */\n\tprotected void setValue(double value) {\n\t\tthis.value = value;\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/neutralnetwork/NeuralNetwork.java",
    "content": "package net.demilich.metastone.game.behaviour.neutralnetwork;\n\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.io.Serializable;\nimport java.util.Random;\n\npublic class NeuralNetwork implements Serializable {\n\n\t// serialver for backwards compatibility\n\tprivate static final long serialVersionUID = 1165374168397424904L;\n\n\t// the random number generator\n\tpublic static final Random random = new Random();\n\n\t/**\n\t * Method which reads and returns a network from the given file\n\t *\n\t * @param filename\n\t *            The file to read from\n\t */\n\tpublic static NeuralNetwork readFrom(String filename) throws IOException, ClassNotFoundException {\n\t\tObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));\n\t\tNeuralNetwork net = (NeuralNetwork) ois.readObject();\n\t\tois.close();\n\n\t\treturn net;\n\t}\n\n\t// the layers of the network\n\tpublic InputUnit[] input;\n\n\tpublic HiddenUnit[][] hidden;\n\n\t/**\n\t * Builds a neural network with the given number of input units, hidden\n\t * units, and output units. Thus, calling\n\t * \n\t * new NeuralNetwork(10, new int[] {20, 5});\n\t *\n\t * creates a neural network with 10 input units, a layer of 20 hidden units,\n\t * and then 5 output units.\n\t *\n\t * @param input\n\t *            The number of input units\n\t * @param hidden\n\t *            The number of hidden units, as well as the number of layers\n\t */\n\tpublic NeuralNetwork(int input, int[] hidden) {\n\t\tthis.input = new InputUnit[input];\n\t\tthis.hidden = new HiddenUnit[hidden.length][];\n\n\t\tfor (int i = 0; i < hidden.length; i++)\n\t\t\tthis.hidden[i] = new HiddenUnit[hidden[i]];\n\n\t\tfor (int i = 0; i < input; i++)\n\t\t\tthis.input[i] = new InputUnit();\n\n\t\tfor (int i = 0; i < hidden.length; i++)\n\t\t\tfor (int j = 0; j < hidden[i]; j++)\n\t\t\t\tif (i == 0)\n\t\t\t\t\tthis.hidden[i][j] = new HiddenUnit(this.input, random);\n\t\t\t\telse\n\t\t\t\t\tthis.hidden[i][j] = new HiddenUnit(this.hidden[i - 1], random);\n\n\t\t// for (int i = 0; i < hidden.length; i++) {\n\t\t// for (int j = 0; j < hidden[i]; j++) {\n\t\t// this.hidden[i][j].randomizeWeights(random);\n\t\t// }\n\t\t// }\n\n\t}\n\n\t/**\n\t * Builds a neural network based on the provided network and copies the\n\t * weights of the provided network into the new one.\n\t *\n\t * @param net\n\t *            The network to base it off of\n\t */\n\tpublic NeuralNetwork(NeuralNetwork net) {\n\t\tthis.input = new InputUnit[net.input.length];\n\t\tthis.hidden = new HiddenUnit[net.hidden.length][];\n\n\t\tfor (int i = 0; i < input.length; i++)\n\t\t\tthis.input[i] = new InputUnit();\n\n\t\tfor (int i = 0; i < net.hidden.length; i++) {\n\t\t\tthis.hidden[i] = new HiddenUnit[net.hidden[i].length];\n\n\t\t\tfor (int j = 0; j < net.hidden[i].length; j++)\n\t\t\t\tif (i == 0)\n\t\t\t\t\tthis.hidden[i][j] = new HiddenUnit(this.input, net.hidden[i][j].weights);\n\t\t\t\telse\n\t\t\t\t\tthis.hidden[i][j] = new HiddenUnit(this.hidden[i - 1], net.hidden[i][j].weights);\n\t\t}\n\t}\n\n\t/**\n\t * Calculates the network value given the provided input\n\t *\n\t * @param input\n\t *            The input to check\n\t * @return The network value from this input\n\t */\n\tpublic double[] getValue(double[] input) {\n\t\tdouble[] result = new double[hidden[hidden.length - 1].length];\n\n\t\tfor (int i = 0; i < input.length; i++)\n\t\t\tthis.input[i].setValue(input[i]);\n\n\t\tfor (int i = 0; i < hidden.length; i++)\n\t\t\tfor (int j = 0; j < hidden[i].length; j++)\n\t\t\t\tthis.hidden[i][j].recompute();\n\n\t\tfor (int j = 0; j < hidden[hidden.length - 1].length; j++)\n\t\t\tresult[j] = this.hidden[hidden.length - 1][j].getValue();\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Method which writes this network to the given file\n\t *\n\t * @param file\n\t *            The file to write to\n\t */\n\tpublic void writeTo(String filename) throws IOException {\n\t\tObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename));\n\t\toos.writeObject(this);\n\t\toos.flush();\n\t\toos.close();\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/neutralnetwork/Unit.java",
    "content": "package net.demilich.metastone.game.behaviour.neutralnetwork;\n\nimport java.io.Serializable;\n\npublic interface Unit extends Serializable {\n\n\t/**\n\t * Returns the current value of this input\n\t *\n\t * @return The current value of this input\n\t */\n\tpublic double getValue();\n\n\t/**\n\t * Recomputes the value of this hidden unit, querying it's prior inputs.\n\t */\n\tpublic void recompute();\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/threat/GameStateValueBehaviour.java",
    "content": "package net.demilich.metastone.game.behaviour.threat;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.metastone.NotificationProxy;\nimport net.demilich.metastone.trainingmode.RequestTrainingDataNotification;\nimport net.demilich.metastone.trainingmode.TrainingData;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.ActionType;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.behaviour.Behaviour;\nimport net.demilich.metastone.game.behaviour.IBehaviour;\nimport net.demilich.metastone.game.behaviour.heuristic.IGameStateHeuristic;\nimport net.demilich.metastone.game.cards.Card;\n\npublic class GameStateValueBehaviour extends Behaviour {\n\n\tprivate final Logger logger = LoggerFactory.getLogger(GameStateValueBehaviour.class);\n\n\tprivate IGameStateHeuristic heuristic;\n\tprivate FeatureVector featureVector;\n\tprivate String nameSuffix = \"\";\n\n\tpublic GameStateValueBehaviour() {\n\t}\n\n\tpublic GameStateValueBehaviour(FeatureVector featureVector, String nameSuffix) {\n\t\tthis.featureVector = featureVector;\n\t\tthis.nameSuffix = nameSuffix;\n\t\tthis.heuristic = new ThreatBasedHeuristic(featureVector);\n\t}\n\n\tprivate double alphaBeta(GameContext context, int playerId, GameAction action, int depth) {\n\t\tGameContext simulation = context.clone();\n\t\tsimulation.getLogic().performGameAction(playerId, action);\n\t\tif (depth == 0 || simulation.getActivePlayerId() != playerId || simulation.gameDecided()) {\n\t\t\treturn heuristic.getScore(simulation, playerId);\n\t\t}\n\n\t\tList<GameAction> validActions = simulation.getValidActions();\n\n\t\tdouble score = Float.NEGATIVE_INFINITY;\n\n\t\tfor (GameAction gameAction : validActions) {\n\t\t\tscore = Math.max(score, alphaBeta(simulation, playerId, gameAction, depth - 1));\n\t\t\tif (score >= 100000) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\treturn score;\n\t}\n\n\tprivate void answerTrainingData(TrainingData trainingData) {\n\t\tfeatureVector = trainingData != null ? trainingData.getFeatureVector() : FeatureVector.getFittest();\n\t\theuristic = new ThreatBasedHeuristic(featureVector);\n\t\tnameSuffix = trainingData != null ? \"(trained)\" : \"(untrained)\";\n\t}\n\n\t@Override\n\tpublic IBehaviour clone() {\n\t\tif (featureVector != null) {\n\t\t\treturn new GameStateValueBehaviour(featureVector.clone(), nameSuffix);\n\t\t}\n\t\treturn new GameStateValueBehaviour();\n\t}\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"Game state value \" + nameSuffix;\n\t}\n\n\t@Override\n\tpublic List<Card> mulligan(GameContext context, Player player, List<Card> cards) {\n\t\trequestTrainingData(player);\n\t\tList<Card> discardedCards = new ArrayList<Card>();\n\t\tfor (Card card : cards) {\n\t\t\tif (card.getBaseManaCost() > 3) {\n\t\t\t\tdiscardedCards.add(card);\n\t\t\t}\n\t\t}\n\t\treturn discardedCards;\n\t}\n\n\t@Override\n\tpublic GameAction requestAction(GameContext context, Player player, List<GameAction> validActions) {\n\t\tif (validActions.size() == 1) {\n\t\t\treturn validActions.get(0);\n\t\t}\n\n\t\tint depth = 2;\n\t\t// when evaluating battlecry and discover actions, only optimize the immediate value\n\t\tif (validActions.get(0).getActionType() == ActionType.BATTLECRY) {\n\t\t\tdepth = 0;\n\t\t} else if (validActions.get(0).getActionType() == ActionType.DISCOVER) {\n\t\t\treturn validActions.get(0);\n\t\t}\n\n\t\tGameAction bestAction = validActions.get(0);\n\t\tdouble bestScore = Double.NEGATIVE_INFINITY;\n\n\t\tfor (GameAction gameAction : validActions) {\n\t\t\tdouble score = alphaBeta(context, player.getId(), gameAction, depth);\n\t\t\tif (score > bestScore) {\n\t\t\t\tbestAction = gameAction;\n\t\t\t\tbestScore = score;\n\t\t\t}\n\t\t}\n\n\t\tlogger.debug(\"Selecting best action {} with score {}\", bestAction, bestScore);\n\n\t\treturn bestAction;\n\t}\n\n\tprivate void requestTrainingData(Player player) {\n\t\tif (heuristic != null) {\n\t\t\treturn;\n\t\t}\n\n\t\tRequestTrainingDataNotification request = new RequestTrainingDataNotification(player.getDeckName(), this::answerTrainingData);\n\t\tNotificationProxy.notifyObservers(request);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/threat/ThreatBasedHeuristic.java",
    "content": "package net.demilich.metastone.game.behaviour.threat;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.behaviour.heuristic.IGameStateHeuristic;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.heroes.Hero;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Minion;\n\npublic class ThreatBasedHeuristic implements IGameStateHeuristic {\n\n\tprivate static List<String> hardRemoval;\n\n\tstatic {\n\t\thardRemoval = new ArrayList<String>();\n\t\thardRemoval.add(\"spell_polymorph\");\n\t\thardRemoval.add(\"spell_execute\");\n\t\thardRemoval.add(\"spell_crush\");\n\t\thardRemoval.add(\"spell_assassinate\");\n\t\thardRemoval.add(\"spell_siphon_soul\");\n\t\thardRemoval.add(\"spell_shadow_word_death\");\n\t\thardRemoval.add(\"spell_naturalize\");\n\t\thardRemoval.add(\"spell_hex\");\n\t\thardRemoval.add(\"spell_humility\");\n\t\thardRemoval.add(\"spell_equality\");\n\t\thardRemoval.add(\"spell_deadly_shot\");\n\t\thardRemoval.add(\"spell_sap\");\n\t\thardRemoval.add(\"minion_doomsayer\");\n\t\thardRemoval.add(\"minion_big_game_hunter\");\n\t}\n\n\tprivate static ThreatLevel calcuateThreatLevel(GameContext context, int playerId) {\n\t\tint damageOnBoard = 0;\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tPlayer opponent = context.getOpponent(player);\n\t\tfor (Minion minion : opponent.getMinions()) {\n\t\t\tdamageOnBoard += minion.getAttack() * minion.getAttributeValue(Attribute.NUMBER_OF_ATTACKS);\n\t\t}\n\t\tdamageOnBoard += getHeroDamage(opponent.getHero());\n\n\t\tint remainingHp = player.getHero().getEffectiveHp() - damageOnBoard;\n\t\tif (remainingHp < 1) {\n\t\t\treturn ThreatLevel.RED;\n\t\t} else if (remainingHp < 15) {\n\t\t\treturn ThreatLevel.YELLOW;\n\t\t}\n\n\t\treturn ThreatLevel.GREEN;\n\t}\n\n\tprivate static int getHeroDamage(Hero hero) {\n\t\tint heroDamage = 0;\n\t\tif (hero.getHeroClass() == HeroClass.MAGE) {\n\t\t\theroDamage += 1;\n\t\t} else if (hero.getHeroClass() == HeroClass.HUNTER) {\n\t\t\theroDamage += 2;\n\t\t} else if (hero.getHeroClass() == HeroClass.DRUID) {\n\t\t\theroDamage += 1;\n\t\t} else if (hero.getHeroClass() == HeroClass.ROGUE) {\n\t\t\theroDamage += 1;\n\t\t}\n\t\tif (hero.getWeapon() != null) {\n\t\t\theroDamage += hero.getWeapon().getWeaponDamage();\n\t\t}\n\t\treturn heroDamage;\n\t}\n\n\tprivate static boolean isHardRemoval(Card card) {\n\t\treturn hardRemoval.contains(card.getCardId());\n\t}\n\n\tprivate final FeatureVector weights;\n\n\tpublic ThreatBasedHeuristic(FeatureVector vector) {\n\t\tthis.weights = vector;\n\t}\n\n\tprivate double calculateMinionScore(Minion minion, ThreatLevel threatLevel) {\n\t\tif (minion.hasAttribute(Attribute.MARKED_FOR_DEATH)) {\n\t\t\treturn 0;\n\t\t}\n\t\tdouble minionScore = weights.get(WeightedFeature.MINION_INTRINSIC_VALUE);\n\t\tminionScore += weights.get(WeightedFeature.MINION_ATTACK_FACTOR)\n\t\t\t\t* (minion.getAttack() - minion.getAttributeValue(Attribute.TEMPORARY_ATTACK_BONUS));\n\t\tminionScore += weights.get(WeightedFeature.MINION_HP_FACTOR) * minion.getHp();\n\n\t\tif (minion.hasAttribute(Attribute.TAUNT)) {\n\t\t\tswitch (threatLevel) {\n\t\t\tcase RED:\n\t\t\t\tminionScore += weights.get(WeightedFeature.MINION_RED_TAUNT_MODIFIER);\n\t\t\t\tbreak;\n\t\t\tcase YELLOW:\n\t\t\t\tminionScore += weights.get(WeightedFeature.MINION_YELLOW_TAUNT_MODIFIER);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tminionScore += weights.get(WeightedFeature.MINION_DEFAULT_TAUNT_MODIFIER);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (minion.hasAttribute(Attribute.WINDFURY)) {\n\t\t\tminionScore += weights.get(WeightedFeature.MINION_WINDFURY_MODIFIER);\n\t\t} else if (minion.hasAttribute(Attribute.MEGA_WINDFURY)) {\n\t\t\tminionScore += 2 * weights.get(WeightedFeature.MINION_WINDFURY_MODIFIER);\n\t\t}\n\n\t\tif (minion.hasAttribute(Attribute.DIVINE_SHIELD)) {\n\t\t\tminionScore += weights.get(WeightedFeature.MINION_DIVINE_SHIELD_MODIFIER);\n\t\t}\n\t\tif (minion.hasAttribute(Attribute.SPELL_DAMAGE)) {\n\t\t\tminionScore += minion.getAttributeValue(Attribute.SPELL_DAMAGE) * weights.get(WeightedFeature.MINION_SPELL_POWER_MODIFIER);\n\t\t}\n\n\t\tif (minion.hasAttribute(Attribute.STEALTH)) {\n\t\t\tminionScore += weights.get(WeightedFeature.MINION_STEALTHED_MODIFIER);\n\t\t}\n\t\tif (minion.hasAttribute(Attribute.UNTARGETABLE_BY_SPELLS)) {\n\t\t\tminionScore += weights.get(WeightedFeature.MINION_UNTARGETABLE_BY_SPELLS_MODIFIER);\n\t\t}\n\n\t\treturn minionScore;\n\t}\n\n\t@Override\n\tpublic double getScore(GameContext context, int playerId) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tPlayer opponent = context.getOpponent(player);\n\t\tif (player.getHero().isDestroyed()) {\n\t\t\treturn Float.NEGATIVE_INFINITY;\n\t\t}\n\t\tif (opponent.getHero().isDestroyed()) {\n\t\t\treturn Float.POSITIVE_INFINITY;\n\t\t}\n\t\tdouble score = 0;\n\n\t\tThreatLevel threatLevel = calcuateThreatLevel(context, playerId);\n\t\tswitch (threatLevel) {\n\t\tcase RED:\n\t\t\tscore += weights.get(WeightedFeature.RED_MODIFIER);\n\t\t\tbreak;\n\t\tcase YELLOW:\n\t\t\tscore += weights.get(WeightedFeature.YELLOW_MODIFIER);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t\tscore += player.getHero().getEffectiveHp() * weights.get(WeightedFeature.OWN_HP_FACTOR);\n\t\tscore += opponent.getHero().getEffectiveHp() * weights.get(WeightedFeature.OPPONENT_HP_FACTOR);\n\t\tfor (Card card : player.getHand()) {\n\t\t\tif (isHardRemoval(card)) {\n\t\t\t\tscore += weights.get(WeightedFeature.HARD_REMOVAL_VALUE);\n\t\t\t}\n\t\t}\n\n\t\tscore += player.getHand().getCount() * weights.get(WeightedFeature.OWN_CARD_COUNT);\n\t\tscore += opponent.getHand().getCount() * weights.get(WeightedFeature.OPPONENT_CARD_COUNT);\n\n\t\tfor (Minion minion : player.getMinions()) {\n\t\t\tscore += calculateMinionScore(minion, threatLevel);\n\t\t}\n\n\t\tfor (Minion minion : opponent.getMinions()) {\n\t\t\tscore -= calculateMinionScore(minion, threatLevel);\n\t\t}\n\n\t\treturn score;\n\t}\n\n\t@Override\n\tpublic void onActionSelected(GameContext context, int playerId) {\n\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/threat/ThreatLevel.java",
    "content": "package net.demilich.metastone.game.behaviour.threat;\n\nenum ThreatLevel {\n\tGREEN, YELLOW, RED\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/threat/cuckoo/CuckooAgent.java",
    "content": "package net.demilich.metastone.game.behaviour.threat.cuckoo;\n\nimport net.demilich.metastone.game.behaviour.threat.FeatureVector;\n\npublic class CuckooAgent implements Comparable<CuckooAgent> {\n\n\tprivate FeatureVector data;\n\tprivate double fitness = -1;\n\n\tpublic CuckooAgent(FeatureVector data) {\n\t\tthis.setData(data);\n\t}\n\n\t@Override\n\tpublic int compareTo(CuckooAgent other) {\n\t\treturn Double.compare(other.getFitness(), getFitness());\n\t}\n\n\tpublic FeatureVector getData() {\n\t\treturn data;\n\t}\n\n\tpublic double getFitness() {\n\t\treturn fitness;\n\t}\n\n\tpublic void setData(FeatureVector data) {\n\t\tthis.data = data;\n\t}\n\n\tpublic void setFitness(double fitness) {\n\t\tif (this.fitness < 0) {\n\t\t\tthis.fitness = fitness;\n\t\t} else {\n\t\t\tthis.fitness = (this.fitness + fitness) * 0.5;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/threat/cuckoo/CuckooLearner.java",
    "content": "package net.demilich.metastone.game.behaviour.threat.cuckoo;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport net.demilich.metastone.game.behaviour.threat.FeatureVector;\nimport net.demilich.metastone.game.behaviour.threat.WeightedFeature;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.utils.MathUtils;\n\npublic class CuckooLearner {\n\n\tprivate static int POPULATION_SIZE = 15;\n\tprivate static double DISCOVERY_RATE = 0.25;\n\n\tprivate static double levyClamped(double base, double min, double max) {\n\t\tint sign = Math.random() < 0.5 ? -1 : 1;\n\t\tdouble value = base + sign * MathUtils.levy(1, 2);\n\t\treturn MathUtils.clamp(value, min, max);\n\t}\n\n\tprivate static FeatureVector levyFlight(FeatureVector base) {\n\t\tFeatureVector variant = base.clone();\n\t\tMap<WeightedFeature, Double> values = base.getValues();\n\t\tfor (WeightedFeature feature : values.keySet()) {\n\t\t\tDouble weight = values.get(feature);\n\t\t\tswitch (feature) {\n\t\t\tcase MINION_ATTACK_FACTOR:\n\t\t\tcase MINION_DEFAULT_TAUNT_MODIFIER:\n\t\t\tcase MINION_YELLOW_TAUNT_MODIFIER:\n\t\t\tcase MINION_RED_TAUNT_MODIFIER:\n\t\t\tcase MINION_DIVINE_SHIELD_MODIFIER:\n\t\t\tcase MINION_INTRINSIC_VALUE:\n\t\t\t\tvariant.set(feature, levyClamped(weight, -10, 100));\n\t\t\t\tbreak;\n\t\t\tcase MINION_HP_FACTOR:\n\t\t\tcase MINION_SPELL_POWER_MODIFIER:\n\t\t\tcase MINION_STEALTHED_MODIFIER:\n\t\t\tcase MINION_UNTARGETABLE_BY_SPELLS_MODIFIER:\n\t\t\tcase MINION_WINDFURY_MODIFIER:\n\t\t\tcase HARD_REMOVAL_VALUE:\n\t\t\t\tvariant.set(feature, levyClamped(weight, 0, 100));\n\t\t\t\tbreak;\n\t\t\tcase OWN_CARD_COUNT:\n\t\t\tcase OWN_HP_FACTOR:\n\t\t\tcase OPPONENT_HP_FACTOR:\n\t\t\tcase OPPONENT_CARD_COUNT:\n\t\t\t\tvariant.set(feature, levyClamped(weight, -100, 100));\n\t\t\t\tbreak;\n\t\t\tcase RED_MODIFIER:\n\t\t\t\tvariant.set(feature, levyClamped(weight, -100, 0));\n\t\t\t\tbreak;\n\t\t\tcase YELLOW_MODIFIER:\n\t\t\t\tvariant.set(feature, levyClamped(weight, -100, 10));\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn variant;\n\t}\n\n\tprivate static CuckooAgent newRandomSolution() {\n\t\tFeatureVector solution = levyFlight(FeatureVector.getDefault());\n\t\treturn new CuckooAgent(solution);\n\t}\n\n\tprivate final List<CuckooAgent> nests;\n\n\tprivate final IFitnessFunction fitnessFunction;\n\n\tprivate CuckooAgent fittest;\n\n\tpublic CuckooLearner(Deck deckToTrain, List<Deck> decks) {\n\t\tnests = new ArrayList<CuckooAgent>(POPULATION_SIZE);\n\t\tfor (int i = 0; i < POPULATION_SIZE; i++) {\n\t\t\tnests.add(newRandomSolution());\n\t\t}\n\t\tfittest = nests.get(0);\n\n\t\tfitnessFunction = new WinRateFitness(deckToTrain, decks);\n\t}\n\n\tpublic void evolve() {\n\t\t// pick a random solution\n\t\tint i = ThreadLocalRandom.current().nextInt(POPULATION_SIZE);\n\t\t// change it randomly\n\t\tSystem.out.println(\"Random nest picked: \" + i);\n\t\tFeatureVector newBreed = levyFlight(nests.get(i).getData());\n\t\tCuckooAgent cuckoo = new CuckooAgent(newBreed);\n\t\tdouble fitnessI = fitnessFunction.evaluate(newBreed);\n\t\tSystem.out.println(\"Fitness I: \" + fitnessI);\n\t\tcuckoo.setFitness(fitnessI);\n\t\tint j = ThreadLocalRandom.current().nextInt(POPULATION_SIZE);\n\t\tCuckooAgent randomNest = nests.get(j);\n\t\tdouble fitnessJ = fitnessFunction.evaluate(randomNest.getData());\n\t\tSystem.out.println(\"Fitness J: \" + fitnessJ);\n\t\trandomNest.setFitness(fitnessJ);\n\n\t\t// if new solution is better than old one, replace it\n\t\tif (fitnessI > fitnessJ) {\n\t\t\tSystem.out.println(\"New solution is better! Replacing old one...\");\n\t\t\tnests.remove(j);\n\t\t\tnests.add(cuckoo);\n\t\t}\n\n\t\t// rank all existing solutions\n\t\tfor (CuckooAgent nest : nests) {\n\t\t\tnest.setFitness(fitnessFunction.evaluate(nest.getData()));\n\t\t}\n\t\tCollections.sort(nests);\n\t\t// discard a portion of the worst ones\n\t\tint discardAmount = (int) (POPULATION_SIZE * DISCOVERY_RATE);\n\t\tfor (int k = 0; k < discardAmount; k++) {\n\t\t\tCuckooAgent notFitEnough = nests.remove(nests.size() - 1);\n\t\t\tSystem.out.println(\"Solution with fitness value \" + notFitEnough.getFitness() + \" has been removed\");\n\t\t}\n\t\t// fill up with new\n\t\twhile (nests.size() < POPULATION_SIZE) {\n\t\t\tCuckooAgent newBirdInTown = newRandomSolution();\n\t\t\tnewBirdInTown.setFitness(fitnessFunction.evaluate(newBirdInTown.getData()));\n\t\t\tnests.add(newBirdInTown);\n\t\t}\n\t\tCollections.sort(nests);\n\t\tfittest = nests.get(0);\n\t\tSystem.out.println(\"New best solution: \" + fittest.getData());\n\t\tSystem.out.println(\"Fitness value: \" + fittest.getFitness());\n\t}\n\n\tpublic FeatureVector getFittest() {\n\t\treturn fittest.getData();\n\t}\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/threat/cuckoo/IFitnessFunction.java",
    "content": "package net.demilich.metastone.game.behaviour.threat.cuckoo;\n\nimport net.demilich.metastone.game.behaviour.threat.FeatureVector;\n\npublic interface IFitnessFunction {\n\n\tdouble evaluate(FeatureVector featureVector);\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/behaviour/threat/cuckoo/WinRateFitness.java",
    "content": "package net.demilich.metastone.game.behaviour.threat.cuckoo;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.behaviour.threat.FeatureVector;\nimport net.demilich.metastone.game.behaviour.threat.GameStateValueBehaviour;\nimport net.demilich.metastone.game.cards.CardSet;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.game.statistics.GameStatistics;\nimport net.demilich.metastone.game.statistics.Statistic;\nimport net.demilich.metastone.game.gameconfig.PlayerConfig;\n\npublic class WinRateFitness implements IFitnessFunction {\n\n\tprivate class PlayGameTask implements Callable<Void> {\n\n\t\tprivate final GameStatistics stats;\n\t\tprivate final FeatureVector solution;\n\n\t\tpublic PlayGameTask(GameStatistics stats, FeatureVector solution) {\n\t\t\tthis.stats = stats;\n\t\t\tthis.solution = solution;\n\t\t}\n\n\t\t@Override\n\t\tpublic Void call() throws Exception {\n\t\t\tPlayerConfig player1Config = new PlayerConfig(deckToTrain, new GameStateValueBehaviour(solution, \"(current)\"));\n\t\t\tplayer1Config.setName(\"Player 1 (learning)\");\n\t\t\tPlayer player1 = new Player(player1Config);\n\n\t\t\tPlayerConfig player2Config = new PlayerConfig(getRandomDeck(), new GameStateValueBehaviour());\n\t\t\tplayer2Config.setName(\"Player 2 (static)\");\n\t\t\tPlayer player2 = new Player(player2Config);\n\n\t\t\tDeckFormat deckFormat = new DeckFormat();\n\t\t\tfor (CardSet set : CardSet.values()) {\n\t\t\t\tdeckFormat.addSet(set);\n\t\t\t}\n\n\t\t\tGameContext newGame = new GameContext(player1, player2, new GameLogic(), deckFormat);\n\t\t\tnewGame.play();\n\t\t\tsynchronized (stats) {\n\t\t\t\tstats.merge(player1.getStatistics());\n\t\t\t}\n\n\t\t\treturn null;\n\t\t}\n\n\t}\n\n\tprivate static final int NUMBER_OF_GAMES = 50;\n\tprivate final Deck deckToTrain;\n\n\tprivate final List<Deck> decks;\n\n\tpublic WinRateFitness(Deck deckToTrain, List<Deck> decks) {\n\t\tthis.deckToTrain = deckToTrain;\n\t\tthis.decks = decks;\n\t}\n\n\t@Override\n\tpublic double evaluate(FeatureVector solution) {\n\t\tGameStatistics stats = new GameStatistics();\n\t\tdouble earlyWinRate = launchGames(solution, stats, 10);\n\t\tif (earlyWinRate < 40) {\n\t\t\treturn earlyWinRate;\n\t\t}\n\t\treturn launchGames(solution, stats, NUMBER_OF_GAMES - 10);\n\n\t}\n\n\tprivate Deck getRandomDeck() {\n\t\treturn decks.get(ThreadLocalRandom.current().nextInt(decks.size()));\n\t}\n\n\tprivate double launchGames(FeatureVector solution, GameStatistics stats, int numberOfGames) {\n\t\tint cores = Runtime.getRuntime().availableProcessors();\n\t\tExecutorService executor = Executors.newFixedThreadPool(cores);\n\n\t\tList<Future<Void>> futures = new ArrayList<Future<Void>>();\n\t\tfor (int i = 0; i < numberOfGames; i++) {\n\t\t\tPlayGameTask task = new PlayGameTask(stats, solution);\n\t\t\tFuture<Void> future = executor.submit(task);\n\t\t\tfutures.add(future);\n\t\t}\n\n\t\texecutor.shutdown();\n\t\tboolean completed = false;\n\t\twhile (!completed) {\n\t\t\tcompleted = true;\n\t\t\tfor (Future<Void> future : futures) {\n\t\t\t\tif (!future.isDone()) {\n\t\t\t\t\tcompleted = false;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tfuture.get();\n\t\t\t\t} catch (InterruptedException | ExecutionException e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t\t}\n\t\t\t}\n\t\t\tfutures.removeIf(future -> future.isDone());\n\t\t\ttry {\n\t\t\t\tThread.sleep(20);\n\t\t\t} catch (InterruptedException e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\n\t\treturn stats.getDouble(Statistic.WIN_RATE) * 100;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/Card.java",
    "content": "package net.demilich.metastone.game.cards;\n\nimport java.util.EnumMap;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.PlayCardAction;\nimport net.demilich.metastone.game.cards.desc.CardDesc;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Race;\nimport net.demilich.metastone.game.spells.desc.BattlecryDesc;\nimport net.demilich.metastone.game.spells.desc.valueprovider.ValueProvider;\nimport net.demilich.metastone.game.targeting.CardLocation;\nimport net.demilich.metastone.game.targeting.CardReference;\nimport net.demilich.metastone.game.targeting.IdFactory;\n\npublic abstract class Card extends Entity {\n\n\tprivate String description = \"\";\n\tprivate final CardType cardType;\n\tprivate final CardSet cardSet;\n\tprivate final Rarity rarity;\n\tprivate HeroClass heroClass;\n\tprivate HeroClass[] heroClasses;\n\tprivate boolean collectible = true;\n\tprivate CardLocation location;\n\tprivate BattlecryDesc battlecry;\n\tprivate ValueProvider manaCostModifier;\n\tprivate final String cardId;\n\n\tpublic Card(CardDesc desc) {\n\t\tcardId = desc.id;\n\t\tsetName(desc.name);\n\t\tsetDescription(desc.description);\n\t\tsetCollectible(desc.collectible);\n\t\tcardType = desc.type;\n\t\tcardSet = desc.set;\n\t\trarity = desc.rarity;\n\t\theroClass = desc.heroClass;\n\t\tif (desc.heroClasses != null) {\n\t\t\theroClasses = desc.heroClasses;\n\t\t}\n\n\t\tsetAttribute(Attribute.BASE_MANA_COST, desc.baseManaCost);\n\t\tif (desc.attributes != null) {\n\t\t\tattributes.putAll(desc.attributes);\n\t\t}\n\n\t\tif (desc.manaCostModifier != null) {\n\t\t\tmanaCostModifier = desc.manaCostModifier.create();\n\t\t}\n\n\t\tif (desc.passiveTrigger != null) {\n\t\t\tattributes.put(Attribute.PASSIVE_TRIGGER, desc.passiveTrigger);\n\t\t}\n\n\t\tif (desc.deckTrigger != null) {\n\t\t\tattributes.put(Attribute.DECK_TRIGGER, desc.deckTrigger);\n\t\t}\n\t}\n\n\t@Override\n\tpublic Card clone() {\n\t\tCard clone = (Card) super.clone();\n\t\tclone.attributes = new EnumMap<>(getAttributes());\n\t\treturn clone;\n\t}\n\n\tpublic boolean evaluateExpression(String operator, int value1, int value2) {\n\t\tswitch(operator) {\n\t\tcase \"=\":\n\t\t\treturn value1 == value2;\n\t\tcase \">\":\n\t\t\treturn value1 > value2;\n\t\tcase \"<\":\n\t\t\treturn value1 < value2;\n\t\tcase \">=\":\n\t\t\treturn value1 >= value2;\n\t\tcase \"<=\":\n\t\t\treturn value1 <= value2;\n\t\tcase \"!=\":\n\t\t\treturn value1 != value2;\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic int getBaseManaCost() {\n\t\treturn getAttributeValue(Attribute.BASE_MANA_COST);\n\t}\n\n\tpublic BattlecryDesc getBattlecry() {\n\t\treturn battlecry;\n\t}\n\n\tpublic String getCardId() {\n\t\treturn cardId;\n\t}\n\n\tpublic CardReference getCardReference() {\n\t\treturn new CardReference(getOwner(), getLocation(), getId(), getName());\n\t}\n\n\tpublic CardSet getCardSet() {\n\t\treturn cardSet;\n\t}\n\n\tpublic CardType getCardType() {\n\t\treturn cardType;\n\t}\n\n\tpublic HeroClass getHeroClass() {\n\t\treturn heroClass;\n\t}\n\n\tpublic HeroClass[] getHeroClasses() {\n\t\treturn heroClasses;\n\t}\n\n\tpublic Card getCopy() {\n\t\tCard copy = clone();\n\t\tcopy.setId(IdFactory.UNASSIGNED);\n\t\tcopy.setLocation(CardLocation.PENDING);\n\t\tcopy.removeAttribute(Attribute.ATTACK_BONUS);\n\t\tcopy.removeAttribute(Attribute.HP_BONUS);\n\t\tcopy.removeAttribute(Attribute.MANA_COST_MODIFIER);\n\t\treturn copy;\n\t}\n\n\tpublic String getDescription() {\n\t\treturn description;\n\t}\n\n\t@Override\n\tpublic EntityType getEntityType() {\n\t\treturn EntityType.CARD;\n\t}\n\n\tpublic CardLocation getLocation() {\n\t\treturn location;\n\t}\n\n\tpublic int getManaCost(GameContext context, Player player) {\n\t\tint actualManaCost = getBaseManaCost();\n\t\tif (manaCostModifier != null) {\n\t\t\tactualManaCost -= manaCostModifier.getValue(context, player, null, this);\n\t\t}\n\t\treturn actualManaCost;\n\t}\n\n\tpublic Rarity getRarity() {\n\t\treturn rarity;\n\t}\n\n\tpublic Race getRace() {\n\t\treturn hasAttribute(Attribute.RACE) ? (Race) getAttribute(Attribute.RACE) : Race.NONE;\n\t}\n\n\tpublic boolean hasBattlecry() {\n\t\treturn this.battlecry != null;\n\t}\n\n\tpublic boolean hasHeroClass(HeroClass heroClass) {\n\t\tif (getHeroClasses() != null) {\n\t\t\tfor (HeroClass h : getHeroClasses()) {\n\t\t\t\tif (heroClass.equals(h)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (heroClass == getHeroClass()) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic boolean isCollectible() {\n\t\treturn collectible;\n\t}\n\n\tpublic boolean matchesFilter(String filter) {\n\t\tif (filter == null || filter == \"\") {\n\t\t\treturn true;\n\t\t}\n\t\tString[] filters = filter.split(\" \");\n\t\tfor (String splitString : filters) {\n\t\t\tif (!matchesSplitFilter(splitString)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\tpublic boolean matchesSplitFilter(String filter) {\n\t\tfilter = filter.toLowerCase();\n\t\tString[] split = filter.split(\"((<|>)=?)|(!?=)\");\n\t\tif (split.length >= 2) {\n\t\t\tint value;\n\t\t\ttry {\n\t\t\t\tvalue = Integer.parseInt(split[1]);\n\t\t\t} catch (Exception e) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tString operator = filter.substring(split[0].length(), filter.indexOf(split[1], split[0].length() + 1));\n\t\t\tif ((split[0].contains(\"mana\") || split[0].contains(\"cost\")) &&\n\t\t\t\t\tevaluateExpression(operator, getBaseManaCost(), value)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (split[0].contains(\"attack\") && hasAttribute(Attribute.BASE_ATTACK) &&\n\t\t\t\t\tevaluateExpression(operator, getAttributeValue(Attribute.BASE_ATTACK), value)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif ((split[0].contains(\"health\") || split[0].contains(\"hp\")) && hasAttribute(Attribute.BASE_HP) &&\n\t\t\t\t\tevaluateExpression(operator, getAttributeValue(Attribute.BASE_HP), value)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\tif (getRarity().toString().toLowerCase().contains(filter)) {\n\t\t\treturn true;\n\t\t}\n\t\tif (getRace() != Race.NONE && getRace().toString().toLowerCase().contains(filter)) {\n\t\t\treturn true;\n\t\t}\n\t\tString cardType = getCardType() == CardType.CHOOSE_ONE ? \"SPELL\" : getCardType().toString();\n\t\tif (cardType.toLowerCase().contains(filter)) {\n\t\t\treturn true;\n\t\t}\n\t\tif ((getHeroClass() == HeroClass.ANY && \"neutral\".contains(filter))\n\t\t\t\t|| (getHeroClass() != HeroClass.ANY && (getHeroClass().toString().toLowerCase().contains(filter)\n\t\t\t\t|| \"class\".contains(filter)))) {\n\t\t\treturn true;\n\t\t}\n\t\tString lowerCaseName = getName().toLowerCase();\n\t\tif (lowerCaseName.contains(filter)) {\n\t\t\treturn true;\n\t\t}\n\t\tString regexName = lowerCaseName.replaceAll(\"[:,\\'\\\\- ]+\", \"\");\n\t\tif (regexName.contains(filter)) {\n\t\t\treturn true;\n\t\t}\n\t\tif (getDescription() != null) {\n\t\t\tString lowerCaseDescription = getDescription().toLowerCase();\n\t\t\tif (lowerCaseDescription.contains(filter)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tpublic abstract PlayCardAction play();\n\n\tpublic void setBattlecry(BattlecryDesc battlecry) {\n\t\tthis.battlecry = battlecry;\n\t}\n\n\tpublic void setCollectible(boolean collectible) {\n\t\tthis.collectible = collectible;\n\t}\n\n\tpublic void setDescription(String description) {\n\t\tthis.description = description;\n\t}\n\n\tpublic void setLocation(CardLocation location) {\n\t\tthis.location = location;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn String.format(\"[%s '%s' %s Manacost:%d]\", getCardType(), getName(), getReference(), getBaseManaCost());\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/CardCatalogue.java",
    "content": "package net.demilich.metastone.game.cards;\r\n\r\nimport java.io.File;\r\nimport java.io.IOException;\r\nimport java.net.URISyntaxException;\r\nimport java.util.ArrayList;\r\nimport java.util.Collection;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\nimport java.util.function.Predicate;\r\n\r\nimport org.jsoup.Jsoup;\r\nimport org.jsoup.Connection.Response;\r\nimport org.jsoup.nodes.Document;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport net.demilich.metastone.BuildConfig;\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.cards.desc.CardDesc;\r\nimport net.demilich.metastone.game.decks.DeckFormat;\r\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\r\nimport net.demilich.metastone.utils.MetastoneProperties;\r\nimport net.demilich.metastone.utils.ResourceInputStream;\r\nimport net.demilich.metastone.utils.ResourceLoader;\r\nimport net.demilich.metastone.utils.UserHomeMetastone;\r\n\r\npublic class CardCatalogue {\r\n\r\n\tpublic static final String CARDS_FOLDER = \"cards\";\r\n\tpublic static final String LOCAL_CARDS_FOLDER = \"../cards/src/main/resources/cards/\";\r\n\tpublic static final String CARDS_FOLDER_PATH = UserHomeMetastone.getPath() + File.separator + CARDS_FOLDER;\r\n\tpublic static final String CARDS_COPIED_PROPERTY = \"cardRevision\";\r\n\r\n\tprivate static Logger logger = LoggerFactory.getLogger(CardCatalogue.class);\r\n\r\n\tprivate final static CardCollection cards = new CardCollection();\r\n\r\n\tpublic static void add(Card card) {\r\n\t\tcards.add(card);\r\n\t}\r\n\r\n\tpublic static CardCollection getAll() {\r\n\t\tCardCollection result = new CardCollection();\r\n\t\tfor (Card card : cards) {\r\n\t\t\tresult.add(card);\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n\r\n\tpublic static Card getCardById(String id) {\r\n\t\tfor (Card card : cards) {\r\n\t\t\tif (card.getCardId() != null && card.getCardId().equalsIgnoreCase(id)) {\r\n\t\t\t\treturn card.clone();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn null;\r\n\t}\r\n\r\n\tpublic static Card getCardByName(String name) {\r\n\t\tfor (Card card : cards) {\r\n\t\t\tif (card.isCollectible() && card.getName().equals(name)) {\r\n\t\t\t\treturn card.clone();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn null;\r\n\t}\r\n\t\r\n\tpublic static Card getCardByBlizzardId(String id) throws IOException{\r\n\t\tResponse response= Jsoup.connect(\"http://metastats.net/cardstats/\" + id.toUpperCase() + \"/\")\r\n\t\t           .ignoreContentType(true)\r\n\t\t           .userAgent(\"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0\")  \r\n\t\t           .referrer(\"http://www.google.com\")   \r\n\t\t           .timeout(12000) \r\n\t\t           .followRedirects(true)\r\n\t\t           .execute();\r\n\t\tDocument doc = response.parse();\r\n\t\treturn getCardByName(doc.getElementsByTag(\"h3\").get(0).text());\r\n\t}\r\n\r\n\tpublic static CardCollection getHeroes() {\r\n\t\treturn query(null, card -> card.isCollectible() && card.getCardType() == CardType.HERO);\r\n\t}\r\n\r\n\tpublic static CardCollection getHeroPowers(DeckFormat deckFormat) {\r\n\t\treturn query(deckFormat, card -> card.isCollectible() && card.getCardType() == CardType.HERO_POWER);\r\n\t}\r\n\r\n\tpublic static CardCollection query(DeckFormat deckFormat) {\r\n\t\treturn query(deckFormat, (CardType) null, (Rarity) null, (HeroClass) null, (Attribute) null);\r\n\t}\r\n\r\n\tpublic static CardCollection query(DeckFormat deckFormat, CardType cardType) {\r\n\t\treturn query(deckFormat, cardType, (Rarity) null, (HeroClass) null, (Attribute) null);\r\n\t}\r\n\r\n\tpublic static CardCollection query(DeckFormat deckFormat, HeroClass heroClass) {\r\n\t\treturn query(deckFormat, (CardType) null, (Rarity) null, heroClass, (Attribute) null);\r\n\t}\r\n\r\n\tpublic static CardCollection query(DeckFormat deckFormat, CardType cardType, Rarity rarity, HeroClass heroClass) {\r\n\t\treturn query(deckFormat, cardType, rarity, heroClass, (Attribute) null);\r\n\t}\r\n\r\n\tpublic static CardCollection query(DeckFormat deckFormat, CardType cardType, Rarity rarity, HeroClass heroClass, Attribute tag) {\r\n\t\tCardCollection result = new CardCollection();\r\n\t\tfor (Card card : cards) {\r\n\t\t\tif (!deckFormat.isInFormat(card)) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif (!card.isCollectible()) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif (cardType != null && !card.getCardType().isCardType(cardType)) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\t// per default, do not include heroes or hero powers\r\n\t\t\tif (card.getCardType().isCardType(CardType.HERO_POWER) || card.getCardType().isCardType(CardType.HERO)) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif (rarity != null && !card.getRarity().isRarity(rarity)) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif (heroClass != null && !card.hasHeroClass(heroClass)) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif (tag != null && !card.hasAttribute(tag)) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tresult.add(card.clone());\r\n\t\t}\r\n\r\n\t\treturn result;\r\n\t}\r\n\r\n\tpublic static CardCollection query(DeckFormat deckFormat, Predicate<Card> filter) {\r\n\t\tCardCollection result = new CardCollection();\r\n\t\tfor (Card card : cards) {\r\n\t\t\tif (deckFormat != null && !deckFormat.isInFormat(card)) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif (filter.test(card)) {\r\n\t\t\t\tresult.add(card);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n\t\r\n\tpublic static void loadLocalCards() throws IOException, URISyntaxException, CardParseException {\r\n\t\t// load cards from ~/metastone/cards on the file system\r\n\t\tCollection<ResourceInputStream> inputStreams = ResourceLoader.loadJsonInputStreams(CARDS_FOLDER, false);\r\n\t\tloadCards(inputStreams);\r\n\t}\r\n\t\r\n\tpublic static void loadCards() throws IOException, URISyntaxException, CardParseException {\r\n\t\t// load cards from ~/metastone/cards on the file system\r\n\t\tCollection<ResourceInputStream> inputStreams = ResourceLoader.loadJsonInputStreams(CARDS_FOLDER_PATH, true);\r\n\t\tloadCards(inputStreams);\r\n\t}\r\n\r\n\t\r\n\tprivate static void loadCards(Collection<ResourceInputStream> inputStreams) throws IOException, URISyntaxException, CardParseException {\r\n\t\tMap<String, CardDesc> cardDesc = new HashMap<String, CardDesc>();\r\n\t\tArrayList<String> badCards = new ArrayList<>();\r\n\t\tCardParser cardParser = new CardParser();\r\n\t\tfor (ResourceInputStream resourceInputStream : inputStreams) {\r\n\t\t\ttry {\r\n\t\t\t\tCardDesc desc = cardParser.parseCard(resourceInputStream);\r\n\t\t\t\tif (cardDesc.containsKey(desc.id)) {\r\n\t\t\t\t\tlogger.error(\"Card id {} is duplicated!\", desc.id);\r\n\t\t\t\t}\r\n\t\t\t\tcardDesc.put(desc.id, desc);\r\n\t\t\t} catch (Exception e) {\r\n\t\t\t\t//logger.error(\"Error parsing card '{}'\", resourceInputStream.fileName);\r\n\t\t\t\tlogger.error(e.toString());\r\n\t\t\t\tbadCards.add(resourceInputStream.fileName);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfor (CardDesc desc : cardDesc.values()) {\r\n\t\t\tCard instance = desc.createInstance();\r\n\t\t\tCardCatalogue.add(instance);\r\n\t\t\tlogger.debug(\"Adding {} to CardCatalogue\", instance);\r\n\t\t}\r\n\t\t\r\n\t\tif (!badCards.isEmpty()) {\r\n\t\t\tthrow new CardParseException(badCards);\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static void copyCardsFromResources() throws IOException, URISyntaxException {\r\n\t\t// if we have not copied cards to the USER_HOME_METASTONE cards folder,\r\n\t\t// then do so now\r\n\t\tint cardRevision = MetastoneProperties.getInt(CARDS_COPIED_PROPERTY, 0);\r\n\t\tSystem.out.println(\"Existing card revision = \" + cardRevision);\r\n\t\tif (BuildConfig.CARD_REVISION > cardRevision) {\r\n\t\t\tlogger.info(\"Card update required: MetaStone card revision is: {}, last card update was with revision {}\", BuildConfig.CARD_REVISION, cardRevision);\r\n\t\t\tResourceLoader.copyFromResources(CARDS_FOLDER, CARDS_FOLDER_PATH);\r\n\r\n\t\t\t// set a property to indicate that we have copied the cards with current version\r\n\t\t\tMetastoneProperties.setProperty(CARDS_COPIED_PROPERTY, String.valueOf(BuildConfig.CARD_REVISION));\r\n\t\t} else {\r\n\t\t\tlogger.info(\"Cards in user home folder are up-to-date: rev {}\", cardRevision);\r\n\t\t}\r\n\t}\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/CardCollection.java",
    "content": "package net.demilich.metastone.game.cards;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.function.Predicate;\n\npublic class CardCollection implements Iterable<Card>, Cloneable {\n\n\tprivate List<Card> cards = new ArrayList<Card>();\n\n\tpublic CardCollection() {\n\n\t}\n\n\tpublic void add(Card card) {\n\t\tcards.add(card);\n\t}\n\n\tpublic void addAll(CardCollection cardCollection) {\n\t\tfor (Card card : cardCollection) {\n\t\t\tcards.add(card.clone());\n\t\t}\n\t}\n\t\n\tpublic void addRandomly(Card card) {\n\t\tint index = ThreadLocalRandom.current().nextInt(cards.size() + 1);\n\t\tcards.add(index, card);\n\t}\n\n\tpublic CardCollection clone() {\n\t\tCardCollection clone = new CardCollection();\n\t\tfor (Card card : cards) {\n\t\t\tclone.add(card.clone());\n\t\t}\n\n\t\treturn clone;\n\t}\n\n\tpublic boolean contains(Card card) {\n\t\treturn cards.contains(card);\n\t}\n\t\n\tpublic boolean containsCard(Card card) {\n\t\tif (card == null) {\n\t\t\treturn false;\n\t\t}\n\t\tfor (Card other : cards) {\n\t\t\tif (other.getCardId().equals(card.getCardId())) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic Card get(int index) {\n\t\treturn cards.get(index);\n\t}\n\n\tpublic int getCount() {\n\t\treturn cards.size();\n\t}\n\n\tpublic Card getRandom() {\n\t\tif (cards.isEmpty()) {\n\t\t\treturn null;\n\t\t}\n\t\treturn cards.get(ThreadLocalRandom.current().nextInt(cards.size()));\n\t}\n\n\tpublic Card getRandomOfType(CardType cardType) {\n\t\tList<Card> relevantCards = new ArrayList<>();\n\t\tfor (Card card : cards) {\n\t\t\tif (card.getCardType().isCardType(cardType)) {\n\t\t\t\trelevantCards.add(card);\n\t\t\t}\n\t\t}\n\t\tif (relevantCards.isEmpty()) {\n\t\t\treturn null;\n\t\t}\n\t\treturn relevantCards.get(ThreadLocalRandom.current().nextInt(relevantCards.size()));\n\t}\n\n\tpublic boolean hasCardOfType(CardType cardType) {\n\t\tfor (Card card : cards) {\n\t\t\tif (card.getCardType().isCardType(cardType)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic boolean isEmpty() {\n\t\treturn cards.isEmpty();\n\t}\n\n\t@Override\n\tpublic Iterator<Card> iterator() {\n\t\treturn cards.iterator();\n\t}\n\n\tpublic Card peekFirst() {\n\t\treturn cards.get(0);\n\t}\n\n\tpublic boolean remove(Card card) {\n\t\treturn cards.remove(card);\n\t}\n\n\tpublic void removeAll() {\n\t\tcards.clear();\n\t}\n\n\tpublic void removeAll(Predicate<Card> filter) {\n\t\tcards.removeIf(filter);\n\t}\n\n\tpublic Card removeFirst() {\n\t\treturn cards.remove(0);\n\t}\n\n\tpublic boolean replace(Card oldCard, Card newCard) {\n\t\tint index = cards.indexOf(oldCard);\n\t\tif (index != -1) {\n\t\t\tcards.set(index, newCard);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic void shuffle() {\n\t\tCollections.shuffle(cards);\n\t}\n\n\tpublic void sortByManaCost() {\n\t\tComparator<Card> manaComparator = new Comparator<Card>() {\n\n\t\t\t@Override\n\t\t\tpublic int compare(Card card1, Card card2) {\n\t\t\t\tInteger manaCost1 = card1.getBaseManaCost();\n\t\t\t\tInteger manaCost2 = card2.getBaseManaCost();\n\t\t\t\treturn manaCost1.compareTo(manaCost2);\n\t\t\t}\n\t\t};\n\t\tcards.sort(manaComparator);\n\t}\n\n\tpublic void sortByName() {\n\t\tcards.sort((card1, card2) -> card1.getName().compareTo(card2.getName()));\n\t}\n\n\tpublic List<Card> toList() {\n\t\treturn new ArrayList<>(cards);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/CardDescType.java",
    "content": "package net.demilich.metastone.game.cards;\n\npublic enum CardDescType {\n\tAURA,\n\tATTACK,\n\tATTRIBUTES,\n\tBASE_ATTACK,\n\tBASE_HP,\n\tBATTLECRY,\n\tBOTH_OPTIONS,\n\tCARD_TYPE,\n\tDEATHRATTLE,\n\tDECK_TRIGGER,\n\tDESCRIPTION,\n\tHERO_CLASS,\n\tMAX_HP,\n\tOPTIONS,\n\tPASSIVE_TRIGGER,\n\tRARITY,\n\tRACE,\n\tSPELL,\n\tTRIGGER,\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/CardParseException.java",
    "content": "package net.demilich.metastone.game.cards;\n\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class CardParseException extends Exception {\n\n\t/**\n\t * \n\t */\n\tprivate static final long serialVersionUID = 1L;\n\t\n\t\n\tpublic CardParseException(List<String> badCards) {\n\t\tsuper(getMessage(badCards));\n\t}\n\n\tprivate static String getMessage(List<String> badCards) {\n\t\tString message = \"The following card files contain errors:\\n\";\n\t\tmessage += Arrays.toString(badCards.toArray());\n\t\tmessage+=\"\\n\\nYou can either fix the errors manually or remove the 'cards.copied' entry from your metastone.properties file to restore all cards.\";\n\t\treturn message;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/CardParser.java",
    "content": "package net.demilich.metastone.game.cards;\r\n\r\nimport java.io.FileNotFoundException;\r\nimport java.io.InputStreamReader;\r\nimport java.lang.reflect.Type;\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.utils.ResourceInputStream;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport com.google.gson.Gson;\r\nimport com.google.gson.GsonBuilder;\r\nimport com.google.gson.JsonElement;\r\nimport com.google.gson.reflect.TypeToken;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.cards.desc.AttributeDeserializer;\r\nimport net.demilich.metastone.game.cards.desc.AuraDeserializer;\r\nimport net.demilich.metastone.game.cards.desc.CardCostModifierDeserializer;\r\nimport net.demilich.metastone.game.cards.desc.CardDesc;\r\nimport net.demilich.metastone.game.cards.desc.ChooseBattlecryCardDesc;\r\nimport net.demilich.metastone.game.cards.desc.ChooseOneCardDesc;\r\nimport net.demilich.metastone.game.cards.desc.ConditionDeserializer;\r\nimport net.demilich.metastone.game.cards.desc.HeroCardDesc;\r\nimport net.demilich.metastone.game.cards.desc.HeroPowerCardDesc;\r\nimport net.demilich.metastone.game.cards.desc.MinionCardDesc;\r\nimport net.demilich.metastone.game.cards.desc.PermanentCardDesc;\r\nimport net.demilich.metastone.game.cards.desc.QuestCardDesc;\r\nimport net.demilich.metastone.game.cards.desc.SecretCardDesc;\r\nimport net.demilich.metastone.game.cards.desc.SpellCardDesc;\r\nimport net.demilich.metastone.game.cards.desc.SpellDeserializer;\r\nimport net.demilich.metastone.game.cards.desc.ValueProviderDeserializer;\r\nimport net.demilich.metastone.game.cards.desc.WeaponCardDesc;\r\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.desc.aura.AuraDesc;\r\nimport net.demilich.metastone.game.spells.desc.condition.ConditionDesc;\r\nimport net.demilich.metastone.game.spells.desc.manamodifier.CardCostModifierDesc;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDeserializer;\r\nimport net.demilich.metastone.game.spells.desc.valueprovider.ValueProviderDesc;\r\n\r\npublic class CardParser {\r\n\r\n\tprivate static Logger logger = LoggerFactory.getLogger(CardParser.class);\r\n\r\n\tprivate final Gson gson;\r\n\r\n\tpublic CardParser() {\r\n\t\tGsonBuilder gsonBuilder = new GsonBuilder();\r\n\t\tgsonBuilder.registerTypeAdapter(SpellDesc.class, new SpellDeserializer());\r\n\t\tType mapType = new TypeToken<Map<Attribute, Object>>() {\r\n\t\t}.getType();\r\n\t\tgsonBuilder.registerTypeAdapter(mapType, new AttributeDeserializer());\r\n\t\tgsonBuilder.registerTypeAdapter(ConditionDesc.class, new ConditionDeserializer());\r\n\t\tgsonBuilder.registerTypeAdapter(EventTriggerDesc.class, new EventTriggerDeserializer());\r\n\t\tgsonBuilder.registerTypeAdapter(AuraDesc.class, new AuraDeserializer());\r\n\t\tgsonBuilder.registerTypeAdapter(ValueProviderDesc.class, new ValueProviderDeserializer());\r\n\t\tgsonBuilder.registerTypeAdapter(CardCostModifierDesc.class, new CardCostModifierDeserializer());\r\n\t\tgson = gsonBuilder.create();\r\n\t}\r\n\r\n\tpublic CardDesc parseCard(ResourceInputStream resourceInputStream) throws FileNotFoundException {\r\n\t\tJsonElement jsonData = gson.fromJson(new InputStreamReader(resourceInputStream.inputStream), JsonElement.class);\r\n\r\n\t\tString id = resourceInputStream.fileName.split(\"(\\\\.json)\")[0];\r\n\t\tjsonData.getAsJsonObject().addProperty(\"id\", id);\r\n\t\tif (!jsonData.getAsJsonObject().has(\"name\")) {\r\n\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'name' attribute!\");\r\n\t\t}\r\n\t\tif (!jsonData.getAsJsonObject().has(\"type\")) {\r\n\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'type' attribute!\");\r\n\t\t}\r\n\t\tif (!jsonData.getAsJsonObject().has(\"heroClass\")) {\r\n\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'heroClass' attribute!\");\r\n\t\t} else {\r\n\t\t\tboolean check = false;\r\n\t\t\tfor (HeroClass heroClass : HeroClass.values()) {\r\n\t\t\t\tif (jsonData.getAsJsonObject().get(\"heroClass\").getAsString().equalsIgnoreCase(heroClass.toString())) {\r\n\t\t\t\t\tcheck = true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (!check) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" has corrupted 'heroClass' attribute!\");\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (!jsonData.getAsJsonObject().has(\"rarity\")) {\r\n\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'rarity' attribute!\");\r\n\t\t} else {\r\n\t\t\tboolean check = false;\r\n\t\t\tfor (Rarity rarity : Rarity.values()) {\r\n\t\t\t\tif (jsonData.getAsJsonObject().get(\"rarity\").getAsString().equalsIgnoreCase(rarity.toString())) {\r\n\t\t\t\t\tcheck = true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (!check) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" has corrupted 'rarity' attribute!\");\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (!jsonData.getAsJsonObject().has(\"collectible\")) {\r\n\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'collectible' attribute!\");\r\n\t\t}\r\n\t\tif (!jsonData.getAsJsonObject().has(\"set\")) {\r\n\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'set' attribute!\");\r\n\t\t}\r\n\t\tCardType type = CardType.valueOf((String) jsonData.getAsJsonObject().get(\"type\").getAsString());\r\n\t\tswitch (type) {\r\n\t\tcase SPELL:\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"baseManaCost\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'baseManaCost' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"description\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'description' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"spell\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'spell' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (jsonData.getAsJsonObject().has(\"trigger\")) {\r\n\t\t\t\treturn gson.fromJson(jsonData, SecretCardDesc.class);\r\n\t\t\t} else if (jsonData.getAsJsonObject().has(\"quest\")) {\r\n\t\t\t\treturn gson.fromJson(jsonData, QuestCardDesc.class);\r\n\t\t\t} else {\r\n\t\t\t\tif (!jsonData.getAsJsonObject().has(\"targetSelection\")) {\r\n\t\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'targetSelection' attribute!\");\r\n\t\t\t\t}\r\n\t\t\t\treturn gson.fromJson(jsonData, SpellCardDesc.class);\r\n\t\t\t}\r\n\t\tcase CHOOSE_ONE:\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"baseManaCost\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'baseManaCost' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"description\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'description' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"options\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'options' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"bothOptions\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'bothOptions' attribute!\");\r\n\t\t\t}\r\n\t\t\treturn gson.fromJson(jsonData, ChooseOneCardDesc.class);\r\n\t\tcase MINION:\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"baseManaCost\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'baseManaCost' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"baseAttack\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'baseAttack' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"baseHp\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'baseHp' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"description\") && (jsonData.getAsJsonObject().has(\"battlecry\") || \r\n\t\t\t\t\tjsonData.getAsJsonObject().has(\"deathrattle\") ||jsonData.getAsJsonObject().has(\"attributes\") || \r\n\t\t\t\t\tjsonData.getAsJsonObject().has(\"trigger\") || jsonData.getAsJsonObject().has(\"passiveTrigger\") || \r\n\t\t\t\t\tjsonData.getAsJsonObject().has(\"deckTrigger\") || jsonData.getAsJsonObject().has(\"options\"))) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'description' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (jsonData.getAsJsonObject().has(\"options\")) {\r\n\t\t\t\treturn gson.fromJson(jsonData, ChooseBattlecryCardDesc.class);\r\n\t\t\t} else {\r\n\t\t\t\treturn gson.fromJson(jsonData, MinionCardDesc.class);\r\n\t\t\t}\r\n\t\tcase PERMANENT:\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"description\") && (jsonData.getAsJsonObject().has(\"battlecry\") || \r\n\t\t\t\t\tjsonData.getAsJsonObject().has(\"deathrattle\") ||jsonData.getAsJsonObject().has(\"attributes\") || \r\n\t\t\t\t\tjsonData.getAsJsonObject().has(\"trigger\") || jsonData.getAsJsonObject().has(\"passiveTrigger\") || \r\n\t\t\t\t\tjsonData.getAsJsonObject().has(\"deckTrigger\") || jsonData.getAsJsonObject().has(\"options\"))) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'description' attribute!\");\r\n\t\t\t}\r\n\t\t\treturn gson.fromJson(jsonData, PermanentCardDesc.class);\r\n\t\tcase WEAPON:\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"baseManaCost\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'baseManaCost' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"damage\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'damage' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"durability\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'durability' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"description\") && (jsonData.getAsJsonObject().has(\"battlecry\") || \r\n\t\t\t\t\tjsonData.getAsJsonObject().has(\"deathrattle\") ||jsonData.getAsJsonObject().has(\"attributes\") || \r\n\t\t\t\t\tjsonData.getAsJsonObject().has(\"trigger\") || jsonData.getAsJsonObject().has(\"passiveTrigger\") || \r\n\t\t\t\t\tjsonData.getAsJsonObject().has(\"deckTrigger\") || jsonData.getAsJsonObject().has(\"onEquip\") || \r\n\t\t\t\t\tjsonData.getAsJsonObject().has(\"onUnequip\"))) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'description' attribute!\");\r\n\t\t\t}\r\n\t\t\treturn gson.fromJson(jsonData, WeaponCardDesc.class);\r\n\t\tcase HERO_POWER:\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"baseManaCost\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'baseManaCost' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"description\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'description' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"spell\") && !jsonData.getAsJsonObject().has(\"options\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'spell' or 'options' attribute!\");\r\n\t\t\t}\r\n\t\t\tif (!jsonData.getAsJsonObject().has(\"targetSelection\")) {\r\n\t\t\t\tthrow new RuntimeException(resourceInputStream.fileName + \" is missing 'targetSelection' attribute!\");\r\n\t\t\t}\r\n\t\t\treturn gson.fromJson(jsonData, HeroPowerCardDesc.class);\t\r\n\t\tcase HERO:\r\n\t\t\treturn gson.fromJson(jsonData, HeroCardDesc.class);\r\n\t\tdefault:\r\n\t\t\tlogger.error(\"Unknown cardType: \" + type);\r\n\t\t\tbreak;\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/CardSet.java",
    "content": "package net.demilich.metastone.game.cards;\r\n\r\npublic enum CardSet {\r\n\tANY,\r\n\tBASIC,\r\n\tCLASSIC,\r\n\tREWARD,\r\n\tPROMO,\r\n\tHALL_OF_FAME,\r\n\tNAXXRAMAS,\r\n\tGOBLINS_VS_GNOMES,\r\n\tBLACKROCK_MOUNTAIN,\r\n\tTHE_GRAND_TOURNAMENT,\r\n\tLEAGUE_OF_EXPLORERS,\r\n\tTHE_OLD_GODS,\r\n\tONE_NIGHT_IN_KARAZHAN,\r\n\tMEAN_STREETS_OF_GADGETZAN,\r\n\t\r\n\tCUSTOM\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/CardType.java",
    "content": "package net.demilich.metastone.game.cards;\n\npublic enum CardType {\n\tHERO,\n\tMINION,\n\tPERMANENT,\n\tSPELL,\n\tWEAPON,\n\tHERO_POWER,\n\tCHOOSE_ONE;\n\t\n\tpublic boolean isCardType(CardType cardType) {\n\t\tif (this == CHOOSE_ONE && cardType == SPELL) {\n\t\t\treturn true;\n\t\t} else if (this == cardType) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/ChooseBattlecryCard.java",
    "content": "package net.demilich.metastone.game.cards;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.actions.BattlecryAction;\nimport net.demilich.metastone.game.actions.PlayCardAction;\nimport net.demilich.metastone.game.actions.PlayMinionCardAction;\nimport net.demilich.metastone.game.cards.desc.ChooseBattlecryCardDesc;\nimport net.demilich.metastone.game.spells.desc.BattlecryDesc;\n\npublic class ChooseBattlecryCard extends MinionCard implements IChooseOneCard {\n\n\tprivate final BattlecryDesc[] battlecryOptions;\n\tprivate final BattlecryDesc battlecryBothOptions;\n\n\tpublic ChooseBattlecryCard(ChooseBattlecryCardDesc desc) {\n\t\tsuper(desc);\n\t\tthis.battlecryOptions = desc.options;\n\t\tthis.battlecryBothOptions = desc.bothOptions;\n\t\tsetAttribute(Attribute.CHOOSE_ONE);\n\t}\n\n\tpublic boolean hasBothOptions() {\n\t\treturn battlecryBothOptions != null;\n\t}\n\n\t@Override\n\tpublic PlayCardAction[] playOptions() {\n\t\tPlayCardAction[] actions = new PlayCardAction[battlecryOptions.length];\n\t\tfor (int i = 0; i < battlecryOptions.length; i++) {\n\t\t\tBattlecryDesc battlecryOption = battlecryOptions[i];\n\t\t\tBattlecryAction battlecry = BattlecryAction.createBattlecry(battlecryOption.spell, battlecryOption.getTargetSelection());\n\t\t\tPlayCardAction option = new PlayMinionCardAction(getCardReference(), battlecry);\n\t\t\toption.setActionSuffix(battlecryOption.description);\n\t\t\toption.setGroupIndex(i);\n\t\t\tactions[i] = option;\n\t\t}\n\t\treturn actions;\n\t}\n\n\t@Override\n\tpublic PlayCardAction playBothOptions() {\n\t\tBattlecryDesc battlecryOption = battlecryBothOptions;\n\t\tBattlecryAction battlecry = BattlecryAction.createBattlecry(battlecryOption.spell, battlecryOption.getTargetSelection());\n\t\tPlayCardAction option = new PlayMinionCardAction(getCardReference(), battlecry);\n\t\toption.setActionSuffix(battlecryOption.description);\n\t\treturn option;\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/ChooseOneCard.java",
    "content": "package net.demilich.metastone.game.cards;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.actions.PlayCardAction;\nimport net.demilich.metastone.game.actions.PlayChooseOneCardAction;\nimport net.demilich.metastone.game.cards.desc.ChooseOneCardDesc;\n\npublic class ChooseOneCard extends Card implements IChooseOneCard {\n\n\tprivate String[] cardIds;\n\tprivate String cardId;\n\n\tpublic ChooseOneCard(ChooseOneCardDesc desc) {\n\t\tsuper(desc);\n\t\tsetAttribute(Attribute.CHOOSE_ONE);\n\t\tcardIds = desc.options;\n\t\tcardId = desc.bothOptions;\n\t}\n\n\t@Override\n\tpublic Card clone() {\n\t\tChooseOneCard clone = (ChooseOneCard) super.clone();\n\t\tclone.cardIds = cardIds;\n\t\tclone.cardId = cardId;\n\t\treturn clone;\n\t}\n\n\tprivate Card getCard(String cardId) {\n\t\tCard card = CardCatalogue.getCardById(cardId);\n\t\tcard.setLocation(getLocation());\n\t\tcard.setOwner(getOwner());\n\t\tcard.setId(getId());\n\t\treturn card;\n\t}\n\t\n\tpublic Card[] getChoiceCards() {\n\t\tCard[] cards = new Card[cardIds.length];\n\t\tfor (int i = 0; i < cardIds.length; i++) {\n\t\t\tcards[i] = getCard(cardIds[i]);\n\t\t}\n\t\treturn cards;\n\t}\n\t\n\tpublic Card getBothChoicesCard() {\n\t\treturn getCard(cardId);\n\t}\n\n\tpublic boolean hasBothOptions() {\n\t\treturn cardId != null;\n\t}\n\n\t@Override\n\tpublic PlayCardAction play() {\n\t\tthrow new UnsupportedOperationException(\"The method .play() should not be called for ChooseOneCard\");\n\t}\n\n\t@Override\n\tpublic PlayCardAction[] playOptions() {\n\t\tPlayCardAction[] actions = new PlayCardAction[cardIds.length];\n\t\tfor (int i = 0; i < cardIds.length; i++) {\n\t\t\tString cardId = cardIds[i];\n\t\t\tSpellCard card = (SpellCard) getCard(cardId);\n\t\t\tPlayChooseOneCardAction cardAction = new PlayChooseOneCardAction(card.getSpell(), this, cardId, card.getTargetRequirement());\n\t\t\tcardAction.setActionSuffix(card.getName());\n\t\t\tcardAction.setGroupIndex(i);\n\t\t\tactions[i] = cardAction;\n\t\t}\n\t\treturn actions;\n\t}\n\n\t@Override\n\tpublic PlayCardAction playBothOptions() {\n\t\tSpellCard card = (SpellCard) getCard(cardId);\n\t\tPlayChooseOneCardAction cardAction = new PlayChooseOneCardAction(card.getSpell(), this, cardId, card.getTargetRequirement());\n\t\tcardAction.setActionSuffix(card.getName());\n\t\tcardAction.setActionSuffix(card.getName());\n\t\treturn cardAction;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/HeroCard.java",
    "content": "package net.demilich.metastone.game.cards;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.HashSet;\r\nimport java.util.Set;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.actions.PlayCardAction;\r\nimport net.demilich.metastone.game.cards.desc.HeroCardDesc;\r\nimport net.demilich.metastone.game.entities.heroes.Hero;\r\nimport net.demilich.metastone.game.heroes.powers.HeroPower;\r\n\r\npublic class HeroCard extends Card {\r\n\r\n\tprivate static final Set<Attribute> inheritedAttributes = new HashSet<Attribute>(\r\n\t\t\tArrays.asList(new Attribute[] { Attribute.HP, Attribute.MAX_HP, Attribute.BASE_HP, Attribute.ARMOR }));\r\n\r\n\tprivate final HeroCardDesc desc;\r\n\r\n\tpublic HeroCard(HeroCardDesc desc) {\r\n\t\tsuper(desc);\r\n\t\tsetAttribute(Attribute.BASE_HP, getAttributeValue(Attribute.MAX_HP));\r\n\t\tthis.desc = desc;\r\n\t}\r\n\r\n\tpublic Hero createHero() {\r\n\t\tHeroPower heroPower = (HeroPower) CardCatalogue.getCardById(desc.heroPower);\r\n\t\tHero hero = new Hero(this, heroPower);\r\n\t\tfor (Attribute gameTag : getAttributes().keySet()) {\r\n\t\t\tif (inheritedAttributes.contains(gameTag)) {\r\n\t\t\t\thero.setAttribute(gameTag, getAttribute(gameTag));\r\n\t\t\t}\r\n\t\t}\r\n\t\thero.setRace(desc.race);\r\n\t\treturn hero;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic PlayCardAction play() {\r\n\t\tthrow new UnsupportedOperationException(\"Hero cards cannot be played\");\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/IChooseOneCard.java",
    "content": "package net.demilich.metastone.game.cards;\n\nimport net.demilich.metastone.game.actions.PlayCardAction;\n\npublic interface IChooseOneCard {\n\tPlayCardAction[] playOptions();\n\tPlayCardAction playBothOptions();\n\tboolean hasBothOptions();\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/MinionCard.java",
    "content": "package net.demilich.metastone.game.cards;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.HashSet;\r\nimport java.util.Set;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.actions.BattlecryAction;\r\nimport net.demilich.metastone.game.actions.PlayCardAction;\r\nimport net.demilich.metastone.game.actions.PlayMinionCardAction;\r\nimport net.demilich.metastone.game.cards.desc.MinionCardDesc;\r\nimport net.demilich.metastone.game.entities.minions.Minion;\r\nimport net.demilich.metastone.game.entities.minions.Race;\r\nimport net.demilich.metastone.game.spells.desc.BattlecryDesc;\r\nimport net.demilich.metastone.game.spells.desc.trigger.TriggerDesc;\r\n\r\npublic class MinionCard extends SummonCard {\r\n\r\n\tprivate static final Set<Attribute> ignoredAttributes = new HashSet<Attribute>(\r\n\t\t\tArrays.asList(new Attribute[] { Attribute.PASSIVE_TRIGGER, Attribute.DECK_TRIGGER, Attribute.MANA_COST_MODIFIER, Attribute.BASE_ATTACK,\r\n\t\t\t\t\tAttribute.BASE_HP, Attribute.SECRET, Attribute.QUEST, Attribute.CHOOSE_ONE, Attribute.BATTLECRY, Attribute.COMBO }));\r\n\r\n\tprivate final MinionCardDesc desc;\r\n\r\n\tpublic MinionCard(MinionCardDesc desc) {\r\n\t\tsuper(desc);\r\n\t\tsetAttribute(Attribute.BASE_ATTACK, desc.baseAttack);\r\n\t\tsetAttribute(Attribute.ATTACK, desc.baseAttack);\r\n\t\tsetAttribute(Attribute.BASE_HP, desc.baseHp);\r\n\t\tsetAttribute(Attribute.HP, desc.baseHp);\r\n\t\tsetAttribute(Attribute.MAX_HP, desc.baseHp);\r\n\t\tif (desc.race != null) {\r\n\t\t\tsetRace(desc.race);\r\n\t\t}\r\n\t\tthis.desc = desc;\r\n\t}\r\n\r\n\tprotected Minion createMinion(Attribute... tags) {\r\n\t\tMinion minion = new Minion(this);\r\n\t\tfor (Attribute gameTag : getAttributes().keySet()) {\r\n\t\t\tif (!ignoredAttributes.contains(gameTag)) {\r\n\t\t\t\tminion.setAttribute(gameTag, getAttribute(gameTag));\r\n\t\t\t}\r\n\t\t}\r\n\t\tminion.setBaseAttack(getBaseAttack());\r\n\t\tminion.setAttack(getAttack());\r\n\t\tminion.setHp(getHp());\r\n\t\tminion.setMaxHp(getHp());\r\n\t\tminion.setBaseHp(getBaseHp());\r\n\t\tBattlecryDesc battlecry = desc.battlecry;\r\n\t\tif (battlecry != null) {\r\n\t\t\tBattlecryAction battlecryAction = BattlecryAction.createBattlecry(battlecry.spell, battlecry.getTargetSelection());\r\n\t\t\tif (battlecry.condition != null) {\r\n\t\t\t\tbattlecryAction.setCondition(battlecry.condition.create());\r\n\t\t\t}\r\n\r\n\t\t\tminion.setBattlecry(battlecryAction);\r\n\t\t}\r\n\r\n\t\tif (desc.deathrattle != null) {\r\n\t\t\tminion.removeAttribute(Attribute.DEATHRATTLES);\r\n\t\t\tminion.addDeathrattle(desc.deathrattle);\r\n\t\t}\r\n\t\tif (desc.trigger != null) {\r\n\t\t\tminion.addSpellTrigger(desc.trigger.create());\r\n\t\t}\r\n\t\tif (desc.triggers != null) {\r\n\t\t\tfor (TriggerDesc trigger : desc.triggers) {\r\n\t\t\t\tminion.addSpellTrigger(trigger.create());\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (desc.aura != null) {\r\n\t\t\tminion.addSpellTrigger(desc.aura.create());\r\n\t\t}\r\n\t\tif (desc.cardCostModifier != null) {\r\n\t\t\tminion.setCardCostModifier(desc.cardCostModifier.create());\r\n\t\t}\r\n\t\tminion.setHp(minion.getMaxHp());\r\n\t\treturn minion;\r\n\t}\r\n\r\n\tpublic int getAttack() {\r\n\t\treturn getAttributeValue(Attribute.ATTACK);\r\n\t}\r\n\t\r\n\tpublic int getBonusAttack() {\r\n\t\treturn getAttributeValue(Attribute.ATTACK_BONUS);\r\n\t}\r\n\r\n\tpublic int getHp() {\r\n\t\treturn getAttributeValue(Attribute.HP);\r\n\t}\r\n\t\r\n\tpublic int getBonusHp() {\r\n\t\treturn getAttributeValue(Attribute.HP_BONUS);\r\n\t}\r\n\r\n\tpublic int getBaseAttack() {\r\n\t\treturn getAttributeValue(Attribute.BASE_ATTACK);\r\n\t}\r\n\r\n\tpublic int getBaseHp() {\r\n\t\treturn getAttributeValue(Attribute.BASE_HP);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic PlayCardAction play() {\r\n\t\treturn new PlayMinionCardAction(getCardReference());\r\n\t}\r\n\r\n\tpublic void setRace(Race race) {\r\n\t\tsetAttribute(Attribute.RACE, race);\r\n\t}\r\n\r\n\tpublic Minion summon() {\r\n\t\treturn createMinion();\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/PermanentCard.java",
    "content": "package net.demilich.metastone.game.cards;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.HashSet;\r\nimport java.util.Set;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.actions.BattlecryAction;\r\nimport net.demilich.metastone.game.actions.PlayCardAction;\r\nimport net.demilich.metastone.game.actions.PlayPermanentCardAction;\r\nimport net.demilich.metastone.game.cards.desc.PermanentCardDesc;\r\nimport net.demilich.metastone.game.entities.minions.Permanent;\r\nimport net.demilich.metastone.game.entities.minions.Race;\r\nimport net.demilich.metastone.game.spells.desc.BattlecryDesc;\r\nimport net.demilich.metastone.game.spells.desc.trigger.TriggerDesc;\r\n\r\npublic class PermanentCard extends SummonCard {\r\n\r\n\tprivate static final Set<Attribute> ignoredAttributes = new HashSet<Attribute>(\r\n\t\t\tArrays.asList(new Attribute[] { Attribute.PASSIVE_TRIGGER, Attribute.DECK_TRIGGER, Attribute.MANA_COST_MODIFIER, Attribute.BASE_ATTACK,\r\n\t\t\t\t\tAttribute.BASE_HP, Attribute.SECRET, Attribute.QUEST, Attribute.CHOOSE_ONE, Attribute.BATTLECRY, Attribute.COMBO }));\r\n\r\n\tprivate final PermanentCardDesc desc;\r\n\r\n\tpublic PermanentCard(PermanentCardDesc desc) {\r\n\t\tsuper(desc);\r\n\t\tthis.desc = desc;\r\n\t}\r\n\r\n\tprotected Permanent createPermanent(Attribute... tags) {\r\n\t\tPermanent permanent = new Permanent(this);\r\n\t\tfor (Attribute gameTag : getAttributes().keySet()) {\r\n\t\t\tif (!ignoredAttributes.contains(gameTag)) {\r\n\t\t\t\tpermanent.setAttribute(gameTag, getAttribute(gameTag));\r\n\t\t\t}\r\n\t\t}\r\n\t\tBattlecryDesc battlecry = desc.battlecry;\r\n\t\tif (battlecry != null) {\r\n\t\t\tBattlecryAction battlecryAction = BattlecryAction.createBattlecry(battlecry.spell, battlecry.getTargetSelection());\r\n\t\t\tif (battlecry.condition != null) {\r\n\t\t\t\tbattlecryAction.setCondition(battlecry.condition.create());\r\n\t\t\t}\r\n\r\n\t\t\tpermanent.setBattlecry(battlecryAction);\r\n\t\t}\r\n\r\n\t\tif (desc.deathrattle != null) {\r\n\t\t\tpermanent.removeAttribute(Attribute.DEATHRATTLES);\r\n\t\t\tpermanent.addDeathrattle(desc.deathrattle);\r\n\t\t}\r\n\t\tif (desc.trigger != null) {\r\n\t\t\tpermanent.addSpellTrigger(desc.trigger.create());\r\n\t\t}\r\n\t\tif (desc.triggers != null) {\r\n\t\t\tfor (TriggerDesc trigger : desc.triggers) {\r\n\t\t\t\tpermanent.addSpellTrigger(trigger.create());\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (desc.aura != null) {\r\n\t\t\tpermanent.addSpellTrigger(desc.aura.create());\r\n\t\t}\r\n\t\tif (desc.cardCostModifier != null) {\r\n\t\t\tpermanent.setCardCostModifier(desc.cardCostModifier.create());\r\n\t\t}\r\n\t\treturn permanent;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic PlayCardAction play() {\r\n\t\treturn new PlayPermanentCardAction(getCardReference());\r\n\t}\r\n\r\n\tpublic void setRace(Race race) {\r\n\t\tsetAttribute(Attribute.RACE, race);\r\n\t}\r\n\r\n\tpublic Permanent summon() {\r\n\t\treturn createPermanent();\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/QuestCard.java",
    "content": "package net.demilich.metastone.game.cards;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.desc.QuestCardDesc;\nimport net.demilich.metastone.game.spells.AddQuestSpell;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.trigger.GameEventTrigger;\nimport net.demilich.metastone.game.spells.trigger.types.Quest;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class QuestCard extends SpellCard {\n\n\tpublic QuestCard(QuestCardDesc desc) {\n\t\tsuper(desc);\n\t\tGameEventTrigger trigger = desc.quest.create();\n\t\tsetQuest(new Quest(trigger, desc.spell, this));\n\t\tsetAttribute(Attribute.QUEST);\n\t}\n\n\tpublic boolean canBeCast(GameContext context, Player player) {\n\t\treturn context.getLogic().canPlayQuest(player, this);\n\t}\n\n\tpublic void setQuest(Quest quest) {\n\t\tSpellDesc spell = AddQuestSpell.create(quest);\n\t\tsetTargetRequirement(TargetSelection.NONE);\n\t\tsetSpell(spell);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/Rarity.java",
    "content": "package net.demilich.metastone.game.cards;\n\npublic enum Rarity {\n\tFREE,\n\tCOMMON,\n\tRARE,\n\tEPIC,\n\tLEGENDARY;\n\t\n\tpublic boolean isRarity(Rarity rarity) {\n\t\tif (this == FREE && rarity == COMMON) {\n\t\t\treturn true;\n\t\t} else if (this == rarity) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/SecretCard.java",
    "content": "package net.demilich.metastone.game.cards;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.desc.SecretCardDesc;\nimport net.demilich.metastone.game.spells.AddSecretSpell;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.trigger.GameEventTrigger;\nimport net.demilich.metastone.game.spells.trigger.types.Secret;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class SecretCard extends SpellCard {\n\n\tpublic SecretCard(SecretCardDesc desc) {\n\t\tsuper(desc);\n\t\tGameEventTrigger trigger = desc.trigger.create();\n\t\tsetSecret(new Secret(trigger, desc.spell, this));\n\t\tsetAttribute(Attribute.SECRET);\n\t}\n\n\tpublic boolean canBeCast(GameContext context, Player player) {\n\t\treturn context.getLogic().canPlaySecret(player, this);\n\t}\n\n\tpublic void setSecret(Secret secret) {\n\t\tSpellDesc spell = AddSecretSpell.create(secret);\n\t\tsetTargetRequirement(TargetSelection.NONE);\n\t\tsetSpell(spell);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/SpellCard.java",
    "content": "package net.demilich.metastone.game.cards;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.PlayCardAction;\nimport net.demilich.metastone.game.actions.PlaySpellCardAction;\nimport net.demilich.metastone.game.cards.desc.SpellCardDesc;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.condition.Condition;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class SpellCard extends Card {\n\n\tprivate SpellDesc spell;\n\tprivate TargetSelection targetRequirement;\n\tprivate Condition condition;\n\n\tpublic SpellCard(SpellCardDesc desc) {\n\t\tsuper(desc);\n\t\tsetTargetRequirement(desc.targetSelection);\n\t\tsetSpell(desc.spell);\n\t\tif (desc.condition != null) {\n\t\t\tcondition = desc.condition.create();\n\t\t}\n\t}\n\n\tpublic boolean canBeCast(GameContext context, Player player) {\n\t\tPlayer opponent = context.getOpponent(player);\n\t\tswitch (targetRequirement) {\n\t\tcase ENEMY_MINIONS:\n\t\t\treturn context.getMinionCount(opponent) > 0;\n\t\tcase FRIENDLY_MINIONS:\n\t\t\treturn context.getMinionCount(player) > 0;\n\t\tcase MINIONS:\n\t\t\treturn context.getTotalMinionCount() > 0;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t\tif (condition != null) {\n\t\t\treturn condition.isFulfilled(context, player, null, null);\n\t\t}\n\t\treturn true;\n\t}\n\n\tpublic boolean canBeCastOn(GameContext context, Player player, Entity target) {\n\t\tEntityFilter filter = spell.getEntityFilter();\n\t\tif (filter == null) {\n\t\t\treturn true;\n\t\t}\n\t\treturn filter.matches(context, player, target);\n\t}\n\n\t@Override\n\tpublic SpellCard clone() {\n\t\tSpellCard clone = (SpellCard) super.clone();\n\t\tif (spell == null) {\n\t\t\tthrow new RuntimeException(\"Spell is NULL for SpellCard \" + getName());\n\t\t}\n\t\tclone.spell = spell.clone();\n\t\tclone.condition = condition;\n\t\treturn clone;\n\t}\n\n\tpublic SpellDesc getSpell() {\n\t\treturn spell;\n\t}\n\n\tpublic TargetSelection getTargetRequirement() {\n\t\treturn targetRequirement;\n\t}\n\n\t@Override\n\tpublic PlayCardAction play() {\n\t\treturn new PlaySpellCardAction(getSpell(), this, getTargetRequirement());\n\t}\n\n\tpublic void setSpell(SpellDesc spell) {\n\t\tthis.spell = spell;\n\t}\n\n\tpublic void setTargetRequirement(TargetSelection targetRequirement) {\n\t\tthis.targetRequirement = targetRequirement;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/SummonCard.java",
    "content": "package net.demilich.metastone.game.cards;\r\n\r\nimport net.demilich.metastone.game.actions.PlayCardAction;\r\nimport net.demilich.metastone.game.cards.desc.SummonCardDesc;\r\nimport net.demilich.metastone.game.entities.minions.Summon;\r\n\r\npublic abstract class SummonCard extends Card {\r\n\r\n\tpublic SummonCard(SummonCardDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic abstract PlayCardAction play();\r\n\r\n\t\r\n\tpublic abstract Summon summon();\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/WeaponCard.java",
    "content": "package net.demilich.metastone.game.cards;\n\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.actions.BattlecryAction;\nimport net.demilich.metastone.game.actions.PlayCardAction;\nimport net.demilich.metastone.game.actions.PlayWeaponCardAction;\nimport net.demilich.metastone.game.cards.desc.WeaponCardDesc;\nimport net.demilich.metastone.game.entities.weapons.Weapon;\nimport net.demilich.metastone.game.spells.desc.BattlecryDesc;\nimport net.demilich.metastone.game.spells.desc.trigger.TriggerDesc;\n\npublic class WeaponCard extends Card {\n\n\tprivate static final Set<Attribute> ignoredAttributes = new HashSet<Attribute>(\n\t\t\tArrays.asList(new Attribute[] { Attribute.PASSIVE_TRIGGER, Attribute.DECK_TRIGGER, Attribute.MANA_COST_MODIFIER, Attribute.BASE_ATTACK,\n\t\t\t\t\tAttribute.BASE_HP, Attribute.SECRET, Attribute.QUEST, Attribute.CHOOSE_ONE, Attribute.BATTLECRY, Attribute.COMBO }));\n\t\n\tprivate final WeaponCardDesc desc;\n\n\tpublic WeaponCard(WeaponCardDesc desc) {\n\t\tsuper(desc);\n\t\tsetAttribute(Attribute.BASE_ATTACK, desc.damage);\n\t\tsetAttribute(Attribute.ATTACK, desc.damage);\n\t\tsetAttribute(Attribute.BASE_HP, desc.durability);\n\t\tsetAttribute(Attribute.HP, desc.durability);\n\t\tsetAttribute(Attribute.MAX_HP, desc.durability);\n\t\tthis.desc = desc;\n\t}\n\n\tprotected Weapon createWeapon(Attribute... tags) {\n\t\tWeapon weapon = new Weapon(this);\n\t\t// assign battlecry if there is one specified\n\t\tfor (Attribute gameTag : getAttributes().keySet()) {\n\t\t\tif (!ignoredAttributes.contains(gameTag)) {\n\t\t\t\tweapon.setAttribute(gameTag, getAttribute(gameTag));\n\t\t\t}\n\t\t}\n\t\tweapon.setAttack(getDamage());\n\t\tweapon.setBaseAttack(getBaseDamage());\n\t\tweapon.setMaxHp(getDurability());\n\t\tweapon.setHp(weapon.getMaxDurability());\n\t\tweapon.setBaseHp(getBaseDurability());\n\t\tBattlecryDesc battlecry = desc.battlecry;\n\t\tif (battlecry != null) {\n\t\t\tBattlecryAction battlecryAction = BattlecryAction.createBattlecry(battlecry.spell, battlecry.getTargetSelection());\n\t\t\tif (battlecry.condition != null) {\n\t\t\t\tbattlecryAction.setCondition(battlecry.condition.create());\n\t\t\t}\n\t\n\t\t\tweapon.setBattlecry(battlecryAction);\n\t\t}\n\t\n\t\tif (desc.deathrattle != null) {\n\t\t\tweapon.removeAttribute(Attribute.DEATHRATTLES);\n\t\t\tweapon.addDeathrattle(desc.deathrattle);\n\t\t}\n\t\tif (desc.trigger != null) {\n\t\t\tweapon.addSpellTrigger(desc.trigger.create());\n\t\t}\n\t\tif (desc.triggers != null) {\n\t\t\tfor (TriggerDesc trigger : desc.triggers) {\n\t\t\t\tweapon.addSpellTrigger(trigger.create());\n\t\t\t}\n\t\t}\n\t\tif (desc.cardCostModifier != null) {\n\t\t\tweapon.setCardCostModifier(desc.cardCostModifier.create());\n\t\t}\n\t\tweapon.setOnEquip(desc.onEquip);\n\t\tweapon.setOnUnequip(desc.onUnequip);\n\t\treturn weapon;\n\t}\n\n\tpublic Weapon getWeapon() {\n\t\treturn createWeapon();\n\t}\n\n\t@Override\n\tpublic PlayCardAction play() {\n\t\treturn new PlayWeaponCardAction(getCardReference());\n\t}\n\n\tpublic int getDamage() {\n\t\treturn getAttributeValue(Attribute.ATTACK);\n\t}\n\t\n\tpublic int getBonusDamage() {\n\t\treturn getAttributeValue(Attribute.ATTACK_BONUS);\n\t}\n\n\tpublic int getDurability() {\n\t\treturn getAttributeValue(Attribute.HP);\n\t}\n\t\n\tpublic int getBonusDurability() {\n\t\treturn getAttributeValue(Attribute.HP_BONUS);\n\t}\n\n\tpublic int getBaseDamage() {\n\t\treturn getAttributeValue(Attribute.BASE_ATTACK);\n\t}\n\n\tpublic int getBaseDurability() {\n\t\treturn getAttributeValue(Attribute.BASE_HP);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/costmodifier/CardCostModifier.java",
    "content": "package net.demilich.metastone.game.cards.costmodifier;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Race;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.logic.CustomCloneable;\nimport net.demilich.metastone.game.spells.TargetPlayer;\nimport net.demilich.metastone.game.spells.desc.manamodifier.CardCostModifierArg;\nimport net.demilich.metastone.game.spells.desc.manamodifier.CardCostModifierDesc;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\nimport net.demilich.metastone.game.spells.desc.valueprovider.AlgebraicOperation;\nimport net.demilich.metastone.game.spells.trigger.GameEventTrigger;\nimport net.demilich.metastone.game.spells.trigger.IGameEventListener;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class CardCostModifier extends CustomCloneable implements IGameEventListener {\n\n\tprivate boolean expired;\n\tprivate int owner;\n\tprivate EntityReference hostReference;\n\tprivate GameEventTrigger expirationTrigger;\n\n\tprivate CardCostModifierDesc desc;\n\n\tpublic CardCostModifier(CardCostModifierDesc desc) {\n\t\tthis.desc = desc;\n\t\tEventTriggerDesc triggerDesc = (EventTriggerDesc) desc.get(CardCostModifierArg.EXPIRATION_TRIGGER);\n\t\tif (triggerDesc != null) {\n\t\t\tthis.expirationTrigger = triggerDesc.create();\n\t\t}\n\t}\n\n\tpublic boolean appliesTo(Card card) {\n\t\tif (expired) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\tif (!getRequiredCardIds().isEmpty() && !getRequiredCardIds().contains(card.getId())) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (getRequiredAttribute() != null && !card.hasAttribute(getRequiredAttribute())) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (getRequiredRace() != null && card.getAttribute(Attribute.RACE) != getRequiredRace()) {\n\t\t\treturn false;\n\t\t}\n\n\t\tswitch (getTargetPlayer()) {\n\t\tcase BOTH:\n\t\t\tbreak;\n\t\tcase OPPONENT:\n\t\t\tif (card.getOwner() == getOwner()) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase SELF:\n\t\t\tif (card.getOwner() != getOwner()) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\n\t\t}\n\t\tif (getCardType() == null && !card.getCardType().isCardType(CardType.HERO_POWER)) {\n\t\t\treturn true;\n\t\t}\n\t\t\n\t\treturn card.getCardType().isCardType(getCardType());\n\t}\n\t\n\t@Override\n\tpublic boolean canFire(GameEvent event) {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic CardCostModifier clone() {\n\t\tCardCostModifier clone = (CardCostModifier) super.clone();\n\t\tclone.expirationTrigger = expirationTrigger != null ? (GameEventTrigger) expirationTrigger.clone() : null;\n\t\treturn clone;\n\t}\n\n\tpublic void expire() {\n\t\texpired = true;\n\t}\n\n\tprotected Object get(CardCostModifierArg arg) {\n\t\treturn desc.get(arg);\n\t}\n\n\tprotected CardType getCardType() {\n\t\treturn (CardType) desc.get(CardCostModifierArg.CARD_TYPE);\n\t}\n\n\t@Override\n\tpublic EntityReference getHostReference() {\n\t\treturn hostReference;\n\t}\n\n\tpublic int getMinValue() {\n\t\treturn desc.getInt(CardCostModifierArg.MIN_VALUE);\n\t}\n\n\t@Override\n\tpublic int getOwner() {\n\t\treturn owner;\n\t}\n\n\tprotected Attribute getRequiredAttribute() {\n\t\treturn (Attribute) desc.get(CardCostModifierArg.REQUIRED_ATTRIBUTE);\n\t}\n\t\n\t@SuppressWarnings(\"unchecked\")\n\tprotected List<Integer> getRequiredCardIds() {\n\t\tif (!desc.contains(CardCostModifierArg.CARD_IDS)) {\n\t\t\treturn new ArrayList<Integer>();\n\t\t}\n\t\treturn (List<Integer>) desc.get(CardCostModifierArg.CARD_IDS);\n\t}\n\n\tprotected Race getRequiredRace() {\n\t\treturn (Race) get(CardCostModifierArg.RACE);\n\t}\n\n\tprotected TargetPlayer getTargetPlayer() {\n\t\tif (!desc.contains(CardCostModifierArg.TARGET_PLAYER)) {\n\t\t\treturn TargetPlayer.SELF;\n\t\t}\n\t\treturn (TargetPlayer) desc.get(CardCostModifierArg.TARGET_PLAYER);\n\t}\n\n\t@Override\n\tpublic boolean interestedIn(GameEventType eventType) {\n\t\tif (expirationTrigger == null) {\n\t\t\treturn false;\n\t\t}\n\t\treturn eventType == expirationTrigger.interestedIn() || expirationTrigger.interestedIn() == GameEventType.ALL;\n\t}\n\n\t@Override\n\tpublic boolean isExpired() {\n\t\treturn expired;\n\t}\n\n\t@Override\n\tpublic void onAdd(GameContext context) {\n\t}\n\n\t@Override\n\tpublic void onGameEvent(GameEvent event) {\n\t\tEntity host = event.getGameContext().resolveSingleTarget(getHostReference());\n\t\tif (expirationTrigger != null && event.getEventType() == expirationTrigger.interestedIn() && expirationTrigger.fires(event, host)) {\n\t\t\texpire();\n\t\t}\n\t}\n\n\t@Override\n\tpublic void onRemove(GameContext context) {\n\t\texpired = true;\n\t}\n\n\tpublic int process(Card card, int currentManaCost) {\n\t\tAlgebraicOperation operation = (AlgebraicOperation) desc.get(CardCostModifierArg.OPERATION);\n\t\tint value = desc.getInt(CardCostModifierArg.VALUE);\n\t\tif (operation != null) {\n\t\t\treturn operation.performOperation(currentManaCost, value);\n\t\t}\n\t\tint modifiedManaCost = currentManaCost + desc.getInt(CardCostModifierArg.VALUE);\n\t\treturn modifiedManaCost;\n\t}\n\n\t@Override\n\tpublic void setHost(Entity host) {\n\t\thostReference = host.getReference();\n\t}\n\n\t@Override\n\tpublic void setOwner(int playerIndex) {\n\t\tthis.owner = playerIndex;\n\t\tif (expirationTrigger != null) {\n\t\t\texpirationTrigger.setOwner(playerIndex);\n\t\t}\n\t}\n\n\t@Override\n\tpublic boolean hasPersistentOwner() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean oneTurnOnly() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean isDelayed() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void delayTimeDown() {\n\t\t\n\t}\n\n\t@Override\n\tpublic boolean canFireCondition(GameEvent event) {\n\t\tif (expirationTrigger != null) {\n\t\t\treturn expirationTrigger.canFireCondition(event);\n\t\t}\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic boolean hasCounter() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void countDown() {\n\t\t\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/costmodifier/OneTurnCostModifier.java",
    "content": "package net.demilich.metastone.game.cards.costmodifier;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.manamodifier.CardCostModifierDesc;\nimport net.demilich.metastone.game.spells.trigger.GameEventTrigger;\nimport net.demilich.metastone.game.spells.trigger.TurnStartTrigger;\n\npublic class OneTurnCostModifier extends CardCostModifier {\n\n\tprivate GameEventTrigger turnStartTrigger = new TurnStartTrigger();\n\n\tpublic OneTurnCostModifier(CardCostModifierDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tpublic OneTurnCostModifier clone() {\n\t\tOneTurnCostModifier clone = (OneTurnCostModifier) super.clone();\n\t\tclone.turnStartTrigger = (GameEventTrigger) turnStartTrigger.clone();\n\t\treturn clone;\n\t}\n\n\t@Override\n\tpublic boolean interestedIn(GameEventType eventType) {\n\t\tif (turnStartTrigger.interestedIn() == eventType) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn super.interestedIn(eventType);\n\t}\n\n\t@Override\n\tpublic void onGameEvent(GameEvent event) {\n\t\tEntity host = event.getGameContext().resolveSingleTarget(getHostReference());\n\t\tif (event.getEventType() == turnStartTrigger.interestedIn() && turnStartTrigger.fires(event, host)) {\n\t\t\texpire();\n\t\t}\n\n\t\tsuper.onGameEvent(event);\n\t}\n\t\n\t@Override\n\tpublic boolean oneTurnOnly() {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic void setOwner(int playerIndex) {\n\t\tsuper.setOwner(playerIndex);\n\t\tturnStartTrigger.setOwner(playerIndex);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/costmodifier/ToggleCostModifier.java",
    "content": "package net.demilich.metastone.game.cards.costmodifier;\n\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.manamodifier.CardCostModifierArg;\nimport net.demilich.metastone.game.spells.desc.manamodifier.CardCostModifierDesc;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\nimport net.demilich.metastone.game.spells.trigger.GameEventTrigger;\n\npublic class ToggleCostModifier extends CardCostModifier {\n\n\tprivate GameEventTrigger toggleOnTrigger;\n\tprivate GameEventTrigger toggleOffTrigger;\n\tprivate boolean ready;\n\n\tpublic ToggleCostModifier(CardCostModifierDesc desc) {\n\t\tsuper(desc);\n\t\tEventTriggerDesc triggerDesc = (EventTriggerDesc) desc.get(CardCostModifierArg.TOGGLE_ON_TRIGGER);\n\t\tthis.toggleOnTrigger = triggerDesc.create();\n\n\t\ttriggerDesc = (EventTriggerDesc) desc.get(CardCostModifierArg.TOGGLE_OFF_TRIGGER);\n\t\tthis.toggleOffTrigger = triggerDesc.create();\n\t}\n\n\t@Override\n\tpublic boolean appliesTo(Card card) {\n\t\tif (!ready) {\n\t\t\treturn false;\n\t\t}\n\t\treturn super.appliesTo(card);\n\t}\n\n\t@Override\n\tpublic CardCostModifier clone() {\n\t\tToggleCostModifier clone = (ToggleCostModifier) super.clone();\n\t\tclone.toggleOnTrigger = toggleOnTrigger.clone();\n\t\tclone.toggleOffTrigger = toggleOffTrigger.clone();\n\t\treturn clone;\n\t}\n\n\t@Override\n\tpublic boolean interestedIn(GameEventType eventType) {\n\t\treturn eventType == toggleOnTrigger.interestedIn() || eventType == toggleOffTrigger.interestedIn();\n\t}\n\n\t@Override\n\tpublic void onGameEvent(GameEvent event) {\n\t\tEntity host = event.getGameContext().resolveSingleTarget(getHostReference());\n\t\tif (toggleOnTrigger.interestedIn() == event.getEventType() && toggleOnTrigger.fires(event, host)) {\n\t\t\tready = true;\n\t\t} else if (toggleOffTrigger.interestedIn() == event.getEventType() && toggleOffTrigger.fires(event, host)) {\n\t\t\tready = false;\n\t\t}\n\t}\n\n\t@Override\n\tpublic void setOwner(int playerIndex) {\n\t\tsuper.setOwner(playerIndex);\n\t\ttoggleOnTrigger.setOwner(playerIndex);\n\t\ttoggleOffTrigger.setOwner(playerIndex);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/ActorCardDesc.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport net.demilich.metastone.game.spells.desc.BattlecryDesc;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.desc.aura.AuraDesc;\r\nimport net.demilich.metastone.game.spells.desc.trigger.TriggerDesc;\r\n\r\npublic abstract class ActorCardDesc extends CardDesc {\r\n\r\n\tpublic BattlecryDesc battlecry;\r\n\tpublic SpellDesc deathrattle;\r\n\tpublic TriggerDesc trigger;\r\n\tpublic TriggerDesc[] triggers;\r\n\tpublic AuraDesc aura;\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/AttributeDeserializer.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport java.lang.reflect.Type;\r\nimport java.util.EnumMap;\r\nimport java.util.Map;\r\n\r\nimport com.google.gson.JsonDeserializationContext;\r\nimport com.google.gson.JsonDeserializer;\r\nimport com.google.gson.JsonElement;\r\nimport com.google.gson.JsonObject;\r\nimport com.google.gson.JsonParseException;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\n\r\npublic class AttributeDeserializer implements JsonDeserializer<Map<Attribute, Object>> {\r\n\r\n\t@Override\r\n\tpublic Map<Attribute, Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)\r\n\t\t\tthrows JsonParseException {\r\n\t\tMap<Attribute, Object> map = new EnumMap<Attribute, Object>(Attribute.class);\r\n\t\tJsonObject jsonData = json.getAsJsonObject();\r\n\t\tparseAttribute(Attribute.HP, jsonData, map, ParseValueType.INTEGER);//TODO Remove from Heroes\r\n\t\tparseAttribute(Attribute.MAX_HP, jsonData, map, ParseValueType.INTEGER);//TODO Remove from Heroes\r\n\t\t\r\n\t\tparseAttribute(Attribute.ATTACK_EQUALS_HP, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.BATTLECRY, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.BOTH_CHOOSE_ONE_OPTIONS, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.CANNOT_ATTACK, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.CANNOT_ATTACK_HERO_ON_SUMMON, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.CANNOT_ATTACK_HEROES, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.CHARGE, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.COMBO, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.DEATHRATTLES, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.DIVINE_SHIELD, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.DOUBLE_BATTLECRIES, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.DOUBLE_DEATHRATTLES, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.ENRAGABLE, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.HEAL_AMPLIFY_MULTIPLIER, jsonData, map, ParseValueType.INTEGER);\r\n\t\tparseAttribute(Attribute.HERO_POWER_CAN_TARGET_MINIONS, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.HERO_POWER_DAMAGE, jsonData, map, ParseValueType.INTEGER);\r\n\t\tparseAttribute(Attribute.HERO_POWER_USAGES, jsonData, map, ParseValueType.INTEGER);\r\n\t\tparseAttribute(Attribute.IMMUNE_HERO, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.INVERT_HEALING, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.MEGA_WINDFURY, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.OPPONENT_SPELL_DAMAGE, jsonData, map, ParseValueType.INTEGER);\r\n\t\tparseAttribute(Attribute.OVERLOAD, jsonData, map, ParseValueType.INTEGER);\r\n\t\tparseAttribute(Attribute.SPELL_AMPLIFY_MULTIPLIER, jsonData, map, ParseValueType.INTEGER);\r\n\t\tparseAttribute(Attribute.SPELL_DAMAGE, jsonData, map, ParseValueType.INTEGER);\r\n\t\tparseAttribute(Attribute.SPELL_DAMAGE_MULTIPLIER, jsonData, map, ParseValueType.INTEGER);\r\n\t\tparseAttribute(Attribute.STEALTH, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.UNTARGETABLE_BY_SPELLS, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.AURA_UNTARGETABLE_BY_SPELLS, jsonData, map, ParseValueType.BOOLEAN);//TODO Remove from Spellstopper\r\n\t\tparseAttribute(Attribute.TAUNT, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\tparseAttribute(Attribute.WINDFURY, jsonData, map, ParseValueType.BOOLEAN);\r\n\t\treturn map;\r\n\t}\r\n\r\n\tprivate void parseAttribute(Attribute attribute, JsonObject jsonData, Map<Attribute, Object> map, ParseValueType valueType) {\r\n\t\tString argName = attribute.toString();\r\n\t\tif (!jsonData.has(argName)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tObject value = ParseUtils.parse(argName, jsonData, valueType);\r\n\t\tBoolean bool = value instanceof Boolean ? (Boolean) value : null;\r\n\t\tif (bool != null && bool == true) {\r\n\t\t\tvalue = 1;\r\n\t\t}\r\n\t\tmap.put(attribute, value);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/AuraDeserializer.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport java.lang.reflect.Type;\r\nimport java.util.Map;\r\n\r\nimport com.google.gson.JsonDeserializationContext;\r\nimport com.google.gson.JsonDeserializer;\r\nimport com.google.gson.JsonElement;\r\nimport com.google.gson.JsonObject;\r\nimport com.google.gson.JsonParseException;\r\n\r\nimport net.demilich.metastone.game.spells.aura.Aura;\r\nimport net.demilich.metastone.game.spells.desc.aura.AuraArg;\r\nimport net.demilich.metastone.game.spells.desc.aura.AuraDesc;\r\n\r\npublic class AuraDeserializer implements JsonDeserializer<AuraDesc> {\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\t@Override\r\n\tpublic AuraDesc deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {\r\n\t\tif (!(json instanceof JsonObject)) {\r\n\t\t\tthrow new JsonParseException(\"Aura parser expected an JsonObject but found \" + json + \" instead\");\r\n\t\t}\r\n\t\tJsonObject jsonData = (JsonObject) json;\r\n\t\tString auraClassName = Aura.class.getPackage().getName() + \".\" + jsonData.get(\"class\").getAsString();\r\n\t\tClass<? extends Aura> auraClass;\r\n\t\ttry {\r\n\t\t\tauraClass = (Class<? extends Aura>) Class.forName(auraClassName);\r\n\t\t} catch (ClassNotFoundException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t\tthrow new JsonParseException(\"Aura parser encountered an invalid class: \" + auraClassName);\r\n\t\t}\r\n\t\tMap<AuraArg, Object> arguments = AuraDesc.build(auraClass);\r\n\t\tparseArgument(AuraArg.FILTER, jsonData, arguments, ParseValueType.ENTITY_FILTER);\r\n\t\tparseArgument(AuraArg.TARGET, jsonData, arguments, ParseValueType.TARGET_REFERENCE);\r\n\t\tparseArgument(AuraArg.ATTRIBUTE, jsonData, arguments, ParseValueType.ATTRIBUTE);\r\n\t\tparseArgument(AuraArg.APPLY_EFFECT, jsonData, arguments, ParseValueType.SPELL);\r\n\t\tparseArgument(AuraArg.REMOVE_EFFECT, jsonData, arguments, ParseValueType.SPELL);\r\n\t\tparseArgument(AuraArg.ATTACK_BONUS, jsonData, arguments, ParseValueType.INTEGER);\r\n\t\tparseArgument(AuraArg.HP_BONUS, jsonData, arguments, ParseValueType.INTEGER);\r\n\r\n\t\treturn new AuraDesc(arguments);\r\n\t}\r\n\r\n\tprivate void parseArgument(AuraArg arg, JsonObject jsonData, Map<AuraArg, Object> arguments, ParseValueType valueType) {\r\n\t\tString argName = ParseUtils.toCamelCase(arg.toString());\r\n\t\tif (!jsonData.has(argName)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tObject value = ParseUtils.parse(argName, jsonData, valueType);\r\n\t\targuments.put(arg, value);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/CardCostModifierDeserializer.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport java.lang.reflect.Type;\r\nimport java.util.Map;\r\n\r\nimport com.google.gson.JsonDeserializationContext;\r\nimport com.google.gson.JsonDeserializer;\r\nimport com.google.gson.JsonElement;\r\nimport com.google.gson.JsonObject;\r\nimport com.google.gson.JsonParseException;\r\n\r\nimport net.demilich.metastone.game.cards.costmodifier.CardCostModifier;\r\nimport net.demilich.metastone.game.spells.desc.manamodifier.CardCostModifierArg;\r\nimport net.demilich.metastone.game.spells.desc.manamodifier.CardCostModifierDesc;\r\n\r\npublic class CardCostModifierDeserializer implements JsonDeserializer<CardCostModifierDesc> {\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\t@Override\r\n\tpublic CardCostModifierDesc deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {\r\n\t\tif (!(json instanceof JsonObject)) {\r\n\t\t\tthrow new JsonParseException(\"ManaModifier parser expected an JsonObject but found \" + json + \" instead\");\r\n\t\t}\r\n\t\tJsonObject jsonData = (JsonObject) json;\r\n\t\tString cardCostModifierClassName = CardCostModifier.class.getPackage().getName() + \".\" + jsonData.get(\"class\").getAsString();\r\n\t\tClass<? extends CardCostModifier> cardCostModifierClass;\r\n\t\ttry {\r\n\t\t\tcardCostModifierClass = (Class<? extends CardCostModifier>) Class.forName(cardCostModifierClassName);\r\n\t\t} catch (ClassNotFoundException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t\tthrow new JsonParseException(\"ManaModifier parser encountered an invalid class: \" + cardCostModifierClassName);\r\n\t\t}\r\n\t\tMap<CardCostModifierArg, Object> arguments = CardCostModifierDesc.build(cardCostModifierClass);\r\n\t\tparseArgument(CardCostModifierArg.TARGET_PLAYER, jsonData, arguments, ParseValueType.TARGET_PLAYER);\r\n\t\tparseArgument(CardCostModifierArg.RACE, jsonData, arguments, ParseValueType.RACE);\r\n\t\tparseArgument(CardCostModifierArg.VALUE, jsonData, arguments, ParseValueType.INTEGER);\r\n\t\tparseArgument(CardCostModifierArg.MIN_VALUE, jsonData, arguments, ParseValueType.INTEGER);\r\n\t\tparseArgument(CardCostModifierArg.CARD_TYPE, jsonData, arguments, ParseValueType.CARD_TYPE);\r\n\t\tparseArgument(CardCostModifierArg.REQUIRED_ATTRIBUTE, jsonData, arguments, ParseValueType.ATTRIBUTE);\r\n\t\tparseArgument(CardCostModifierArg.EXPIRATION_TRIGGER, jsonData, arguments, ParseValueType.EVENT_TRIGGER);\r\n\t\tparseArgument(CardCostModifierArg.TOGGLE_ON_TRIGGER, jsonData, arguments, ParseValueType.EVENT_TRIGGER);\r\n\t\tparseArgument(CardCostModifierArg.TOGGLE_OFF_TRIGGER, jsonData, arguments, ParseValueType.EVENT_TRIGGER);\r\n\t\tparseArgument(CardCostModifierArg.TARGET, jsonData, arguments, ParseValueType.TARGET_REFERENCE);\r\n\t\tparseArgument(CardCostModifierArg.OPERATION, jsonData, arguments, ParseValueType.ALGEBRAIC_OPERATION);\r\n\r\n\t\treturn new CardCostModifierDesc(arguments);\r\n\t}\r\n\r\n\tprivate void parseArgument(CardCostModifierArg arg, JsonObject jsonData, Map<CardCostModifierArg, Object> arguments,\r\n\t\t\tParseValueType valueType) {\r\n\t\tString argName = ParseUtils.toCamelCase(arg.toString());\r\n\t\tif (!jsonData.has(argName)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tObject value = ParseUtils.parse(argName, jsonData, valueType);\r\n\t\targuments.put(arg, value);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/CardDesc.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.CardSet;\r\nimport net.demilich.metastone.game.cards.CardType;\r\nimport net.demilich.metastone.game.cards.Rarity;\r\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\r\nimport net.demilich.metastone.game.spells.desc.trigger.TriggerDesc;\r\nimport net.demilich.metastone.game.spells.desc.valueprovider.ValueProviderDesc;\r\n\r\npublic abstract class CardDesc {\r\n\r\n\tpublic String id;\r\n\tpublic String name;\r\n\tpublic String description;\r\n\tpublic CardType type;\r\n\tpublic HeroClass heroClass;\r\n\tpublic HeroClass[] heroClasses;\r\n\tpublic Rarity rarity;\r\n\tpublic CardSet set;\r\n\tpublic int baseManaCost;\r\n\tpublic boolean collectible = true;\r\n\tpublic Map<Attribute, Object> attributes;\r\n\tpublic int fileFormatVersion = 1;\r\n\tpublic ValueProviderDesc manaCostModifier;\r\n\tpublic TriggerDesc passiveTrigger;\r\n\tpublic TriggerDesc deckTrigger;\r\n\r\n\tpublic abstract Card createInstance();\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/ChooseBattlecryCardDesc.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.ChooseBattlecryCard;\r\nimport net.demilich.metastone.game.spells.desc.BattlecryDesc;\r\n\r\npublic class ChooseBattlecryCardDesc extends MinionCardDesc {\r\n\r\n\tpublic BattlecryDesc[] options;\r\n\tpublic BattlecryDesc bothOptions;\r\n\r\n\t@Override\r\n\tpublic Card createInstance() {\r\n\t\treturn new ChooseBattlecryCard(this);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/ChooseOneCardDesc.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.ChooseOneCard;\r\n\r\npublic class ChooseOneCardDesc extends CardDesc {\r\n\r\n\tpublic String[] options;\r\n\tpublic String bothOptions;\r\n\r\n\t@Override\r\n\tpublic Card createInstance() {\r\n\t\treturn new ChooseOneCard(this);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/ConditionDeserializer.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport java.lang.reflect.Type;\r\nimport java.util.Map;\r\n\r\nimport com.google.gson.JsonDeserializationContext;\r\nimport com.google.gson.JsonDeserializer;\r\nimport com.google.gson.JsonElement;\r\nimport com.google.gson.JsonObject;\r\nimport com.google.gson.JsonParseException;\r\n\r\nimport net.demilich.metastone.game.spells.desc.condition.Condition;\r\nimport net.demilich.metastone.game.spells.desc.condition.ConditionArg;\r\nimport net.demilich.metastone.game.spells.desc.condition.ConditionDesc;\r\n\r\npublic class ConditionDeserializer implements JsonDeserializer<ConditionDesc> {\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\t@Override\r\n\tpublic ConditionDesc deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {\r\n\t\tif (!(json instanceof JsonObject)) {\r\n\t\t\tthrow new JsonParseException(\"Condition parser expected an JsonObject but found \" + json + \" instead\");\r\n\t\t}\r\n\t\tJsonObject jsonData = (JsonObject) json;\r\n\t\tString conditionClassName = Condition.class.getPackage().getName() + \".\" + jsonData.get(\"class\").getAsString();\r\n\t\tClass<? extends Condition> conditionClass;\r\n\t\ttry {\r\n\t\t\tconditionClass = (Class<? extends Condition>) Class.forName(conditionClassName);\r\n\t\t} catch (ClassNotFoundException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t\tthrow new JsonParseException(\"Condition parser encountered an invalid class: \" + conditionClassName);\r\n\t\t}\r\n\t\tMap<ConditionArg, Object> arguments = ConditionDesc.build(conditionClass);\r\n\t\tparseArgument(ConditionArg.RACE, jsonData, arguments, ParseValueType.RACE);\r\n\t\tparseArgument(ConditionArg.VALUE, jsonData, arguments, ParseValueType.INTEGER);\r\n\t\tparseArgument(ConditionArg.VALUE1, jsonData, arguments, ParseValueType.VALUE);\r\n\t\tparseArgument(ConditionArg.VALUE2, jsonData, arguments, ParseValueType.VALUE);\r\n\t\tparseArgument(ConditionArg.TARGET_PLAYER, jsonData, arguments, ParseValueType.TARGET_PLAYER);\r\n\t\tparseArgument(ConditionArg.TARGET, jsonData, arguments, ParseValueType.TARGET_REFERENCE);\r\n\t\tparseArgument(ConditionArg.OPERATION, jsonData, arguments, ParseValueType.OPERATION);\r\n\t\tparseArgument(ConditionArg.INVERT, jsonData, arguments, ParseValueType.BOOLEAN);\r\n\t\tparseArgument(ConditionArg.ATTRIBUTE, jsonData, arguments, ParseValueType.ATTRIBUTE);\r\n\t\tparseArgument(ConditionArg.CARD_TYPE, jsonData, arguments, ParseValueType.CARD_TYPE);\r\n\t\tparseArgument(ConditionArg.CONDITIONS, jsonData, arguments, ParseValueType.CONDITION_ARRAY);\r\n\t\tparseArgument(ConditionArg.CARD_ID, jsonData, arguments, ParseValueType.STRING);\r\n\t\tparseArgument(ConditionArg.CARD_IDS, jsonData, arguments, ParseValueType.STRING_ARRAY);\r\n\t\tparseArgument(ConditionArg.FILTER, jsonData, arguments, ParseValueType.ENTITY_FILTER);\r\n\t\tparseArgument(ConditionArg.CARD_FILTER, jsonData, arguments, ParseValueType.ENTITY_FILTER);\r\n\r\n\t\treturn new ConditionDesc(arguments);\r\n\t}\r\n\r\n\tprivate void parseArgument(ConditionArg arg, JsonObject jsonData, Map<ConditionArg, Object> arguments, ParseValueType valueType) {\r\n\t\tString argName = ParseUtils.toCamelCase(arg.toString());\r\n\t\tif (!jsonData.has(argName)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tObject value = ParseUtils.parse(argName, jsonData, valueType);\r\n\t\targuments.put(arg, value);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/Desc.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.valueprovider.ValueProvider;\r\n\r\npublic class Desc<T> {\r\n\r\n\tprotected final Map<T, Object> arguments;\r\n\r\n\tpublic Desc(Map<T, Object> arguments) {\r\n\t\tthis.arguments = arguments;\r\n\t}\r\n\r\n\tpublic boolean contains(T arg) {\r\n\t\treturn arguments.containsKey(arg);\r\n\t}\r\n\r\n\tpublic Object get(T arg) {\r\n\t\treturn arguments.get(arg);\r\n\t}\r\n\r\n\tpublic boolean getBool(T arg) {\r\n\t\treturn arguments.containsKey(arg) ? (boolean) get(arg) : false;\r\n\t}\r\n\r\n\tpublic int getInt(T arg) {\r\n\t\treturn arguments.containsKey(arg) ? (int) get(arg) : 0;\r\n\t}\r\n\r\n\tpublic String getString(T arg) {\r\n\t\treturn arguments.containsKey(arg) ? (String) get(arg) : \"\";\r\n\t}\r\n\t\r\n\tpublic int getValue(T arg, GameContext context, Player player, Entity target, Entity host, int defaultValue) {\r\n\t\tObject storedValue = arguments.get(arg);\r\n\t\tif (storedValue == null) {\r\n\t\t\treturn defaultValue;\r\n\t\t}\r\n\t\tif (storedValue instanceof ValueProvider) {\r\n\t\t\tValueProvider valueProvider = (ValueProvider) storedValue;\r\n\t\t\treturn valueProvider.getValue(context, player, target, host);\r\n\t\t}\r\n\t\treturn (int)storedValue;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/FilterDeserializer.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport java.lang.reflect.Type;\r\nimport java.util.Map;\r\n\r\nimport com.google.gson.JsonDeserializationContext;\r\nimport com.google.gson.JsonDeserializer;\r\nimport com.google.gson.JsonElement;\r\nimport com.google.gson.JsonObject;\r\nimport com.google.gson.JsonParseException;\r\n\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\nimport net.demilich.metastone.game.spells.desc.filter.FilterArg;\r\nimport net.demilich.metastone.game.spells.desc.filter.FilterDesc;\r\n\r\npublic class FilterDeserializer implements JsonDeserializer<FilterDesc> {\r\n\t@SuppressWarnings(\"unchecked\")\r\n\t@Override\r\n\tpublic FilterDesc deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {\r\n\t\tif (!(json instanceof JsonObject)) {\r\n\t\t\tthrow new JsonParseException(\"ValueProvider parser expected an JsonObject but found \" + json + \" instead\");\r\n\t\t}\r\n\t\tJsonObject jsonData = (JsonObject) json;\r\n\t\tString filterClassName = EntityFilter.class.getPackage().getName() + \".\" + jsonData.get(\"class\").getAsString();\r\n\t\tClass<? extends EntityFilter> filterClass;\r\n\t\ttry {\r\n\t\t\tfilterClass = (Class<? extends EntityFilter>) Class.forName(filterClassName);\r\n\t\t} catch (ClassNotFoundException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t\tthrow new JsonParseException(\"ValueProvider parser encountered an invalid class: \" + filterClassName);\r\n\t\t}\r\n\t\tMap<FilterArg, Object> arguments = FilterDesc.build(filterClass);\r\n\r\n\t\tparseArgument(FilterArg.VALUE, jsonData, arguments, ParseValueType.INTEGER);\r\n\t\tparseArgument(FilterArg.TARGET_PLAYER, jsonData, arguments, ParseValueType.TARGET_PLAYER);\r\n\t\tparseArgument(FilterArg.ATTRIBUTE, jsonData, arguments, ParseValueType.ATTRIBUTE);\r\n\t\tparseArgument(FilterArg.RACE, jsonData, arguments, ParseValueType.RACE);\r\n\t\tparseArgument(FilterArg.OPERATION, jsonData, arguments, ParseValueType.OPERATION);\r\n\t\tparseArgument(FilterArg.INVERT, jsonData, arguments, ParseValueType.BOOLEAN);\r\n\t\tparseArgument(FilterArg.CARD_TYPE, jsonData, arguments, ParseValueType.CARD_TYPE);\r\n\t\tparseArgument(FilterArg.HERO_CLASS, jsonData, arguments, ParseValueType.HERO_CLASS);\r\n\t\tparseArgument(FilterArg.HERO_CLASSES, jsonData, arguments, ParseValueType.HERO_CLASS_ARRAY);\r\n\t\tparseArgument(FilterArg.RARITY, jsonData, arguments, ParseValueType.RARITY);\r\n\t\tparseArgument(FilterArg.MANA_COST, jsonData, arguments, ParseValueType.VALUE);\r\n\t\tparseArgument(FilterArg.CARD_ID, jsonData, arguments, ParseValueType.STRING);\r\n\t\tparseArgument(FilterArg.FILTERS, jsonData, arguments, ParseValueType.ENTITY_FILTER_ARRAY);\r\n\t\tparseArgument(FilterArg.TARGET, jsonData, arguments, ParseValueType.TARGET_REFERENCE);\r\n\r\n\t\treturn new FilterDesc(arguments);\r\n\t}\r\n\r\n\tprivate void parseArgument(FilterArg arg, JsonObject jsonData, Map<FilterArg, Object> arguments, ParseValueType valueType) {\r\n\t\tString argName = ParseUtils.toCamelCase(arg.toString());\r\n\t\tif (!jsonData.has(argName)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tObject value = ParseUtils.parse(argName, jsonData, valueType);\r\n\t\targuments.put(arg, value);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/HeroCardDesc.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.HeroCard;\r\nimport net.demilich.metastone.game.entities.minions.Race;\r\n\r\npublic class HeroCardDesc extends CardDesc {\r\n\r\n\tpublic String heroPower;\r\n\tpublic Race race = Race.NONE;\r\n\r\n\t@Override\r\n\tpublic Card createInstance() {\r\n\t\treturn new HeroCard(this);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/HeroPowerCardDesc.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.heroes.powers.HeroPower;\r\nimport net.demilich.metastone.game.heroes.powers.HeroPowerChooseOne;\r\n\r\npublic class HeroPowerCardDesc extends SpellCardDesc {\r\n\t\r\n\tpublic String[] options;\r\n\tpublic String bothOptions;\r\n\r\n\t@Override\r\n\tpublic Card createInstance() {\r\n\t\tif (options != null && options.length > 0) {\r\n\t\t\treturn new HeroPowerChooseOne(this);\r\n\t\t}\r\n\t\treturn new HeroPower(this);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/MinionCardDesc.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.MinionCard;\r\nimport net.demilich.metastone.game.entities.minions.Race;\r\n\r\npublic class MinionCardDesc extends SummonCardDesc {\r\n\r\n\tpublic int baseAttack;\r\n\tpublic int baseHp;\r\n\tpublic Race race;\r\n\r\n\t@Override\r\n\tpublic Card createInstance() {\r\n\t\treturn new MinionCard(this);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/ParseUtils.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport com.google.gson.JsonArray;\r\nimport com.google.gson.JsonElement;\r\nimport com.google.gson.JsonObject;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.PlayerAttribute;\r\nimport net.demilich.metastone.game.actions.ActionType;\r\nimport net.demilich.metastone.game.cards.CardDescType;\r\nimport net.demilich.metastone.game.cards.CardType;\r\nimport net.demilich.metastone.game.cards.Rarity;\r\nimport net.demilich.metastone.game.entities.EntityType;\r\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\r\nimport net.demilich.metastone.game.entities.minions.Race;\r\nimport net.demilich.metastone.game.entities.minions.RelativeToSource;\r\nimport net.demilich.metastone.game.spells.TargetPlayer;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.desc.condition.Condition;\r\nimport net.demilich.metastone.game.spells.desc.condition.ConditionDesc;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\nimport net.demilich.metastone.game.spells.desc.filter.FilterDesc;\r\nimport net.demilich.metastone.game.spells.desc.filter.Operation;\r\nimport net.demilich.metastone.game.spells.desc.manamodifier.CardCostModifierDesc;\r\nimport net.demilich.metastone.game.spells.desc.source.SourceDesc;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDeserializer;\r\nimport net.demilich.metastone.game.spells.desc.trigger.TriggerDesc;\r\nimport net.demilich.metastone.game.spells.desc.valueprovider.AlgebraicOperation;\r\nimport net.demilich.metastone.game.spells.desc.valueprovider.ValueProviderDesc;\r\nimport net.demilich.metastone.game.targeting.CardLocation;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\nimport net.demilich.metastone.game.targeting.TargetSelection;\r\nimport net.demilich.metastone.game.targeting.TargetType;\r\n\r\npublic class ParseUtils {\r\n\r\n\tprivate static SpellDeserializer spellParser = new SpellDeserializer();\r\n\tprivate static ValueProviderDeserializer valueProviderParser = new ValueProviderDeserializer();\r\n\tprivate static FilterDeserializer filterParser = new FilterDeserializer();\r\n\tprivate static SourceDeserializer sourceParser = new SourceDeserializer();\r\n\tprivate static ConditionDeserializer conditionParser = new ConditionDeserializer();\r\n\tprivate static EventTriggerDeserializer triggerParser = new EventTriggerDeserializer();\r\n\tprivate static CardCostModifierDeserializer manaModifierParser = new CardCostModifierDeserializer();\r\n\r\n\tpublic static Object parse(String argName, JsonObject jsonData, ParseValueType valueType) {\r\n\t\tJsonElement entry = jsonData.get(argName);\r\n\t\tswitch (valueType) {\r\n\t\tcase INTEGER:\r\n\t\t\treturn entry.getAsInt();\r\n\t\tcase BOOLEAN:\r\n\t\t\treturn entry.getAsBoolean();\r\n\t\tcase STRING:\r\n\t\t\treturn entry.getAsString();\r\n\t\tcase STRING_ARRAY: {\r\n\t\t\tJsonArray jsonArray = entry.getAsJsonArray();\r\n\t\t\tString[] array = new String[jsonArray.size()];\r\n\t\t\tfor (int i = 0; i < array.length; i++) {\r\n\t\t\t\tarray[i] = jsonArray.get(i).getAsString();\r\n\t\t\t}\r\n\t\t\treturn array;\r\n\t\t}\r\n\t\tcase TARGET_SELECTION:\r\n\t\t\treturn Enum.valueOf(TargetSelection.class, entry.getAsString());\r\n\t\tcase TARGET_REFERENCE:\r\n\t\t\treturn parseEntityReference(entry.getAsString());\r\n\t\tcase TARGET_PLAYER:\r\n\t\t\treturn Enum.valueOf(TargetPlayer.class, entry.getAsString());\r\n\t\tcase RACE:\r\n\t\t\treturn Enum.valueOf(Race.class, entry.getAsString());\r\n\t\tcase SPELL:\r\n\t\t\treturn spellParser.deserialize(entry, SpellDesc.class, null);\r\n\t\tcase SPELL_ARRAY: {\r\n\t\t\tJsonArray jsonArray = entry.getAsJsonArray();\r\n\t\t\tSpellDesc[] array = new SpellDesc[jsonArray.size()];\r\n\t\t\tfor (int i = 0; i < array.length; i++) {\r\n\t\t\t\tarray[i] = spellParser.deserialize(jsonArray.get(i), SpellDesc.class, null);\r\n\t\t\t}\r\n\t\t\treturn array;\r\n\t\t}\r\n\t\tcase ATTRIBUTE:\r\n\t\t\treturn Enum.valueOf(Attribute.class, entry.getAsString());\r\n\t\tcase PLAYER_ATTRIBUTE:\r\n\t\t\treturn Enum.valueOf(PlayerAttribute.class, entry.getAsString());\r\n\t\tcase RARITY:\r\n\t\t\treturn Enum.valueOf(Rarity.class, entry.getAsString());\r\n\t\tcase HERO_CLASS:\r\n\t\t\treturn Enum.valueOf(HeroClass.class, entry.getAsString());\r\n\t\tcase HERO_CLASS_ARRAY: {\r\n\t\t\tJsonArray jsonArray = entry.getAsJsonArray();\r\n\t\t\tHeroClass[] array = new HeroClass[jsonArray.size()];\r\n\t\t\tfor (int i = 0; i < array.length; i++) {\r\n\t\t\t\tarray[i] = Enum.valueOf(HeroClass.class, jsonArray.get(i).getAsString());\r\n\t\t\t}\r\n\t\t\treturn array;\r\n\t\t}\r\n\t\tcase BOARD_POSITION_RELATIVE:\r\n\t\t\treturn Enum.valueOf(RelativeToSource.class, entry.getAsString());\r\n\t\tcase CARD_LOCATION:\r\n\t\t\treturn Enum.valueOf(CardLocation.class, entry.getAsString());\r\n\t\tcase OPERATION:\r\n\t\t\treturn Enum.valueOf(Operation.class, entry.getAsString());\r\n\t\tcase CARD_TYPE:\r\n\t\t\treturn Enum.valueOf(CardType.class, entry.getAsString());\r\n\t\tcase ENTITY_TYPE:\r\n\t\t\treturn Enum.valueOf(EntityType.class, entry.getAsString());\r\n\t\tcase ACTION_TYPE:\r\n\t\t\treturn Enum.valueOf(ActionType.class, entry.getAsString());\r\n\t\tcase TARGET_TYPE:\r\n\t\t\treturn Enum.valueOf(TargetType.class, entry.getAsString());\r\n\t\tcase CARD_DESC_TYPE:\r\n\t\t\treturn Enum.valueOf(CardDescType.class, entry.getAsString());\r\n\t\tcase ALGEBRAIC_OPERATION:\r\n\t\t\treturn Enum.valueOf(AlgebraicOperation.class, entry.getAsString());\r\n\t\tcase VALUE:\r\n\t\t\t// value is either an int or a ValueProvider\r\n\t\t\t// if it is not an object, parse it as int, else fall-through to VALUE_PROVIDER case\r\n\t\t\tif (!entry.isJsonObject()) {\r\n\t\t\t\treturn entry.getAsInt();\r\n\t\t\t}\r\n\t\tcase VALUE_PROVIDER:\r\n\t\t\tValueProviderDesc valueProviderDesc = valueProviderParser.deserialize(entry, ValueProviderDesc.class, null);\r\n\t\t\treturn valueProviderDesc.create();\r\n\t\tcase ENTITY_FILTER: {\r\n\t\t\tFilterDesc filterDesc = filterParser.deserialize(entry, FilterDesc.class, null);\r\n\t\t\treturn filterDesc.create();\r\n\t\t}\r\n\t\tcase CARD_SOURCE: {\r\n\t\t\tSourceDesc sourceDesc = sourceParser.deserialize(entry, SourceDesc.class, null);\r\n\t\t\treturn sourceDesc.create();\r\n\t\t}\r\n\t\tcase ENTITY_FILTER_ARRAY: {\r\n\t\t\tJsonArray jsonArray = entry.getAsJsonArray();\r\n\t\t\tEntityFilter[] array = new EntityFilter[jsonArray.size()];\r\n\t\t\tfor (int i = 0; i < array.length; i++) {\r\n\t\t\t\tFilterDesc filterDesc = filterParser.deserialize(jsonArray.get(i), FilterDesc.class, null);\r\n\t\t\t\tarray[i] = filterDesc.create();\r\n\t\t\t}\r\n\t\t\treturn array;\r\n\t\t}\r\n\t\tcase CONDITION: {\r\n\t\t\tConditionDesc conditionDesc = conditionParser.deserialize(entry, ConditionDesc.class, null);\r\n\t\t\treturn conditionDesc.create();\r\n\t\t}\r\n\t\tcase CONDITION_ARRAY: {\r\n\t\t\tJsonArray jsonArray = entry.getAsJsonArray();\r\n\t\t\tCondition[] array = new Condition[jsonArray.size()];\r\n\t\t\tfor (int i = 0; i < array.length; i++) {\r\n\t\t\t\tConditionDesc conditionDesc = conditionParser.deserialize(jsonArray.get(i), ConditionDesc.class, null);\r\n\t\t\t\tarray[i] = conditionDesc.create();\r\n\t\t\t}\r\n\t\t\treturn array;\r\n\t\t}\r\n\t\tcase TRIGGER:\r\n\t\t\tJsonObject triggerObject = entry.getAsJsonObject();\r\n\t\t\tTriggerDesc triggerDesc = new TriggerDesc();\r\n\t\t\ttriggerDesc.eventTrigger = triggerParser.deserialize(triggerObject.get(\"eventTrigger\"), EventTriggerDesc.class, null);\r\n\t\t\ttriggerDesc.spell = spellParser.deserialize(triggerObject.get(\"spell\"), SpellDesc.class, null);\r\n\t\t\ttriggerDesc.oneTurn = triggerObject.has(\"oneTurn\") ? triggerObject.get(\"oneTurn\").getAsBoolean() : false;\r\n\t\t\ttriggerDesc.persistentOwner = triggerObject.has(\"persistentOwner\") ? triggerObject.get(\"persistentOwner\").getAsBoolean() : false;\r\n\t\t\ttriggerDesc.turnDelay = triggerObject.has(\"turnDelay\") ? triggerObject.get(\"turnDelay\").getAsInt() : 0;\r\n\t\t\treturn triggerDesc;\r\n\t\tcase EVENT_TRIGGER:\r\n\t\t\treturn triggerParser.deserialize(entry, EventTriggerDesc.class, null);\r\n\t\tcase CARD_COST_MODIFIER:\r\n\t\t\treturn manaModifierParser.deserialize(entry, CardCostModifierDesc.class, null);\r\n\t\tdefault:\r\n\t\t\tbreak;\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\tprivate static EntityReference parseEntityReference(String str) {\r\n\t\tString lowerCaseName = str.toLowerCase();\r\n\t\tswitch (lowerCaseName) {\r\n\t\tcase \"none\":\r\n\t\t\treturn EntityReference.NONE;\r\n\t\tcase \"enemy_characters\":\r\n\t\t\treturn EntityReference.ENEMY_CHARACTERS;\r\n\t\tcase \"enemy_minions\":\r\n\t\t\treturn EntityReference.ENEMY_MINIONS;\r\n\t\tcase \"enemy_hero\":\r\n\t\t\treturn EntityReference.ENEMY_HERO;\r\n\t\tcase \"enemy_weapon\":\r\n\t\t\treturn EntityReference.ENEMY_WEAPON;\r\n\t\tcase \"friendly_characters\":\r\n\t\t\treturn EntityReference.FRIENDLY_CHARACTERS;\r\n\t\tcase \"friendly_minions\":\r\n\t\t\treturn EntityReference.FRIENDLY_MINIONS;\r\n\t\tcase \"other_friendly_minions\":\r\n\t\t\treturn EntityReference.OTHER_FRIENDLY_MINIONS;\r\n\t\tcase \"adjacent_minions\":\r\n\t\t\treturn EntityReference.ADJACENT_MINIONS;\r\n\t\tcase \"opposite_minions\":\r\n\t\t\treturn EntityReference.OPPOSITE_MINIONS;\r\n\t\tcase \"friendly_hero\":\r\n\t\t\treturn EntityReference.FRIENDLY_HERO;\r\n\t\tcase \"friendly_weapon\":\r\n\t\t\treturn EntityReference.FRIENDLY_WEAPON;\r\n\t\tcase \"all_minions\":\r\n\t\t\treturn EntityReference.ALL_MINIONS;\r\n\t\tcase \"all_characters\":\r\n\t\t\treturn EntityReference.ALL_CHARACTERS;\r\n\t\tcase \"all_other_characters\":\r\n\t\t\treturn EntityReference.ALL_OTHER_CHARACTERS;\r\n\t\tcase \"all_other_minions\":\r\n\t\t\treturn EntityReference.ALL_OTHER_MINIONS;\r\n\t\tcase \"event_target\":\r\n\t\t\treturn EntityReference.EVENT_TARGET;\r\n\t\tcase \"target\":\r\n\t\t\treturn EntityReference.TARGET;\r\n\t\tcase \"spell_target\":\r\n\t\t\treturn EntityReference.SPELL_TARGET;\r\n\t\tcase \"pending_card\":\r\n\t\t\treturn EntityReference.PENDING_CARD;\r\n\t\tcase \"event_card\":\r\n\t\t\treturn EntityReference.EVENT_CARD;\r\n\t\tcase \"self\":\r\n\t\t\treturn EntityReference.SELF;\r\n\t\tcase \"attacker\":\r\n\t\t\treturn EntityReference.ATTACKER_REFERENCE;\r\n\t\tcase \"friendly_hand\":\r\n\t\t\treturn EntityReference.FRIENDLY_HAND;\r\n\t\tcase \"enemy_hand\":\r\n\t\t\treturn EntityReference.ENEMY_HAND;\r\n\t\tcase \"leftmost_friendly_minion\":\r\n\t\t\treturn EntityReference.LEFTMOST_FRIENDLY_MINION;\r\n\t\tcase \"leftmost_enemy_minion\":\r\n\t\t\treturn EntityReference.LEFTMOST_ENEMY_MINION;\r\n\t\tcase \"friendly_player\":\r\n\t\t\treturn EntityReference.FRIENDLY_PLAYER;\r\n\t\tcase \"enemy_player\":\r\n\t\t\treturn EntityReference.ENEMY_PLAYER;\r\n\t\tcase \"minions_to_left\":\r\n\t\t\treturn EntityReference.MINIONS_TO_LEFT;\r\n\t\tcase \"minions_to_right\":\r\n\t\t\treturn EntityReference.MINIONS_TO_RIGHT;\r\n\t\tdefault:\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static String toCamelCase(String input) {\r\n\t\tString inputLowerCase = input.toLowerCase();\r\n\t\tStringBuilder sb = new StringBuilder();\r\n\t\tfinal char delim = '_';\r\n\t\tchar value;\r\n\t\tboolean capitalize = false;\r\n\t\tfor (int i = 0; i < inputLowerCase.length(); ++i) {\r\n\t\t\tvalue = inputLowerCase.charAt(i);\r\n\t\t\tif (value == delim) {\r\n\t\t\t\tcapitalize = true;\r\n\t\t\t} else if (capitalize) {\r\n\t\t\t\tsb.append(Character.toUpperCase(value));\r\n\t\t\t\tcapitalize = false;\r\n\t\t\t} else {\r\n\t\t\t\tsb.append(value);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn sb.toString();\r\n\t}\r\n\r\n\tpublic static boolean tryParseBool(String value) {\r\n\t\ttry {\r\n\t\t\tBoolean.parseBoolean(value);\r\n\t\t\treturn true;\r\n\t\t} catch (Exception e) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static boolean tryParseInt(String value) {\r\n\t\ttry {\r\n\t\t\tInteger.parseInt(value);\r\n\t\t\treturn true;\r\n\t\t} catch (Exception e) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/ParseValueType.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\npublic enum ParseValueType {\r\n\tBOOLEAN,\r\n\tINTEGER,\r\n\tTARGET_SELECTION,\r\n\tTARGET_REFERENCE,\r\n\tTARGET_PLAYER,\r\n\tRACE,\r\n\tSPELL,\r\n\tSPELL_ARRAY,\r\n\tATTRIBUTE,\r\n\tPLAYER_ATTRIBUTE,\r\n\tVALUE_PROVIDER,\r\n\tENTITY_FILTER,\r\n\tENTITY_FILTER_ARRAY,\r\n\tSTRING,\r\n\tSTRING_ARRAY,\r\n\tBOARD_POSITION_RELATIVE,\r\n\tCARD_LOCATION,\r\n\tOPERATION,\r\n\tALGEBRAIC_OPERATION,\r\n\tCONDITION,\r\n\tCONDITION_ARRAY,\r\n\tCARD_TYPE,\r\n\tENTITY_TYPE,\r\n\tACTION_TYPE,\r\n\tTARGET_TYPE,\r\n\tTRIGGER,\r\n\tEVENT_TRIGGER,\r\n\tCARD_COST_MODIFIER,\r\n\tRARITY,\r\n\tHERO_CLASS,\r\n\tHERO_CLASS_ARRAY,\r\n\tVALUE,\r\n\tCARD_DESC_TYPE,\r\n\tCARD_SOURCE,\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/PermanentCardDesc.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.PermanentCard;\r\n\r\npublic class PermanentCardDesc extends SummonCardDesc {\r\n\r\n\t@Override\r\n\tpublic Card createInstance() {\r\n\t\treturn new PermanentCard(this);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/QuestCardDesc.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.QuestCard;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\n\r\npublic class QuestCardDesc extends SpellCardDesc {\r\n\r\n\tpublic EventTriggerDesc quest;\r\n\r\n\t@Override\r\n\tpublic Card createInstance() {\r\n\t\treturn new QuestCard(this);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/SecretCardDesc.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.SecretCard;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\n\r\npublic class SecretCardDesc extends SpellCardDesc {\r\n\r\n\tpublic EventTriggerDesc trigger;\r\n\r\n\t@Override\r\n\tpublic Card createInstance() {\r\n\t\treturn new SecretCard(this);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/SourceDeserializer.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport java.lang.reflect.Type;\r\nimport java.util.Map;\r\n\r\nimport com.google.gson.JsonDeserializationContext;\r\nimport com.google.gson.JsonDeserializer;\r\nimport com.google.gson.JsonElement;\r\nimport com.google.gson.JsonObject;\r\nimport com.google.gson.JsonParseException;\r\n\r\nimport net.demilich.metastone.game.spells.desc.source.CardSource;\r\nimport net.demilich.metastone.game.spells.desc.source.SourceArg;\r\nimport net.demilich.metastone.game.spells.desc.source.SourceDesc;\r\n\r\npublic class SourceDeserializer implements JsonDeserializer<SourceDesc> {\r\n\t@SuppressWarnings(\"unchecked\")\r\n\t@Override\r\n\tpublic SourceDesc deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {\r\n\t\tif (!(json instanceof JsonObject)) {\r\n\t\t\tthrow new JsonParseException(\"ValueProvider parser expected an JsonObject but found \" + json + \" instead\");\r\n\t\t}\r\n\t\tJsonObject jsonData = (JsonObject) json;\r\n\t\tString cardSourceClassName = CardSource.class.getPackage().getName() + \".\" + jsonData.get(\"class\").getAsString();\r\n\t\tClass<? extends CardSource> cardSourceClass;\r\n\t\ttry {\r\n\t\t\tcardSourceClass = (Class<? extends CardSource>) Class.forName(cardSourceClassName);\r\n\t\t} catch (ClassNotFoundException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t\tthrow new JsonParseException(\"ValueProvider parser encountered an invalid class: \" + cardSourceClassName);\r\n\t\t}\r\n\t\tMap<SourceArg, Object> arguments = SourceDesc.build(cardSourceClass);\r\n\r\n//\t\tparseArgument(SourceArg.VALUE, jsonData, arguments, ParseValueType.INTEGER);\r\n\t\tparseArgument(SourceArg.TARGET_PLAYER, jsonData, arguments, ParseValueType.TARGET_PLAYER);\r\n//\t\tparseArgument(SourceArg.ATTRIBUTE, jsonData, arguments, ParseValueType.ATTRIBUTE);\r\n//\t\tparseArgument(SourceArg.RACE, jsonData, arguments, ParseValueType.RACE);\r\n//\t\tparseArgument(SourceArg.OPERATION, jsonData, arguments, ParseValueType.OPERATION);\r\n\t\tparseArgument(SourceArg.INVERT, jsonData, arguments, ParseValueType.BOOLEAN);\r\n//\t\tparseArgument(SourceArg.CARD_TYPE, jsonData, arguments, ParseValueType.CARD_TYPE);\r\n//\t\tparseArgument(SourceArg.HERO_CLASS, jsonData, arguments, ParseValueType.HERO_CLASS);\r\n//\t\tparseArgument(SourceArg.HERO_CLASSES, jsonData, arguments, ParseValueType.HERO_CLASS_ARRAY);\r\n//\t\tparseArgument(SourceArg.RARITY, jsonData, arguments, ParseValueType.RARITY);\r\n//\t\tparseArgument(SourceArg.MANA_COST, jsonData, arguments, ParseValueType.VALUE);\r\n//\t\tparseArgument(SourceArg.CARD_ID, jsonData, arguments, ParseValueType.STRING);\r\n//\t\tparseArgument(SourceArg.FILTERS, jsonData, arguments, ParseValueType.ENTITY_FILTER_ARRAY);\r\n//\t\tparseArgument(SourceArg.TARGET, jsonData, arguments, ParseValueType.TARGET_REFERENCE);\r\n\r\n\t\treturn new SourceDesc(arguments);\r\n\t}\r\n\r\n\tprivate void parseArgument(SourceArg arg, JsonObject jsonData, Map<SourceArg, Object> arguments, ParseValueType valueType) {\r\n\t\tString argName = ParseUtils.toCamelCase(arg.toString());\r\n\t\tif (!jsonData.has(argName)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tObject value = ParseUtils.parse(argName, jsonData, valueType);\r\n\t\targuments.put(arg, value);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/SpellCardDesc.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.SpellCard;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.desc.condition.ConditionDesc;\r\nimport net.demilich.metastone.game.targeting.TargetSelection;\r\n\r\npublic class SpellCardDesc extends CardDesc {\r\n\r\n\tpublic TargetSelection targetSelection;\r\n\tpublic SpellDesc spell;\r\n\tpublic ConditionDesc condition;\r\n\r\n\t@Override\r\n\tpublic Card createInstance() {\r\n\t\treturn new SpellCard(this);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/SpellDeserializer.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport java.lang.reflect.Type;\r\nimport java.util.Map;\r\n\r\nimport com.google.gson.JsonDeserializationContext;\r\nimport com.google.gson.JsonDeserializer;\r\nimport com.google.gson.JsonElement;\r\nimport com.google.gson.JsonObject;\r\nimport com.google.gson.JsonParseException;\r\n\r\nimport net.demilich.metastone.game.spells.Spell;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class SpellDeserializer implements JsonDeserializer<SpellDesc> {\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\t@Override\r\n\tpublic SpellDesc deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\r\n\t\tif (!(json instanceof JsonObject)) {\r\n\t\t\tthrow new JsonParseException(\"SpellDesc parser expected an JsonObject but found \" + json + \" instead\");\r\n\t\t}\r\n\t\tJsonObject jsonData = (JsonObject) json;\r\n\t\tString spellClassName = Spell.class.getPackage().getName() + \".\" + jsonData.get(\"class\").getAsString();\r\n\t\tClass<? extends Spell> spellClass;\r\n\t\ttry {\r\n\t\t\tspellClass = (Class<? extends Spell>) Class.forName(spellClassName);\r\n\t\t} catch (ClassNotFoundException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t\tthrow new JsonParseException(\"SpellDesc parser encountered an invalid spell class: \" + spellClassName);\r\n\t\t}\r\n\t\tMap<SpellArg, Object> spellArgs = SpellDesc.build(spellClass);\r\n\t\tparseArgument(SpellArg.ATTACK_BONUS, jsonData, spellArgs, ParseValueType.VALUE);\r\n\t\tparseArgument(SpellArg.ATTRIBUTE, jsonData, spellArgs, ParseValueType.ATTRIBUTE);\r\n\t\tparseArgument(SpellArg.ARMOR_BONUS, jsonData, spellArgs, ParseValueType.VALUE);\r\n\t\tparseArgument(SpellArg.BOARD_POSITION_RELATIVE, jsonData, spellArgs, ParseValueType.BOARD_POSITION_RELATIVE);\r\n\t\tparseArgument(SpellArg.CANNOT_RECEIVE_OWNED, jsonData, spellArgs, ParseValueType.BOOLEAN);\r\n\t\tparseArgument(SpellArg.CARD, jsonData, spellArgs, ParseValueType.STRING);\r\n\t\tparseArgument(SpellArg.CARD_COST_MODIFIER, jsonData, spellArgs, ParseValueType.CARD_COST_MODIFIER);\r\n\t\tparseArgument(SpellArg.CARD_DESC_TYPE, jsonData, spellArgs, ParseValueType.CARD_DESC_TYPE);\r\n\t\tparseArgument(SpellArg.CARD_FILTER, jsonData, spellArgs, ParseValueType.ENTITY_FILTER);\r\n\t\tparseArgument(SpellArg.CARD_FILTERS, jsonData, spellArgs, ParseValueType.ENTITY_FILTER_ARRAY);\r\n\t\tparseArgument(SpellArg.CARD_LOCATION, jsonData, spellArgs, ParseValueType.CARD_LOCATION);\r\n\t\tparseArgument(SpellArg.CARD_SOURCE, jsonData, spellArgs, ParseValueType.CARD_SOURCE);\r\n\t\tparseArgument(SpellArg.CARD_TYPE, jsonData, spellArgs, ParseValueType.CARD_TYPE);\r\n\t\tparseArgument(SpellArg.CARDS, jsonData, spellArgs, ParseValueType.STRING_ARRAY);\r\n\t\tparseArgument(SpellArg.CONDITION, jsonData, spellArgs, ParseValueType.CONDITION);\r\n\t\tparseArgument(SpellArg.CONDITIONS, jsonData, spellArgs, ParseValueType.CONDITION_ARRAY);\r\n\t\tparseArgument(SpellArg.DESCRIPTION, jsonData, spellArgs, ParseValueType.STRING);\r\n\t\tparseArgument(SpellArg.EXCLUSIVE, jsonData, spellArgs, ParseValueType.BOOLEAN);\r\n\t\tparseArgument(SpellArg.FILTER, jsonData, spellArgs, ParseValueType.ENTITY_FILTER);\r\n\t\tparseArgument(SpellArg.FULL_MANA_CRYSTALS, jsonData, spellArgs, ParseValueType.BOOLEAN);\r\n\t\tparseArgument(SpellArg.HOW_MANY, jsonData, spellArgs, ParseValueType.VALUE);\r\n\t\tparseArgument(SpellArg.HP_BONUS, jsonData, spellArgs, ParseValueType.VALUE);\r\n\t\tparseArgument(SpellArg.IGNORE_SPELL_DAMAGE, jsonData, spellArgs, ParseValueType.BOOLEAN);\r\n\t\tparseArgument(SpellArg.INCLUDE_UNCOLLECTIBLE, jsonData, spellArgs, ParseValueType.BOOLEAN);\r\n\t\tparseArgument(SpellArg.MANA, jsonData, spellArgs, ParseValueType.VALUE);\r\n\t\tparseArgument(SpellArg.MANA_MODIFIER, jsonData, spellArgs, ParseValueType.VALUE);\r\n\t\tparseArgument(SpellArg.MAX_DAMAGE, jsonData, spellArgs, ParseValueType.INTEGER);\r\n\t\tparseArgument(SpellArg.MIN_DAMAGE, jsonData, spellArgs, ParseValueType.INTEGER);\r\n\t\tparseArgument(SpellArg.NAME, jsonData, spellArgs, ParseValueType.STRING);\r\n\t\tparseArgument(SpellArg.OPERATION, jsonData, spellArgs, ParseValueType.ALGEBRAIC_OPERATION);\r\n\t\tparseArgument(SpellArg.OPTIONS, jsonData, spellArgs, ParseValueType.BOOLEAN);\r\n\t\tparseArgument(SpellArg.RANDOM_TARGET, jsonData, spellArgs, ParseValueType.BOOLEAN);\r\n\t\tparseArgument(SpellArg.REVERT_TRIGGER, jsonData, spellArgs, ParseValueType.EVENT_TRIGGER);\r\n\t\tparseArgument(SpellArg.SECOND_REVERT_TRIGGER, jsonData, spellArgs, ParseValueType.EVENT_TRIGGER);\r\n\t\tparseArgument(SpellArg.SECONDARY_NAME, jsonData, spellArgs, ParseValueType.STRING);\r\n\t\tparseArgument(SpellArg.SECONDARY_TARGET, jsonData, spellArgs, ParseValueType.TARGET_REFERENCE);\r\n\t\tparseArgument(SpellArg.SECONDARY_VALUE, jsonData, spellArgs, ParseValueType.VALUE);\r\n\t\tparseArgument(SpellArg.SPELL, jsonData, spellArgs, ParseValueType.SPELL);\r\n\t\tparseArgument(SpellArg.SPELL_1, jsonData, spellArgs, ParseValueType.SPELL);\r\n\t\tparseArgument(SpellArg.SPELL_2, jsonData, spellArgs, ParseValueType.SPELL);\r\n\t\tparseArgument(SpellArg.SPELLS, jsonData, spellArgs, ParseValueType.SPELL_ARRAY);\r\n\t\tparseArgument(SpellArg.TARGET, jsonData, spellArgs, ParseValueType.TARGET_REFERENCE);\r\n\t\tparseArgument(SpellArg.TARGET_PLAYER, jsonData, spellArgs, ParseValueType.TARGET_PLAYER);\r\n\t\tparseArgument(SpellArg.TARGET_SELECTION, jsonData, spellArgs, ParseValueType.TARGET_SELECTION);\r\n\t\tparseArgument(SpellArg.TRIGGER, jsonData, spellArgs, ParseValueType.TRIGGER);\r\n\t\tparseArgument(SpellArg.VALUE, jsonData, spellArgs, ParseValueType.VALUE);\r\n\t\treturn new SpellDesc(spellArgs);\r\n\t}\r\n\r\n\tprivate void parseArgument(SpellArg spellArg, JsonObject jsonData, Map<SpellArg, Object> spellArgs, ParseValueType valueType) {\r\n\t\tString argName = ParseUtils.toCamelCase(spellArg.toString());\r\n\t\tif (!jsonData.has(argName)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tObject value = ParseUtils.parse(argName, jsonData, valueType);\r\n\t\tspellArgs.put(spellArg, value);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/SummonCardDesc.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport net.demilich.metastone.game.spells.desc.manamodifier.CardCostModifierDesc;\r\n\r\npublic abstract class SummonCardDesc extends ActorCardDesc {\r\n\r\n\tpublic CardCostModifierDesc cardCostModifier;\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/ValueProviderDeserializer.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport java.lang.reflect.Type;\r\nimport java.util.Map;\r\n\r\nimport com.google.gson.JsonDeserializationContext;\r\nimport com.google.gson.JsonDeserializer;\r\nimport com.google.gson.JsonElement;\r\nimport com.google.gson.JsonObject;\r\nimport com.google.gson.JsonParseException;\r\n\r\nimport net.demilich.metastone.game.spells.desc.valueprovider.ValueProvider;\r\nimport net.demilich.metastone.game.spells.desc.valueprovider.ValueProviderArg;\r\nimport net.demilich.metastone.game.spells.desc.valueprovider.ValueProviderDesc;\r\n\r\npublic class ValueProviderDeserializer implements JsonDeserializer<ValueProviderDesc> {\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\t@Override\r\n\tpublic ValueProviderDesc deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\r\n\t\tif (!(json instanceof JsonObject)) {\r\n\t\t\tthrow new JsonParseException(\"ValueProvider parser expected an JsonObject but found \" + json + \" instead\");\r\n\t\t}\r\n\t\tJsonObject jsonData = (JsonObject) json;\r\n\t\tString valueProviderClassName = ValueProvider.class.getPackage().getName() + \".\" + jsonData.get(\"class\").getAsString();\r\n\t\tClass<? extends ValueProvider> valueProviderClass;\r\n\t\ttry {\r\n\t\t\tvalueProviderClass = (Class<? extends ValueProvider>) Class.forName(valueProviderClassName);\r\n\t\t} catch (ClassNotFoundException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t\tthrow new JsonParseException(\"ValueProvider parser encountered an invalid class: \" + valueProviderClassName);\r\n\t\t}\r\n\t\tMap<ValueProviderArg, Object> arguments = ValueProviderDesc.build(valueProviderClass);\r\n\r\n\t\tparseArgument(ValueProviderArg.TARGET, jsonData, arguments, ParseValueType.TARGET_REFERENCE);\r\n\t\tparseArgument(ValueProviderArg.PLAYER_ATTRIBUTE, jsonData, arguments, ParseValueType.PLAYER_ATTRIBUTE);\r\n\t\tparseArgument(ValueProviderArg.ATTRIBUTE, jsonData, arguments, ParseValueType.ATTRIBUTE);\r\n\t\tparseArgument(ValueProviderArg.MULTIPLIER, jsonData, arguments, ParseValueType.INTEGER);\r\n\t\tparseArgument(ValueProviderArg.VALUE, jsonData, arguments, ParseValueType.INTEGER);\r\n\t\tparseArgument(ValueProviderArg.MIN, jsonData, arguments, ParseValueType.INTEGER);\r\n\t\tparseArgument(ValueProviderArg.MAX, jsonData, arguments, ParseValueType.INTEGER);\r\n\t\tparseArgument(ValueProviderArg.OFFSET, jsonData, arguments, ParseValueType.INTEGER);\r\n\t\tparseArgument(ValueProviderArg.IF_TRUE, jsonData, arguments, ParseValueType.INTEGER);\r\n\t\tparseArgument(ValueProviderArg.IF_FALSE, jsonData, arguments, ParseValueType.INTEGER);\r\n\t\tparseArgument(ValueProviderArg.RACE, jsonData, arguments, ParseValueType.RACE);\r\n\t\tparseArgument(ValueProviderArg.TARGET_PLAYER, jsonData, arguments, ParseValueType.TARGET_PLAYER);\r\n\t\tparseArgument(ValueProviderArg.CONDITION, jsonData, arguments, ParseValueType.CONDITION);\r\n\t\tparseArgument(ValueProviderArg.FILTER, jsonData, arguments, ParseValueType.ENTITY_FILTER);\r\n\t\tparseArgument(ValueProviderArg.OPERATION, jsonData, arguments, ParseValueType.ALGEBRAIC_OPERATION);\r\n\t\tparseArgument(ValueProviderArg.VALUE_1, jsonData, arguments, ParseValueType.VALUE);\r\n\t\tparseArgument(ValueProviderArg.VALUE_2, jsonData, arguments, ParseValueType.VALUE);\r\n\r\n\t\treturn new ValueProviderDesc(arguments);\r\n\t}\r\n\r\n\tprivate void parseArgument(ValueProviderArg arg, JsonObject jsonData, Map<ValueProviderArg, Object> arguments,\r\n\t\t\tParseValueType valueType) {\r\n\t\tString argName = ParseUtils.toCamelCase(arg.toString());\r\n\t\tif (!jsonData.has(argName)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tObject value = ParseUtils.parse(argName, jsonData, valueType);\r\n\t\targuments.put(arg, value);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/cards/desc/WeaponCardDesc.java",
    "content": "package net.demilich.metastone.game.cards.desc;\r\n\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.WeaponCard;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.desc.manamodifier.CardCostModifierDesc;\r\n\r\npublic class WeaponCardDesc extends ActorCardDesc {\r\n\r\n\tpublic int damage;\r\n\tpublic int durability;\r\n\tpublic SpellDesc onEquip;\r\n\tpublic SpellDesc onUnequip;\r\n\tpublic CardCostModifierDesc cardCostModifier;\r\n\r\n\t@Override\r\n\tpublic Card createInstance() {\r\n\t\treturn new WeaponCard(this);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/decks/Deck.java",
    "content": "package net.demilich.metastone.game.decks;\n\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.logic.GameLogic;\n\npublic class Deck {\n\n\tprivate String name = \"\";\n\tprivate final HeroClass heroClass;\n\tprivate final CardCollection cards = new CardCollection();\n\tprivate String description;\n\tprivate String filename;\n\tprivate boolean arbitrary;\n\n\tpublic Deck(HeroClass heroClass) {\n\t\tthis.heroClass = heroClass;\n\t}\n\n\tpublic Deck(HeroClass heroClass, boolean arbitrary) {\n\t\tthis.heroClass = heroClass;\n\t\tthis.arbitrary = arbitrary;\n\t}\n\n\tpublic int containsHowMany(Card card) {\n\t\tint count = 0;\n\t\tfor (Card cardInDeck : cards) {\n\t\t\tif (card.getCardId().equals(cardInDeck.getCardId())) {\n\t\t\t\tcount++;\n\t\t\t}\n\t\t}\n\t\treturn count;\n\t}\n\n\tpublic CardCollection getCards() {\n\t\treturn cards;\n\t}\n\n\tpublic CardCollection getCardsCopy() {\n\t\treturn getCards().clone();\n\t}\n\n\tpublic String getDescription() {\n\t\treturn description;\n\t}\n\n\tpublic HeroClass getHeroClass() {\n\t\treturn heroClass;\n\t}\n\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\t\n\tpublic boolean isArbitrary() {\n\t\treturn arbitrary;\n\t}\n\n\tpublic boolean isComplete() {\n\t\treturn cards.getCount() == GameLogic.DECK_SIZE;\n\t}\n\t\n\tpublic boolean isFull() {\n\t\treturn cards.getCount() == GameLogic.MAX_DECK_SIZE;\n\t}\n\n\tpublic boolean isMetaDeck() {\n\t\treturn getHeroClass() == HeroClass.DECK_COLLECTION;\n\t}\n\t\n\tpublic boolean isTooBig() {\n\t\treturn cards.getCount() > GameLogic.DECK_SIZE;\n\t}\n\n\tpublic void setDescription(String description) {\n\t\tthis.description = description;\n\t}\n\n\tpublic void setName(String name) {\n\t\tthis.name = name;\n\t}\n\n\tpublic String getFilename() {\n\t\treturn filename;\n\t}\n\n\tpublic void setFilename(String filename) {\n\t\tthis.filename = filename;\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/decks/DeckFactory.java",
    "content": "package net.demilich.metastone.game.decks;\n\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\n\npublic class DeckFactory {\n\n\tpublic static Deck getDeckConsistingof(int count, Card... cards) {\n\t\tCardCollection cardCollection = new CardCollection();\n\t\tfor (int i = 0; i < count; i++) {\n\t\t\tint randomIndex = ThreadLocalRandom.current().nextInt(cards.length);\n\t\t\tcardCollection.add(cards[randomIndex].clone());\n\t\t}\n\t\tDeck deck = new Deck(HeroClass.ANY);\n\t\tdeck.setName(\"[Debug deck]\");\n\t\tdeck.getCards().addAll(cardCollection);\n\t\treturn deck;\n\t}\n\n\tpublic static Deck getRandomDeck(HeroClass heroClass, DeckFormat deckFormat) {\n\t\treturn new RandomDeck(heroClass, deckFormat);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/decks/DeckFormat.java",
    "content": "package net.demilich.metastone.game.decks;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardSet;\n\npublic class DeckFormat {\n\n\tprivate String name = \"\";\n\tprivate String filename;\n\tprivate List<CardSet> sets;\n\n\tpublic DeckFormat() {\n\t\tsets = new ArrayList<CardSet>();\n\t}\n\n\tpublic void addSet(CardSet cardSet) {\n\t\tsets.add(cardSet);\n\t}\n\n\tpublic boolean isInFormat(Card card) {\n\t\tif (sets.contains(card.getCardSet())) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic boolean isInFormat(CardSet set) {\n\t\treturn sets.contains(set);\n\t}\n\n\tpublic boolean isInFormat(Deck deck) {\n\t\tfor (Card card : deck.getCards()) {\n\t\t\tif (!isInFormat(card)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\tpublic List<CardSet> getCardSets() {\n\t\treturn new ArrayList<CardSet>(sets);\n\t}\n\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\tpublic void setName(String name) {\n\t\tthis.name = name;\n\t}\n\n\tpublic String getFilename() {\n\t\treturn filename;\n\t}\n\n\tpublic void setFilename(String filename) {\n\t\tthis.filename = filename;\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/decks/MetaDeck.java",
    "content": "package net.demilich.metastone.game.decks;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\n\npublic class MetaDeck extends Deck {\n\n\tprivate final List<Deck> decks;\n\n\tpublic MetaDeck() {\n\t\tthis(new ArrayList<Deck>());\n\t}\n\n\tpublic MetaDeck(List<Deck> decks) {\n\t\tsuper(HeroClass.DECK_COLLECTION);\n\t\tthis.decks = decks;\n\t}\n\n\tpublic List<Deck> getDecks() {\n\t\treturn decks;\n\t}\n\n\tpublic boolean isComplete() {\n\t\treturn decks.size() > 1;\n\t}\n\n\tpublic Deck selectRandom() {\n\t\treturn getDecks().get(ThreadLocalRandom.current().nextInt(getDecks().size()));\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/decks/RandomDeck.java",
    "content": "package net.demilich.metastone.game.decks;\r\n\r\nimport java.util.concurrent.ThreadLocalRandom;\r\n\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.CardCatalogue;\r\nimport net.demilich.metastone.game.cards.CardCollection;\r\nimport net.demilich.metastone.game.cards.CardType;\r\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\r\nimport net.demilich.metastone.game.decks.validation.DefaultDeckValidator;\r\nimport net.demilich.metastone.game.decks.validation.IDeckValidator;\r\n\r\npublic class RandomDeck extends Deck {\r\n\t\r\n\tprivate DeckFormat deckFormat;\r\n\r\n\tpublic RandomDeck(HeroClass heroClass, DeckFormat deckFormat) {\r\n\t\tsuper(heroClass);\r\n\t\tthis.deckFormat = deckFormat;\r\n\t\tsetName(\"[Random deck]\");\r\n\t}\r\n\r\n\t@Override\r\n\tpublic CardCollection getCardsCopy() {\r\n\t\tDeck copyDeck = new Deck(getHeroClass());\r\n\t\tIDeckValidator deckValidator = new DefaultDeckValidator();\r\n\t\tCardCollection classCards = CardCatalogue.query(deckFormat, card -> {\r\n\t\t\treturn card.isCollectible() && !card.getCardType().isCardType(CardType.HERO) && !card.getCardType().isCardType(CardType.HERO_POWER) && card.hasHeroClass(getHeroClass());\r\n\t\t});\r\n\t\tCardCollection neutralCards = CardCatalogue.query(deckFormat, card -> {\r\n\t\t\treturn card.isCollectible() && !card.getCardType().isCardType(CardType.HERO) && !card.getCardType().isCardType(CardType.HERO_POWER) && card.hasHeroClass(HeroClass.ANY);\r\n\t\t});\r\n\r\n\t\twhile (!copyDeck.isComplete()) {\r\n\t\t\t// random deck consists of roughly 50% class cards and 50% neutral\r\n\t\t\t// cards\r\n\t\t\tCard randomCard = ThreadLocalRandom.current().nextBoolean() ? classCards.getRandom() : neutralCards.getRandom();\r\n\t\t\tif (deckValidator.canAddCardToDeck(randomCard, copyDeck)) {\r\n\t\t\t\tcopyDeck.getCards().add(randomCard);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn copyDeck.getCardsCopy();\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/decks/validation/ArbitraryDeckValidator.java",
    "content": "package net.demilich.metastone.game.decks.validation;\n\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.logic.GameLogic;\n\npublic class ArbitraryDeckValidator implements IDeckValidator {\n\n\t@Override\n\tpublic boolean canAddCardToDeck(Card card, Deck deck) {\n\t\treturn deck.getCards().getCount() <= GameLogic.MAX_DECK_SIZE;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/decks/validation/DefaultDeckValidator.java",
    "content": "package net.demilich.metastone.game.decks.validation;\n\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.Rarity;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.logic.GameLogic;\n\npublic class DefaultDeckValidator implements IDeckValidator {\n\n\t@Override\n\tpublic boolean canAddCardToDeck(Card card, Deck deck) {\n\t\tif (deck.getCards().getCount() >= GameLogic.MAX_DECK_SIZE) {\n\t\t\treturn false;\n\t\t}\n\t\tint cardInDeckCount = deck.containsHowMany(card);\n\t\treturn card.getRarity() == Rarity.LEGENDARY ? cardInDeckCount < 1 : cardInDeckCount < 2;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/decks/validation/IDeckValidator.java",
    "content": "package net.demilich.metastone.game.decks.validation;\n\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.decks.Deck;\n\npublic interface IDeckValidator {\n\n\tboolean canAddCardToDeck(Card card, Deck deck);\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/entities/Actor.java",
    "content": "package net.demilich.metastone.game.entities;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.EnumMap;\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.actions.BattlecryAction;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.costmodifier.CardCostModifier;\r\nimport net.demilich.metastone.game.entities.minions.Race;\r\nimport net.demilich.metastone.game.logic.GameLogic;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.trigger.SpellTrigger;\r\n\r\npublic abstract class Actor extends Entity {\r\n\r\n\tprivate Card sourceCard;\r\n\tprivate List<SpellTrigger> spellTriggers = new ArrayList<SpellTrigger>();\r\n\tprivate CardCostModifier cardCostModifier;\r\n\r\n\tpublic Actor(Card sourceCard) {\r\n\t\tthis.setName(sourceCard != null ? sourceCard.getName() : null);\r\n\t\tthis.sourceCard = sourceCard;\r\n\t}\r\n\r\n\tpublic void addDeathrattle(SpellDesc deathrattleSpell) {\r\n\t\tif (!hasAttribute(Attribute.DEATHRATTLES)) {\r\n\t\t\tsetAttribute(Attribute.DEATHRATTLES, new ArrayList<SpellDesc>());\r\n\t\t}\r\n\t\tgetDeathrattles().add(deathrattleSpell);\r\n\t}\r\n\r\n\tpublic void addSpellTrigger(SpellTrigger spellTrigger) {\r\n\t\tspellTriggers.add(spellTrigger);\r\n\t}\r\n\r\n\tpublic boolean canAttackThisTurn() {\r\n\t\tif (hasAttribute(Attribute.CANNOT_ATTACK)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tif (hasAttribute(Attribute.FROZEN)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tif (hasAttribute(Attribute.SUMMONING_SICKNESS) && !hasAttribute(Attribute.CHARGE)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\treturn getAttack() > 0 && ((getAttributeValue(Attribute.NUMBER_OF_ATTACKS) + getAttributeValue(Attribute.EXTRA_ATTACKS)) > 0 || hasAttribute(Attribute.UNLIMITED_ATTACKS));\r\n\t}\r\n\r\n\tpublic void clearSpellTriggers() {\r\n\t\tthis.spellTriggers = new ArrayList<SpellTrigger>();\r\n\t}\r\n\r\n\t@Override\r\n\tpublic Actor clone() {\r\n\t\tActor clone = (Actor) super.clone();\r\n\t\tclone.attributes = new EnumMap<>(getAttributes());\r\n\t\tclone.clearSpellTriggers();\r\n\t\tfor (SpellTrigger trigger : getSpellTriggers()) {\r\n\t\t\tclone.spellTriggers.add(trigger.clone());\r\n\t\t}\r\n\t\tif (hasAttribute(Attribute.DEATHRATTLES)) {\r\n\t\t\tclone.removeAttribute(Attribute.DEATHRATTLES);\r\n\t\t\tfor (SpellDesc deathrattleSpell : getDeathrattles()) {\r\n\t\t\t\tSpellDesc deathrattleClone = deathrattleSpell.clone();\r\n\t\t\t\tclone.addDeathrattle(deathrattleClone);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn clone;\r\n\t}\r\n\r\n\tprotected boolean displayGameTag(Attribute tag) {\r\n\t\treturn tag == Attribute.CHARGE || tag == Attribute.ENRAGED || tag == Attribute.FROZEN || tag == Attribute.DIVINE_SHIELD\r\n\t\t\t\t|| tag == Attribute.WINDFURY || tag == Attribute.SPELL_DAMAGE || tag == Attribute.STEALTH || tag == Attribute.TAUNT\r\n\t\t\t\t|| tag == Attribute.CANNOT_ATTACK || tag == Attribute.UNTARGETABLE_BY_SPELLS || tag == Attribute.AURA_UNTARGETABLE_BY_SPELLS\r\n\t\t\t\t|| tag == Attribute.MEGA_WINDFURY;\r\n\t}\r\n\r\n\tpublic int getAttack() {\r\n\t\tint attack = getAttributeValue(Attribute.ATTACK) + getAttributeValue(Attribute.ATTACK_BONUS)\r\n\t\t\t\t+ getAttributeValue(Attribute.AURA_ATTACK_BONUS) + getAttributeValue(Attribute.TEMPORARY_ATTACK_BONUS)\r\n\t\t\t\t+ getAttributeValue(Attribute.CONDITIONAL_ATTACK_BONUS);\r\n\t\treturn Math.max(0, attack);\r\n\t}\r\n\r\n\tpublic int getBaseAttack() {\r\n\t\treturn getAttributeValue(Attribute.BASE_ATTACK);\r\n\t}\r\n\r\n\tpublic int getBaseHp() {\r\n\t\treturn getAttributeValue(Attribute.BASE_HP);\r\n\t}\r\n\r\n\tpublic BattlecryAction getBattlecry() {\r\n\t\treturn (BattlecryAction) getAttribute(Attribute.BATTLECRY);\r\n\t}\r\n\r\n\tpublic CardCostModifier getCardCostModifier() {\r\n\t\treturn cardCostModifier;\r\n\t}\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\tpublic List<SpellDesc> getDeathrattles() {\r\n\t\treturn (List<SpellDesc>) getAttribute(Attribute.DEATHRATTLES);\r\n\t}\r\n\r\n\tpublic int getHp() {\r\n\t\treturn getAttributeValue(Attribute.HP);\r\n\t}\r\n\r\n\tpublic int getMaxHp() {\r\n\t\treturn getAttributeValue(Attribute.MAX_HP) + getAttributeValue(Attribute.HP_BONUS)\r\n\t\t\t\t+ getAttributeValue(Attribute.AURA_HP_BONUS);\r\n\t}\r\n\r\n\tpublic Race getRace() {\r\n\t\treturn (Race) getAttribute(Attribute.RACE);\r\n\t}\r\n\r\n\tpublic Card getSourceCard() {\r\n\t\treturn sourceCard;\r\n\t}\r\n\r\n\tpublic List<SpellTrigger> getSpellTriggers() {\r\n\t\treturn new ArrayList<SpellTrigger>(spellTriggers);\r\n\t}\r\n\r\n\tpublic boolean hasSpellTrigger() {\r\n\t\treturn spellTriggers.size() != 0;\r\n\t}\r\n\t\r\n\tpublic int getMaxNumberOfAttacks() {\r\n\t\tif (hasAttribute(Attribute.MEGA_WINDFURY)) {\r\n\t\t\treturn GameLogic.MEGA_WINDFURY_ATTACKS;\r\n\t\t} else if (hasAttribute(Attribute.WINDFURY)) {\r\n\t\t\treturn GameLogic.WINDFURY_ATTACKS;\r\n\t\t}\r\n\t\treturn 1;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic boolean isDestroyed() {\r\n\t\treturn getHp() < 1 || super.isDestroyed();\r\n\t}\r\n\r\n\tpublic boolean isWounded() {\r\n\t\treturn getHp() != getMaxHp();\r\n\t}\r\n\r\n\tpublic void modifyAuraHpBonus(int value) {\r\n\t\tmodifyAttribute(Attribute.AURA_HP_BONUS, value);\r\n\t\tif (value > 0) {\r\n\t\t\tmodifyAttribute(Attribute.HP, value);\r\n\t\t}\r\n\t\tif (getHp() > getMaxHp()) {\r\n\t\t\tsetHp(getMaxHp());\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void modifyHpBonus(int value) {\r\n\t\tmodifyAttribute(Attribute.HP_BONUS, value);\r\n\t\tif (value > 0) {\r\n\t\t\tmodifyAttribute(Attribute.HP, value);\r\n\t\t}\r\n\t\tif (getHp() > getMaxHp()) {\r\n\t\t\tsetHp(getMaxHp());\r\n\t\t}\r\n\t\t\t\r\n\t}\r\n\r\n\tpublic void setAttack(int value) {\r\n\t\tsetAttribute(Attribute.ATTACK, value);\r\n\t}\r\n\r\n\tpublic void setBaseAttack(int value) {\r\n\t\tsetAttribute(Attribute.BASE_ATTACK, value);\r\n\t}\r\n\r\n\tpublic void setBaseHp(int value) {\r\n\t\tsetAttribute(Attribute.BASE_HP, value);\r\n\t}\r\n\r\n\tpublic void setBattlecry(BattlecryAction battlecry) {\r\n\t\tsetAttribute(Attribute.BATTLECRY, battlecry);\r\n\t}\r\n\r\n\tpublic void setCardCostModifier(CardCostModifier cardCostModifier) {\r\n\t\tthis.cardCostModifier = cardCostModifier;\r\n\t}\r\n\r\n\tpublic void setHp(int value) {\r\n\t\tsetAttribute(Attribute.HP, value);\r\n\t}\r\n\r\n\tpublic void setMaxHp(int value) {\r\n\t\tsetAttribute(Attribute.MAX_HP, value);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void setOwner(int ownerIndex) {\r\n\t\tsuper.setOwner(ownerIndex);\r\n\t\tfor (SpellTrigger trigger : spellTriggers) {\r\n\t\t\ttrigger.setHost(this);\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void setRace(Race race) {\r\n\t\tsetAttribute(Attribute.RACE, race);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String toString() {\r\n\t\tString result = \"[\" + getEntityType() + \" '\" + getName() + \"'id:\" + getId() + \" \";\r\n\t\tresult += getAttack() + \"/\" + getHp();\r\n\t\tString prefix = \" \";\r\n\t\tfor (Attribute tag : getAttributes().keySet()) {\r\n\t\t\tif (displayGameTag(tag)) {\r\n\t\t\t\tresult += prefix + tag;\r\n\t\t\t\tprefix = \", \";\r\n\t\t\t}\r\n\t\t}\r\n\t\tresult += \" hashCode: \" + hashCode();\r\n\t\tresult += \"]\";\r\n\t\treturn result;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/entities/Entity.java",
    "content": "package net.demilich.metastone.game.entities;\r\n\r\nimport java.util.EnumMap;\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.logic.CustomCloneable;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\nimport net.demilich.metastone.game.targeting.IdFactory;\r\n\r\npublic abstract class Entity extends CustomCloneable {\r\n\r\n\tprivate String name;\r\n\tprotected Map<Attribute, Object> attributes = new EnumMap<Attribute, Object>(Attribute.class);\r\n\tprivate int id = IdFactory.UNASSIGNED;\r\n\tprivate int ownerIndex = -1;\r\n\r\n\t@Override\r\n\tpublic Entity clone() {\r\n\t\tEntity clone = (Entity) super.clone();\r\n\t\treturn clone;\r\n\t}\r\n\r\n\tpublic Object getAttribute(Attribute attribute) {\r\n\t\treturn attributes.get(attribute);\r\n\t}\r\n\r\n\tpublic Map<Attribute, Object> getAttributes() {\r\n\t\treturn attributes;\r\n\t}\r\n\r\n\tpublic int getAttributeValue(Attribute attribute) {\r\n\t\treturn attributes.containsKey(attribute) ? (int) attributes.get(attribute) : 0;\r\n\t}\r\n\r\n\tpublic abstract EntityType getEntityType();\r\n\r\n\tpublic int getId() {\r\n\t\treturn id;\r\n\t}\r\n\r\n\tpublic String getName() {\r\n\t\treturn name;\r\n\t}\r\n\r\n\tpublic int getOwner() {\r\n\t\treturn ownerIndex;\r\n\t}\r\n\r\n\tpublic EntityReference getReference() {\r\n\t\treturn EntityReference.pointTo(this);\r\n\t}\r\n\r\n\tpublic boolean hasAttribute(Attribute attribute) {\r\n\t\tObject value = attributes.get(attribute);\r\n\t\tif (value == null) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tif (value instanceof Integer) {\r\n\t\t\treturn ((int) value) != 0;\r\n\t\t}\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic boolean isDestroyed() {\r\n\t\treturn hasAttribute(Attribute.DESTROYED);\r\n\t}\r\n\r\n\tpublic void modifyAttribute(Attribute attribute, int value) {\r\n\t\tif (!attributes.containsKey(attribute)) {\r\n\t\t\tsetAttribute(attribute, 0);\r\n\t\t}\r\n\t\tsetAttribute(attribute, getAttributeValue(attribute) + value);\r\n\t}\r\n\t\r\n\tpublic void modifyHpBonus(int value) {\r\n\t\tmodifyAttribute(Attribute.HP_BONUS, value);\r\n\t}\r\n\r\n\tpublic void removeAttribute(Attribute attribute) {\r\n\t\tattributes.remove(attribute);\r\n\t}\r\n\r\n\tpublic void setAttribute(Attribute attribute) {\r\n\t\tattributes.put(attribute, 1);\r\n\t}\r\n\r\n\tpublic void setAttribute(Attribute attribute, int value) {\r\n\t\tattributes.put(attribute, value);\r\n\t}\r\n\r\n\tpublic void setAttribute(Attribute attribute, Object value) {\r\n\t\tattributes.put(attribute, value);\r\n\t}\r\n\r\n\tpublic void setId(int id) {\r\n\t\tthis.id = id;\r\n\t}\r\n\r\n\tpublic void setName(String name) {\r\n\t\tthis.name = name;\r\n\t}\r\n\r\n\tpublic void setOwner(int ownerIndex) {\r\n\t\tthis.ownerIndex = ownerIndex;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/entities/EntityType.java",
    "content": "package net.demilich.metastone.game.entities;\n\npublic enum EntityType {\n\tANY,\n\tHERO,\n\tMINION,\n\tPERMANENT,\n\tWEAPON,\n\tCARD,\n\tPLAYER,\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/entities/heroes/Hero.java",
    "content": "package net.demilich.metastone.game.entities.heroes;\n\nimport java.util.EnumMap;\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.cards.HeroCard;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.entities.weapons.Weapon;\nimport net.demilich.metastone.game.heroes.powers.HeroPower;\n\npublic class Hero extends Actor {\n\n\tprivate HeroClass heroClass;\n\tprivate HeroPower heroPower;\n\tprivate Weapon weapon;\n\n\tpublic Hero(HeroCard heroCard, HeroPower heroPower) {\n\t\tsuper(heroCard);\n\t\tsetName(heroCard.getName());\n\t\tthis.setHeroClass(heroCard.getHeroClass());\n\t\tthis.setHeroPower(heroPower);\n\t}\n\n\tpublic void activateWeapon(boolean active) {\n\t\tif (weapon != null) {\n\t\t\tweapon.setActive(active);\n\t\t}\n\t}\n\n\t@Override\n\tpublic Hero clone() {\n\t\tHero clone = (Hero) super.clone();\n\t\tif (weapon != null) {\n\t\t\tclone.setWeapon(getWeapon().clone());\n\t\t}\n\t\t\n\t\tclone.setHeroPower((HeroPower) getHeroPower().clone());\n\t\treturn clone;\n\t}\n\n\tpublic int getArmor() {\n\t\treturn getAttributeValue(Attribute.ARMOR);\n\t}\n\n\t@Override\n\tpublic int getAttack() {\n\t\tint attack = super.getAttack();\n\t\tif (weapon != null && weapon.isActive()) {\n\t\t\tattack += weapon.getWeaponDamage();\n\t\t}\n\t\treturn attack;\n\t}\n\n\tpublic Map<Attribute, Object> getAttributesCopy() {\n\t\tMap<Attribute, Object> copy = new EnumMap<>(Attribute.class);\n\t\tfor (Attribute attribute : attributes.keySet()) {\n\t\t\tcopy.put(attribute, attributes.get(attribute));\n\t\t}\n\t\treturn copy;\n\t}\n\n\tpublic int getEffectiveHp() {\n\t\treturn getHp() + getArmor();\n\t}\n\n\t@Override\n\tpublic EntityType getEntityType() {\n\t\treturn EntityType.HERO;\n\t}\n\n\tpublic HeroClass getHeroClass() {\n\t\treturn heroClass;\n\t}\n\n\tpublic HeroPower getHeroPower() {\n\t\treturn heroPower;\n\t}\n\n\tpublic Weapon getWeapon() {\n\t\treturn weapon;\n\t}\n\n\tpublic void modifyArmor(int armor) {\n\t\t// armor cannot fall below zero\n\t\tint newArmor = Math.max(getArmor() + armor, 0);\n\t\tsetAttribute(Attribute.ARMOR, newArmor);\n\t}\n\t\n\tpublic void setHeroClass(HeroClass heroClass) {\n\t\tthis.heroClass = heroClass;\n\t}\n\n\tpublic void setHeroPower(HeroPower heroPower) {\n\t\tthis.heroPower = heroPower;\n\t\theroPower.setOwner(getOwner());\n\t}\n\n\t@Override\n\tpublic void setOwner(int ownerIndex) {\n\t\tsuper.setOwner(ownerIndex);\n\t\theroPower.setOwner(ownerIndex);\n\t}\n\n\tpublic void setWeapon(Weapon weapon) {\n\t\tthis.weapon = weapon;\n\t\tif (weapon != null) {\n\t\t\tweapon.setOwner(getOwner());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/entities/heroes/HeroClass.java",
    "content": "package net.demilich.metastone.game.entities.heroes;\n\npublic enum HeroClass {\n\tANY,\n\tDECK_COLLECTION,\n\n\tNEUTRAL,\n\n\tDRUID,\n\tHUNTER,\n\tMAGE,\n\tPALADIN,\n\tPRIEST,\n\tROGUE,\n\tSHAMAN,\n\tWARLOCK,\n\tWARRIOR,\n\n\tSELF,\n\tOPPONENT,\n\tBOSS;\n\n\tpublic boolean isBaseClass() {\n\t\tHeroClass[] nonBaseClasses = {ANY, NEUTRAL, SELF, DECK_COLLECTION, OPPONENT, BOSS};\n\t\tfor (int i=0; i<nonBaseClasses.length; i++) {\n\t\t\tif (nonBaseClasses[i] == this) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/entities/heroes/MetaHero.java",
    "content": "package net.demilich.metastone.game.entities.heroes;\n\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.HeroCard;\nimport net.demilich.metastone.game.cards.Rarity;\nimport net.demilich.metastone.game.cards.desc.HeroCardDesc;\n\npublic class MetaHero extends HeroCard {\n\n\tprivate static HeroCardDesc createDesc() {\n\t\tHeroCardDesc desc = new HeroCardDesc();\n\t\tdesc.collectible = false;\n\t\tdesc.heroClass = HeroClass.DECK_COLLECTION;\n\t\tdesc.name = \"Depending on deck\";\n\t\tdesc.id = \"hero_meta\";\n\t\tdesc.rarity = Rarity.FREE;\n\t\treturn desc;\n\t}\n\n\tpublic static HeroCard getHeroCard(HeroClass heroClass) {\n\t\tswitch (heroClass) {\n\t\tcase DRUID:\n\t\t\treturn (HeroCard) CardCatalogue.getCardById(\"hero_malfurion\");\n\t\tcase HUNTER:\n\t\t\treturn (HeroCard) CardCatalogue.getCardById(\"hero_rexxar\");\n\t\tcase MAGE:\n\t\t\treturn (HeroCard) CardCatalogue.getCardById(\"hero_jaina\");\n\t\tcase PALADIN:\n\t\t\treturn (HeroCard) CardCatalogue.getCardById(\"hero_uther\");\n\t\tcase PRIEST:\n\t\t\treturn (HeroCard) CardCatalogue.getCardById(\"hero_anduin\");\n\t\tcase ROGUE:\n\t\t\treturn (HeroCard) CardCatalogue.getCardById(\"hero_valeera\");\n\t\tcase SHAMAN:\n\t\t\treturn (HeroCard) CardCatalogue.getCardById(\"hero_thrall\");\n\t\tcase WARLOCK:\n\t\t\treturn (HeroCard) CardCatalogue.getCardById(\"hero_guldan\");\n\t\tcase WARRIOR:\n\t\t\treturn (HeroCard) CardCatalogue.getCardById(\"hero_garrosh\");\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t\treturn null;\n\t}\n\t\n\tpublic MetaHero() {\n\t\tsuper(createDesc());\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/entities/minions/Minion.java",
    "content": "package net.demilich.metastone.game.entities.minions;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.EntityType;\n\npublic class Minion extends Summon {\n\n\tpublic Minion(MinionCard sourceCard) {\n\t\tsuper(sourceCard);\n\t\tRace race = sourceCard.hasAttribute(Attribute.RACE) ? (Race) sourceCard.getAttribute(Attribute.RACE) : Race.NONE;\n\t\tsetRace(race);\n\t}\n\n\t@Override\n\tpublic Minion clone() {\n\t\tMinion clone = (Minion) super.clone();\n\t\treturn clone;\n\t}\n\n\t@Override\n\tpublic int getAttack() {\n\t\tif (hasAttribute(Attribute.ATTACK_EQUALS_HP)) {\n\t\t\treturn getHp();\n\t\t}\n\t\treturn super.getAttack();\n\t}\n\n\t@Override\n\tpublic EntityType getEntityType() {\n\t\treturn EntityType.MINION;\n\t}\n\n\tprotected void setBaseStats(int baseAttack, int baseHp) {\n\t\tsetBaseAttack(baseAttack);\n\t\tsetBaseHp(baseHp);\n\t}\n\t\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/entities/minions/Permanent.java",
    "content": "package net.demilich.metastone.game.entities.minions;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.cards.PermanentCard;\nimport net.demilich.metastone.game.entities.EntityType;\n\npublic class Permanent extends Summon {\n\n\tpublic Permanent(PermanentCard sourceCard) {\n\t\tsuper(sourceCard);\n\t\tsetRace(Race.NONE);\n\t}\n\n\t@Override\n\tpublic Permanent clone() {\n\t\tPermanent clone = (Permanent) super.clone();\n\t\treturn clone;\n\t}\n\n\t@Override\n\tpublic EntityType getEntityType() {\n\t\treturn EntityType.PERMANENT;\n\t}\n\n\t@Override\n\tpublic boolean isDestroyed() {\n\t\treturn hasAttribute(Attribute.DESTROYED);\n\t}\n\t\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/entities/minions/Race.java",
    "content": "package net.demilich.metastone.game.entities.minions;\n\npublic enum Race {\n\tNONE,\n\t\n\tBEAST,\n\tDEMON,\n\tDRAGON,\n\tELEMENTAL,\n\tMECH,\n\tMURLOC,\n\tPIRATE,\n\tTOTEM,\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/entities/minions/RelativeToSource.java",
    "content": "package net.demilich.metastone.game.entities.minions;\r\n\r\npublic enum RelativeToSource {\r\n\r\n\tUNDEFINED, LEFT, RIGHT\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/entities/minions/Summon.java",
    "content": "package net.demilich.metastone.game.entities.minions;\n\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Actor;\n\npublic abstract class Summon extends Actor {\n\n\tpublic Summon(Card sourceCard) {\n\t\tsuper(sourceCard);\n\t}\n\n\t@Override\n\tpublic Summon clone() {\n\t\tSummon clone = (Summon) super.clone();\n\t\treturn clone;\n\t}\n\t\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/entities/weapons/Weapon.java",
    "content": "package net.demilich.metastone.game.entities.weapons;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class Weapon extends Actor {\n\n\tprivate boolean active;\n\tprivate SpellDesc onEquip;\n\tprivate SpellDesc onUnequip;\n\n\tpublic Weapon(Card sourceCard) {\n\t\tsuper(sourceCard);\n\t}\n\n\t@Override\n\tpublic Weapon clone() {\n\t\treturn (Weapon) super.clone();\n\t}\n\n\tpublic int getBaseDurability() {\n\t\treturn getAttributeValue(Attribute.BASE_HP);\n\t}\n\n\tpublic int getDurability() {\n\t\treturn getAttributeValue(Attribute.HP);\n\t}\n\n\t@Override\n\tpublic EntityType getEntityType() {\n\t\treturn EntityType.WEAPON;\n\t}\n\n\tpublic int getMaxDurability() {\n\t\treturn getAttributeValue(Attribute.MAX_HP) + getAttributeValue(Attribute.HP_BONUS);\n\t}\n\n\tpublic int getWeaponDamage() {\n\t\treturn Math.max(0, getAttributeValue(Attribute.ATTACK) + getAttributeValue(Attribute.CONDITIONAL_ATTACK_BONUS)) + getAttributeValue(Attribute.ATTACK_BONUS);\n\t}\n\n\tpublic boolean isActive() {\n\t\treturn active;\n\t}\n\n\tpublic boolean isBroken() {\n\t\treturn !hasAttribute(Attribute.HP);\n\t}\n\n\t@Override\n\tpublic boolean isDestroyed() {\n\t\treturn hasAttribute(Attribute.DESTROYED) || isBroken();\n\t}\n\n\tpublic void onEquip(GameContext context, Player player) {\n\t\tif (onEquip != null) {\n\t\t\tcontext.getLogic().castSpell(player.getId(), onEquip, getReference(), EntityReference.NONE, false);\n\t\t}\n\t}\n\n\tpublic void onUnequip(GameContext context, Player player) {\n\t\tif (onUnequip != null) {\n\t\t\tcontext.getLogic().castSpell(player.getId(), onUnequip, getReference(), EntityReference.NONE, false);\n\t\t}\n\t}\n\n\tpublic void setActive(boolean active) {\n\t\tthis.active = active;\n\t}\n\n\tpublic void setOnEquip(SpellDesc onEquip) {\n\t\tthis.onEquip = onEquip;\n\t}\n\n\tpublic void setOnUnequip(SpellDesc onUnequip) {\n\t\tthis.onUnequip = onUnequip;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\tString result = \"[\" + getEntityType() + \" '\" + getName() + \"'id:\" + getId() + \" \";\n\t\tresult += getWeaponDamage() + \"/\" + getDurability();\n\t\tString prefix = \" \";\n\t\tfor (Attribute tag : getAttributes().keySet()) {\n\t\t\tif (displayGameTag(tag)) {\n\t\t\t\tresult += prefix + tag;\n\t\t\t\tprefix = \", \";\n\t\t\t}\n\t\t}\n\t\tresult += \" hashCode: \" + hashCode();\n\t\tresult += \"]\";\n\t\treturn result;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/AfterPhysicalAttackEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class AfterPhysicalAttackEvent extends GameEvent {\n\n\tprivate final Actor attacker;\n\tprivate final Actor defender;\n\tprivate final int damageDealt;\n\n\tpublic AfterPhysicalAttackEvent(GameContext context, Actor attacker, Actor defender, int damageDealt) {\n\t\tsuper(context, defender.getOwner(), attacker.getOwner());\n\t\tthis.attacker = attacker;\n\t\tthis.defender = defender;\n\t\tthis.damageDealt = damageDealt;\n\t}\n\n\tpublic Actor getAttacker() {\n\t\treturn attacker;\n\t}\n\n\tpublic int getDamageDealt() {\n\t\treturn damageDealt;\n\t}\n\n\tpublic Actor getDefender() {\n\t\treturn defender;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventSource() {\n\t\treturn getAttacker();\n\t}\n\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getDefender();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.AFTER_PHYSICAL_ATTACK;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/AfterSpellCastedEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class AfterSpellCastedEvent extends GameEvent {\n\n\tprivate final Card sourceCard;\n\tprivate final Entity spellTarget;\n\n\tpublic AfterSpellCastedEvent(GameContext context, int playerId, Card sourceCard, Entity target) {\n\t\tsuper(context, target == null ? -1 : target.getOwner(), playerId);\n\t\tthis.sourceCard = sourceCard;\n\t\tthis.spellTarget = target;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventSource() {\n\t\treturn getSourceCard();\n\t}\n\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn spellTarget;\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.AFTER_SPELL_CASTED;\n\t}\n\n\tpublic Card getSourceCard() {\n\t\treturn sourceCard;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/AfterSummonEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class AfterSummonEvent extends GameEvent {\n\n\tprivate final Actor minion;\n\tprivate final Card source;\n\n\tpublic AfterSummonEvent(GameContext context, Actor minion, Card source) {\n\t\tsuper(context, minion.getOwner(), -1);\n\t\tthis.minion = minion;\n\t\tthis.source = source;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getMinion();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.AFTER_SUMMON;\n\t}\n\n\tpublic Actor getMinion() {\n\t\treturn minion;\n\t}\n\n\tpublic Card getSource() {\n\t\treturn source;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"[After Summon Event MINION \" + minion + \" from SOURCE \" + source + \"]\";\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/ArmorGainedEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.heroes.Hero;\n\npublic class ArmorGainedEvent extends GameEvent {\n\n\tprivate final Hero hero;\n\n\tpublic ArmorGainedEvent(GameContext context, Hero hero) {\n\t\tsuper(context, hero.getOwner(), -1);\n\t\tthis.hero = hero;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn hero;\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.ARMOR_GAINED;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/BeforeSummonEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class BeforeSummonEvent extends GameEvent {\n\n\tprivate final Actor minion;\n\tprivate final Card source;\n\n\tpublic BeforeSummonEvent(GameContext context, Actor minion, Card source) {\n\t\tsuper(context, minion.getOwner(), -1);\n\t\tthis.minion = minion;\n\t\tthis.source = source;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getMinion();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.BEFORE_SUMMON;\n\t}\n\n\tpublic Actor getMinion() {\n\t\treturn minion;\n\t}\n\n\tpublic Card getSource() {\n\t\treturn source;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"[Pre-Summon Event MINION \" + minion + \" from SOURCE \" + source + \"]\";\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/BoardChangedEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class BoardChangedEvent extends GameEvent {\n\n\tpublic BoardChangedEvent(GameContext context) {\n\t\tsuper(context, -1, -1);\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.BOARD_CHANGED;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/CardPlayedEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class CardPlayedEvent extends GameEvent {\n\n\tprivate final Card card;\n\n\tpublic CardPlayedEvent(GameContext context, int playerId, Card card) {\n\t\tsuper(context, playerId, -1);\n\t\tthis.card = card;\n\t}\n\n\tpublic Card getCard() {\n\t\treturn card;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getCard();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.PLAY_CARD;\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/CardRevealedEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class CardRevealedEvent extends GameEvent {\n\n\tprivate final Card card;\n\tprivate final double delay;\n\n\tpublic CardRevealedEvent(GameContext context, int playerId, Card card, double delay) {\n\t\tsuper(context, playerId, -1);\n\t\tthis.card = card;\n\t\tthis.delay = delay;\n\t}\n\n\tpublic Card getCard() {\n\t\treturn card;\n\t}\n\t\n\tpublic double getDelay() {\n\t\treturn delay;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getCard();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.REVEAL_CARD;\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/DamageEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class DamageEvent extends GameEvent {\n\n\tprivate final Entity victim;\n\tprivate final int damage;\n\tprivate final Entity source;\n\n\tpublic DamageEvent(GameContext context, Entity victim, Entity source, int damage) {\n\t\tsuper(context, victim.getOwner(), source.getOwner());\n\t\tthis.victim = victim;\n\t\tthis.source = source;\n\t\tthis.damage = damage;\n\t}\n\n\tpublic int getDamage() {\n\t\treturn damage;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventSource() {\n\t\treturn getSource();\n\t}\n\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getVictim();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.DAMAGE;\n\t}\n\n\tpublic Entity getSource() {\n\t\treturn source;\n\t}\n\n\tpublic Entity getVictim() {\n\t\treturn victim;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/DiscardEvent.java",
    "content": "package net.demilich.metastone.game.events;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class DiscardEvent extends GameEvent {\r\n\tprivate final Card card;\r\n\r\n\tpublic DiscardEvent(GameContext context, int playerId, Card card) {\r\n\t\tsuper(context, playerId, -1);\r\n\t\tthis.card = card;\r\n\t}\r\n\r\n\tpublic Card getCard() {\r\n\t\treturn card;\r\n\t}\r\n\t\r\n\t@Override\r\n\tpublic Entity getEventTarget() {\r\n\t\treturn getCard();\r\n\t}\r\n\r\n\t@Override\r\n\tpublic GameEventType getEventType() {\r\n\t\treturn GameEventType.DISCARD;\r\n\t}\r\n\t\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/DrawCardEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class DrawCardEvent extends GameEvent {\n\n\tprivate final Card card;\n\tprivate final CardType sourceType;\n\tprivate final boolean drawn;\n\n\tpublic DrawCardEvent(GameContext context, int playerId, Card card, CardType sourceType, boolean drawn) {\n\t\tsuper(context, playerId, -1);\n\t\tthis.card = card;\n\t\tthis.sourceType = sourceType;\n\t\tthis.drawn = drawn;\n\t}\n\n\tpublic Card getCard() {\n\t\treturn card;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn card;\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.DRAW_CARD;\n\t}\n\n\tpublic CardType getSourceType() {\n\t\treturn sourceType;\n\t}\n\t\n\tpublic boolean isDrawn() {\n\t\treturn drawn;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/EnrageChangedEvent.java",
    "content": "package net.demilich.metastone.game.events;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class EnrageChangedEvent extends GameEvent {\r\n\r\n\tprivate final Entity target;\r\n\r\n\tpublic EnrageChangedEvent(GameContext context, Entity target) {\r\n\t\tsuper(context, target.getOwner(), -1);\r\n\t\tthis.target = target;\r\n\t}\r\n\t\r\n\t@Override\r\n\tpublic Entity getEventTarget() {\r\n\t\treturn target;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic GameEventType getEventType() {\r\n\t\treturn GameEventType.ENRAGE_CHANGED;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/GameEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic abstract class GameEvent {\n\n\tprivate final GameContext context;\n\tprivate final int targetPlayerId;\n\tprivate final int sourcePlayerId;\n\n\tpublic GameEvent(GameContext context, int targetPlayerId, int sourcePlayerId) {\n\t\tthis.context = context;\n\t\tthis.targetPlayerId = targetPlayerId;\n\t\tthis.sourcePlayerId = sourcePlayerId;\n\t}\n\n\t/**\n\t * Spells may specify to be cast on the event target; this is dependent on\n\t * the actual event. For example, a SummonEvent may return the summoned\n\t * minion, a DamageEvent may return the damaged minion/hero, etc.\n\t * \n\t * @return\n\t */\n\tpublic abstract Entity getEventTarget();\n\t\n\tpublic Entity getEventSource() {\n\t\treturn null;\n\t}\n\t\n\tpublic abstract GameEventType getEventType();\n\n\tpublic GameContext getGameContext() {\n\t\treturn context;\n\t}\n\n\tpublic int getTargetPlayerId() {\n\t\treturn targetPlayerId;\n\t}\n\t\n\tpublic int getSourcePlayerId() {\n\t\treturn sourcePlayerId;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"[EVENT \" + getClass().getSimpleName() + \"]\";\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/GameEventType.java",
    "content": "package net.demilich.metastone.game.events;\n\npublic enum GameEventType {\n\tALL,\n\n\tAFTER_PHYSICAL_ATTACK,\n\tAFTER_SPELL_CASTED,\n\tAFTER_SUMMON,\n\tARMOR_GAINED,\n\tBEFORE_SUMMON,\n\tBOARD_CHANGED,\n\tDAMAGE,\n\tDISCARD,\n\tDRAW_CARD,\n\tENRAGE_CHANGED,\n\tGAME_START,\n\tHEAL,\n\tHERO_POWER_USED,\n\tJOUST,\n\tKILL,\n\tOVERLOAD,\n\tPHYSICAL_ATTACK,\n\tPLAY_CARD,\n\tPRE_DAMAGE,\n\tQUEST_PLAYED,\n\tQUEST_SUCCESSFUL,\n\tREVEAL_CARD,\n\tSECRET_PLAYED,\n\tSECRET_REVEALED,\n\tSPELL_CASTED,\n\tSUMMON,\n\tTARGET_ACQUISITION,\n\tTURN_END,\n\tTURN_START,\n\tSILENCE,\n\tWEAPON_DESTROYED,\n\tWEAPON_EQUIPPED,\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/GameStartEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class GameStartEvent extends GameEvent {\n\n\n\tpublic GameStartEvent(GameContext context, int playerId) {\n\t\tsuper(context, playerId, -1);\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.GAME_START;\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/HealEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class HealEvent extends GameEvent {\n\n\tprivate final Entity target;\n\tprivate final int healing;\n\n\tpublic HealEvent(GameContext context, int playerId, Entity target, int healing) {\n\t\tsuper(context, playerId, -1);\n\t\tthis.target = target;\n\t\tthis.healing = healing;\n\t}\n\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getTarget();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.HEAL;\n\t}\n\n\tpublic int getHealing() {\n\t\treturn healing;\n\t}\n\n\tpublic Entity getTarget() {\n\t\treturn target;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/HeroPowerUsedEvent.java",
    "content": "package net.demilich.metastone.game.events;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.heroes.powers.HeroPower;\r\n\r\npublic class HeroPowerUsedEvent extends GameEvent {\r\n\r\n\tprivate final HeroPower heroPower;\r\n\r\n\tpublic HeroPowerUsedEvent(GameContext context, int playerId, HeroPower heroPower) {\r\n\t\tsuper(context, playerId, -1);\r\n\t\tthis.heroPower = heroPower;\r\n\t}\r\n\t\r\n\t@Override\r\n\tpublic Entity getEventTarget() {\r\n\t\treturn getHeroPower();\r\n\t}\r\n\r\n\t@Override\r\n\tpublic GameEventType getEventType() {\r\n\t\treturn GameEventType.HERO_POWER_USED;\r\n\t}\r\n\r\n\tpublic HeroPower getHeroPower() {\r\n\t\treturn heroPower;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/JoustEvent.java",
    "content": "package net.demilich.metastone.game.events;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class JoustEvent extends GameEvent {\r\n\r\n\tprivate final boolean won;\r\n\tprivate final Card ownCard;\r\n\tprivate final Card opponentCard;\r\n\r\n\tpublic JoustEvent(GameContext context, int playerId, boolean won, Card ownCard, Card opponentCard) {\r\n\t\tsuper(context, playerId, -1);\r\n\t\tthis.won = won;\r\n\t\tthis.ownCard = ownCard;\r\n\t\tthis.opponentCard = opponentCard;\r\n\t}\r\n\t\r\n\t@Override\r\n\tpublic Entity getEventTarget() {\r\n\t\treturn getOwnCard();\r\n\t}\r\n\r\n\t@Override\r\n\tpublic GameEventType getEventType() {\r\n\t\treturn GameEventType.JOUST;\r\n\t}\r\n\r\n\tpublic boolean isWon() {\r\n\t\treturn won;\r\n\t}\r\n\r\n\tpublic Card getOwnCard() {\r\n\t\treturn ownCard;\r\n\t}\r\n\r\n\tpublic Card getOpponentCard() {\r\n\t\treturn opponentCard;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/KillEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class KillEvent extends GameEvent {\n\n\tprivate final Entity victim;\n\n\tpublic KillEvent(GameContext context, Entity victim) {\n\t\tsuper(context, victim.getOwner(), -1);\n\t\tthis.victim = victim;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getVictim();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.KILL;\n\t}\n\n\tpublic Entity getVictim() {\n\t\treturn victim;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/OverloadEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class OverloadEvent extends GameEvent {\n\n\tprivate Card card;\n\n\tpublic OverloadEvent(GameContext context, int playerId, Card card) {\n\t\tsuper(context, playerId, -1);\n\t\tthis.card = card;\n\t}\n\t\n\tpublic Card getCard() {\n\t\treturn card;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getCard();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.OVERLOAD;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/PhysicalAttackEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class PhysicalAttackEvent extends GameEvent {\n\n\tprivate final Actor attacker;\n\tprivate final Actor defender;\n\tprivate final int damageDealt;\n\n\tpublic PhysicalAttackEvent(GameContext context, Actor attacker, Actor defender, int damageDealt) {\n\t\tsuper(context, defender.getOwner(), attacker.getOwner());\n\t\tthis.attacker = attacker;\n\t\tthis.defender = defender;\n\t\tthis.damageDealt = damageDealt;\n\t}\n\n\tpublic Actor getAttacker() {\n\t\treturn attacker;\n\t}\n\n\tpublic int getDamageDealt() {\n\t\treturn damageDealt;\n\t}\n\n\tpublic Actor getDefender() {\n\t\treturn defender;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventSource() {\n\t\treturn getAttacker();\n\t}\n\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getDefender();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.PHYSICAL_ATTACK;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/PreDamageEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class PreDamageEvent extends GameEvent {\n\n\tprivate final Entity victim;\n\tprivate final Entity source;\n\n\tpublic PreDamageEvent(GameContext context, Entity victim, Entity source) {\n\t\tsuper(context, victim.getOwner(), source.getOwner());\n\t\tthis.victim = victim;\n\t\tthis.source = source;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getVictim();\n\t}\n\t\n\t@Override\n\tpublic Entity getEventSource() {\n\t\treturn getSource();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.PRE_DAMAGE;\n\t}\n\n\tpublic Entity getSource() {\n\t\treturn source;\n\t}\n\n\tpublic Entity getVictim() {\n\t\treturn victim;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/QuestPlayedEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.QuestCard;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class QuestPlayedEvent extends GameEvent {\n\n\tprivate final QuestCard questCard;\n\n\tpublic QuestPlayedEvent(GameContext context, int playerId, QuestCard questCard) {\n\t\tsuper(context, playerId, -1);\n\t\tthis.questCard = questCard;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getQuestCard();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.SECRET_PLAYED;\n\t}\n\n\tpublic QuestCard getQuestCard() {\n\t\treturn questCard;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/QuestSuccessfulEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.QuestCard;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class QuestSuccessfulEvent extends GameEvent {\n\n\tprivate final QuestCard quest;\n\n\tpublic QuestSuccessfulEvent(GameContext context, QuestCard quest, int playerId) {\n\t\tsuper(context, playerId, -1);\n\t\tthis.quest = quest;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getQuest();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.SECRET_REVEALED;\n\t}\n\n\tpublic QuestCard getQuest() {\n\t\treturn quest;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/SecretPlayedEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.SecretCard;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class SecretPlayedEvent extends GameEvent {\n\n\tprivate final SecretCard secretCard;\n\n\tpublic SecretPlayedEvent(GameContext context, int playerId, SecretCard secretCard) {\n\t\tsuper(context, playerId, -1);\n\t\tthis.secretCard = secretCard;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getSecretCard();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.SECRET_PLAYED;\n\t}\n\n\tpublic SecretCard getSecretCard() {\n\t\treturn secretCard;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/SecretRevealedEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.SecretCard;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class SecretRevealedEvent extends GameEvent {\n\n\tprivate final SecretCard secret;\n\n\tpublic SecretRevealedEvent(GameContext context, SecretCard secret, int playerId) {\n\t\tsuper(context, playerId, -1);\n\t\tthis.secret = secret;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getSecret();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.SECRET_REVEALED;\n\t}\n\n\tpublic SecretCard getSecret() {\n\t\treturn secret;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/SilenceEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\n\npublic class SilenceEvent extends GameEvent {\n\n\tprivate final Minion target;\n\n\tpublic SilenceEvent(GameContext context, int playerId, Minion target) {\n\t\tsuper(context, playerId, -1);\n\t\tthis.target = target;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getTarget();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.SILENCE;\n\t}\n\n\tpublic Minion getTarget() {\n\t\treturn target;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/SpellCastedEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class SpellCastedEvent extends GameEvent {\n\n\tprivate final Card sourceCard;\n\n\tpublic SpellCastedEvent(GameContext context, int playerId, Card sourceCard) {\n\t\tsuper(context, playerId, playerId);\n\t\tthis.sourceCard = sourceCard;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventSource() {\n\t\treturn getSourceCard();\n\t}\n\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getSourceCard();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.SPELL_CASTED;\n\t}\n\n\tpublic Card getSourceCard() {\n\t\treturn sourceCard;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/SummonEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class SummonEvent extends GameEvent {\n\n\tprivate final Actor minion;\n\tprivate final Card source;\n\n\tpublic SummonEvent(GameContext context, Actor minion, Card source) {\n\t\tsuper(context, minion.getOwner(), -1);\n\t\tthis.minion = minion;\n\t\tthis.source = source;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getMinion();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.SUMMON;\n\t}\n\n\tpublic Actor getMinion() {\n\t\treturn minion;\n\t}\n\n\tpublic Card getSource() {\n\t\treturn source;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"[Summon Event MINION \" + minion + \" from SOURCE \" + source + \"]\";\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/TargetAcquisitionEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.actions.ActionType;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class TargetAcquisitionEvent extends GameEvent {\n\n\tprivate final Entity target;\n\tprivate final Entity source;\n\tprivate final ActionType actionType;\n\n\tpublic TargetAcquisitionEvent(GameContext context, int playerId, ActionType actionType, Entity source, Entity target) {\n\t\tsuper(context, playerId, source.getOwner());\n\t\tthis.actionType = actionType;\n\t\tthis.source = source;\n\t\tthis.target = target;\n\t}\n\n\tpublic ActionType getActionType() {\n\t\treturn actionType;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventSource() {\n\t\treturn getSource();\n\t}\n\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getTarget();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.TARGET_ACQUISITION;\n\t}\n\n\tpublic Entity getSource() {\n\t\treturn source;\n\t}\n\n\tpublic Entity getTarget() {\n\t\treturn target;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/TurnEndEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class TurnEndEvent extends GameEvent {\n\n\tpublic TurnEndEvent(GameContext context, int playerId) {\n\t\tsuper(context, playerId, -1);\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.TURN_END;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/TurnStartEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class TurnStartEvent extends GameEvent {\n\n\n\tpublic TurnStartEvent(GameContext context, int playerId) {\n\t\tsuper(context, playerId, -1);\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.TURN_START;\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/WeaponDestroyedEvent.java",
    "content": "package net.demilich.metastone.game.events;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.weapons.Weapon;\n\npublic class WeaponDestroyedEvent extends GameEvent {\n\n\tprivate final Weapon weapon;\n\n\tpublic WeaponDestroyedEvent(GameContext context, Weapon weapon) {\n\t\tsuper(context, weapon.getOwner(), -1);\n\t\tthis.weapon = weapon;\n\t}\n\t\n\t@Override\n\tpublic Entity getEventTarget() {\n\t\treturn getWeapon();\n\t}\n\n\t@Override\n\tpublic GameEventType getEventType() {\n\t\treturn GameEventType.WEAPON_DESTROYED;\n\t}\n\n\tpublic Weapon getWeapon() {\n\t\treturn weapon;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/events/WeaponEquippedEvent.java",
    "content": "package net.demilich.metastone.game.events;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.weapons.Weapon;\r\n\r\npublic class WeaponEquippedEvent extends GameEvent {\r\n\tprivate final Weapon weapon;\r\n\r\n\tpublic WeaponEquippedEvent(GameContext context, Weapon weapon) {\r\n\t\tsuper(context, weapon.getOwner(), -1);\r\n\t\tthis.weapon = weapon;\r\n\t}\r\n\t\r\n\t@Override\r\n\tpublic Entity getEventTarget() {\r\n\t\treturn getWeapon();\r\n\t}\r\n\r\n\t@Override\r\n\tpublic GameEventType getEventType() {\r\n\t\treturn GameEventType.WEAPON_EQUIPPED;\r\n\t}\r\n\r\n\tpublic Weapon getWeapon() {\r\n\t\treturn weapon;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/gameconfig/GameConfig.java",
    "content": "package net.demilich.metastone.game.gameconfig;\n\nimport net.demilich.metastone.game.decks.DeckFormat;\n\npublic class GameConfig {\n\n\tprivate int numberOfGames;\n\tprivate PlayerConfig playerConfig1;\n\tprivate PlayerConfig playerConfig2;\n\tprivate DeckFormat deckFormat;\n\n\tpublic DeckFormat getDeckFormat() {\n\t\treturn deckFormat;\n\t}\n\n\tpublic int getNumberOfGames() {\n\t\treturn numberOfGames;\n\t}\n\n\tpublic PlayerConfig getPlayerConfig1() {\n\t\treturn playerConfig1;\n\t}\n\n\tpublic PlayerConfig getPlayerConfig2() {\n\t\treturn playerConfig2;\n\t}\n\n\tpublic void setDeckFormat(DeckFormat deckFormat) {\n\t\tthis.deckFormat = deckFormat;\n\t}\n\n\tpublic void setNumberOfGames(int numberOfGames) {\n\t\tthis.numberOfGames = numberOfGames;\n\t}\n\n\tpublic void setPlayerConfig1(PlayerConfig playerConfig1) {\n\t\tthis.playerConfig1 = playerConfig1;\n\t}\n\n\tpublic void setPlayerConfig2(PlayerConfig playerConfig2) {\n\t\tthis.playerConfig2 = playerConfig2;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/gameconfig/PlayerConfig.java",
    "content": "package net.demilich.metastone.game.gameconfig;\n\nimport net.demilich.metastone.game.behaviour.IBehaviour;\nimport net.demilich.metastone.game.cards.HeroCard;\nimport net.demilich.metastone.game.decks.Deck;\nimport net.demilich.metastone.game.decks.MetaDeck;\nimport net.demilich.metastone.game.entities.heroes.MetaHero;\n\npublic class PlayerConfig {\n\n\tprivate String name;\n\tprivate HeroCard heroCard;\n\tprivate Deck deck;\n\tprivate IBehaviour behaviour;\n\tprivate boolean hideCards;\n\n\tprivate Deck deckForPlay;\n\tprivate HeroCard heroForPlay;\n\n\tpublic PlayerConfig() {\n\t}\n\n\tpublic PlayerConfig(Deck deck, IBehaviour behaviour) {\n\t\tthis.deck = deck;\n\t\tthis.behaviour = behaviour;\n\t}\n\n\tpublic void build() {\n\t\tif (deck instanceof MetaDeck) {\n\t\t\tMetaDeck metaDeck = (MetaDeck) deck;\n\t\t\tdeckForPlay = metaDeck.selectRandom();\n\n\t\t\theroForPlay = MetaHero.getHeroCard(deckForPlay.getHeroClass());\n\t\t} else {\n\t\t\tdeckForPlay = deck;\n\t\t\theroForPlay = heroCard;\n\t\t}\n\t}\n\n\tpublic IBehaviour getBehaviour() {\n\t\treturn behaviour;\n\t}\n\n\tpublic Deck getDeck() {\n\t\treturn deck;\n\t}\n\n\tpublic Deck getDeckForPlay() {\n\t\treturn deckForPlay;\n\t}\n\n\tpublic HeroCard getHeroCard() {\n\t\treturn heroCard;\n\t}\n\n\tpublic HeroCard getHeroForPlay() {\n\t\treturn heroForPlay;\n\t}\n\n\tpublic String getName() {\n\t\treturn name != null ? name : heroCard.getName();\n\t}\n\n\tpublic boolean hideCards() {\n\t\treturn hideCards;\n\t}\n\n\tpublic void setBehaviour(IBehaviour behaviour) {\n\t\tthis.behaviour = behaviour;\n\t}\n\n\tpublic void setDeck(Deck deck) {\n\t\tthis.deck = deck;\n\t}\n\n\tpublic void setHeroCard(HeroCard HeroCard) {\n\t\tthis.heroCard = HeroCard;\n\t}\n\n\tpublic void setHideCards(boolean hideCards) {\n\t\tthis.hideCards = hideCards;\n\t}\n\n\tpublic void setName(String name) {\n\t\tthis.name = name;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/heroes/powers/HeroPower.java",
    "content": "package net.demilich.metastone.game.heroes.powers;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.HeroPowerAction;\nimport net.demilich.metastone.game.actions.PlayCardAction;\nimport net.demilich.metastone.game.cards.SpellCard;\nimport net.demilich.metastone.game.cards.desc.HeroPowerCardDesc;\nimport net.demilich.metastone.game.targeting.CardLocation;\n\npublic class HeroPower extends SpellCard {\n\n\tprivate int used;\n\n\tpublic HeroPower(HeroPowerCardDesc desc) {\n\t\tsuper(desc);\n\t\tsetLocation(CardLocation.HERO_POWER);\n\t}\n\n\tpublic int hasBeenUsed() {\n\t\treturn used;\n\t}\n\n\tpublic void markUsed() {\n\t\tthis.used++;\n\t}\n\n\tpublic void onWillUse(GameContext context, Player player) {\n\n\t}\n\n\t@Override\n\tpublic PlayCardAction play() {\n\t\treturn new HeroPowerAction(getSpell(), this, getTargetRequirement());\n\t}\n\t\n\tpublic void setUsed(int used) {\n\t\tthis.used = used;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/heroes/powers/HeroPowerChooseOne.java",
    "content": "package net.demilich.metastone.game.heroes.powers;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.actions.HeroPowerAction;\r\nimport net.demilich.metastone.game.actions.PlayCardAction;\r\nimport net.demilich.metastone.game.cards.CardCatalogue;\r\nimport net.demilich.metastone.game.cards.IChooseOneCard;\r\nimport net.demilich.metastone.game.cards.SpellCard;\r\nimport net.demilich.metastone.game.cards.desc.HeroPowerCardDesc;\r\nimport net.demilich.metastone.game.spells.NullSpell;\r\n\r\npublic class HeroPowerChooseOne extends HeroPower implements IChooseOneCard {\r\n\r\n\tprivate String[] cardIds;\r\n\tprivate String cardId;\r\n\r\n\tpublic HeroPowerChooseOne(HeroPowerCardDesc desc) {\r\n\t\tsuper(desc);\r\n\t\tsetAttribute(Attribute.CHOOSE_ONE);\r\n\t\tthis.cardIds = desc.options;\r\n\t\tthis.cardId = desc.bothOptions;\r\n\t\tsetSpell(NullSpell.create());\r\n\t}\r\n\r\n\tprivate SpellCard getCard(String cardId) {\r\n\t\tSpellCard card = (SpellCard) CardCatalogue.getCardById(cardId);\r\n\t\tcard.setLocation(getLocation());\r\n\t\tcard.setOwner(getOwner());\r\n\t\tcard.setId(getId());\r\n\t\treturn card;\r\n\t}\r\n\r\n\tpublic boolean hasBothOptions() {\r\n\t\treturn cardId != null;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic PlayCardAction play() {\r\n\t\tthrow new UnsupportedOperationException(\"The method .play() should not be called for HeroPowerChooseOne\");\r\n\t}\r\n\r\n\t@Override\r\n\tpublic PlayCardAction[] playOptions() {\r\n\t\tPlayCardAction[] actions = new PlayCardAction[cardIds.length];\r\n\t\tfor (int i = 0; i < cardIds.length; i++) {\r\n\t\t\tString cardId = cardIds[i];\r\n\t\t\tSpellCard card = getCard(cardId);\r\n\r\n\t\t\tPlayCardAction cardAction = new HeroPowerAction(card.getSpell(), this, getTargetRequirement());\r\n\t\t\tcardAction.setActionSuffix(card.getName());\r\n\t\t\tcardAction.setGroupIndex(i);\r\n\t\t\tactions[i] = cardAction;\r\n\t\t}\r\n\t\treturn actions;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic PlayCardAction playBothOptions() {\r\n\t\tSpellCard card = getCard(cardId);\r\n\r\n\t\tPlayCardAction cardAction = new HeroPowerAction(card.getSpell(), this, getTargetRequirement());\r\n\t\tcardAction.setActionSuffix(card.getName());\r\n\t\treturn cardAction;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/logic/ActionLogic.java",
    "content": "package net.demilich.metastone.game.logic;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.TurnState;\nimport net.demilich.metastone.game.actions.EndTurnAction;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.actions.PhysicalAttackAction;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.IChooseOneCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.heroes.Hero;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.heroes.powers.HeroPower;\nimport net.demilich.metastone.game.targeting.CardLocation;\nimport net.demilich.metastone.game.targeting.CardReference;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class ActionLogic {\n\n\tprivate final TargetLogic targetLogic = new TargetLogic();\n\n\tpublic GameAction getAutoHeroPower(GameContext context, Player player) {\n\t\treturn getHeroPowerActions(context, player).get(0);\n\t}\n\n\tprivate List<GameAction> getHeroAttackActions(GameContext context, Player player) {\n\t\tList<GameAction> heroAttackActions = new ArrayList<GameAction>();\n\t\tHero hero = player.getHero();\n\t\tif (!hero.canAttackThisTurn()) {\n\t\t\treturn heroAttackActions;\n\t\t}\n\t\trollout(new PhysicalAttackAction(hero.getReference()), context, player, heroAttackActions);\n\n\t\treturn heroAttackActions;\n\t}\n\n\tprivate List<GameAction> getHeroPowerActions(GameContext context, Player player) {\n\t\tList<GameAction> heroPowerActions = new ArrayList<GameAction>();\n\t\tHeroPower heroPower = player.getHero().getHeroPower();\n\t\theroPower.onWillUse(context, player);\n\t\tCardReference heroPowerReference = new CardReference(player.getId(), CardLocation.HERO_POWER, heroPower.getId(),\n\t\t\t\theroPower.getName());\n\t\tif (!context.getLogic().canPlayCard(player.getId(), heroPowerReference)) {\n\t\t\treturn heroPowerActions;\n\t\t}\n\t\tif (heroPower.hasAttribute(Attribute.CHOOSE_ONE)) {\n\t\t\tIChooseOneCard chooseOneCard = (IChooseOneCard) heroPower;\n\t\t\tfor (GameAction chooseOneAction : chooseOneCard.playOptions()) {\n\t\t\t\trollout(chooseOneAction, context, player, heroPowerActions);\n\t\t\t}\n\t\t} else {\n\t\t\trollout(heroPower.play(), context, player, heroPowerActions);\n\t\t}\n\n\t\treturn heroPowerActions;\n\t}\n\n\tprivate List<GameAction> getPhysicalAttackActions(GameContext context, Player player) {\n\t\tList<GameAction> physicalAttackActions = new ArrayList<GameAction>();\n\t\tphysicalAttackActions.addAll(getHeroAttackActions(context, player));\n\n\t\tfor (Minion minion : player.getMinions()) {\n\t\t\tif (!minion.canAttackThisTurn()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\trollout(new PhysicalAttackAction(minion.getReference()), context, player, physicalAttackActions);\n\t\t}\n\t\treturn physicalAttackActions;\n\t}\n\n\tprivate List<GameAction> getPlayCardActions(GameContext context, Player player) {\n\t\tList<GameAction> playCardActions = new ArrayList<GameAction>();\n\t\tplayCardActions.addAll(getHeroPowerActions(context, player));\n\n\t\tfor (Card card : player.getHand()) {\n\t\t\tCardReference cardReference = new CardReference(player.getId(), CardLocation.HAND, card.getId(), card.getName());\n\t\t\tif (!context.getLogic().canPlayCard(player.getId(), cardReference)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (card.hasAttribute(Attribute.CHOOSE_ONE)) {\n\t\t\t\tIChooseOneCard chooseOneCard = (IChooseOneCard) card;\n\t\t\t\tif (context.getLogic().hasAttribute(player, Attribute.BOTH_CHOOSE_ONE_OPTIONS) && chooseOneCard.hasBothOptions()) {\n\t\t\t\t\tGameAction chooseOneAction = chooseOneCard.playBothOptions();\n\t\t\t\t\trollout(chooseOneAction, context, player, playCardActions);\n\t\t\t\t} else {\n\t\t\t\t\tfor (GameAction chooseOneAction : chooseOneCard.playOptions()) {\n\t\t\t\t\t\trollout(chooseOneAction, context, player, playCardActions);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\trollout(card.play(), context, player, playCardActions);\n\t\t\t}\n\n\t\t}\n\t\treturn playCardActions;\n\t}\n\n\tpublic List<GameAction> getValidActions(GameContext context, Player player) {\n\t\tList<GameAction> validActions = new ArrayList<GameAction>();\n\t\tvalidActions.addAll(getPhysicalAttackActions(context, player));\n\t\tvalidActions.addAll(getPlayCardActions(context, player));\n\t\tif (context.getTurnState() != TurnState.TURN_ENDED) {\n\t\t\tvalidActions.add(new EndTurnAction());\n\t\t}\n\n\t\treturn validActions;\n\t}\n\n\tpublic boolean hasAutoHeroPower(GameContext context, Player player) {\n\t\tHeroPower heroPower = player.getHero().getHeroPower();\n\t\theroPower.onWillUse(context, player);\n\t\tCardReference heroPowerReference = new CardReference(player.getId(), CardLocation.HERO_POWER, heroPower.getId(),\n\t\t\t\theroPower.getName());\n\t\treturn (context.getLogic().canPlayCard(player.getId(), heroPowerReference) && heroPower.getTargetRequirement() == TargetSelection.AUTO);\n\t}\n\n\tpublic void rollout(GameAction action, GameContext context, Player player, Collection<GameAction> actions) {\n\t\tcontext.getLogic().processTargetModifiers(player, action);\n\t\tif (action.getTargetRequirement() == TargetSelection.NONE || action.getTargetRequirement() == TargetSelection.AUTO) {\n\t\t\tactions.add(action);\n\t\t} else {\n\t\t\tfor (Entity validTarget : targetLogic.getValidTargets(context, player, action)) {\n\t\t\t\tGameAction rolledOutAction = action.clone();\n\t\t\t\trolledOutAction.setTarget(validTarget);\n\t\t\t\tactions.add(rolledOutAction);\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/logic/CustomCloneable.java",
    "content": "package net.demilich.metastone.game.logic;\n\npublic class CustomCloneable implements Cloneable {\n\n\t@Override\n\tpublic CustomCloneable clone() {\n\t\ttry {\n\t\t\treturn (CustomCloneable) super.clone();\n\t\t} catch (CloneNotSupportedException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/logic/GameLogic.java",
    "content": "package net.demilich.metastone.game.logic;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Queue;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.BuildConfig;\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.Environment;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.ActionType;\nimport net.demilich.metastone.game.actions.BattlecryAction;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.actions.PlaySpellCardAction;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.cards.QuestCard;\nimport net.demilich.metastone.game.cards.SecretCard;\nimport net.demilich.metastone.game.cards.SpellCard;\nimport net.demilich.metastone.game.cards.costmodifier.CardCostModifier;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.entities.heroes.Hero;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.entities.minions.Permanent;\nimport net.demilich.metastone.game.entities.minions.Race;\nimport net.demilich.metastone.game.entities.minions.Summon;\nimport net.demilich.metastone.game.entities.weapons.Weapon;\nimport net.demilich.metastone.game.events.AfterPhysicalAttackEvent;\nimport net.demilich.metastone.game.events.AfterSpellCastedEvent;\nimport net.demilich.metastone.game.events.AfterSummonEvent;\nimport net.demilich.metastone.game.events.ArmorGainedEvent;\nimport net.demilich.metastone.game.events.BeforeSummonEvent;\nimport net.demilich.metastone.game.events.BoardChangedEvent;\nimport net.demilich.metastone.game.events.CardPlayedEvent;\nimport net.demilich.metastone.game.events.DamageEvent;\nimport net.demilich.metastone.game.events.DiscardEvent;\nimport net.demilich.metastone.game.events.DrawCardEvent;\nimport net.demilich.metastone.game.events.EnrageChangedEvent;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameStartEvent;\nimport net.demilich.metastone.game.events.HealEvent;\nimport net.demilich.metastone.game.events.HeroPowerUsedEvent;\nimport net.demilich.metastone.game.events.JoustEvent;\nimport net.demilich.metastone.game.events.KillEvent;\nimport net.demilich.metastone.game.events.OverloadEvent;\nimport net.demilich.metastone.game.events.PhysicalAttackEvent;\nimport net.demilich.metastone.game.events.PreDamageEvent;\nimport net.demilich.metastone.game.events.QuestPlayedEvent;\nimport net.demilich.metastone.game.events.QuestSuccessfulEvent;\nimport net.demilich.metastone.game.events.SecretPlayedEvent;\nimport net.demilich.metastone.game.events.SecretRevealedEvent;\nimport net.demilich.metastone.game.events.SilenceEvent;\nimport net.demilich.metastone.game.events.SpellCastedEvent;\nimport net.demilich.metastone.game.events.SummonEvent;\nimport net.demilich.metastone.game.events.TargetAcquisitionEvent;\nimport net.demilich.metastone.game.events.TurnEndEvent;\nimport net.demilich.metastone.game.events.TurnStartEvent;\nimport net.demilich.metastone.game.events.WeaponDestroyedEvent;\nimport net.demilich.metastone.game.events.WeaponEquippedEvent;\nimport net.demilich.metastone.game.heroes.powers.HeroPower;\nimport net.demilich.metastone.game.spells.Spell;\nimport net.demilich.metastone.game.spells.SpellUtils;\nimport net.demilich.metastone.game.spells.aura.Aura;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.SpellFactory;\nimport net.demilich.metastone.game.spells.desc.trigger.TriggerDesc;\nimport net.demilich.metastone.game.spells.trigger.IGameEventListener;\nimport net.demilich.metastone.game.spells.trigger.SpellTrigger;\nimport net.demilich.metastone.game.spells.trigger.types.Quest;\nimport net.demilich.metastone.game.spells.trigger.types.Secret;\nimport net.demilich.metastone.game.targeting.CardLocation;\nimport net.demilich.metastone.game.targeting.CardReference;\nimport net.demilich.metastone.game.targeting.EntityReference;\nimport net.demilich.metastone.game.targeting.IdFactory;\nimport net.demilich.metastone.game.targeting.TargetSelection;\nimport net.demilich.metastone.utils.MathUtils;\n\npublic class GameLogic implements Cloneable {\n\n\tpublic static Logger logger = LoggerFactory.getLogger(GameLogic.class);\n\n\tpublic static final int MAX_PLAYERS = 2;\n\tpublic static final int MAX_MINIONS = 7;\n\tpublic static final int MAX_HAND_CARDS = 10;\n\tpublic static final int MAX_HERO_HP = 30;\n\tpublic static final int STARTER_CARDS = 3;\n\tpublic static final int MAX_MANA = 10;\n\tpublic static final int MAX_QUESTS = 1;\n\tpublic static final int MAX_SECRETS = 5;\n\tpublic static final int DECK_SIZE = 30;\n\tpublic static final int MAX_DECK_SIZE = 60;\n\tpublic static final int TURN_LIMIT = 100;\n\n\tpublic static final int WINDFURY_ATTACKS = 2;\n\tpublic static final int MEGA_WINDFURY_ATTACKS = 4;\n\n\tpublic static final String TEMP_CARD_LABEL = \"temp_card_id_\";\n\n\tprivate static final int INFINITE = -1;\n\n\tprivate static boolean hasPlayerLost(Player player) {\n\t\treturn player.getHero().getHp() < 1 || player.getHero().hasAttribute(Attribute.DESTROYED)\n\t\t\t\t|| player.hasAttribute(Attribute.DESTROYED);\n\t}\n\n\tprivate final TargetLogic targetLogic = new TargetLogic();\n\tprivate final ActionLogic actionLogic = new ActionLogic();\n\tprivate final SpellFactory spellFactory = new SpellFactory();\n\tprivate final IdFactory idFactory;\n\tprivate GameContext context;\n\n\tprivate boolean loggingEnabled = true;\n\n\t// DEBUG\n\tprivate final int MAX_HISTORY_ENTRIES = 100;\n\tprivate Queue<String> debugHistory = new LinkedList<>();\n\n\tpublic GameLogic() {\n\t\tidFactory = new IdFactory();\n\t}\n\n\tprivate GameLogic(IdFactory idFactory) {\n\t\tthis.idFactory = idFactory;\n\t}\n\n\tpublic void addGameEventListener(Player player, IGameEventListener gameEventListener, Entity target) {\n\t\tdebugHistory.add(\"Player \" + player.getId() + \" has set event listener \" + gameEventListener.getClass().getName() + \" from entity \" + target.getName() + \"[Reference ID: \" + target.getId() + \"]\");\n\t\tgameEventListener.setHost(target);\n\t\tif (!gameEventListener.hasPersistentOwner() || gameEventListener.getOwner() == -1) {\n\t\t\tgameEventListener.setOwner(player.getId());\n\t\t}\n\n\t\tgameEventListener.onAdd(context);\n\t\tcontext.addTrigger(gameEventListener);\n\t\tlog(\"New spelltrigger was added for {} on {}: {}\", player.getName(), target, gameEventListener);\n\t}\n\n\tpublic void addManaModifier(Player player, CardCostModifier cardCostModifier, Entity target) {\n\t\tcontext.getCardCostModifiers().add(cardCostModifier);\n\t\taddGameEventListener(player, cardCostModifier, target);\n\t}\n\n\tpublic void afterCardPlayed(int playerId, CardReference cardReference) {\n\t\tPlayer player = context.getPlayer(playerId);\n\n\t\tplayer.modifyAttribute(Attribute.COMBO, +1);\n\t\tCard card = context.resolveCardReference(cardReference);\n\t\t\n\t\tcard.removeAttribute(Attribute.MANA_COST_MODIFIER);\n\t}\n\n\tpublic int applyAmplify(Player player, int baseValue, Attribute attribute) {\n\t\tint amplify = getTotalAttributeMultiplier(player, attribute);\n\t\treturn baseValue * amplify;\n\t}\n\n\tpublic void applyAttribute(Entity entity, Attribute attr) {\n\t\tif (attr == Attribute.MEGA_WINDFURY && entity.hasAttribute(Attribute.WINDFURY) && !entity.hasAttribute(Attribute.MEGA_WINDFURY)) {\n\t\t\tentity.modifyAttribute(Attribute.NUMBER_OF_ATTACKS, MEGA_WINDFURY_ATTACKS - WINDFURY_ATTACKS);\n\t\t} else\n\t\t\tif (attr == Attribute.WINDFURY && !entity.hasAttribute(Attribute.WINDFURY) && !entity.hasAttribute(Attribute.MEGA_WINDFURY)) {\n\t\t\tentity.modifyAttribute(Attribute.NUMBER_OF_ATTACKS, WINDFURY_ATTACKS - 1);\n\t\t} else if (attr == Attribute.MEGA_WINDFURY && !entity.hasAttribute(Attribute.WINDFURY) && !entity.hasAttribute(Attribute.MEGA_WINDFURY)) {\n\t\t\tentity.modifyAttribute(Attribute.NUMBER_OF_ATTACKS, MEGA_WINDFURY_ATTACKS - 1);\n\t\t}\n\t\tentity.setAttribute(attr);\n\t\tlog(\"Applying attr {} to {}\", attr, entity);\n\t}\n\n\t/**\n\t * Applies hero power damage increases\n\t * @param player\n\t * \t\t\tThe Player to grab additional hero power damage from\n\t * @param baseValue\n\t * \t\t\tThe base damage the hero power does\n\t * @return\n\t * \t\t\tIncreased hero power damage\n\t */\n\tpublic int applyHeroPowerDamage(Player player, int baseValue) {\n\t\tint spellpower = getTotalAttributeValue(player, Attribute.HERO_POWER_DAMAGE);\n\t\treturn baseValue + spellpower;\n\t}\n\n\t/**\n\t * Applies spell damage increases\n\t * @param player\n\t * \t\t\tThe Player to grab the additional spell damage from\n\t * @param source\n\t * \t\t\tThe source Card\n\t * @param baseValue\n\t * \t\t\tThe base damage the spell does\n\t * @return\n\t * \t\t\tIncreased spell damage\n\t */\n\tpublic int applySpellpower(Player player, Entity source, int baseValue) {\n\t\tint spellpower = getTotalAttributeValue(player, Attribute.SPELL_DAMAGE)\n\t\t\t\t+ getTotalAttributeValue(context.getOpponent(player), Attribute.OPPONENT_SPELL_DAMAGE);\n\t\tif (source.hasAttribute(Attribute.SPELL_DAMAGE_MULTIPLIER)) {\n\t\t\tspellpower *= source.getAttributeValue(Attribute.SPELL_DAMAGE_MULTIPLIER);\n\t\t}\n\t\treturn baseValue + spellpower;\n\t}\n\n\t/**\n\t * Assigns an ID to each Card in a given deck\n\t * @param cardCollection\n\t * \t\tThe Deck to assign IDs to\n\t */\n\tprivate void assignCardIds(CardCollection cardCollection) {\n\t\tfor (Card card : cardCollection) {\n\t\t\tcard.setId(idFactory.generateId());\n\t\t\tcard.setLocation(CardLocation.DECK);\n\t\t}\n\t}\n\n\tpublic boolean attributeExists(Attribute attr) {\n\t\tfor (Player player : context.getPlayers()) {\n\t\t\tif (player.getHero().hasAttribute(attr)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tfor (Entity summon : player.getSummons()) {\n\t\t\t\tif (summon.hasAttribute(attr) && !summon.hasAttribute(Attribute.PENDING_DESTROY)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tpublic boolean canPlayCard(int playerId, CardReference cardReference) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tCard card = context.resolveCardReference(cardReference);\n\t\tint manaCost = getModifiedManaCost(player, card);\n\t\tif (card.getCardType().isCardType(CardType.SPELL)\n\t\t\t\t&& player.hasAttribute(Attribute.SPELLS_COST_HEALTH)\n\t\t\t\t&& player.getHero().getEffectiveHp() < manaCost) {\n\t\t\treturn false;\n\t\t} else if (card.getCardType().isCardType(CardType.MINION)\n\t\t\t\t&& (Race) card.getAttribute(Attribute.RACE) == Race.MURLOC\n\t\t\t\t&& player.hasAttribute(Attribute.MURLOCS_COST_HEALTH)\n\t\t\t\t&& player.getHero().getEffectiveHp() < manaCost) {\n\t\t\treturn false;\n\t\t} else if (player.getMana() < manaCost && manaCost != 0\n\t\t\t\t&& !((card.getCardType().isCardType(CardType.SPELL)\n\t\t\t\t&& player.hasAttribute(Attribute.SPELLS_COST_HEALTH))\n\t\t\t\t|| ((Race) card.getAttribute(Attribute.RACE) == Race.MURLOC\n\t\t\t\t&& player.hasAttribute(Attribute.MURLOCS_COST_HEALTH)))) {\n\t\t\treturn false;\n\t\t}\n\t\tif (card.getCardType().isCardType(CardType.HERO_POWER)) {\n\t\t\tHeroPower power = (HeroPower) card;\n\t\t\tint heroPowerUsages = getGreatestAttributeValue(player, Attribute.HERO_POWER_USAGES);\n\t\t\tif (heroPowerUsages == 0) {\n\t\t\t\theroPowerUsages = 1;\n\t\t\t}\n\t\t\tif (heroPowerUsages != INFINITE && power.hasBeenUsed() >= heroPowerUsages) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else if (card.getCardType().isCardType(CardType.MINION)) {\n\t\t\treturn canSummonMoreMinions(player);\n\t\t}\n\n\t\tif (card instanceof SpellCard) {\n\t\t\tSpellCard spellCard = (SpellCard) card;\n\t\t\treturn spellCard.canBeCast(context, player);\n\t\t}\n\t\treturn true;\n\t}\n\n\tpublic boolean canPlayQuest(Player player, QuestCard card) {\n\t\treturn player.getSecrets().size() < MAX_SECRETS && player.getQuests().size() < MAX_QUESTS && !player.getQuests().contains(card.getCardId());\n\t}\n\n\tpublic boolean canPlaySecret(Player player, SecretCard card) {\n\t\treturn player.getSecrets().size() < MAX_SECRETS && !player.getSecrets().contains(card.getCardId());\n\t}\n\n\tpublic boolean canSummonMoreMinions(Player player) {\n\t\treturn player.getSummons().size() < MAX_MINIONS;\n\t}\n\n\tpublic void castChooseOneSpell(int playerId, SpellDesc spellDesc, EntityReference sourceReference, EntityReference targetReference, String cardId) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tEntity source = null;\n\t\tif (sourceReference != null) {\n\t\t\ttry {\n\t\t\t\tsource = context.resolveSingleTarget(sourceReference);\n\t\t\t} catch (Exception e) {\n\t\t\t\te.printStackTrace();\n\t\t\t\tlogger.error(\"Error resolving source entity while casting spell: \" + spellDesc);\n\t\t\t}\n\t\t}\n\t\tEntityReference spellTarget = spellDesc.hasPredefinedTarget() ? spellDesc.getTarget() : targetReference;\n\t\tList<Entity> targets = targetLogic.resolveTargetKey(context, player, source, spellTarget);\n\t\tCard sourceCard = null;\n\t\tSpellCard chosenCard = (SpellCard) context.getCardById(cardId);\n\t\tsourceCard = source.getEntityType() == EntityType.CARD ? (Card) source : null;\n\t\tif (!spellDesc.hasPredefinedTarget() && targets != null && targets.size() == 1) {\n\t\t\tif (chosenCard.getTargetRequirement() != TargetSelection.NONE) {\n\t\t\t\tcontext.getEnvironment().remove(Environment.TARGET_OVERRIDE);\n\t\t\t\tcontext.getEnvironment().put(Environment.CHOOSE_ONE_CARD, chosenCard.getCardId());\n\t\t\t\tGameEvent spellTargetEvent = new TargetAcquisitionEvent(context, playerId, ActionType.SPELL, chosenCard, targets.get(0));\n\t\t\t\tcontext.fireGameEvent(spellTargetEvent);\n\t\t\t\tEntity targetOverride = context\n\t\t\t\t\t\t.resolveSingleTarget((EntityReference) context.getEnvironment().get(Environment.TARGET_OVERRIDE));\n\t\t\t\tif (targetOverride != null && targetOverride.getId() != IdFactory.UNASSIGNED) {\n\t\t\t\t\ttargets.remove(0);\n\t\t\t\t\ttargets.add(targetOverride);\n\t\t\t\t\tspellDesc = spellDesc.addArg(SpellArg.FILTER, null);\n\t\t\t\t\tlog(\"Target for spell {} has been changed! New target {}\", chosenCard, targets.get(0));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\tSpell spell = spellFactory.getSpell(spellDesc);\n\t\t\tspell.cast(context, player, spellDesc, source, targets);\n\t\t} catch (Exception e) {\n\t\t\tif (source != null) {\n\t\t\t\tlogger.error(\"Error while playing card: \" + source.getName());\n\t\t\t}\n\t\t\tlogger.error(\"Error while casting spell: \" + spellDesc);\n\t\t\tpanicDump();\n\t\t\te.printStackTrace();\n\t\t}\n\n\t\tcontext.getEnvironment().remove(Environment.TARGET_OVERRIDE);\n\t\tcontext.getEnvironment().remove(Environment.CHOOSE_ONE_CARD);\n\n\t\tcheckForDeadEntities();\n\t\tif (targets == null || targets.size() != 1) {\n\t\t\tcontext.fireGameEvent(new AfterSpellCastedEvent(context, playerId, sourceCard, null));\n\t\t} else {\n\t\t\tcontext.fireGameEvent(new AfterSpellCastedEvent(context, playerId, sourceCard, targets.get(0)));\n\t\t}\n\t}\n\n\tpublic void castSpell(int playerId, SpellDesc spellDesc, EntityReference sourceReference, EntityReference targetReference,\n\t\t\tboolean childSpell) {\n\t\tcastSpell(playerId, spellDesc, sourceReference, targetReference, TargetSelection.NONE, childSpell);\n\t}\n\n\tpublic void castSpell(int playerId, SpellDesc spellDesc, EntityReference sourceReference, EntityReference targetReference,\n\t\t\tTargetSelection targetSelection, boolean childSpell) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tEntity source = null;\n\t\tif (sourceReference != null) {\n\t\t\ttry {\n\t\t\t\tsource = context.resolveSingleTarget(sourceReference);\n\t\t\t} catch (Exception e) {\n\t\t\t\te.printStackTrace();\n\t\t\t\tlogger.error(\"Error resolving source entity while casting spell: \" + spellDesc);\n\t\t\t}\n\n\t\t}\n\t\t//SpellCard spellCard = null;\n\t\tEntityReference spellTarget = spellDesc.hasPredefinedTarget() ? spellDesc.getTarget() : targetReference;\n\t\tList<Entity> targets = targetLogic.resolveTargetKey(context, player, source, spellTarget);\n\t\t// target can only be changed when there is one target\n\t\t// note: this code block is basically exclusively for the SpellBender\n\t\t// Secret, but it can easily be expanded if targets of area of effect\n\t\t// spell should be changeable as well\n\t\tCard sourceCard = null;\n\t\tif (source != null) {\n\t\t\tsourceCard = source.getEntityType() == EntityType.CARD ? (Card) source : null;\n\t\t}\n\t\tif (sourceCard != null && sourceCard.getCardType().isCardType(CardType.SPELL) && !spellDesc.hasPredefinedTarget() && targets != null\n\t\t\t\t&& targets.size() == 1) {\n\t\t\tif (sourceCard.getCardType().isCardType(CardType.SPELL) && targetSelection != TargetSelection.NONE && !childSpell) {\n\t\t\t\tGameEvent spellTargetEvent = new TargetAcquisitionEvent(context, playerId, ActionType.SPELL, sourceCard, targets.get(0));\n\t\t\t\tcontext.fireGameEvent(spellTargetEvent);\n\t\t\t\tEntity targetOverride = context\n\t\t\t\t\t\t.resolveSingleTarget((EntityReference) context.getEnvironment().get(Environment.TARGET_OVERRIDE));\n\t\t\t\tif (targetOverride != null && targetOverride.getId() != IdFactory.UNASSIGNED) {\n\t\t\t\t\ttargets.remove(0);\n\t\t\t\t\ttargets.add(targetOverride);\n\t\t\t\t\tspellDesc = spellDesc.addArg(SpellArg.FILTER, null);\n\t\t\t\t\tlog(\"Target for spell {} has been changed! New target {}\", sourceCard, targets.get(0));\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t\ttry {\n\t\t\tSpell spell = spellFactory.getSpell(spellDesc);\n\t\t\tspell.cast(context, player, spellDesc, source, targets);\n\t\t} catch (Exception e) {\n\t\t\tif (source != null) {\n\t\t\t\tlogger.error(\"Error while playing card: \" + source.getName());\n\t\t\t}\n\t\t\tlogger.error(\"Error while casting spell: \" + spellDesc);\n\t\t\tpanicDump();\n\t\t\te.printStackTrace();\n\t\t}\n\n\t\tif (sourceCard != null && sourceCard.getCardType().isCardType(CardType.SPELL) && !childSpell) {\n\t\t\tcontext.getEnvironment().remove(Environment.TARGET_OVERRIDE);\n\n\t\t\tcheckForDeadEntities();\n\t\t\tif (targets == null || targets.size() != 1) {\n\t\t\t\tcontext.fireGameEvent(new AfterSpellCastedEvent(context, playerId, sourceCard, null));\n\t\t\t} else {\n\t\t\t\tcontext.fireGameEvent(new AfterSpellCastedEvent(context, playerId, sourceCard, targets.get(0)));\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic void changeHero(Player player, Hero hero) {\n\t\thero.setId(player.getHero().getId());\n\t\tif (hero.getHeroClass() == null || hero.getHeroClass() == HeroClass.ANY) {\n\t\t\thero.setHeroClass(player.getHero().getHeroClass());\n\t\t}\n\n\t\tlog(\"{}'s hero has been changed to {}\", player.getName(), hero);\n\t\thero.setOwner(player.getId());\n\t\thero.setWeapon(player.getHero().getWeapon());\n\t\tplayer.setHero(hero);\n\t\trefreshAttacksPerRound(hero);\n\t}\n\n\tpublic void checkForDeadEntities() {\n\t\tcheckForDeadEntities(0);\n\t}\n\n\t/**\n\t * Checks all player minions and weapons for destroyed actors and proceeds\n\t * with the removal in correct order\n\t */\n\tpublic void checkForDeadEntities(int i) {\n\t\t// sanity check, this method should never call itself that often\n\t\tif (i > 20) {\n\t\t\tpanicDump();\n\t\t\tthrow new RuntimeException(\"Infinite death checking loop\");\n\t\t}\n\n\t\tList<Actor> destroyList = new ArrayList<>();\n\t\tfor (Player player : context.getPlayers()) {\n\n\t\t\tif (player.getHero().isDestroyed() || player.hasAttribute(Attribute.DESTROYED)) {\n\t\t\t\tdestroyList.add(player.getHero());\n\t\t\t}\n\n\t\t\tfor (Summon summon : player.getSummons()) {\n\t\t\t\tif (summon.isDestroyed()) {\n\t\t\t\t\tdestroyList.add(summon);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (player.getHero().getWeapon() != null && player.getHero().getWeapon().isDestroyed()) {\n\t\t\t\tdestroyList.add(player.getHero().getWeapon());\n\t\t\t}\n\t\t}\n\n\t\tif (destroyList.isEmpty()) {\n\t\t\treturn;\n\t\t}\n\n\t\t// sort the destroyed actors by their id. This implies that actors with a lower id entered the game ealier than those with higher ids!\n\t\tCollections.sort(destroyList, (a1, a2) -> Integer.compare(a1.getId(), a2.getId()));\n\t\t// this method performs the actual removal\n\t\tdestroy(destroyList.toArray(new Actor[0]));\n\t\tif (context.gameDecided()) {\n\t\t\treturn;\n\t\t}\n\t\t// deathrattles have been resolved, which may lead to other actors being destroyed now, so we need to check again\n\t\tcheckForDeadEntities(i + 1);\n\t}\n\n\t@Override\n\tpublic GameLogic clone() {\n\t\tGameLogic clone = new GameLogic(idFactory.clone());\n\t\tclone.debugHistory = new LinkedList<>(debugHistory);\n\t\treturn clone;\n\t}\n\n\tpublic int damage(Player player, Actor target, int baseDamage, Entity source) {\n\t\treturn damage(player, target, baseDamage, source, false);\n\t}\n\n\tpublic int damage(Player player, Actor target, int baseDamage, Entity source, boolean ignoreSpellDamage) {\n\t\t// sanity check to prevent StackOverFlowError with Mistress of Pain +\n\t\t// Auchenai Soulpriest\n\t\tif (target.getHp() < -100) {\n\t\t\treturn 0;\n\t\t}\n\t\tint damage = baseDamage;\n\t\tCard sourceCard = source != null && source.getEntityType() == EntityType.CARD ? (Card) source : null;\n\t\tif (!ignoreSpellDamage && sourceCard != null) {\n\t\t\tif (sourceCard.getCardType().isCardType(CardType.SPELL)) {\n\t\t\t\tdamage = applySpellpower(player, source, baseDamage);\n\t\t\t} else if (sourceCard.getCardType().isCardType(CardType.HERO_POWER)) {\n\t\t\t\tdamage = applyHeroPowerDamage(player, damage);\n\t\t\t}\n\t\t\tif (sourceCard.getCardType().isCardType(CardType.SPELL) || sourceCard.getCardType().isCardType(CardType.HERO_POWER)) {\n\t\t\t\tdamage = applyAmplify(player, damage, Attribute.SPELL_AMPLIFY_MULTIPLIER);\n\t\t\t}\n\t\t}\n\t\tint damageDealt = 0;\n\t\tif (target.hasAttribute(Attribute.TAKE_DOUBLE_DAMAGE)) {\n\t\t\tdamage *= 2;\n\t\t}\n\t\tcontext.getDamageStack().push(damage);\n\t\tcontext.fireGameEvent(new PreDamageEvent(context, target, source));\n\t\tdamage = context.getDamageStack().pop();\n\t\tif (damage > 0) {\n\t\t\tsource.removeAttribute(Attribute.STEALTH);\n\t\t}\n\t\tswitch (target.getEntityType()) {\n\t\tcase MINION:\n\t\t\tdamageDealt = damageMinion((Actor) target, damage);\n\t\t\tbreak;\n\t\tcase HERO:\n\t\t\tdamageDealt = damageHero((Hero) target, damage);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\n\t\ttarget.setAttribute(Attribute.LAST_HIT, damageDealt);\n\t\tif (damageDealt > 0) {\n\t\t\tDamageEvent damageEvent = new DamageEvent(context, target, source, damageDealt);\n\t\t\tcontext.fireGameEvent(damageEvent);\n\t\t\tplayer.getStatistics().damageDealt(damageDealt);\n\t\t}\n\n\t\treturn damageDealt;\n\t}\n\n\tprivate int damageHero(Hero hero, int damage) {\n\t\tif (hero.hasAttribute(Attribute.IMMUNE) || hasAttribute(context.getPlayer(hero.getOwner()), Attribute.IMMUNE_HERO)) {\n\t\t\tlog(\"{} is IMMUNE and does not take damage\", hero);\n\t\t\treturn 0;\n\t\t}\n\t\tint effectiveHp = hero.getHp() + hero.getArmor();\n\t\thero.modifyArmor(-damage);\n\t\tint newHp = Math.min(hero.getHp(), effectiveHp - damage);\n\t\thero.setHp(newHp);\n\t\tlog(hero.getName() + \" receives \" + damage + \" damage, hp now: \" + hero.getHp() + \"(\" + hero.getArmor() + \")\");\n\t\treturn damage;\n\t}\n\n\tprivate int damageMinion(Actor minion, int damage) {\n\t\tif (minion.hasAttribute(Attribute.DIVINE_SHIELD)) {\n\t\t\tremoveAttribute(minion, Attribute.DIVINE_SHIELD);\n\t\t\tlog(\"{}'s DIVINE SHIELD absorbs the damage\", minion);\n\t\t\treturn 0;\n\t\t}\n\t\tif (minion.hasAttribute(Attribute.IMMUNE)) {\n\t\t\tlog(\"{} is IMMUNE and does not take damage\", minion);\n\t\t\treturn 0;\n\t\t}\n\t\tif (damage >= minion.getHp() && minion.hasAttribute(Attribute.CANNOT_REDUCE_HP_BELOW_1)) {\n\t\t\tdamage = minion.getHp() - 1;\n\t\t}\n\n\t\tlog(\"{} is damaged for {}\", minion, damage);\n\t\tminion.setHp(minion.getHp() - damage);\n\t\thandleEnrage(minion);\n\t\treturn damage;\n\t}\n\n\tpublic void destroy(Actor... targets) {\n\t\tint[] boardPositions = new int[targets.length];\n\n\t\tfor (int i = 0; i < targets.length; i++) {\n\t\t\tActor target = targets[i];\n\t\t\tremoveSpellTriggers(target, false);\n\t\t\tPlayer owner = context.getPlayer(target.getOwner());\n\t\t\tcontext.getPlayer(target.getOwner()).getGraveyard().add(target);\n\n\t\t\tint boardPosition = owner.getSummons().indexOf(target);\n\t\t\tboardPositions[i] = boardPosition;\n\t\t}\n\n\t\tfor (int i = 0; i < targets.length; i++) {\n\t\t\tActor target = targets[i];\n\t\t\tlog(\"{} is destroyed\", target);\n\t\t\tPlayer owner = context.getPlayer(target.getOwner());\n\t\t\towner.getSummons().remove(target);\n\t\t}\n\n\t\tfor (int i = 0; i < targets.length; i++) {\n\t\t\tActor target = targets[i];\n\t\t\tPlayer owner = context.getPlayer(target.getOwner());\n\t\t\tswitch (target.getEntityType()) {\n\t\t\tcase HERO:\n\t\t\t\tlog(\"Hero {} has been destroyed.\", target.getName());\n\t\t\t\tapplyAttribute(target, Attribute.DESTROYED);\n\t\t\t\tapplyAttribute(context.getPlayer(target.getOwner()), Attribute.DESTROYED);\n\t\t\t\tbreak;\n\t\t\tcase MINION:\n\t\t\t\tdestroyMinion((Minion) target);\n\t\t\t\tbreak;\n\t\t\tcase WEAPON:\n\t\t\t\tdestroyWeapon((Weapon) target);\n\t\t\t\tbreak;\n\t\t\tcase ANY:\n\t\t\tdefault:\n\t\t\t\tlogger.error(\"Trying to destroy unknown entity type {}\", target.getEntityType());\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tresolveDeathrattles(owner, target, boardPositions[i]);\n\t\t}\n\t\t\n\t\tfor (Actor target : targets) {\n\t\t\tremoveSpellTriggers(target, true);\n\t\t}\n\n\t\tcontext.fireGameEvent(new BoardChangedEvent(context));\n\t}\n\n\tprivate void destroyMinion(Minion minion) {\n\t\tcontext.getEnvironment().put(Environment.KILLED_MINION, minion.getReference());\n\t\tKillEvent killEvent = new KillEvent(context, minion);\n\t\tcontext.fireGameEvent(killEvent);\n\t\tcontext.getEnvironment().remove(Environment.KILLED_MINION);\n\n\t\tminion.setAttribute(Attribute.DESTROYED);\n\t\tminion.setAttribute(Attribute.DIED_ON_TURN, context.getTurn());\n\t}\n\n\tprivate void destroyWeapon(Weapon weapon) {\n\t\tPlayer owner = context.getPlayer(weapon.getOwner());\n\t\t// resolveDeathrattles(owner, weapon);\n\t\tif (owner.getHero().getWeapon() != null && owner.getHero().getWeapon().getId() == weapon.getId()) {\n\t\t\towner.getHero().setWeapon(null);\n\t\t}\n\t\tweapon.onUnequip(context, owner);\n\t\tcontext.fireGameEvent(new WeaponDestroyedEvent(context, weapon));\n\t}\n\n\tpublic int determineBeginner(int... playerIds) {\n\t\treturn ThreadLocalRandom.current().nextBoolean() ? playerIds[0] : playerIds[1];\n\t}\n\n\tpublic void discardCard(Player player, Card card) {\n\t\tlogger.debug(\"{} discards {}\", player.getName(), card);\n\t\t// only a 'real' discard should fire a DiscardEvent\n\t\tif (card.getLocation() == CardLocation.HAND) {\n\t\t\tcontext.fireGameEvent(new DiscardEvent(context, player.getId(), card));\n\t\t}\n\n\t\tremoveCard(player.getId(), card);\n\t}\n\n\tpublic Card drawCard(int playerId, Entity source) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tCardCollection deck = player.getDeck();\n\t\tif (deck.isEmpty()) {\n\t\t\tHero hero = player.getHero();\n\t\t\tint fatigue = player.hasAttribute(Attribute.FATIGUE) ? player.getAttributeValue(Attribute.FATIGUE) : 0;\n\t\t\tfatigue++;\n\t\t\tplayer.setAttribute(Attribute.FATIGUE, fatigue);\n\t\t\tdamage(player, hero, fatigue, hero);\n\t\t\tlog(\"{}'s deck is empty, taking {} fatigue damage!\", player.getName(), fatigue);\n\t\t\tplayer.getStatistics().fatigueDamage(fatigue);\n\t\t\treturn null;\n\t\t}\n\n\t\tCard card = deck.getRandom();\n\t\treturn drawCard(playerId, card, source);\n\t}\n\n\tpublic Card drawCard(int playerId, Card card, Entity source) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tplayer.getStatistics().cardDrawn();\n\t\tplayer.getDeck().remove(card);\n\t\treceiveCard(playerId, card, source, true);\n\t\treturn card;\n\t}\n\n\tpublic void drawSetAsideCard(int playerId, Card card) {\n\t\tif (card.getId() == IdFactory.UNASSIGNED) {\n\t\t\tcard.setId(idFactory.generateId());\n\t\t}\n\t\tcard.setOwner(playerId);\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tplayer.getSetAsideZone().add(card);\n\t}\n\n\tpublic void endTurn(int playerId) {\n\t\tPlayer player = context.getPlayer(playerId);\n\n\t\tHero hero = player.getHero();\n\t\thero.removeAttribute(Attribute.TEMPORARY_ATTACK_BONUS);\n\t\thero.removeAttribute(Attribute.HERO_POWER_USAGES);\n\t\thandleFrozen(hero);\n\t\tfor (Summon summon : player.getSummons()) {\n\t\t\tsummon.removeAttribute(Attribute.TEMPORARY_ATTACK_BONUS);\n\t\t\thandleFrozen(summon);\n\t\t}\n\t\tplayer.removeAttribute(Attribute.COMBO);\n\t\thero.activateWeapon(false);\n\t\tlog(\"{} ends his turn.\", player.getName());\n\t\tcontext.fireGameEvent(new TurnEndEvent(context, playerId));\n\t\tfor (Iterator<CardCostModifier> iterator = context.getCardCostModifiers().iterator(); iterator.hasNext();) {\n\t\t\tCardCostModifier cardCostModifier = iterator.next();\n\t\t\tif (cardCostModifier.isExpired()) {\n\t\t\t\titerator.remove();\n\t\t\t}\n\t\t}\n\t\tcheckForDeadEntities();\n\t}\n\n\tpublic void equipWeapon(int playerId, Weapon weapon) {\n\t\tequipWeapon(playerId, weapon, false);\n\t}\n\n\tpublic void equipWeapon(int playerId, Weapon weapon, boolean battlecry) {\n\t\tPlayer player = context.getPlayer(playerId);\n\n\t\tweapon.setId(idFactory.generateId());\n\t\tWeapon currentWeapon = player.getHero().getWeapon();\n\t\t\n\t\tif (currentWeapon != null) {\n\t\t\tplayer.getSetAsideZone().add(currentWeapon);\n\t\t}\n\n\t\tlog(\"{} equips weapon {}\", player.getHero(), weapon);\n\t\tplayer.getHero().setWeapon(weapon);\n\n\t\tif (battlecry && weapon.getBattlecry() != null) {\n\t\t\tresolveBattlecry(playerId, weapon);\n\t\t}\n\t\t\n\t\tif (currentWeapon != null) {\n\t\t\tlog(\"{} discards currently equipped weapon {}\", player.getHero(), currentWeapon);\n\t\t\tdestroy(currentWeapon);\n\t\t\tplayer.getSetAsideZone().remove(currentWeapon);\n\t\t}\n\t\t\n\t\tplayer.getStatistics().equipWeapon(weapon);\n\t\tweapon.onEquip(context, player);\n\t\tweapon.setActive(context.getActivePlayerId() == playerId);\n\t\tif (weapon.hasSpellTrigger()) {\n\t\t\tfor (SpellTrigger spellTrigger : weapon.getSpellTriggers()) {\n\t\t\t\taddGameEventListener(player, spellTrigger, weapon);\n\t\t\t}\n\t\t}\n\t\tif (weapon.getCardCostModifier() != null) {\n\t\t\taddManaModifier(player, weapon.getCardCostModifier(), weapon);\n\t\t}\n\t\tcheckForDeadEntities();\n\t\tcontext.fireGameEvent(new WeaponEquippedEvent(context, weapon));\n\t\tcontext.fireGameEvent(new BoardChangedEvent(context));\n\t}\n\n\tpublic void fight(Player player, Actor attacker, Actor defender) {\n\t\tlog(\"{} attacks {}\", attacker, defender);\n\n\t\tcontext.getEnvironment().put(Environment.ATTACKER_REFERENCE, attacker.getReference());\n\n\t\tTargetAcquisitionEvent targetAcquisitionEvent = new TargetAcquisitionEvent(context, player.getId(), ActionType.PHYSICAL_ATTACK,\n\t\t\t\tattacker, defender);\n\t\tcontext.fireGameEvent(targetAcquisitionEvent);\n\t\tActor target = defender;\n\t\tif (context.getEnvironment().containsKey(Environment.TARGET_OVERRIDE)) {\n\t\t\ttarget = (Actor) context.resolveSingleTarget((EntityReference) context.getEnvironment().get(Environment.TARGET_OVERRIDE));\n\t\t}\n\t\tcontext.getEnvironment().remove(Environment.TARGET_OVERRIDE);\n\n\t\t// if (attacker.hasTag(GameTag.FUMBLE) && randomBool()) {\n\t\t// log(\"{} fumbled and hits another target\", attacker);\n\t\t// target = getAnotherRandomTarget(player, attacker, defender,\n\t\t// EntityReference.ENEMY_CHARACTERS);\n\t\t// }\n\n\t\tif (target != defender) {\n\t\t\tlog(\"Target of attack was changed! New Target: {}\", target);\n\t\t}\n\n\t\tif (attacker.hasAttribute(Attribute.IMMUNE_WHILE_ATTACKING)) {\n\t\t\tapplyAttribute(attacker, Attribute.IMMUNE);\n\t\t}\n\n\t\tremoveAttribute(attacker, Attribute.STEALTH);\n\n\t\tint attackerDamage = attacker.getAttack();\n\t\tint defenderDamage = target.getAttack();\n\t\tcontext.fireGameEvent(new PhysicalAttackEvent(context, attacker, target, attackerDamage));\n\t\t// secret may have killed attacker ADDENDUM: or defender\n\t\tif (attacker.isDestroyed() || target.isDestroyed()) {\n\t\t\tcontext.getEnvironment().remove(Environment.ATTACKER_REFERENCE);\n\t\t\treturn;\n\t\t}\n\n\t\tif (target.getOwner() == -1) {\n\t\t\tlogger.error(\"Target has no owner!! {}\", target);\n\t\t}\n\n\t\tPlayer owningPlayer = context.getPlayer(target.getOwner());\n\t\tboolean damaged = damage(owningPlayer, target, attackerDamage, attacker) > 0;\n\t\tif (defenderDamage > 0) {\n\t\t\tdamage(player, attacker, defenderDamage, target);\n\t\t}\n\t\tif (attacker.hasAttribute(Attribute.IMMUNE_WHILE_ATTACKING)) {\n\t\t\tattacker.removeAttribute(Attribute.IMMUNE);\n\t\t}\n\n\t\tif (attacker.getEntityType() == EntityType.HERO) {\n\t\t\tHero hero = (Hero) attacker;\n\t\t\tWeapon weapon = hero.getWeapon();\n\t\t\tif (weapon != null && weapon.isActive()) {\n\t\t\t\tmodifyDurability(hero.getWeapon(), -1);\n\t\t\t}\n\t\t}\n\t\tattacker.modifyAttribute(Attribute.NUMBER_OF_ATTACKS, -1);\n\n\t\tcontext.fireGameEvent(new AfterPhysicalAttackEvent(context, attacker, target, damaged ? attackerDamage : 0));\n\n\t\tcontext.getEnvironment().remove(Environment.ATTACKER_REFERENCE);\n\t}\n\n\tpublic void gainArmor(Player player, int armor) {\n\t\tlogger.debug(\"{} gains {} armor\", player.getHero(), armor);\n\t\tplayer.getHero().modifyArmor(armor);\n\t\tplayer.getStatistics().armorGained(armor);\n\t\tif (armor > 0) {\n\t\t\tcontext.fireGameEvent(new ArmorGainedEvent(context, player.getHero()));\n\t\t}\n\t}\n\n\tpublic String generateCardID() {\n\t\treturn TEMP_CARD_LABEL + idFactory.generateId();\n\t}\n\n\tpublic Actor getAnotherRandomTarget(Player player, Actor attacker, Actor originalTarget, EntityReference potentialTargets) {\n\t\tList<Entity> validTargets = context.resolveTarget(player, null, potentialTargets);\n\t\t// cannot redirect to attacker\n\t\tvalidTargets.remove(attacker);\n\t\t// cannot redirect to original target\n\t\tvalidTargets.remove(originalTarget);\n\t\tif (validTargets.isEmpty()) {\n\t\t\treturn originalTarget;\n\t\t}\n\n\t\treturn (Actor) SpellUtils.getRandomTarget(validTargets);\n\t}\n\n\t/**\n\t * Returns the first value of the attribute encountered. This method should\n\t * be used with caution, as the result is random if there are different\n\t * values of the same attribute in play.\n\t * \n\t * @param player\n\t * @param attr\n\t *            Which attribute to find\n\t * @param defaultValue\n\t *            The value returned if no occurrence of the attribute is found\n\t * @return the first occurrence of the value of attribute or defaultValue\n\t */\n\tpublic int getAttributeValue(Player player, Attribute attr, int defaultValue) {\n\t\tfor (Summon summon : player.getSummons()) {\n\t\t\tif (summon.hasAttribute(attr)) {\n\t\t\t\treturn summon.getAttributeValue(attr);\n\t\t\t}\n\t\t}\n\n\t\treturn defaultValue;\n\t}\n\n\tpublic GameAction getAutoHeroPowerAction(int playerId) {\n\t\treturn actionLogic.getAutoHeroPower(context, context.getPlayer(playerId));\n\t}\n\n\t/**\n\t * Return the greatest value of the attribute from all Actors of a Player.\n\t * This method will return infinite if an Attribute value is negative, so\n\t * use this method with caution.\n\t * \n\t * @param player\n\t *            Which Player to check\n\t * @param attr\n\t *            Which attribute to find\n\t * @return The highest value from all sources. -1 is considered infinite.\n\t */\n\tpublic int getGreatestAttributeValue(Player player, Attribute attr) {\n\t\tint greatest = Math.max(INFINITE, player.getHero().getAttributeValue(attr));\n\t\tif (greatest == INFINITE) {\n\t\t\treturn greatest;\n\t\t}\n\t\tfor (Summon summon : player.getSummons()) {\n\t\t\tif (summon.hasAttribute(attr)) {\n\t\t\t\tif (summon.getAttributeValue(attr) > greatest) {\n\t\t\t\t\tgreatest = summon.getAttributeValue(attr);\n\t\t\t\t}\n\t\t\t\tif (summon.getAttributeValue(attr) == INFINITE) {\n\t\t\t\t\treturn INFINITE;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn greatest;\n\t}\n\n\tpublic MatchResult getMatchResult(Player player, Player opponent) {\n\t\tboolean playerLost = hasPlayerLost(player);\n\t\tboolean opponentLost = hasPlayerLost(opponent);\n\t\tif (playerLost && opponentLost) {\n\t\t\treturn MatchResult.DOUBLE_LOSS;\n\t\t} else if (playerLost || opponentLost) {\n\t\t\treturn MatchResult.WON;\n\t\t}\n\t\treturn MatchResult.RUNNING;\n\t}\n\n\tpublic int getModifiedManaCost(Player player, Card card) {\n\t\tint manaCost = card.getManaCost(context, player);\n\t\tint minValue = 0;\n\t\tfor (CardCostModifier costModifier : context.getCardCostModifiers()) {\n\t\t\tif (!costModifier.appliesTo(card)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tmanaCost = costModifier.process(card, manaCost);\n\t\t\tif (costModifier.getMinValue() > minValue) {\n\t\t\t\tminValue = costModifier.getMinValue();\n\t\t\t}\n\t\t}\n\t\tif (card.hasAttribute(Attribute.MANA_COST_MODIFIER)) {\n\t\t\tmanaCost += card.getAttributeValue(Attribute.MANA_COST_MODIFIER);\n\t\t}\n\t\tmanaCost = MathUtils.clamp(manaCost, minValue, Integer.MAX_VALUE);\n\t\treturn manaCost;\n\t}\n\n\tpublic List<IGameEventListener> getQuests(Player player) {\n\t\tList<IGameEventListener> quests = context.getTriggersAssociatedWith(player.getHero().getReference());\n\t\tfor (Iterator<IGameEventListener> iterator = quests.iterator(); iterator.hasNext();) {\n\t\t\tIGameEventListener trigger = iterator.next();\n\t\t\tif (!(trigger instanceof Quest)) {\n\t\t\t\titerator.remove();\n\t\t\t}\n\t\t}\n\t\treturn quests;\n\t}\n\n\tpublic List<IGameEventListener> getSecrets(Player player) {\n\t\tList<IGameEventListener> secrets = context.getTriggersAssociatedWith(player.getHero().getReference());\n\t\tfor (Iterator<IGameEventListener> iterator = secrets.iterator(); iterator.hasNext();) {\n\t\t\tIGameEventListener trigger = iterator.next();\n\t\t\tif (!(trigger instanceof Secret)) {\n\t\t\t\titerator.remove();\n\t\t\t}\n\t\t}\n\t\treturn secrets;\n\t}\n\n\tpublic int getTotalAttributeValue(Attribute attr) {\n\t\tint total = 0;\n\t\tfor (Player player : context.getPlayers()) {\n\t\t\ttotal += getTotalAttributeValue(player, attr);\n\t\t}\n\t\treturn total;\n\t}\n\n\tpublic int getTotalAttributeValue(Player player, Attribute attr) {\n\t\tint total = player.getHero().getAttributeValue(attr);\n\t\tfor (Summon summon : player.getSummons()) {\n\t\t\tif (!summon.hasAttribute(attr)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttotal += summon.getAttributeValue(attr);\n\t\t}\n\t\treturn total;\n\t}\n\n\tpublic int getTotalAttributeMultiplier(Player player, Attribute attribute) {\n\t\tint total = 1;\n\t\tif (player.getHero().hasAttribute(attribute)) {\n\t\t\tplayer.getHero().getAttributeValue(attribute);\n\t\t}\n\t\tfor (Summon summon : player.getSummons()) {\n\t\t\tif (summon.hasAttribute(attribute)) {\n\t\t\t\ttotal *= summon.getAttributeValue(attribute);\n\t\t\t}\n\t\t}\n\t\treturn total;\n\t}\n\n\tpublic List<GameAction> getValidActions(int playerId) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\treturn actionLogic.getValidActions(context, player);\n\t}\n\n\tpublic List<Entity> getValidTargets(int playerId, GameAction action) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\treturn targetLogic.getValidTargets(context, player, action);\n\t}\n\n\tpublic Player getWinner(Player player, Player opponent) {\n\t\tboolean playerLost = hasPlayerLost(player);\n\t\tboolean opponentLost = hasPlayerLost(opponent);\n\t\tif (playerLost && opponentLost) {\n\t\t\treturn null;\n\t\t} else if (opponentLost) {\n\t\t\treturn player;\n\t\t} else if (playerLost) {\n\t\t\treturn opponent;\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate void handleEnrage(Actor entity) {\n\t\tif (!entity.hasAttribute(Attribute.ENRAGABLE)) {\n\t\t\treturn;\n\t\t}\n\t\tboolean enraged = entity.getHp() < entity.getMaxHp();\n\t\t// enrage state has not changed; do nothing\n\t\tif (entity.hasAttribute(Attribute.ENRAGED) == enraged) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (enraged) {\n\t\t\tlog(\"{} is now enraged\", entity);\n\t\t\tentity.setAttribute(Attribute.ENRAGED);\n\t\t} else {\n\t\t\tlog(\"{} is no longer enraged\", entity);\n\t\t\tentity.removeAttribute(Attribute.ENRAGED);\n\t\t}\n\n\t\tcontext.fireGameEvent(new EnrageChangedEvent(context, entity));\n\t}\n\n\tprivate void handleFrozen(Actor actor) {\n\t\tif (!actor.hasAttribute(Attribute.FROZEN)) {\n\t\t\treturn;\n\t\t}\n\t\tif (actor.getAttributeValue(Attribute.NUMBER_OF_ATTACKS) >= actor.getMaxNumberOfAttacks()) {\n\t\t\tremoveAttribute(actor, Attribute.FROZEN);\n\t\t}\n\t}\n\n\tpublic boolean hasAttribute(Player player, Attribute attr) {\n\t\tif (player.getHero().hasAttribute(attr)) {\n\t\t\treturn true;\n\t\t}\n\t\tfor (Summon summon : player.getSummons()) {\n\t\t\tif (summon.hasAttribute(attr) && !summon.hasAttribute(Attribute.PENDING_DESTROY)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tpublic boolean hasAutoHeroPower(int player) {\n\t\treturn actionLogic.hasAutoHeroPower(context, context.getPlayer(player));\n\t}\n\n\tpublic boolean hasCard(Player player, Card card) {\n\t\tfor (Card heldCard : player.getHand()) {\n\t\t\tif (card.getCardId().equals(heldCard.getCardId())) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\tif (player.getHero().getHeroPower().getCardId().equals(card.getCardId())) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic void heal(Player player, Actor target, int healing, Entity source) {\n\t\tif (hasAttribute(player, Attribute.INVERT_HEALING)) {\n\t\t\tlog(\"All healing inverted, deal damage instead!\");\n\t\t\tdamage(player, target, healing, source);\n\t\t\treturn;\n\t\t}\n\t\tif (source != null && source instanceof Card\n\t\t\t\t&& (((Card) source).getCardType().isCardType(CardType.SPELL)\n\t\t\t\t|| ((Card) source).getCardType().isCardType(CardType.HERO_POWER))) {\n\t\t\thealing = applyAmplify(player, healing, Attribute.HEAL_AMPLIFY_MULTIPLIER);\n\t\t}\n\t\tboolean success = false;\n\t\tswitch (target.getEntityType()) {\n\t\tcase MINION:\n\t\t\tsuccess = healMinion((Actor) target, healing);\n\t\t\tbreak;\n\t\tcase HERO:\n\t\t\tsuccess = healHero((Hero) target, healing);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\n\t\tif (success) {\n\t\t\tHealEvent healEvent = new HealEvent(context, player.getId(), target, healing);\n\t\t\tcontext.fireGameEvent(healEvent);\n\t\t\tplayer.getStatistics().heal(healing);\n\t\t}\n\t}\n\n\tprivate boolean healHero(Hero hero, int healing) {\n\t\tint newHp = Math.min(hero.getMaxHp(), hero.getHp() + healing);\n\t\tint oldHp = hero.getHp();\n\t\tif (logger.isDebugEnabled()) {\n\t\t\tlog(hero + \" is healed for \" + healing + \", hp now: \" + newHp / hero.getMaxHp());\n\t\t}\n\n\t\thero.setHp(newHp);\n\t\treturn newHp != oldHp;\n\t}\n\n\tprivate boolean healMinion(Actor minion, int healing) {\n\t\tint newHp = Math.min(minion.getMaxHp(), minion.getHp() + healing);\n\t\tint oldHp = minion.getHp();\n\t\tif (logger.isDebugEnabled()) {\n\t\t\tlog(minion + \" is healed for \" + healing + \", hp now: \" + newHp + \"/\" + minion.getMaxHp());\n\t\t}\n\n\t\tminion.setHp(newHp);\n\t\thandleEnrage(minion);\n\t\treturn newHp != oldHp;\n\t}\n\n\tpublic void init(int playerId, boolean begins) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tplayer.getHero().setId(idFactory.generateId());\n\t\tplayer.getHero().setOwner(player.getId());\n\t\tplayer.getHero().setMaxHp(player.getHero().getAttributeValue(Attribute.BASE_HP));\n\t\tplayer.getHero().setHp(player.getHero().getAttributeValue(Attribute.BASE_HP));\n\n\t\tplayer.getHero().getHeroPower().setId(idFactory.generateId());\n\t\tassignCardIds(player.getDeck());\n\t\tassignCardIds(player.getHand());\n\n\t\tlog(\"Setting hero hp to {} for {}\", player.getHero().getHp(), player.getName());\n\n\t\tplayer.getDeck().shuffle();\n\n\t\tmulligan(player, begins);\n\n\t\tfor (Card card : player.getDeck()) {\n\t\t\tif (card.getAttribute(Attribute.DECK_TRIGGER) != null) {\n\t\t\t\tTriggerDesc triggerDesc = (TriggerDesc) card.getAttribute(Attribute.DECK_TRIGGER);\n\t\t\t\taddGameEventListener(player, triggerDesc.create(), card);\n\t\t\t}\n\t\t}\n\t\tfor (Card card : player.getHand()) {\n\t\t\tif (card.getAttribute(Attribute.DECK_TRIGGER) != null) {\n\t\t\t\tTriggerDesc triggerDesc = (TriggerDesc) card.getAttribute(Attribute.DECK_TRIGGER);\n\t\t\t\taddGameEventListener(player, triggerDesc.create(), card);\n\t\t\t}\n\t\t}\n\n\t\tGameStartEvent gameStartEvent = new GameStartEvent(context, player.getId());\n\t\tcontext.fireGameEvent(gameStartEvent);\n\t}\n\n\tpublic boolean isLoggingEnabled() {\n\t\treturn loggingEnabled;\n\t}\n\n\tpublic JoustEvent joust(Player player) {\n\t\tCard ownCard = player.getDeck().getRandomOfType(CardType.MINION);\n\t\tCard opponentCard = null;\n\t\tboolean won = false;\n\t\t// no minions left in deck - automatically loose joust\n\t\tif (ownCard == null) {\n\t\t\twon = false;\n\t\t\tlog(\"Jousting LOST - no minion card left\");\n\t\t} else {\n\t\t\tPlayer opponent = context.getOpponent(player);\n\t\t\topponentCard = opponent.getDeck().getRandomOfType(CardType.MINION);\n\t\t\t// opponent has no minions left in deck - automatically win joust\n\t\t\tif (opponentCard == null) {\n\t\t\t\twon = true;\n\t\t\t\tlog(\"Jousting WON - opponent has no minion card left\");\n\t\t\t} else {\n\t\t\t\t// both players have minion cards left, the initiator needs to\n\t\t\t\t// have the one with\n\t\t\t\t// higher mana cost to win the joust\n\t\t\t\twon = ownCard.getBaseManaCost() > opponentCard.getBaseManaCost();\n\n\t\t\t\tlog(\"Jousting {} - {} vs. {}\", won ? \"WON\" : \"LOST\", ownCard, opponentCard);\n\t\t\t}\n\t\t}\n\t\tJoustEvent joustEvent = new JoustEvent(context, player.getId(), won, ownCard, opponentCard);\n\t\tcontext.fireGameEvent(joustEvent);\n\t\treturn joustEvent;\n\t}\n\n\tprivate void log(String message) {\n\t\tlogToDebugHistory(message);\n\t\tif (isLoggingEnabled() && logger.isDebugEnabled()) {\n\t\t\tlogger.debug(message);\n\t\t}\n\t}\n\n\tprivate void log(String message, Object param1) {\n\t\tlogToDebugHistory(message, param1);\n\t\tif (isLoggingEnabled() && logger.isDebugEnabled()) {\n\t\t\tlogger.debug(message, param1);\n\t\t}\n\t}\n\n\tprivate void log(String message, Object param1, Object param2) {\n\t\tlogToDebugHistory(message, param1, param2);\n\t\tif (isLoggingEnabled() && logger.isDebugEnabled()) {\n\t\t\tlogger.debug(message, param1, param2);\n\t\t}\n\t}\n\n\tprivate void log(String message, Object param1, Object param2, Object param3) {\n\t\tlogToDebugHistory(message, param1, param2, param3);\n\t\tif (isLoggingEnabled() && logger.isDebugEnabled()) {\n\t\t\tlogger.debug(message, param1, param2, param3);\n\t\t}\n\t}\n\n\tprivate void logToDebugHistory(String message, Object... params) {\n\t\tif (!BuildConfig.DEV_BUILD) {\n\t\t\treturn;\n\t\t}\n\t\tif (debugHistory.size() == MAX_HISTORY_ENTRIES) {\n\t\t\tdebugHistory.poll();\n\t\t}\n\t\tif (params != null && params.length > 0) {\n\t\t\tmessage = message.replaceAll(\"\\\\{\\\\}\", \"%s\");\n\t\t\tmessage = String.format(message, params);\n\t\t}\n\n\t\tdebugHistory.add(message);\n\t}\n\n\tpublic void markAsDestroyed(Actor target) {\n\t\tif (target != null) {\n\t\t\ttarget.setAttribute(Attribute.DESTROYED);\n\t\t}\n\t}\n\n\tpublic void mindControl(Player player, Summon summon) {\n\t\tlog(\"{} mind controls {}\", player.getName(), summon);\n\t\tPlayer opponent = context.getOpponent(player);\n\t\tif (!opponent.getSummons().contains(summon)) {\n\t\t\t// logger.warn(\"Minion {} cannot be mind-controlled, because\n\t\t\t// opponent does not own it.\", minion);\n\t\t\treturn;\n\t\t}\n\t\tif (canSummonMoreMinions(player)) {\n\t\t\tcontext.getOpponent(player).getSummons().remove(summon);\n\t\t\tplayer.getSummons().add(summon);\n\t\t\tsummon.setOwner(player.getId());\n\t\t\tapplyAttribute(summon, Attribute.SUMMONING_SICKNESS);\n\t\t\trefreshAttacksPerRound(summon);\n\t\t\tList<IGameEventListener> triggers = context.getTriggersAssociatedWith(summon.getReference());\n\t\t\tremoveSpellTriggers(summon);\n\t\t\tfor (IGameEventListener trigger : triggers) {\n\t\t\t\taddGameEventListener(player, trigger, summon);\n\t\t\t}\n\t\t\tcontext.fireGameEvent(new BoardChangedEvent(context));\n\t\t} else {\n\t\t\tmarkAsDestroyed(summon);\n\t\t}\n\t}\n\n\tpublic void modifyCurrentMana(int playerId, int mana) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tint newMana = Math.min(player.getMana() + mana, MAX_MANA);\n\t\tplayer.setMana(newMana);\n\t}\n\n\tpublic void modifyDurability(Weapon weapon, int durability) {\n\t\tlog(\"Durability of weapon {} is changed by {}\", weapon, durability);\n\n\t\tweapon.modifyAttribute(Attribute.HP, durability);\n\t\tif (durability > 0) {\n\t\t\tweapon.modifyAttribute(Attribute.MAX_HP, durability);\n\t\t}\n\t}\n\n\tpublic void modifyMaxHp(Actor actor, int value) {\n\t\tactor.setMaxHp(value);\n\t\tactor.setHp(value);\n\t\thandleEnrage(actor);\n\t}\n\n\tpublic void modifyMaxMana(Player player, int delta) {\n\t\tlog(\"Maximum mana was changed by {} for {}\", delta, player.getName());\n\t\tint maxMana = MathUtils.clamp(player.getMaxMana() + delta, 0, GameLogic.MAX_MANA);\n\t\tplayer.setMaxMana(maxMana);\n\t\tif (delta < 0 && player.getMana() > player.getMaxMana()) {\n\t\t\tmodifyCurrentMana(player.getId(), delta);\n\t\t}\n\t}\n\n\tprivate void mulligan(Player player, boolean begins) {\n\t\tint numberOfStarterCards = begins ? STARTER_CARDS : STARTER_CARDS + 1;\n\t\tList<Card> starterCards = new ArrayList<>();\n\t\tfor (int j = 0; j < numberOfStarterCards; j++) {\n\t\t\tCard randomCard = player.getDeck().getRandom();\n\t\t\tif (randomCard != null) {\n\t\t\t\tplayer.getDeck().remove(randomCard);\n\t\t\t\tlog(\"Player {} been offered card {} for mulligan\", player.getName(), randomCard);\n\t\t\t\tstarterCards.add(randomCard);\n\t\t\t}\n\t\t}\n\n\t\tList<Card> discardedCards = player.getBehaviour().mulligan(context, player, starterCards);\n\n\t\t// remove player selected cards from starter cards\n\t\tfor (Card discardedCard : discardedCards) {\n\t\t\tlog(\"Player {} mulligans {} \", player.getName(), discardedCard);\n\t\t\tstarterCards.remove(discardedCard);\n\t\t}\n\n\t\t// draw random cards from deck until required starter card count is\n\t\t// reached\n\t\twhile (starterCards.size() < numberOfStarterCards) {\n\t\t\tCard randomCard = player.getDeck().getRandom();\n\t\t\tplayer.getDeck().remove(randomCard);\n\t\t\tstarterCards.add(randomCard);\n\t\t}\n\n\t\t// put the mulligan cards back in the deck\n\t\tfor (Card discardedCard : discardedCards) {\n\t\t\tplayer.getDeck().add(discardedCard);\n\t\t}\n\n\t\tfor (Card starterCard : starterCards) {\n\t\t\tif (starterCard != null) {\n\t\t\t\treceiveCard(player.getId(), starterCard);\n\t\t\t}\n\t\t}\n\n\t\t// second player gets the coin additionally\n\t\tif (!begins) {\n\t\t\tCard theCoin = CardCatalogue.getCardById(\"spell_the_coin\");\n\t\t\treceiveCard(player.getId(), theCoin);\n\t\t}\n\t}\n\n\tpublic void panicDump() {\n\t\tlogger.error(\"=========PANIC DUMP=========\");\n\t\tfor (String entry : debugHistory) {\n\t\t\tlogger.error(entry);\n\t\t}\n\t}\n\n\tpublic void performGameAction(int playerId, GameAction action) {\n\t\tdebugHistory.add(action.toString());\n\t\tif (playerId != context.getActivePlayerId()) {\n\t\t\tlogger.warn(\"Player {} tries to perform an action, but it is not his turn!\", context.getPlayer(playerId).getName());\n\t\t}\n\t\tif (action.getTargetRequirement() != TargetSelection.NONE) {\n\t\t\tEntity target = context.resolveSingleTarget(action.getTargetKey());\n\t\t\tif (target != null) {\n\t\t\t\tcontext.getEnvironment().put(Environment.TARGET, target.getReference());\n\t\t\t} else {\n\t\t\t\tcontext.getEnvironment().put(Environment.TARGET, null);\n\t\t\t}\n\t\t}\n\n\t\taction.execute(context, playerId);\n\n\t\tcontext.getEnvironment().remove(Environment.TARGET);\n\t\tif (action.getActionType() != ActionType.BATTLECRY) {\n\t\t\tcheckForDeadEntities();\n\t\t}\n\t}\n\n\tpublic void playCard(int playerId, CardReference cardReference) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tCard card = context.resolveCardReference(cardReference);\n\n\t\tint modifiedManaCost = getModifiedManaCost(player, card);\n\t\tif (card.getCardType().isCardType(CardType.SPELL)\n\t\t\t\t&& player.hasAttribute(Attribute.SPELLS_COST_HEALTH)) {\n\t\t\tcontext.getEnvironment().put(Environment.LAST_MANA_COST, 0);\n\t\t\tdamage(player, player.getHero(), modifiedManaCost, card, true);\n\t\t} else if ((Race) card.getAttribute(Attribute.RACE) == Race.MURLOC\n\t\t\t\t&& player.getHero().hasAttribute(Attribute.MURLOCS_COST_HEALTH)) {\n\t\t\tcontext.getEnvironment().put(Environment.LAST_MANA_COST, 0);\n\t\t\tdamage(player, player.getHero(), modifiedManaCost, card, true);\n\t\t} else {\n\t\t\tcontext.getEnvironment().put(Environment.LAST_MANA_COST, modifiedManaCost);\n\t\t\tmodifyCurrentMana(playerId, -modifiedManaCost);\n\t\t\tplayer.getStatistics().manaSpent(modifiedManaCost);\n\t\t}\n\t\tlog(\"{} plays {}\", player.getName(), card);\n\n\t\tplayer.getStatistics().cardPlayed(card, context.getTurn());\n\t\tCardPlayedEvent cardPlayedEvent = new CardPlayedEvent(context, playerId, card);\n\t\tcontext.fireGameEvent(cardPlayedEvent);\n\n\t\tif (card.hasAttribute(Attribute.OVERLOAD)) {\n\t\t\tcontext.fireGameEvent(new OverloadEvent(context, playerId, card));\n\t\t}\n\n\t\tremoveCard(playerId, card);\n\n\t\tif ((card.getCardType().isCardType(CardType.SPELL))) {\n\t\t\tGameEvent spellCastedEvent = new SpellCastedEvent(context, playerId, card);\n\t\t\tcontext.fireGameEvent(spellCastedEvent);\n\t\t\tif (card.hasAttribute(Attribute.COUNTERED)) {\n\t\t\t\tlog(\"{} was countered!\", card.getName());\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif (card.hasAttribute(Attribute.OVERLOAD)) {\n\t\t\tplayer.modifyAttribute(Attribute.OVERLOAD, card.getAttributeValue(Attribute.OVERLOAD));\n\t\t}\n\t}\n\n\tpublic void playQuest(Player player, Quest quest) {\n\t\tplayQuest(player, quest, true);\n\t}\n\n\tpublic void playQuest(Player player, Quest quest, boolean fromHand) {\n\t\tlog(\"{} has a new quest activated: {}\", player.getName(), quest.getSource());\n\t\taddGameEventListener(player, quest, player.getHero());\n\t\tplayer.getSecrets().add(quest.getSource().getCardId());\n\t\tplayer.getQuests().add(quest.getSource().getCardId());\n\t\tif (fromHand) {\n\t\t\tcontext.fireGameEvent(new QuestPlayedEvent(context, player.getId(), (QuestCard) quest.getSource()));\n\t\t}\n\t}\n\n\tpublic void playSecret(Player player, Secret secret) {\n\t\tplaySecret(player, secret, true);\n\t}\n\n\tpublic void playSecret(Player player, Secret secret, boolean fromHand) {\n\t\tlog(\"{} has a new secret activated: {}\", player.getName(), secret.getSource());\n\t\taddGameEventListener(player, secret, player.getHero());\n\t\tplayer.getSecrets().add(secret.getSource().getCardId());\n\t\tif (fromHand) {\n\t\t\tcontext.fireGameEvent(new SecretPlayedEvent(context, player.getId(), (SecretCard) secret.getSource()));\n\t\t}\n\t}\n\n\tpublic void processTargetModifiers(Player player, GameAction action) {\n\t\tHeroPower heroPower = player.getHero().getHeroPower();\n\t\tif (heroPower.getHeroClass() != HeroClass.HUNTER) {\n\t\t\treturn;\n\t\t}\n\t\tif (action.getActionType() == ActionType.HERO_POWER && hasAttribute(player, Attribute.HERO_POWER_CAN_TARGET_MINIONS)) {\n\t\t\tPlaySpellCardAction spellCardAction = (PlaySpellCardAction) action;\n\t\t\tSpellDesc targetChangedSpell = spellCardAction.getSpell().removeArg(SpellArg.TARGET);\n\t\t\tspellCardAction.setSpell(targetChangedSpell);\n\t\t\tspellCardAction.setTargetRequirement(TargetSelection.ANY);\n\t\t}\n\t}\n\n\t/**\n\t * \n\t * @param max\n\t *            Upper bound of random number (exclusive)\n\t * @return Random number between 0 and max (exclusive)\n\t */\n\tpublic int random(int max) {\n\t\treturn ThreadLocalRandom.current().nextInt(max);\n\t}\n\n\tpublic boolean randomBool() {\n\t\treturn ThreadLocalRandom.current().nextBoolean();\n\t}\n\n\tpublic void receiveCard(int playerId, Card card) {\n\t\treceiveCard(playerId, card, null);\n\t}\n\n\tpublic void receiveCard(int playerId, Card card, Entity source) {\n\t\treceiveCard(playerId, card, source, false);\n\t}\n\n\tpublic void receiveCard(int playerId, Card card, Entity source, boolean drawn) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tif (card.getId() == IdFactory.UNASSIGNED) {\n\t\t\tcard.setId(idFactory.generateId());\n\t\t}\n\n\t\tcard.setOwner(playerId);\n\t\tCardCollection hand = player.getHand();\n\n\t\tif (hand.getCount() < MAX_HAND_CARDS) {\n\t\t\tif (card.getAttribute(Attribute.PASSIVE_TRIGGER) != null) {\n\t\t\t\tTriggerDesc triggerDesc = (TriggerDesc) card.getAttribute(Attribute.PASSIVE_TRIGGER);\n\t\t\t\taddGameEventListener(player, triggerDesc.create(), card);\n\t\t\t}\n\t\t\t\n\t\t\tlog(\"{} receives card {}\", player.getName(), card);\n\t\t\thand.add(card);\n\t\t\tcard.setLocation(CardLocation.HAND);\n\t\t\tCardType sourceType = null;\n\t\t\tif (source instanceof Card) {\n\t\t\t\tCard sourceCard = (Card) source;\n\t\t\t\tsourceType = sourceCard.getCardType();\n\t\t\t}\n\t\t\tcontext.fireGameEvent(new DrawCardEvent(context, playerId, card, sourceType, drawn));\n\t\t} else {\n\t\t\tlog(\"{} has too many cards on his hand, card destroyed: {}\", player.getName(), card);\n\t\t\tdiscardCard(player, card);\n\t\t}\n\t}\n\n\tpublic void refreshAttacksPerRound(Entity entity) {\n\t\tint attacks = 1;\n\t\tif (entity.hasAttribute(Attribute.MEGA_WINDFURY)) {\n\t\t\tattacks = MEGA_WINDFURY_ATTACKS;\n\t\t} else if (entity.hasAttribute(Attribute.WINDFURY)) {\n\t\t\tattacks = WINDFURY_ATTACKS;\n\t\t}\n\t\tentity.setAttribute(Attribute.NUMBER_OF_ATTACKS, attacks);\n\t}\n\n\tpublic void removeAttribute(Entity entity, Attribute attr) {\n\t\tif (!entity.hasAttribute(attr)) {\n\t\t\treturn;\n\t\t}\n\t\tif (attr == Attribute.MEGA_WINDFURY && entity.hasAttribute(Attribute.WINDFURY)) {\n\t\t\tentity.modifyAttribute(Attribute.NUMBER_OF_ATTACKS, WINDFURY_ATTACKS - MEGA_WINDFURY_ATTACKS);\n\t\t}\n\t\tif (attr == Attribute.WINDFURY && !entity.hasAttribute(Attribute.MEGA_WINDFURY)) {\n\t\t\tentity.modifyAttribute(Attribute.NUMBER_OF_ATTACKS, 1 - WINDFURY_ATTACKS);\n\t\t} else if (attr == Attribute.MEGA_WINDFURY) {\n\t\t\tentity.modifyAttribute(Attribute.NUMBER_OF_ATTACKS, 1 - MEGA_WINDFURY_ATTACKS);\n\t\t}\n\t\tentity.removeAttribute(attr);\n\t\tlog(\"Removing attribute {} from {}\", attr, entity);\n\t}\n\n\tpublic void removeCard(int playerId, Card card) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tlog(\"Card {} has been moved from the HAND to the GRAVEYARD\", card);\n\t\tcard.setLocation(CardLocation.GRAVEYARD);\n\t\tremoveSpellTriggers(card);\n\t\tplayer.getHand().remove(card);\n\t\tplayer.getGraveyard().add(card);\n\t}\n\n\tpublic void removeAllCards(int playerId) {\n\t\tfor (Card card : context.getPlayer(playerId).getHand().toList()) {\n\t\t\tremoveCard(playerId, card);\n\t\t}\n\t}\n\n\tpublic void removeCardFromDeck(int playerID, Card card) {\n\t\tPlayer player = context.getPlayer(playerID);\n\t\tlog(\"Card {} has been moved from the DECK to the GRAVEYARD\", card);\n\t\tcard.setLocation(CardLocation.GRAVEYARD);\n\t\tremoveSpellTriggers(card);\n\t\tplayer.getDeck().remove(card);\n\t\tplayer.getGraveyard().add(card);\n\t}\n\n\tpublic void removeQuests(Player player) {\n\t\tlog(\"All quests for {} have been destroyed\", player.getName());\n\t\t// This actually works amazingly\n\t\tfor (IGameEventListener quest : getQuests(player)) {\n\t\t\tquest.onRemove(context);\n\t\t\tcontext.removeTrigger(quest);\n\t\t}\n\t\tplayer.getSecrets().removeAll(player.getQuests());\n\t\tplayer.getQuests().clear();\n\t}\n\n\tpublic void removeSummon(Summon summon, boolean peacefully) {\n\t\tremoveSpellTriggers(summon);\n\n\t\tlog(\"{} was removed\", summon);\n\n\t\tsummon.setAttribute(Attribute.DESTROYED);\n\n\t\tPlayer owner = context.getPlayer(summon.getOwner());\n\t\towner.getSummons().remove(summon);\n\t\tif (peacefully) {\n\t\t\towner.getSetAsideZone().add(summon);\n\t\t} else {\n\t\t\towner.getGraveyard().add(summon);\n\t\t}\n\t\tcontext.fireGameEvent(new BoardChangedEvent(context));\n\t}\n\n\tpublic void removeSecrets(Player player) {\n\t\tlog(\"All secrets for {} have been destroyed\", player.getName());\n\t\t// this only works while Secrets are the only SpellTrigger on the heroes\n\t\t// Web - Lol, it works now.\n\t\tfor (IGameEventListener secret : getSecrets(player)) {\n\t\t\tsecret.onRemove(context);\n\t\t\tcontext.removeTrigger(secret);\n\t\t}\n\t\tplayer.getSecrets().clear();\n\t\tplayer.getSecrets().addAll(player.getQuests());\n\t}\n\n\tprivate void removeSpellTriggers(Entity entity) {\n\t\tremoveSpellTriggers(entity, true);\n\t}\n\n\tprivate void removeSpellTriggers(Entity entity, boolean removeAuras) {\n\t\tEntityReference entityReference = entity.getReference();\n\t\tfor (IGameEventListener trigger : context.getTriggersAssociatedWith(entityReference)) {\n\t\t\tif (!removeAuras && trigger instanceof Aura) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlog(\"SpellTrigger {} was removed for {}\", trigger, entity);\n\t\t\ttrigger.onRemove(context);\n\t\t}\n\t\tcontext.removeTriggersAssociatedWith(entityReference, removeAuras);\n\t\tfor (Iterator<CardCostModifier> iterator = context.getCardCostModifiers().iterator(); iterator.hasNext();) {\n\t\t\tCardCostModifier cardCostModifier = iterator.next();\n\t\t\tif (cardCostModifier.getHostReference().equals(entityReference)) {\n\t\t\t\titerator.remove();\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic void replaceCard(int playerId, Card oldCard, Card newCard) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tif (newCard.getId() == IdFactory.UNASSIGNED) {\n\t\t\tnewCard.setId(idFactory.generateId());\n\t\t}\n\t\t\n\t\tif (!player.getHand().contains(oldCard)) {\n\t\t\treturn;\n\t\t}\n\n\t\tnewCard.setOwner(playerId);\n\t\tCardCollection hand = player.getHand();\n\n\t\tif (newCard.getAttribute(Attribute.PASSIVE_TRIGGER) != null) {\n\t\t\tTriggerDesc triggerDesc = (TriggerDesc) newCard.getAttribute(Attribute.PASSIVE_TRIGGER);\n\t\t\taddGameEventListener(player, triggerDesc.create(), newCard);\n\t\t}\n\n\t\tlog(\"{} replaces card {} with card {}\", player.getName(), oldCard, newCard);\n\t\thand.replace(oldCard, newCard);\n\t\tremoveCard(playerId, oldCard);\n\t\tnewCard.setLocation(CardLocation.HAND);\n\t\tcontext.fireGameEvent(new DrawCardEvent(context, playerId, newCard, null, false));\n\t}\n\t\n\tpublic void replaceCardInDeck(int playerId, Card oldCard, Card newCard) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tif (newCard.getId() == IdFactory.UNASSIGNED) {\n\t\t\tnewCard.setId(idFactory.generateId());\n\t\t}\n\t\t\n\t\tif (!player.getDeck().contains(oldCard)) {\n\t\t\treturn;\n\t\t}\n\n\t\tnewCard.setOwner(playerId);\n\t\tCardCollection deck = player.getDeck();\n\n\t\tif (newCard.getAttribute(Attribute.DECK_TRIGGER) != null) {\n\t\t\tTriggerDesc triggerDesc = (TriggerDesc) newCard.getAttribute(Attribute.DECK_TRIGGER);\n\t\t\taddGameEventListener(player, triggerDesc.create(), newCard);\n\t\t}\n\n\t\tlog(\"{} replaces card {} with card {}\", player.getName(), oldCard, newCard);\n\t\tdeck.replace(oldCard, newCard);\n\t\tremoveCardFromDeck(playerId, oldCard);\n\t\tnewCard.setLocation(CardLocation.DECK);\n\t}\n\n\tprivate void resolveBattlecry(int playerId, Actor actor) {\n\t\tBattlecryAction battlecry = actor.getBattlecry();\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tif (!battlecry.canBeExecuted(context, player)) {\n\t\t\treturn;\n\t\t}\n\n\t\tGameAction battlecryAction = null;\n\t\tbattlecry.setSource(actor.getReference());\n\t\tif (battlecry.getTargetRequirement() != TargetSelection.NONE) {\n\t\t\tList<Entity> validTargets = targetLogic.getValidTargets(context, player, battlecry);\n\t\t\tif (validTargets.isEmpty()) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tList<GameAction> battlecryActions = new ArrayList<>();\n\t\t\tfor (Entity validTarget : validTargets) {\n\t\t\t\tGameAction targetedBattlecry = battlecry.clone();\n\t\t\t\ttargetedBattlecry.setTarget(validTarget);\n\t\t\t\tbattlecryActions.add(targetedBattlecry);\n\t\t\t}\n\t\t\t\n\t\t\tif (attributeExists(Attribute.ALL_RANDOM_FINAL_DESTINATION)) {\n\t\t\t\tbattlecryAction = battlecryActions.get(random(battlecryActions.size()));\n\t\t\t} else {\n\t\t\t\tbattlecryAction = player.getBehaviour().requestAction(context, player, battlecryActions);\n\t\t\t}\n\t\t} else {\n\t\t\tbattlecryAction = battlecry;\n\t\t}\n\t\tif (hasAttribute(player, Attribute.DOUBLE_BATTLECRIES) && actor.getSourceCard().hasAttribute(Attribute.BATTLECRY)) {\n\t\t\t// You need DOUBLE_BATTLECRIES before your battlecry action, not after.\n\t\t\tperformGameAction(playerId, battlecryAction);\n\t\t\tif (!battlecry.canBeExecuted(context, player)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tperformGameAction(playerId, battlecryAction);\n\t\t} else {\n\t\t\tperformGameAction(playerId, battlecryAction);\n\t\t}\n\t}\n\n\tpublic void resolveDeathrattles(Player player, Actor actor) {\n\t\tresolveDeathrattles(player, actor, -1);\n\t}\n\n\tpublic void resolveDeathrattles(Player player, Actor actor, int boardPosition) {\n\t\tif (!actor.hasAttribute(Attribute.DEATHRATTLES)) {\n\t\t\treturn;\n\t\t}\n\t\tif (boardPosition == -1) {\n\t\t\tplayer.getSummons().indexOf(actor);\n\t\t}\n\t\tboolean doubleDeathrattles = hasAttribute(player, Attribute.DOUBLE_DEATHRATTLES);\n\t\tEntityReference sourceReference = actor.getReference();\n\t\tfor (SpellDesc deathrattleTemplate : actor.getDeathrattles()) {\n\t\t\tSpellDesc deathrattle = deathrattleTemplate.addArg(SpellArg.BOARD_POSITION_ABSOLUTE, boardPosition);\n\t\t\tcastSpell(player.getId(), deathrattle, sourceReference, EntityReference.NONE, false);\n\t\t\tif (doubleDeathrattles) {\n\t\t\t\tcastSpell(player.getId(), deathrattle, sourceReference, EntityReference.NONE, false);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic void questTriggered(Player player, Quest quest) {\n\t\tlog(\"Quest was trigged: {}\", quest.getSource());\n\t\tplayer.getSecrets().remove(quest.getSource().getCardId());\n\t\tplayer.getQuests().remove(quest.getSource().getCardId());\n\t\tcontext.fireGameEvent(new QuestSuccessfulEvent(context, (QuestCard) quest.getSource(), player.getId()));\n\t}\n\n\tpublic void secretTriggered(Player player, Secret secret) {\n\t\tlog(\"Secret was trigged: {}\", secret.getSource());\n\t\tplayer.getSecrets().remove(secret.getSource().getCardId());\n\t\tcontext.fireGameEvent(new SecretRevealedEvent(context, (SecretCard) secret.getSource(), player.getId()));\n\t}\n\n\t// TODO: circular dependency. Very ugly, refactor!\n\tpublic void setContext(GameContext context) {\n\t\tthis.context = context;\n\t}\n\n\tpublic void setLoggingEnabled(boolean loggingEnabled) {\n\t\tthis.loggingEnabled = loggingEnabled;\n\t}\n\n\tpublic void shuffleToDeck(Player player, Card card) {\n\t\tif (card.getId() == IdFactory.UNASSIGNED) {\n\t\t\tcard.setId(idFactory.generateId());\n\t\t}\n\t\tcard.setLocation(CardLocation.DECK);\n\n\t\tif (player.getDeck().getCount() < MAX_DECK_SIZE) {\n\t\t\tplayer.getDeck().addRandomly(card);\n\t\t\t\n\t\t\tif (card.getAttribute(Attribute.DECK_TRIGGER) != null) {\n\t\t\t\tTriggerDesc triggerDesc = (TriggerDesc) card.getAttribute(Attribute.DECK_TRIGGER);\n\t\t\t\taddGameEventListener(player, triggerDesc.create(), card);\n\t\t\t}\n\t\t\tlog(\"Card {} has been shuffled to {}'s deck\", card, player.getName());\n\t\t}\n\t}\n\n\tpublic void silence(int playerId, Minion target) {\n\t\tcontext.fireGameEvent(new SilenceEvent(context, playerId, target));\n\t\tfinal HashSet<Attribute> immuneToSilence = new HashSet<Attribute>();\n\t\timmuneToSilence.add(Attribute.HP);\n\t\timmuneToSilence.add(Attribute.MAX_HP);\n\t\timmuneToSilence.add(Attribute.BASE_HP);\n\t\timmuneToSilence.add(Attribute.BASE_ATTACK);\n\t\timmuneToSilence.add(Attribute.SUMMONING_SICKNESS);\n\t\timmuneToSilence.add(Attribute.AURA_ATTACK_BONUS);\n\t\timmuneToSilence.add(Attribute.AURA_HP_BONUS);\n\t\timmuneToSilence.add(Attribute.AURA_UNTARGETABLE_BY_SPELLS);\n\t\timmuneToSilence.add(Attribute.RACE);\n\t\timmuneToSilence.add(Attribute.NUMBER_OF_ATTACKS);\n\n\t\tList<Attribute> tags = new ArrayList<Attribute>();\n\t\ttags.addAll(target.getAttributes().keySet());\n\t\tfor (Attribute attr : tags) {\n\t\t\tif (immuneToSilence.contains(attr)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tremoveAttribute(target, attr);\n\t\t}\n\t\tremoveSpellTriggers(target);\n\n\t\tint oldMaxHp = target.getMaxHp();\n\t\ttarget.setMaxHp(target.getAttributeValue(Attribute.BASE_HP));\n\t\ttarget.setAttack(target.getAttributeValue(Attribute.BASE_ATTACK));\n\t\tif (target.getHp() > target.getMaxHp()) {\n\t\t\ttarget.setHp(target.getMaxHp());\n\t\t} else if (oldMaxHp < target.getMaxHp()) {\n\t\t\ttarget.setHp(target.getHp() + target.getMaxHp() - oldMaxHp);\n\t\t}\n\n\t\tlog(\"{} was silenced\", target);\n\t}\n\n\tpublic void startTurn(int playerId) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tif (player.getMaxMana() < MAX_MANA) {\n\t\t\tplayer.setMaxMana(player.getMaxMana() + 1);\n\t\t}\n\t\tplayer.getStatistics().startTurn();\n\n\t\tplayer.setLockedMana(player.getAttributeValue(Attribute.OVERLOAD));\n\t\tint mana = Math.min(player.getMaxMana() - player.getLockedMana(), MAX_MANA);\n\t\tplayer.setMana(mana);\n\t\tString manaString = player.getMana() + \"/\" + player.getMaxMana();\n\t\tif (player.getLockedMana() > 0) {\n\t\t\tmanaString += \" (\" + player.getLockedMana() + \" locked by overload)\";\n\t\t}\n\t\tlog(\"{} starts his turn with {} mana\", player.getName(), manaString);\n\n\t\tplayer.removeAttribute(Attribute.OVERLOAD);\n\t\tfor (Summon summon : player.getSummons()) {\n\t\t\tsummon.removeAttribute(Attribute.TEMPORARY_ATTACK_BONUS);\n\t\t}\n\n\t\tplayer.getHero().getHeroPower().setUsed(0);\n\t\tplayer.getHero().activateWeapon(true);\n\t\trefreshAttacksPerRound(player.getHero());\n\t\tfor (Summon summon : player.getSummons()) {\n\t\t\tsummon.removeAttribute(Attribute.SUMMONING_SICKNESS);\n\t\t\trefreshAttacksPerRound(summon);\n\t\t}\n\t\tcontext.fireGameEvent(new TurnStartEvent(context, player.getId()));\n\t\tdrawCard(playerId, null);\n\t\tcheckForDeadEntities();\n\t}\n\n\tpublic boolean summon(int playerId, Summon summon) {\n\t\treturn summon(playerId, summon, null, -1, false);\n\t}\n\n\tpublic boolean summon(int playerId, Summon summon, Card source, int index, boolean resolveBattlecry) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tif (!canSummonMoreMinions(player)) {\n\t\t\tlog(\"{} cannot summon any more summons, {} is destroyed\", player.getName(), summon);\n\t\t\treturn false;\n\t\t}\n\t\tsummon.setId(idFactory.generateId());\n\t\tsummon.setOwner(player.getId());\n\n\t\tcontext.getSummonReferenceStack().push(summon.getReference());\n\n\t\tlog(\"{} summons {}\", player.getName(), summon);\n\n\t\tif (index < 0 || index >= player.getSummons().size()) {\n\t\t\tplayer.getSummons().add(summon);\n\t\t} else {\n\t\t\tplayer.getSummons().add(index, summon);\n\t\t}\n\t\tif (summon instanceof Minion) {\n\t\t\tMinion minion = (Minion) summon;\n\n\t\t\tcontext.fireGameEvent(new BeforeSummonEvent(context, minion, source));\n\t\t}\n\t\tcontext.fireGameEvent(new BoardChangedEvent(context));\n\t\t\n\t\tif (resolveBattlecry && summon.getBattlecry() != null) {\n\t\t\tresolveBattlecry(player.getId(), summon);\n\t\t\tcheckForDeadEntities();\n\t\t}\n\n\t\tif (context.getEnvironment().get(Environment.TRANSFORM_REFERENCE) != null) {\n\t\t\tsummon = (Summon) context.resolveSingleTarget((EntityReference) context.getEnvironment().get(Environment.TRANSFORM_REFERENCE));\n\t\t\tsummon.setBattlecry(null);\n\t\t\tcontext.getEnvironment().remove(Environment.TRANSFORM_REFERENCE);\n\t\t}\n\n\t\tcontext.fireGameEvent(new BoardChangedEvent(context));\n\n\t\tif (summon instanceof Minion) {\n\t\t\tMinion minion = (Minion) summon;\n\t\t\tplayer.getStatistics().minionSummoned(minion, context.getTurn());\n\n\t\t\tif (context.getEnvironment().get(Environment.TARGET_OVERRIDE) != null) {\n\t\t\t\tActor actor = (Actor) context.resolveSingleTarget((EntityReference) context.getEnvironment().get(Environment.TARGET_OVERRIDE));\n\t\t\t\tcontext.getEnvironment().remove(Environment.TARGET_OVERRIDE);\n\t\t\t\tSummonEvent summonEvent = new SummonEvent(context, actor, source);\n\t\t\t\tcontext.fireGameEvent(summonEvent);\n\t\t\t} else {\n\t\t\t\tSummonEvent summonEvent = new SummonEvent(context, minion, source);\n\t\t\t\tcontext.fireGameEvent(summonEvent);\n\t\t\t}\n\n\t\t\tapplyAttribute(minion, Attribute.SUMMONING_SICKNESS);\n\t\t\trefreshAttacksPerRound(minion);\n\t\t} else if (summon instanceof Permanent) {\n\t\t\tPermanent permanent = (Permanent) summon;\n\t\t\tplayer.getStatistics().permanentSummoned(permanent, context.getTurn());\n\t\t\t\n\t\t}\n\n\t\tif (summon.hasSpellTrigger()) {\n\t\t\tfor (SpellTrigger trigger : summon.getSpellTriggers()) {\n\t\t\t\taddGameEventListener(player, trigger, summon);\n\t\t\t}\n\t\t}\n\n\t\tif (summon.getCardCostModifier() != null) {\n\t\t\taddManaModifier(player, summon.getCardCostModifier(), summon);\n\t\t}\n\n\t\tif (summon instanceof Minion) {\n\t\t\tMinion minion = (Minion) summon;\n\t\t\tif (source != null) {\n\t\t\t\tsource.setAttribute(Attribute.ATTACK, source.getAttributeValue(Attribute.BASE_ATTACK));\n\t\t\t\tsource.setAttribute(Attribute.ATTACK_BONUS, 0);\n\t\t\t\tsource.setAttribute(Attribute.MAX_HP, source.getAttributeValue(Attribute.BASE_HP));\n\t\t\t\tsource.setAttribute(Attribute.HP, source.getAttributeValue(Attribute.BASE_HP));\n\t\t\t\tsource.setAttribute(Attribute.HP_BONUS, 0);\n\t\t\t}\n\t\t\thandleEnrage(minion);\n\n\t\t\tcontext.getSummonReferenceStack().pop();\n\t\t\tif (player.getSummons().contains(minion)) {\n\t\t\t\tcontext.fireGameEvent(new AfterSummonEvent(context, minion, source));\n\t\t\t}\n\t\t}\n\t\tcontext.fireGameEvent(new BoardChangedEvent(context));\n\t\treturn true;\n\t}\n\n\t/**\n\t * Transforms a Minion into a new Minion.\n\t * \n\t * @param minion\n\t *            The original minion in play\n\t * @param newMinion\n\t *            The new minion to transform into\n\t */\n\tpublic void transformMinion(Summon summon, Summon newSummon) {\n\t\t// Remove any spell triggers associated with the old minion.\n\t\tremoveSpellTriggers(summon);\n\n\t\tPlayer owner = context.getPlayer(summon.getOwner());\n\t\tint index = owner.getSummons().indexOf(summon);\n\t\towner.getSummons().remove(summon);\n\t\t\n\t\t// If we want to straight up remove a minion from existence without\n\t\t// killing it, this would be the best way.\n\t\tif (newSummon != null) {\n\t\t\tlog(\"{} was transformed to {}\", summon, newSummon);\n\n\t\t\t// Give the new minion an ID.\n\t\t\tnewSummon.setId(idFactory.generateId());\n\t\t\tnewSummon.setOwner(owner.getId());\n\t\n\t\t\t// If the minion being transforms is being summoned, replace the old\n\t\t\t// minion on the stack.\n\t\t\t// Otherwise, summon the add the new minion.\n\t\t\t// However, do not give a summon event.\n\t\t\tif (!context.getSummonReferenceStack().isEmpty() && context.getSummonReferenceStack().peek().equals(summon.getReference())\n\t\t\t\t\t&& !context.getEnvironment().containsKey(Environment.TRANSFORM_REFERENCE)) {\n\t\t\t\tcontext.getEnvironment().put(Environment.TRANSFORM_REFERENCE, newSummon.getReference());\n\t\t\t\towner.getSummons().add(index, newSummon);\n\t\n\t\t\t\t// It's quite possible that this is actually supposed to add the\n\t\t\t\t// minion to the zone it was originally in.\n\t\t\t\t// This means minions in the SetAsideZone or the Graveyard that are\n\t\t\t\t// targeted (through bizarre mechanics)\n\t\t\t\t// add the minion to there. This will be tested eventually with\n\t\t\t\t// Resurrect, Recombobulator, and Illidan.\n\t\t\t\t// Since this is unknown, this is the patch for it.\n\t\t\t} else if (!owner.getSetAsideZone().contains(summon)) {\n\t\t\t\tif (index < 0 || index >= owner.getSummons().size()) {\n\t\t\t\t\towner.getSummons().add(newSummon);\n\t\t\t\t} else {\n\t\t\t\t\towner.getSummons().add(index, newSummon);\n\t\t\t\t}\n\t\n\t\t\t\tapplyAttribute(newSummon, Attribute.SUMMONING_SICKNESS);\n\t\t\t\trefreshAttacksPerRound(newSummon);\n\t\n\t\t\t\tif (newSummon.hasSpellTrigger()) {\n\t\t\t\t\tfor (SpellTrigger spellTrigger : newSummon.getSpellTriggers()) {\n\t\t\t\t\t\taddGameEventListener(owner, spellTrigger, newSummon);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\n\t\t\t\tif (newSummon.getCardCostModifier() != null) {\n\t\t\t\t\taddManaModifier(owner, newSummon.getCardCostModifier(), newSummon);\n\t\t\t\t}\n\t\n\t\t\t\thandleEnrage(newSummon);\n\t\t\t} else {\n\t\t\t\towner.getSetAsideZone().add(newSummon);\n\t\t\t\tnewSummon.setId(idFactory.generateId());\n\t\t\t\tnewSummon.setOwner(owner.getId());\n\t\t\t\tremoveSpellTriggers(newSummon);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\n\t\t}\n\n\t\t// Move the old minion to the Set Aside Zone\n\t\towner.getSetAsideZone().add(summon);\n\n\t\tcontext.fireGameEvent(new BoardChangedEvent(context));\n\t}\n\n\tpublic void useHeroPower(int playerId) {\n\t\tPlayer player = context.getPlayer(playerId);\n\t\tHeroPower power = player.getHero().getHeroPower();\n\t\tint modifiedManaCost = getModifiedManaCost(player, power);\n\t\tmodifyCurrentMana(playerId, -modifiedManaCost);\n\t\tlog(\"{} uses {}\", player.getName(), power);\n\t\tpower.markUsed();\n\t\tplayer.getStatistics().cardPlayed(power, context.getTurn());\n\t\tcontext.fireGameEvent(new HeroPowerUsedEvent(context, playerId, power));\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/logic/MatchResult.java",
    "content": "package net.demilich.metastone.game.logic;\n\npublic enum MatchResult {\n\tRUNNING, WON, DOUBLE_LOSS\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/logic/TargetLogic.java",
    "content": "package net.demilich.metastone.game.logic;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.Environment;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.actions.ActionType;\r\nimport net.demilich.metastone.game.actions.GameAction;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.heroes.Hero;\r\nimport net.demilich.metastone.game.entities.minions.Minion;\r\nimport net.demilich.metastone.game.entities.minions.Summon;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\nimport net.demilich.metastone.game.targeting.TargetSelection;\r\n\r\npublic class TargetLogic {\r\n\r\n\tprivate static Logger logger = LoggerFactory.getLogger(TargetLogic.class);\r\n\r\n\tprivate static List<Entity> singleTargetAsList(Entity target) {\r\n\t\tArrayList<Entity> list = new ArrayList<>(1);\r\n\t\tlist.add(target);\r\n\t\treturn list;\r\n\t}\r\n\r\n\tprivate boolean containsTaunters(List<Minion> minions) {\r\n\t\tfor (Entity entity : minions) {\r\n\t\t\tif (entity.hasAttribute(Attribute.TAUNT) && !entity.hasAttribute(Attribute.STEALTH) && !entity.hasAttribute(Attribute.IMMUNE)) {\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n\tprivate List<Entity> filterTargets(GameContext context, Player player, GameAction action, List<Entity> potentialTargets) {\r\n\t\tList<Entity> validTargets = new ArrayList<>();\r\n\t\tfor (Entity entity : potentialTargets) {\r\n\t\t\t// special case for 'SYSTEM' action, which are used in Sandbox Mode\r\n\t\t\t// we do not want to restrict those actions by STEALTH or\r\n\t\t\t// UNTARGETABLE_BY_SPELLS\r\n\t\t\tif (action.getActionType() == ActionType.SYSTEM && action.canBeExecutedOn(context, player, entity)) {\r\n\t\t\t\tvalidTargets.add(entity);\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif ((action.getActionType() == ActionType.SPELL || action.getActionType() == ActionType.HERO_POWER)\r\n\t\t\t\t\t&& (entity.hasAttribute(Attribute.UNTARGETABLE_BY_SPELLS) || (entity.hasAttribute(Attribute.AURA_UNTARGETABLE_BY_SPELLS)))) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tif (entity.getOwner() != player.getId() && (entity.hasAttribute(Attribute.STEALTH) || entity.hasAttribute(Attribute.IMMUNE))) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif (entity.getOwner() != player.getId() && entity instanceof Hero && context.getLogic().hasAttribute(context.getPlayer(entity.getOwner()), Attribute.IMMUNE_HERO)) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tif (action.canBeExecutedOn(context, player, entity)) {\r\n\t\t\t\tvalidTargets.add(entity);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn validTargets;\r\n\t}\r\n\r\n\tpublic Entity findEntity(GameContext context, EntityReference targetKey) {\r\n\t\tint targetId = targetKey.getId();\r\n\t\tEntity environmentResult = findInEnvironment(context, targetKey);\r\n\t\tif (environmentResult != null) {\r\n\t\t\treturn environmentResult;\r\n\t\t}\r\n\t\tfor (Player player : context.getPlayers()) {\r\n\t\t\tif (player.getId() == targetId) {\r\n\t\t\t\treturn player;\r\n\t\t\t}\r\n\t\t\tif (player.getHero().getId() == targetId) {\r\n\t\t\t\treturn player.getHero();\r\n\t\t\t} else if (player.getHero().getWeapon() != null && player.getHero().getWeapon().getId() == targetId) {\r\n\t\t\t\treturn player.getHero().getWeapon();\r\n\t\t\t} \r\n\r\n\t\t\tfor (Summon summon : player.getSummons()) {\r\n\t\t\t\tif (summon.getId() == targetId) {\r\n\t\t\t\t\treturn summon;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tfor (Entity entity : player.getGraveyard()) {\r\n\t\t\t\tif (entity.getId() == targetId) {\r\n\t\t\t\t\treturn entity;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tfor (Entity entity : player.getSetAsideZone()) {\r\n\t\t\t\tif (entity.getId() == targetId) {\r\n\t\t\t\t\treturn entity;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tEntity cardResult = findInCards(context.getPlayer1(), targetId);\r\n\t\tif (cardResult == null) {\r\n\t\t\tcardResult = findInCards(context.getPlayer2(), targetId);\r\n\t\t}\r\n\t\tif (cardResult != null) {\r\n\t\t\treturn cardResult;\r\n\t\t}\r\n\r\n\t\tlogger.error(\"Id \" + targetId + \" not found!\");\r\n\t\tlogger.error(context.toString());\r\n\t\tlogger.error(context.getEnvironment().toString());\r\n\t\tthrow new RuntimeException(\"Target not found exception: \" + targetKey);\r\n\t}\r\n\r\n\tprivate Entity findInCards(Player player, int targetId) {\r\n\t\tif (player.getHero().getHeroPower().getId() == targetId) {\r\n\t\t\treturn player.getHero().getHeroPower();\r\n\t\t}\r\n\t\tfor (Card card : player.getHand()) {\r\n\t\t\tif (card.getId() == targetId) {\r\n\t\t\t\treturn card;\r\n\t\t\t}\r\n\t\t}\r\n\t\tfor (Card card : player.getDeck()) {\r\n\t\t\tif (card.getId() == targetId) {\r\n\t\t\t\treturn card;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn null;\r\n\t}\r\n\r\n\tprivate Entity findInEnvironment(GameContext context, EntityReference targetKey) {\r\n\t\tif (!context.getEventTargetStack().isEmpty() && targetKey == EntityReference.EVENT_TARGET) {\r\n\t\t\treturn context.resolveSingleTarget(context.getEventTargetStack().peek());\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\tprivate List<Entity> getEntities(GameContext context, Player player, TargetSelection targetRequirement) {\r\n\t\tPlayer opponent = context.getOpponent(player);\r\n\t\tList<Entity> entities = new ArrayList<>();\r\n\t\tif (targetRequirement == TargetSelection.ENEMY_HERO || targetRequirement == TargetSelection.ENEMY_CHARACTERS\r\n\t\t\t\t|| targetRequirement == TargetSelection.ANY || targetRequirement == TargetSelection.HEROES) {\r\n\t\t\tentities.add(opponent.getHero());\r\n\t\t}\r\n\t\tif (targetRequirement == TargetSelection.ENEMY_MINIONS || targetRequirement == TargetSelection.ENEMY_CHARACTERS\r\n\t\t\t\t|| targetRequirement == TargetSelection.MINIONS || targetRequirement == TargetSelection.ANY) {\r\n\t\t\tentities.addAll(opponent.getMinions());\r\n\t\t}\r\n\t\tif (targetRequirement == TargetSelection.FRIENDLY_HERO || targetRequirement == TargetSelection.FRIENDLY_CHARACTERS\r\n\t\t\t\t|| targetRequirement == TargetSelection.ANY || targetRequirement == TargetSelection.HEROES) {\r\n\t\t\tentities.add(player.getHero());\r\n\t\t}\r\n\t\tif (targetRequirement == TargetSelection.FRIENDLY_MINIONS || targetRequirement == TargetSelection.FRIENDLY_CHARACTERS\r\n\t\t\t\t|| targetRequirement == TargetSelection.MINIONS || targetRequirement == TargetSelection.ANY) {\r\n\t\t\tentities.addAll(player.getMinions());\r\n\t\t}\r\n\t\tList<Entity> destroyedEntities = new ArrayList<Entity>();\r\n\t\tfor (Entity entity : entities) {\r\n\t\t\tif (entity != null && entity.hasAttribute(Attribute.PENDING_DESTROY)) {\r\n\t\t\t\tdestroyedEntities.add(entity);\r\n\t\t\t}\r\n\t\t}\r\n\t\tentities.removeAll(destroyedEntities);\r\n\t\treturn entities;\r\n\t}\r\n\r\n\tprivate List<Entity> getTaunters(List<Minion> entities) {\r\n\t\tList<Entity> taunters = new ArrayList<>();\r\n\t\tfor (Actor entity : entities) {\r\n\t\t\tif (entity.hasAttribute(Attribute.TAUNT) && !entity.hasAttribute(Attribute.STEALTH) && !entity.hasAttribute(Attribute.IMMUNE)) {\r\n\t\t\t\ttaunters.add(entity);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn taunters;\r\n\t}\r\n\r\n\tpublic List<Entity> getValidTargets(GameContext context, Player player, GameAction action) {\r\n\t\tTargetSelection targetRequirement = action.getTargetRequirement();\r\n\t\tActionType actionType = action.getActionType();\r\n\t\tPlayer opponent = context.getOpponent(player);\r\n\r\n\t\t// if there is a minion with TAUNT and the action is of type physical\r\n\t\t// attack only allow corresponding minions as targets\r\n\t\tif (actionType == ActionType.PHYSICAL_ATTACK\r\n\t\t\t\t&& (targetRequirement == TargetSelection.ENEMY_CHARACTERS || targetRequirement == TargetSelection.ENEMY_MINIONS)\r\n\t\t\t\t&& containsTaunters(opponent.getMinions())) {\r\n\t\t\treturn getTaunters(opponent.getMinions());\r\n\t\t}\r\n\t\tif (actionType == ActionType.SUMMON) {\r\n\t\t\t// you can summon next to any friendly minion or provide no target\r\n\t\t\t// (=null)\r\n\t\t\t// in which case the minion will appear to the very right of your\r\n\t\t\t// board\r\n\t\t\tList<Entity> summonTargets = getEntities(context, player, targetRequirement);\r\n\t\t\tsummonTargets.add(null);\r\n\t\t\treturn summonTargets;\r\n\t\t}\r\n\t\tList<Entity> potentialTargets = getEntities(context, player, targetRequirement);\r\n\t\treturn filterTargets(context, player, action, potentialTargets);\r\n\t}\r\n\r\n\tpublic List<Entity> resolveTargetKey(GameContext context, Player player, Entity source, EntityReference targetKey) {\r\n\t\tif (targetKey == null || targetKey == EntityReference.NONE) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t\tif (targetKey == EntityReference.ALL_CHARACTERS) {\r\n\t\t\treturn getEntities(context, player, TargetSelection.ANY);\r\n\t\t} else if (targetKey == EntityReference.ALL_MINIONS) {\r\n\t\t\treturn getEntities(context, player, TargetSelection.MINIONS);\r\n\t\t} else if (targetKey == EntityReference.ENEMY_CHARACTERS) {\r\n\t\t\treturn getEntities(context, player, TargetSelection.ENEMY_CHARACTERS);\r\n\t\t} else if (targetKey == EntityReference.ENEMY_HERO) {\r\n\t\t\treturn getEntities(context, player, TargetSelection.ENEMY_HERO);\r\n\t\t} else if (targetKey == EntityReference.ENEMY_MINIONS) {\r\n\t\t\treturn getEntities(context, player, TargetSelection.ENEMY_MINIONS);\r\n\t\t} else if (targetKey == EntityReference.FRIENDLY_CHARACTERS) {\r\n\t\t\treturn getEntities(context, player, TargetSelection.FRIENDLY_CHARACTERS);\r\n\t\t} else if (targetKey == EntityReference.FRIENDLY_HERO) {\r\n\t\t\treturn getEntities(context, player, TargetSelection.FRIENDLY_HERO);\r\n\t\t} else if (targetKey == EntityReference.FRIENDLY_MINIONS) {\r\n\t\t\treturn getEntities(context, player, TargetSelection.FRIENDLY_MINIONS);\r\n\t\t} else if (targetKey == EntityReference.OTHER_FRIENDLY_MINIONS) {\r\n\t\t\tList<Entity> targets = getEntities(context, player, TargetSelection.FRIENDLY_MINIONS);\r\n\t\t\ttargets.remove(source);\r\n\t\t\treturn targets;\r\n\t\t} else if (targetKey == EntityReference.ALL_OTHER_CHARACTERS) {\r\n\t\t\tList<Entity> targets = getEntities(context, player, TargetSelection.ANY);\r\n\t\t\ttargets.remove(source);\r\n\t\t\treturn targets;\r\n\t\t} else if (targetKey == EntityReference.ALL_OTHER_MINIONS) {\r\n\t\t\tList<Entity> targets = getEntities(context, player, TargetSelection.MINIONS);\r\n\t\t\ttargets.remove(source);\r\n\t\t\treturn targets;\r\n\t\t} else if (targetKey == EntityReference.ADJACENT_MINIONS) {\r\n\t\t\treturn new ArrayList<>(context.getAdjacentSummons(player, source.getReference()));\r\n\t\t} else if (targetKey == EntityReference.OPPOSITE_MINIONS) {\r\n\t\t\treturn new ArrayList<>(context.getOppositeSummons(player, source.getReference()));\r\n\t\t} else if (targetKey == EntityReference.MINIONS_TO_LEFT) {\r\n\t\t\treturn new ArrayList<>(context.getLeftSummons(player, source.getReference()));\r\n\t\t} else if (targetKey == EntityReference.MINIONS_TO_RIGHT) {\r\n\t\t\treturn new ArrayList<>(context.getRightSummons(player, source.getReference()));\r\n\t\t} else if (targetKey == EntityReference.SELF) {\r\n\t\t\treturn singleTargetAsList(source);\r\n\t\t} else if (targetKey == EntityReference.EVENT_TARGET) {\r\n\t\t\treturn singleTargetAsList(context.resolveSingleTarget(context.getEventTargetStack().peek()));\r\n\t\t} else if (targetKey == EntityReference.TARGET) {\r\n\t\t\treturn singleTargetAsList(context.resolveSingleTarget((EntityReference) context.getEnvironment().get(Environment.TARGET)));\r\n\t\t} else if (targetKey == EntityReference.SPELL_TARGET) {\r\n\t\t\treturn singleTargetAsList(context.resolveSingleTarget((EntityReference) context.getEnvironment().get(Environment.SPELL_TARGET)));\r\n\t\t} else if (targetKey == EntityReference.KILLED_MINION) {\r\n\t\t\treturn singleTargetAsList(context.resolveSingleTarget((EntityReference) context.getEnvironment().get(Environment.KILLED_MINION)));\r\n\t\t} else if (targetKey == EntityReference.ATTACKER_REFERENCE) {\r\n\t\t\treturn singleTargetAsList(context.resolveSingleTarget((EntityReference) context.getEnvironment().get(Environment.ATTACKER_REFERENCE)));\r\n\t\t} else if (targetKey == EntityReference.PENDING_CARD) {\r\n\t\t\treturn singleTargetAsList((Entity) context.getPendingCard());\r\n\t\t} else if (targetKey == EntityReference.EVENT_CARD) {\r\n\t\t\treturn singleTargetAsList((Entity) context.getEventCard());\r\n\t\t} else if (targetKey == EntityReference.FRIENDLY_WEAPON) {\r\n\t\t\tif (player.getHero().getWeapon() != null) {\r\n\t\t\t\treturn singleTargetAsList(player.getHero().getWeapon());\r\n\t\t\t} else {\r\n\t\t\t\treturn new ArrayList<>();\r\n\t\t\t}\r\n\t\t} else if (targetKey == EntityReference.ENEMY_WEAPON) {\r\n\t\t\tPlayer opponent = context.getOpponent(player);\r\n\t\t\tif (opponent.getHero().getWeapon() != null) {\r\n\t\t\t\treturn singleTargetAsList(opponent.getHero().getWeapon());\r\n\t\t\t} else {\r\n\t\t\t\treturn new ArrayList<>();\r\n\t\t\t}\r\n\t\t} else if (targetKey == EntityReference.FRIENDLY_HAND) {\r\n\t\t\treturn new ArrayList<>(player.getHand().toList());\r\n\t\t} else if (targetKey == EntityReference.ENEMY_HAND) {\r\n\t\t\treturn new ArrayList<>(context.getOpponent(player).getHand().toList());\r\n\t\t} else if (targetKey == EntityReference.FRIENDLY_PLAYER) {\r\n\t\t\treturn singleTargetAsList(player);\r\n\t\t} else if (targetKey == EntityReference.ENEMY_PLAYER) {\r\n\t\t\treturn singleTargetAsList(context.getOpponent(player));\r\n\t\t}\r\n\r\n\t\treturn singleTargetAsList(findEntity(context, targetKey));\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/AddAttributeSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.trigger.GameEventTrigger;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class AddAttributeSpell extends RevertableSpell {\n\n\tpublic static SpellDesc create(Attribute tag) {\n\t\treturn create(tag, null);\n\t}\n\n\tpublic static SpellDesc create(Attribute tag, GameEventTrigger revertTrigger) {\n\t\treturn create(null, tag, revertTrigger);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, Attribute tag) {\n\t\treturn create(target, tag, null);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, Attribute tag, GameEventTrigger revertTrigger) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(AddAttributeSpell.class);\n\t\targuments.put(SpellArg.ATTRIBUTE, tag);\n\t\targuments.put(SpellArg.REVERT_TRIGGER, revertTrigger);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected SpellDesc getReverseSpell(SpellDesc desc, EntityReference target) {\n\t\treturn RemoveAttributeSpell.create(target, (Attribute) desc.get(SpellArg.ATTRIBUTE));\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tAttribute tag = (Attribute) desc.get(SpellArg.ATTRIBUTE);\n\t\tcontext.getLogic().applyAttribute(target, tag);\n\t\tsuper.onCast(context, player, desc, source, target);\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/AddDeathrattleSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class AddDeathrattleSpell extends Spell {\n\n\tpublic static SpellDesc create(EntityReference target, SpellDesc deathrattle) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(AddDeathrattleSpell.class);\n\t\targuments.put(SpellArg.SPELL, deathrattle);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(SpellDesc deathrattle) {\n\t\treturn create(null, deathrattle);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tActor minion = (Actor) target;\n\t\tSpellDesc deathrattle = (SpellDesc) desc.get(SpellArg.SPELL);\n\t\tminion.addDeathrattle(deathrattle);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/AddQuestSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.trigger.types.Quest;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class AddQuestSpell extends Spell {\n\n\tpublic static SpellDesc create(Quest quest) {\n\t\treturn create (EntityReference.FRIENDLY_PLAYER, quest);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, Quest quest) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(AddQuestSpell.class);\n\t\targuments.put(SpellArg.QUEST, quest);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tQuest quest = (Quest) desc.get(SpellArg.QUEST);\n\t\tcontext.getLogic().playQuest(player, quest);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/AddSecretSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.trigger.types.Secret;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class AddSecretSpell extends Spell {\n\n\tpublic static SpellDesc create(Secret secret) {\n\t\treturn create (EntityReference.FRIENDLY_PLAYER, secret);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, Secret secret) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(AddSecretSpell.class);\n\t\targuments.put(SpellArg.SECRET, secret);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tSecret secret = (Secret) desc.get(SpellArg.SECRET);\n\t\tcontext.getLogic().playSecret(player, secret);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/AddSpellTriggerSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.trigger.TriggerDesc;\nimport net.demilich.metastone.game.spells.trigger.SpellTrigger;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class AddSpellTriggerSpell extends Spell {\n\n\tpublic static SpellDesc create(EntityReference target, TriggerDesc trigger) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(AddSpellTriggerSpell.class);\n\t\targuments.put(SpellArg.TRIGGER, trigger);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(TriggerDesc trigger) {\n\t\treturn create(null, trigger);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tTriggerDesc triggerDesc = (TriggerDesc) desc.get(SpellArg.TRIGGER);\n\t\tSpellTrigger spellTrigger = triggerDesc.create();\n\t\tcontext.getLogic().addGameEventListener(player, spellTrigger, target);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/AdjacentEffectSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Summon;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class AdjacentEffectSpell extends Spell {\n\n\tpublic static SpellDesc create(EntityReference target, SpellDesc primarySpell, SpellDesc secondarySpell) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(AdjacentEffectSpell.class);\n\t\tif (primarySpell != null) {\n\t\t\targuments.put(SpellArg.SPELL_1, primarySpell);\n\t\t}\n\t\tif (secondarySpell != null) {\n\t\t\targuments.put(SpellArg.SPELL_2, secondarySpell);\n\t\t}\n\t\tif (primarySpell == null && secondarySpell == null) {\n\t\t\tthrow new IllegalArgumentException(\"Both primary- and secondary spell are NULL; at least one of them must be set\");\n\t\t}\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(SpellDesc primarySpell, SpellDesc secondarySpell) {\n\t\treturn create(null, primarySpell, secondarySpell);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tEntityReference sourceReference = source != null ? source.getReference() : null;\n\t\tList<Summon> adjacentSummons = context.getAdjacentSummons(player, target.getReference());\n\n\t\tSpellDesc primary = (SpellDesc) desc.get(SpellArg.SPELL_1);\n\t\tif (primary != null) {\n\t\t\tcontext.getLogic().castSpell(player.getId(), primary, sourceReference, target.getReference(), true);\n\t\t}\n\n\t\tSpellDesc secondary = (SpellDesc) desc.get(SpellArg.SPELL_2);\n\t\tif (secondary == null) {\n\t\t\tsecondary = primary;\n\t\t}\n\t\tfor (Entity adjacent : adjacentSummons) {\n\t\t\tcontext.getLogic().castSpell(player.getId(), secondary, sourceReference, adjacent.getReference(), true);\n\t\t}\n\t}\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/AuraBuffSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\nimport java.util.function.Predicate;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class AuraBuffSpell extends Spell {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(AuraBuffSpell.class);\n\n\tpublic static SpellDesc create(int attackBonus) {\n\t\treturn create(attackBonus, 0);\n\t}\n\n\tpublic static SpellDesc create(int attackBonus, int hpBonus) {\n\t\treturn create(attackBonus, hpBonus, null);\n\t}\n\n\tpublic static SpellDesc create(int attackBonus, int hpBonus, Predicate<Entity> targetFilter) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(AuraBuffSpell.class);\n\t\targuments.put(SpellArg.ATTACK_BONUS, attackBonus);\n\t\targuments.put(SpellArg.HP_BONUS, hpBonus);\n\t\tif (targetFilter != null) {\n\t\t\targuments.put(SpellArg.FILTER, targetFilter);\n\t\t}\n\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint attackBonus = desc.getValue(SpellArg.ATTACK_BONUS, context, player, target, source, 0);\n\t\tint hpBonus = desc.getValue(SpellArg.HP_BONUS, context, player, target, source, 0);\n\t\tActor targetActor = (Actor) target;\n\t\tlogger.debug(\"{} gains ({} from aura effect)\", targetActor, attackBonus + \"/\" + hpBonus);\n\t\tif (attackBonus != 0) {\n\t\t\ttargetActor.modifyAttribute(Attribute.AURA_ATTACK_BONUS, attackBonus);\n\t\t}\n\t\tif (hpBonus != 0) {\n\t\t\ttargetActor.modifyAuraHpBonus(hpBonus);\n\t\t}\n\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/BuffHeroSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.heroes.Hero;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class BuffHeroSpell extends Spell {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(BuffHeroSpell.class);\n\n\tpublic static SpellDesc create(int attackBonus, int armorBonus) {\n\t\treturn create(EntityReference.FRIENDLY_HERO, attackBonus, armorBonus);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, int attackBonus, int armorBonus) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(BuffHeroSpell.class);\n\t\targuments.put(SpellArg.ATTACK_BONUS, attackBonus);\n\t\targuments.put(SpellArg.ARMOR_BONUS, armorBonus);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tHero hero = (Hero) target;\n\t\tint attackBonus = desc.getValue(SpellArg.ATTACK_BONUS, context, player, target, source, 0);\n\t\tint armorBonus = desc.getValue(SpellArg.ARMOR_BONUS, context, player, target, source, 0);\n\n\t\tif (attackBonus != 0) {\n\t\t\tlogger.debug(\"{} gains {} attack\", hero, attackBonus);\n\t\t\thero.modifyAttribute(Attribute.TEMPORARY_ATTACK_BONUS, attackBonus);\n\t\t}\n\t\tif (armorBonus != 0) {\n\t\t\tcontext.getLogic().gainArmor(player, armorBonus);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/BuffSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport java.util.Map;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class BuffSpell extends Spell {\r\n\r\n\tprivate static Logger logger = LoggerFactory.getLogger(BuffSpell.class);\r\n\r\n\tpublic static SpellDesc create(EntityReference target, int value) {\r\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(BuffSpell.class);\r\n\t\targuments.put(SpellArg.VALUE, value);\r\n\t\targuments.put(SpellArg.TARGET, target);\r\n\t\treturn new SpellDesc(arguments);\r\n\t}\r\n\r\n\tpublic static SpellDesc create(EntityReference target, int attackBonus, int hpBonus) {\r\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(BuffSpell.class);\r\n\t\targuments.put(SpellArg.ATTACK_BONUS, attackBonus);\r\n\t\targuments.put(SpellArg.HP_BONUS, hpBonus);\r\n\t\targuments.put(SpellArg.TARGET, target);\r\n\t\treturn new SpellDesc(arguments);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tint attackBonus = desc.getValue(SpellArg.ATTACK_BONUS, context, player, target, source, 0);\r\n\t\tint hpBonus = desc.getValue(SpellArg.HP_BONUS, context, player, target, source, 0);\r\n\t\tint value = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\r\n\t\t\r\n\t\tif (value != 0) {\r\n\t\t\tattackBonus = hpBonus = value;\r\n\t\t}\r\n\t\tlogger.debug(\"{} gains ({})\", target, attackBonus + \"/\" + hpBonus);\r\n\r\n\t\tif (attackBonus != 0) {\r\n\t\t\ttarget.modifyAttribute(Attribute.ATTACK_BONUS, attackBonus);\r\n\t\t}\r\n\t\tif (hpBonus != 0) {\r\n\t\t\ttarget.modifyHpBonus(hpBonus);\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/BuffWeaponSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.weapons.Weapon;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class BuffWeaponSpell extends Spell {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(BuffWeaponSpell.class);\n\n\tpublic static SpellDesc create(int attackBonus, int durabilityBonus) {\n\t\treturn create(EntityReference.FRIENDLY_WEAPON, attackBonus, durabilityBonus);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, int attackBonus, int durabilityBonus) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(BuffWeaponSpell.class);\n\t\targuments.put(SpellArg.ATTACK_BONUS, attackBonus);\n\t\targuments.put(SpellArg.HP_BONUS, durabilityBonus);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint damageBonus = desc.getValue(SpellArg.ATTACK_BONUS, context, player, target, source, 0);\n\t\tint durabilityBonus = desc.getValue(SpellArg.HP_BONUS, context, player, target, source, 0);\n\n\t\tWeapon weapon = (Weapon) target;\n\t\tif (weapon == null) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug(\"{} gains ({})\", weapon, damageBonus + \"/\" + durabilityBonus);\n\t\tif (damageBonus != 0) {\n\t\t\tweapon.modifyAttribute(Attribute.ATTACK, damageBonus);\n\t\t}\n\t\tif (durabilityBonus != 0) {\n\t\t\tcontext.getLogic().modifyDurability(weapon, durabilityBonus);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/CardCostModifierSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.costmodifier.CardCostModifier;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.spells.desc.manamodifier.CardCostModifierArg;\nimport net.demilich.metastone.game.spells.desc.manamodifier.CardCostModifierDesc;\nimport net.demilich.metastone.game.spells.desc.valueprovider.AlgebraicOperation;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class CardCostModifierSpell extends Spell {\n\n\tpublic static SpellDesc create(CardCostModifierDesc cardCostModifierDesc) {\n\t\treturn create(cardCostModifierDesc, null);\n\t}\n\n\tpublic static SpellDesc create(CardCostModifierDesc cardCostModifierDesc, EntityFilter cardFilter) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(CardCostModifierSpell.class);\n\t\targuments.put(SpellArg.CARD_COST_MODIFIER, cardCostModifierDesc);\n\t\targuments.put(SpellArg.CARD_FILTER, cardFilter);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, AlgebraicOperation operation, int value) {\n\t\treturn CardCostModifierSpell.create(target, operation, value, null);\n\t}\n\n\n\tpublic static SpellDesc create(EntityReference target, AlgebraicOperation operation, int value, EntityFilter cardFilter) {\n\t\tCardCostModifierDesc manaModifierDesc = new CardCostModifierDesc(CardCostModifierDesc.build(CardCostModifier.class));\n\t\tmanaModifierDesc = manaModifierDesc.addArg(CardCostModifierArg.OPERATION, operation);\n\t\tmanaModifierDesc = manaModifierDesc.addArg(CardCostModifierArg.TARGET, target);\n\t\tmanaModifierDesc = manaModifierDesc.addArg(CardCostModifierArg.VALUE, value);\n\t\treturn CardCostModifierSpell.create(manaModifierDesc, cardFilter);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tCardCostModifierDesc manaModifierDesc = (CardCostModifierDesc) desc.get(SpellArg.CARD_COST_MODIFIER);\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\n\t\tif (manaModifierDesc.get(CardCostModifierArg.TARGET) != null) {\n\t\t\t// First, resolve the targets, so that you can get the current cards this affects.\n\t\t\t// This spell SPECIFICALLY targets cards, even if those cards would change. So,\n\t\t\t// targeting FRIENDLY_HAND would pull cards in the hand NOW, as opposed to cards\n\t\t\t// that will be added next turn.\n\t\t\tList<Entity> cards = context.resolveTarget(player, source, (EntityReference) manaModifierDesc.get(CardCostModifierArg.TARGET));\n\t\t\tList<Integer> cardIds = new ArrayList<Integer>();\n\t\t\tfor (Entity card : cards) {\n\t\t\t\tif (cardFilter == null || cardFilter.matches(context, player, card)) {\n\t\t\t\t\tcardIds.add(card.getId());\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tif (cardIds.isEmpty()) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tmanaModifierDesc = manaModifierDesc.removeArg(CardCostModifierArg.TARGET);\n\t\t\tmanaModifierDesc = manaModifierDesc.addArg(CardCostModifierArg.CARD_IDS, cardIds);\n\t\t} else if (target != null && target instanceof Card) {\n\t\t\tList<Integer> cardIds = new ArrayList<Integer>();\n\t\t\tcardIds.add(target.getId());\n\t\t\tmanaModifierDesc = manaModifierDesc.addArg(CardCostModifierArg.CARD_IDS, cardIds);\n\t\t}\n\t\tif (target == null) {\n\t\t\ttarget = player;\n\t\t}\n\t\tcontext.getLogic().addManaModifier(player, manaModifierDesc.create(), target);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/CastRandomSpellSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.BattlecryAction;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.cards.ChooseOneCard;\nimport net.demilich.metastone.game.cards.SpellCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.CardRevealedEvent;\nimport net.demilich.metastone.game.events.OverloadEvent;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.CardFilter;\nimport net.demilich.metastone.game.spells.desc.source.CardSource;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class CastRandomSpellSpell extends Spell {\n\t\n\tLogger logger = LoggerFactory.getLogger(CastRandomSpellSpell.class);\n\n\tpublic static SpellDesc create(int value) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(CastRandomSpellSpell.class);\n\t\targuments.put(SpellArg.VALUE, value);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\t// This spell is crazy.\n\t\tCardFilter filter = (CardFilter) desc.get(SpellArg.CARD_FILTER);\n\t\tCardCollection spells = CardCatalogue.query(context.getDeckFormat(), CardType.SPELL);\n\t\tCardSource cardSource = (CardSource) desc.get(SpellArg.CARD_SOURCE);\n\t\tif (cardSource != null) {\n\t\t\tspells = cardSource.getCards(context, player);\n\t\t}\n\t\tCardCollection filteredSpells = new CardCollection();\n\t\tfor (Card spell : spells) {\n\t\t\tif (filter == null || filter.matches(context, player, spell)) {\n\t\t\t\tfilteredSpells.add(spell);\n\t\t\t}\n\t\t}\n\t\t// Straight up insane.\n\t\t\n\t\t// Set behavior to random. Because we're already insane.\n\t\t// This allows Discover effects and targeting to actually be random.\n\t\tPlayer originalPlayer = player;\n\t\tPlayer opponent = context.getOpponent(player);\n\t\topponent.setAttribute(Attribute.ALL_RANDOM_YOGG_ONLY_FINAL_DESTINATION, true);\n\t\tplayer.setAttribute(Attribute.ALL_RANDOM_YOGG_ONLY_FINAL_DESTINATION, true); \n\t\t// HAHAHAHAHAHAHAHAHAHA!\n\t\t\n\t\tint numberOfSpellsToCast = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\n\t\tfor (int i = 0; i < numberOfSpellsToCast; i++) {\n\t\t\t// In case Yogg changes sides, this should case who the spells are being cast for.\n\t\t\tplayer = context.getPlayer(source.getOwner());\n\t\t\t// If Yogg is removed from the board, stop casting spells.\n\t\t\tif (!player.getSummons().contains(source)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tCard randomCard = filteredSpells.getRandom();\n\t\t\tlogger.debug(\"Yogg-Saron chooses to play \" + randomCard.getName());\n\t\t\tCardRevealedEvent revealEvent = new CardRevealedEvent(context, player.getId(), randomCard, 1.2 * (i + 1));\n\t\t\tcontext.fireGameEvent(revealEvent);\n\t\t\tif (randomCard instanceof ChooseOneCard && !context.getLogic().hasAttribute(player, Attribute.BOTH_CHOOSE_ONE_OPTIONS)) {\n\t\t\t\t// While it might seem odd to do this, Choose One spells are still chosen\n\t\t\t\t// randomly, even if the choice isn't available.\n\t\t\t\tChooseOneCard chooseOneCard = (ChooseOneCard) randomCard;\n\t\t\t\tCard[] cards = chooseOneCard.getChoiceCards();\n\t\t\t\trandomCard = cards[context.getLogic().random(cards.length)];\n\t\t\t} else if (randomCard instanceof ChooseOneCard && context.getLogic().hasAttribute(player, Attribute.BOTH_CHOOSE_ONE_OPTIONS)) {\n\t\t\t\tChooseOneCard chooseOneCard = (ChooseOneCard) randomCard;\n\t\t\t\trandomCard = chooseOneCard.getBothChoicesCard();\n\t\t\t}\n\t\t\tSpellCard spellCard = (SpellCard) randomCard;\n\t\t\tif (spellCard.canBeCast(context, player)) {\n\t\t\t\tSpellCard copyCard = spellCard.clone();\n\t\t\t\tcontext.getLogic().drawSetAsideCard(player.getId(), copyCard);\n\n\t\t\t\tBattlecryAction battlecry = BattlecryAction.createBattlecry(copyCard.getSpell(), copyCard.getTargetRequirement());\n\t\t\t\tGameAction battlecryAction = null;\n\t\t\t\tbattlecry.setSource(copyCard.getReference());\n\t\t\t\tif (battlecry.getTargetRequirement() != TargetSelection.NONE) {\n\t\t\t\t\tList<Entity> validTargets = context.getLogic().getValidTargets(player.getId(), battlecry);\n\t\t\t\t\tif (validTargets.isEmpty()) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tList<GameAction> battlecryActions = new ArrayList<>();\n\t\t\t\t\tfor (Entity validTarget : validTargets) {\n\t\t\t\t\t\tGameAction targetedBattlecry = battlecry.clone();\n\t\t\t\t\t\ttargetedBattlecry.setTarget(validTarget);\n\t\t\t\t\t\tbattlecryActions.add(targetedBattlecry);\n\t\t\t\t\t}\n\t\t\t\t\tbattlecryAction = battlecryActions.get(context.getLogic().random(battlecryActions.size()));\n\t\t\t\t} else {\n\t\t\t\t\tbattlecryAction = battlecry;\n\t\t\t\t}\n\t\t\t\tcontext.getLogic().performGameAction(player.getId(), battlecryAction);\n\t\t\t\t// If the card has Overload, then start overloading...\n\t\t\t\tif (spellCard.hasAttribute(Attribute.OVERLOAD)) {\n\t\t\t\t\tplayer.modifyAttribute(Attribute.OVERLOAD, spellCard.getAttributeValue(Attribute.OVERLOAD));\n\t\t\t\t\tcontext.fireGameEvent(new OverloadEvent(context, player.getId(), spellCard));\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Technically, this is only half correct. Yogg-Saron should not stop if\n\t\t\t// your opponent has died, but should if you do. But, it works for now.\n\t\t\tcontext.getLogic().checkForDeadEntities();\n\t\t}\n\n\t\topponent.removeAttribute(Attribute.ALL_RANDOM_YOGG_ONLY_FINAL_DESTINATION);\n\t\toriginalPlayer.removeAttribute(Attribute.ALL_RANDOM_YOGG_ONLY_FINAL_DESTINATION);\n\t\t// *ahem* Back to normal.\n\t}\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/CastRepeatedlySpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.condition.Condition;\n\npublic class CastRepeatedlySpell extends Spell {\n\n\tpublic static SpellDesc create(SpellDesc spell, int value) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(CastRandomSpellSpell.class);\n\t\targuments.put(SpellArg.SPELL, spell);\n\t\targuments.put(SpellArg.HOW_MANY, value);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\t\n\t\tint iterations = desc.getValue(SpellArg.HOW_MANY, context, player, target, source, 1);\n\t\tSpellDesc spell = (SpellDesc) desc.get(SpellArg.SPELL);\n\t\tCondition condition = (Condition) desc.get(SpellArg.CONDITION);\n\t\t\n\t\tfor (int i = 0; i < iterations; i++) {\n\t\t\tif (target == null) {\n\t\t\t\tSpellUtils.castChildSpell(context, player, spell, source, null);\n\t\t\t\tif (condition != null && condition.isFulfilled(context, player, source, null)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tList<Entity> targets = context.resolveTarget(player, source, desc.getTarget());\n\t\t\t\tif (targets.isEmpty()) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tEntity randomTarget = SpellUtils.getRandomTarget(targets);\n\t\t\t\tSpellUtils.castChildSpell(context, player, spell, source, randomTarget);\n\t\t\t\tif (condition != null && condition.isFulfilled(context, player, source, randomTarget)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ChangeHeroPowerSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.heroes.Hero;\r\nimport net.demilich.metastone.game.heroes.powers.HeroPower;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class ChangeHeroPowerSpell extends Spell {\r\n\r\n\tprivate static Logger logger = LoggerFactory.getLogger(ChangeHeroPowerSpell.class);\r\n\r\n\tprotected void changeHeroPower(GameContext context, String newHeroPower, Hero hero) {\r\n\t\tHeroPower heroPower = (HeroPower) context.getCardById(newHeroPower);\r\n\t\tlogger.debug(\"{}'s hero power was changed to {}\", hero.getName(), heroPower);\r\n\t\thero.setHeroPower(heroPower);\r\n\t}\r\n\t\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tString heroPowerName = (String) desc.get(SpellArg.CARD);\r\n\t\tchangeHeroPower(context, heroPowerName, player.getHero());\r\n\t}\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ChangeHeroSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.HeroCard;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class ChangeHeroSpell extends Spell {\r\n\r\n\tpublic static SpellDesc create(String heroCardId) {\r\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(ChangeHeroSpell.class);\r\n\t\targuments.put(SpellArg.CARD, heroCardId);\r\n\t\treturn new SpellDesc(arguments);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tString heroCardId = (String) desc.get(SpellArg.CARD);\r\n\t\tHeroCard heroCard = (HeroCard) context.getCardById(heroCardId);\r\n\t\tcontext.getLogic().changeHero(player, heroCard.createHero());\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ClearOverloadSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class ClearOverloadSpell extends Spell {\r\n\r\n\tpublic static SpellDesc create() {\r\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(ClearOverloadSpell.class);\r\n\t\treturn new SpellDesc(arguments);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tint lockedMana = player.getLockedMana();\r\n\t\tif (lockedMana > 0) {\r\n\t\t\tcontext.getLogic().modifyCurrentMana(player.getId(), lockedMana);\r\n\t\t\tplayer.setLockedMana(0);\r\n\t\t}\r\n\r\n\t\tplayer.removeAttribute(Attribute.OVERLOAD);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/CloneMinionSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.heroes.Hero;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class CloneMinionSpell extends Spell {\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tif (target instanceof Hero) {\n\t\t\ttarget = ((MinionCard) context.getPendingCard()).summon();\n\t\t}\n\t\tMinion template = (Minion) target;\n\t\tMinion clone = template.clone();\n\n\t\tcontext.getLogic().summon(player.getId(), clone);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ComboSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class ComboSpell extends ConditionalEffectSpell {\r\n\r\n\tpublic static SpellDesc create(SpellDesc either, SpellDesc or, boolean exclusive) {\r\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(ComboSpell.class);\r\n\t\targuments.put(SpellArg.SPELL_1, either);\r\n\t\targuments.put(SpellArg.SPELL_2, or);\r\n\t\targuments.put(SpellArg.EXCLUSIVE, exclusive);\r\n\t\treturn new SpellDesc(arguments);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isConditionFulfilled(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\treturn player.hasAttribute(Attribute.COMBO);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ConditionalAttackBonusSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.valueprovider.ValueProvider;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class ConditionalAttackBonusSpell extends Spell {\n\n\tpublic static SpellDesc create(EntityReference target, ValueProvider valueProvider) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(ConditionalAttackBonusSpell.class);\n\t\targuments.put(SpellArg.VALUE, valueProvider);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(ValueProvider valueProvider) {\n\t\treturn create(null, valueProvider);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint attackBonus = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\n\t\ttarget.setAttribute(Attribute.CONDITIONAL_ATTACK_BONUS, attackBonus);\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ConditionalEffectSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.condition.Condition;\n\npublic class ConditionalEffectSpell extends Spell {\n\n\tprotected boolean isConditionFulfilled(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tCondition condition = (Condition) desc.get(SpellArg.CONDITION);\n\t\treturn condition.isFulfilled(context, player, source, target);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tboolean exclusive = desc.getBool(SpellArg.EXCLUSIVE);\n\t\tSpellDesc primarySpell = (SpellDesc) desc.get(SpellArg.SPELL_1);\n\t\tSpellDesc secondarySpell = (SpellDesc) desc.get(SpellArg.SPELL_2);\n\n\t\tif (exclusive) {\n\t\t\tSpellUtils.castChildSpell(context, player, isConditionFulfilled(context, player, desc, source, target) ? secondarySpell : primarySpell,\n\t\t\t\t\tsource, target);\n\t\t} else {\n\t\t\tSpellUtils.castChildSpell(context, player, primarySpell, source, target);\n\t\t\tif (isConditionFulfilled(context, player, desc, source, target)) {\n\t\t\t\tSpellUtils.castChildSpell(context, player, secondarySpell, source, target);\n\t\t\t}\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ConditionalSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.desc.condition.Condition;\r\n\r\npublic class ConditionalSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\t// case 1 - only one condition\r\n\t\tCondition condition = (Condition) desc.get(SpellArg.CONDITION);\r\n\t\tif (condition != null) {\r\n\t\t\tSpellDesc spell = (SpellDesc) desc.get(SpellArg.SPELL);\r\n\t\t\tif (condition.isFulfilled(context, player, source, target)) {\r\n\t\t\t\tSpellUtils.castChildSpell(context, player, spell, source, target);\r\n\t\t\t}\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\t// case 2 - multiple conditions and multiple spells\r\n\t\tCondition[] conditions = (Condition[]) desc.get(SpellArg.CONDITIONS);\r\n\t\tSpellDesc[] spells = (SpellDesc[]) desc.get(SpellArg.SPELLS);\r\n\t\tfor (int i = 0; i < conditions.length; i++) {\r\n\t\t\tif (conditions[i].isFulfilled(context, player, source, target)) {\r\n\t\t\t\tSpellUtils.castChildSpell(context, player, spells[i], source, target);\t\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/CopyCardSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.CardLocation;\n\npublic class CopyCardSpell extends Spell {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(CopyCardSpell.class);\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint numberOfCardsToCopy = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\n\t\tif (target != null) {\n\t\t\tCard targetCard = null;\n\t\t\tif (target.getEntityType() == EntityType.CARD) {\n\t\t\t\ttargetCard = (Card) target;\n\t\t\t} else if (target.getEntityType() == EntityType.MINION) {\n\t\t\t\tMinion minion = (Minion) target;\n\t\t\t\ttargetCard = minion.getSourceCard();\n\t\t\t}\n\t\t\tfor (int i = 0; i < numberOfCardsToCopy; i++) {\n\t\t\t\tcontext.getLogic().receiveCard(player.getId(), targetCard.getCopy());\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tCardLocation cardLocation = (CardLocation) desc.get(SpellArg.CARD_LOCATION);\n\n\t\tPlayer opponent = context.getOpponent(player);\n\t\tCardCollection sourceCollection = null;\n\t\tswitch (cardLocation) {\n\t\tcase DECK:\n\t\t\tsourceCollection = opponent.getDeck();\n\t\t\tbreak;\n\t\tcase HAND:\n\t\t\tsourceCollection = opponent.getHand();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tlogger.error(\"Trying to copy cards from invalid cardLocation {}\", cardLocation);\n\t\t\tbreak;\n\t\t}\n\n\t\tfor (int i = 0; i < numberOfCardsToCopy; i++) {\n\t\t\tif (sourceCollection.isEmpty()) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tCard clone = sourceCollection.getRandom().getCopy();\n\t\t\tcontext.getLogic().receiveCard(player.getId(), clone);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/CopyDeathrattleSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class CopyDeathrattleSpell extends Spell {\n\n\tpublic static SpellDesc create(EntityReference target) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(CopyDeathrattleSpell.class);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tActor copyTo = (Actor) source;\n\t\tActor copyFrom = (Actor) target;\n\t\tif (copyFrom.getDeathrattles() == null) {\n\t\t\treturn;\n\t\t}\n\t\tfor (SpellDesc deathrattle : copyFrom.getDeathrattles()) {\n\t\t  copyTo.addDeathrattle(deathrattle);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/CopyHeroPower.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class CopyHeroPower extends ChangeHeroPowerSpell {\r\n\t\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tPlayer opponent = context.getOpponent(player);\r\n\t\tString opponentHeroPowerId = opponent.getHero().getHeroPower().getCardId();\r\n\t\tchangeHeroPower(context, opponentHeroPowerId, player.getHero());\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/CreateCardSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardDescType;\nimport net.demilich.metastone.game.cards.CardSet;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.cards.Rarity;\nimport net.demilich.metastone.game.cards.desc.SpellCardDesc;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class CreateCardSpell extends Spell {\n\t\n\tLogger logger = LoggerFactory.getLogger(CreateCardSpell.class);\n\n\tprivate SpellDesc[] discoverCardParts(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tList<SpellDesc> spells = new ArrayList<SpellDesc>();\n\t\tSpellDesc[] spellArray = (SpellDesc[]) desc.get(SpellArg.SPELLS);\n\t\tfor (SpellDesc spell : spellArray) {\n\t\t\tspells.add(spell);\n\t\t}\n\t\t\n\t\tMap<SpellDesc, Integer> spellOrder = new HashMap<SpellDesc, Integer>();\n\t\tfor (int i = 0; i < spells.size(); i++)\n\t\t{\n\t\t    SpellDesc spell = spells.get(i);\n\t\t    spellOrder.put(spell, i);\n\t\t}\n\t\t\n\t\tint count = desc.getValue(SpellArg.HOW_MANY, context, player, target, source, 3);\n\t\tint value = desc.getValue(SpellArg.SECONDARY_VALUE, context, player, target, source, 2);\n\t\tboolean exclusive = desc.getBool(SpellArg.EXCLUSIVE);\n\t\tList<Integer> chosenSpellInts = new ArrayList<Integer>();\n\t\tfor (int i = 0; i < value; i++) {\n\t\t\tList<SpellDesc> spellChoices = new ArrayList<SpellDesc>();\n\t\t\tList<SpellDesc> spellsCopy = new ArrayList<SpellDesc>(spells);\n\t\t\tfor (int j = 0; j < count; j++) {\n\t\t\t\tif (!spellsCopy.isEmpty()) {\n\t\t\t\t\tSpellDesc spell = spellsCopy.get(context.getLogic().random(spellsCopy.size()));\n\t\t\t\t\tspellChoices.add(spell);\n\t\t\t\t\tspellsCopy.remove(spell);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!spellChoices.isEmpty()) {\n\t\t\t\tSpellDesc chosenSpell = SpellUtils.getSpellDiscover(context, player, desc, spellChoices).getSpell();\n\t\t\t\tchosenSpellInts.add(spellOrder.get(chosenSpell));\n\t\t\t\tif (exclusive) {\n\t\t\t\t\tspellChoices.remove(chosenSpell);\n\t\t\t\t\tspells.remove(chosenSpell);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tCollections.sort(chosenSpellInts);\n\t\tSpellDesc[] chosenSpells = new SpellDesc[chosenSpellInts.size()];\n\t\tfor (int i = 0; i < chosenSpellInts.size(); i++) {\n\t\t\tchosenSpells[i] = spellArray[chosenSpellInts.get(i)];\n\t\t}\n\t\treturn chosenSpells;\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tHeroClass heroClass = HeroClass.ANY;\n\t\tRarity rarity = Rarity.FREE;\n\t\tCardSet cardSet = CardSet.BASIC;\n\t\tSpellDesc[] spells = discoverCardParts(context, player, desc, source, target);\n\t\tswitch (source.getEntityType()) {\n\t\tcase ANY:\n\t\t\tbreak;\n\t\tcase CARD:\n\t\t\tbreak;\n\t\tcase HERO:\n\t\t\tbreak;\n\t\tcase MINION:\n\t\t\tMinion sourceMinion = (Minion) source;\n\t\t\theroClass = sourceMinion.getSourceCard().getHeroClass();\n\t\t\trarity = Rarity.FREE;\n\t\t\tcardSet = sourceMinion.getSourceCard().getCardSet();\n\t\t\tbreak;\n\t\tcase PLAYER:\n\t\t\tbreak;\n\t\tcase WEAPON:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t\tCard newCard = null;\n\t\tswitch ((CardType) desc.get(SpellArg.CARD_TYPE)) {\n\t\tcase SPELL:\n\t\t\tList<SpellDesc> spellList = new ArrayList<SpellDesc>();\n\t\t\tString description = \"\";\n\t\t\tTargetSelection targetSelection = TargetSelection.NONE;\n\t\t\tfor (SpellDesc spell : spells) {\n\t\t\t\tCardDescType cardDescType = (CardDescType) spell.get(SpellArg.CARD_DESC_TYPE);\n\t\t\t\tif (cardDescType == CardDescType.SPELL) {\n\t\t\t\t\tdescription += spell.getString(SpellArg.DESCRIPTION) + \" \";\n\t\t\t\t\tspellList.add(spell);\n\t\t\t\t\tTargetSelection checkTS = (TargetSelection) spell.get(SpellArg.TARGET_SELECTION);\n\t\t\t\t\tif (checkTS != null && checkTS.compareTo(targetSelection) > 0) {\n\t\t\t\t\t\ttargetSelection = checkTS;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tSpellDesc[] spellArray = new SpellDesc[spellList.size()];\n\t\t\tspellList.toArray(spellArray);\n\t\t\tSpellDesc spell = MetaSpell.create(target != null ? target.getReference() : null, false, spellArray);\n\t\t\tSpellCardDesc spellCardDesc = new SpellCardDesc();\n\t\t\tspellCardDesc.id = context.getLogic().generateCardID();\n\t\t\tspellCardDesc.name = desc.getString(SpellArg.SECONDARY_NAME);\n\t\t\tspellCardDesc.heroClass = heroClass;\n\t\t\tspellCardDesc.type = CardType.SPELL;\n\t\t\tspellCardDesc.rarity = rarity;\n\t\t\tspellCardDesc.description = description;\n\t\t\tspellCardDesc.targetSelection = targetSelection;\n\t\t\tspellCardDesc.spell = spell;\n\t\t\t//spellCardDesc.attributes.put(key, value);\n\t\t\tspellCardDesc.set = cardSet;\n\t\t\tspellCardDesc.collectible = false;\n\t\t\tspellCardDesc.baseManaCost = desc.getValue(SpellArg.MANA, context, player, target, source, 0);\n\t\t\tnewCard = spellCardDesc.createInstance();\n\t\t\tbreak;\n\t\tcase CHOOSE_ONE:\n\t\tcase HERO_POWER:\n\t\tcase MINION:\n\t\tcase WEAPON:\n\t\tdefault:\n\t\t\treturn;\n\t\t}\n\t\tif (newCard != null) {\n\t\t\tcontext.addTempCard(newCard);\n\t\t\tcontext.getLogic().receiveCard(player.getId(), newCard.clone());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/CreateSummonSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.CardSet;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.cards.Rarity;\nimport net.demilich.metastone.game.cards.desc.MinionCardDesc;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class CreateSummonSpell extends Spell {\n\t\n\tLogger logger = LoggerFactory.getLogger(CreateSummonSpell.class);\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tString description = \"\";\n\t\tMinionCardDesc minionCardDesc = new MinionCardDesc();\n\t\tminionCardDesc.id = context.getLogic().generateCardID();\n\t\tminionCardDesc.name = desc.getString(SpellArg.NAME);\n\t\tminionCardDesc.baseAttack = desc.getValue(SpellArg.ATTACK_BONUS, context, player, target, source, 0);\n\t\tminionCardDesc.baseHp = desc.getValue(SpellArg.HP_BONUS, context, player, target, source, 0);\n\t\tminionCardDesc.heroClass = HeroClass.ANY;\n\t\tminionCardDesc.type = CardType.MINION;\n\t\tminionCardDesc.rarity = Rarity.FREE;\n\t\tminionCardDesc.description = description;\n\t\tAttribute attribute = (Attribute) desc.get(SpellArg.ATTRIBUTE);\n\t\tif (attribute != null) {\n\t\t\tminionCardDesc.attributes.put(attribute, true);\n\t\t}\n\t\tminionCardDesc.set = CardSet.BASIC;\n\t\tminionCardDesc.collectible = false;\n\t\tminionCardDesc.baseManaCost = desc.getValue(SpellArg.MANA, context, player, target, source, 0);\n\t\tMinionCard newCard = (MinionCard) minionCardDesc.createInstance();\n\t\tcontext.addTempCard(newCard);\n\t\t\n\t\tint boardPosition = SpellUtils.getBoardPosition(context, player, desc, source);\n\t\tint count = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\n\t\tSpellDesc spell = (SpellDesc) desc.get(SpellArg.SPELL);\n\t\tSpellDesc successfulSummonSpell = (SpellDesc) desc.get(SpellArg.SPELL_1);\n\t\tfor (int i = 0; i < count; i++) {\n\t\t\tMinionCard minionCard = (MinionCard) newCard.clone();\n\t\t\tMinion minion = minionCard.summon();\n\t\t\tif (context.getLogic().summon(player.getId(), minion, null, boardPosition, false) && successfulSummonSpell != null) {\n\t\t\t\tSpellUtils.castChildSpell(context, player, successfulSummonSpell, source, minion);\n\t\t\t}\n\t\t\tSpellUtils.castChildSpell(context, player, spell, source, target);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DamageSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\nimport java.util.function.Predicate;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.valueprovider.ValueProvider;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class DamageSpell extends Spell {\n\n\tpublic static SpellDesc create(EntityReference target, int damage) {\n\t\treturn create(target, damage, false);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, int damage, boolean randomTarget) {\n\t\treturn create(target, damage, null, randomTarget);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, int damage, Predicate<Entity> targetFilter, boolean randomTarget) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(DamageSpell.class);\n\t\targuments.put(SpellArg.VALUE, damage);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\targuments.put(SpellArg.RANDOM_TARGET, randomTarget);\n\t\tif (targetFilter != null) {\n\t\t\targuments.put(SpellArg.FILTER, targetFilter);\n\t\t}\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, ValueProvider damageModfier) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(DamageSpell.class);\n\t\targuments.put(SpellArg.VALUE, damageModfier);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(int damage) {\n\t\treturn create(null, damage);\n\t}\n\n\tpublic static SpellDesc create(ValueProvider damageModfier) {\n\t\treturn create(null, damageModfier);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint damage = 0;\n\t\t// TODO Rewrite to more accurate way to grab Damage Stack damage.\n\t\tif (!desc.contains(SpellArg.VALUE) && !context.getDamageStack().isEmpty()) {\n\t\t\tdamage = context.getDamageStack().peek();\n\t\t} else {\n\t\t\tdamage = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\n\t\t}\n\t\t\n\t\tboolean ignoreSpellDamage = desc.getBool(SpellArg.IGNORE_SPELL_DAMAGE);\n\t\tcontext.getLogic().damage(player, (Actor) target, damage, source, ignoreSpellDamage);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DestroyAllExceptOneSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\n\npublic class DestroyAllExceptOneSpell extends DestroySpell {\n\t\n\tpublic static Logger logger = LoggerFactory.getLogger(DestroyAllExceptOneSpell.class);\n\n\t@Override\n\tpublic void cast(GameContext context, Player player, SpellDesc desc, Entity source, List<Entity> targets) {\n\t\tif (targets == null || targets.isEmpty()) {\n\t\t\treturn;\n\t\t}\n\t\tEntityFilter filter = desc.getEntityFilter();\n\t\tList<Entity> destroyedTargets = new ArrayList<Entity>(targets);\n\t\tList<Entity> potentialSurvivors = SpellUtils.getValidTargets(context, player, destroyedTargets, filter);\n\t\tif (!potentialSurvivors.isEmpty()) {\n\t\t\tEntity randomTarget = SpellUtils.getRandomTarget(potentialSurvivors);\n\t\t\tdestroyedTargets.remove(randomTarget);\t\n\t\t}\n\t\t\n\t\tfor (Entity entity : destroyedTargets) {\n\t\t\tonCast(context, player, null, null, entity);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DestroySecretsSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class DestroySecretsSpell extends Spell {\n\n\tpublic static SpellDesc create(TargetPlayer targetPlayer) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(DestroySecretsSpell.class);\n\t\targuments.put(SpellArg.TARGET_PLAYER, targetPlayer);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tcontext.getLogic().removeSecrets(player);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DestroySpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\nimport java.util.function.Predicate;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class DestroySpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\treturn create(null);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target) {\n\t\treturn create(target, false);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, boolean randomTarget) {\n\t\treturn create(target, null, randomTarget);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, Predicate<Entity> targetFilter, boolean randomTarget) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(DestroySpell.class);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\targuments.put(SpellArg.RANDOM_TARGET, randomTarget);\n\t\tif (targetFilter != null) {\n\t\t\targuments.put(SpellArg.FILTER, targetFilter);\n\t\t}\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tcontext.getLogic().markAsDestroyed((Actor) target);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DiscardCardsFromDeckSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class DiscardCardsFromDeckSpell extends Spell {\n\n\tpublic static SpellDesc create(int howMany) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(DiscardCardsFromDeckSpell.class);\n\t\targuments.put(SpellArg.VALUE, howMany);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint howMany = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\n\t\tfor (int i = 0; i < howMany; i++) {\n\t\t\t// Question: If I have no cards left and my Fel Reaver discards 3,\n\t\t\t// do I draw 3 Fatigues or do I only Fatigue more when I draw a\n\t\t\t// card?\n\t\t\t// Answer: Fel Reaver won't trigger fatigue\n\t\t\t// Source: Blue post\n\t\t\tif (player.getDeck().isEmpty()) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tCard card = player.getDeck().getRandom();\n\t\t\tcontext.getLogic().removeCardFromDeck(player.getId(), card);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DiscardSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class DiscardSpell extends Spell {\n\n\tpublic static final int ALL_CARDS = -1;\n\n\tpublic static SpellDesc create() {\n\t\treturn create(1);\n\t}\n\n\tpublic static SpellDesc create(int numberOfCards) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(DiscardSpell.class);\n\t\targuments.put(SpellArg.VALUE, numberOfCards);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\n\t\tint numberOfCards = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\n\n\t\tCardCollection discardableCards = new CardCollection();\n\t\tfor (Card card : player.getHand()) {\n\t\t\tif (cardFilter == null || cardFilter.matches(context, player, card)) {\n\t\t\t\tdiscardableCards.add(card);\n\t\t\t}\n\t\t}\n\t\t\n\t\tint cardCount = numberOfCards == ALL_CARDS ? discardableCards.getCount() : numberOfCards;\n\n\t\tfor (int i = 0; i < cardCount; i++) {\n\t\t\tCard randomHandCard = discardableCards.getRandom();\n\t\t\tif (randomHandCard == null) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcontext.getLogic().discardCard(player, randomHandCard);\n\t\t\tdiscardableCards.remove(randomHandCard);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DiscoverCardSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class DiscoverCardSpell extends Spell {\n\t\n\tpublic static SpellDesc create(EntityReference target, SpellDesc spell) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(DiscoverCardSpell.class);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\targuments.put(SpellArg.SPELL, spell);  \n\t\treturn new SpellDesc(arguments);\n\t}\n\t\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tCardCollection result = new CardCollection();\n\t\tboolean cannotReceiveOwned = desc.getBool(SpellArg.CANNOT_RECEIVE_OWNED);\n\t\tfor (Card card : SpellUtils.getCards(context, desc)) {\n\t\t\tif (!cannotReceiveOwned || !context.getLogic().hasCard(player, card)) {\n\t\t\t\tresult.add(card);\n\t\t\t}\n\t\t}\n\t\t\n\t\t\n\t\tCardCollection cards = new CardCollection();\n\t\t\n\t\tint count = desc.getValue(SpellArg.HOW_MANY, context, player, target, source, 3);\n\t\tfor (int i = 0; i < count; i++) {\n\t\t\tif (!result.isEmpty()) {\n\t\t\t\tCard card = result.getRandom();\n\t\t\t\tcards.add(card);\n\t\t\t\tresult.remove(card);\n\t\t\t}\n\t\t}\n\t\t\n\t\tif (!cards.isEmpty()) {\n\t\t\tSpellUtils.castChildSpell(context, player, SpellUtils.getDiscover(context, player, desc, cards).getSpell(), source, target);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DiscoverDrawSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class DiscoverDrawSpell extends Spell {\n\t\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tCardCollection cards = new CardCollection();\n\t\t\n\t\tif (player.getDeck().isEmpty()) {\n\t\t  return;\n\t\t}\n\t\t\n\t\tint count = desc.getValue(SpellArg.HOW_MANY, context, player, target, source, 3);\n\t\tfor (int i = 0; i < count; i++) {\n\t\t\tif (!player.getDeck().isEmpty()) {\n\t\t\t\tCard card = player.getDeck().peekFirst();\n\t\t\t\tcontext.getLogic().removeCardFromDeck(player.getId(), card);\n\t\t\t\tcards.add(card);\n\t\t\t}\n\t\t}\n\t\t\n\t\tif (!cards.isEmpty()) {\n\t\t\tSpellUtils.castChildSpell(context, player, SpellUtils.getDiscover(context, player, desc, cards).getSpell(), source, target);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DiscoverFilteredCardSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.spells.desc.source.CardSource;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class DiscoverFilteredCardSpell extends Spell {\n\t\n\tpublic static SpellDesc create(EntityReference target, SpellDesc spell) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(DiscoverFilteredCardSpell.class);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\targuments.put(SpellArg.SPELL, spell);\n\t\treturn new SpellDesc(arguments);\n\t}\n\t\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\n\t\tEntityFilter[] cardFilters = (EntityFilter[]) desc.get(SpellArg.CARD_FILTERS);\n\t\tCardCollection cards = CardCatalogue.query(context.getDeckFormat());\n\t\tCardSource cardSource = (CardSource) desc.get(SpellArg.CARD_SOURCE);\n\t\tif (cardSource != null) {\n\t\t\tcards = cardSource.getCards(context, player);\n\t\t}\n\t\tint count = desc.getValue(SpellArg.HOW_MANY, context, player, target, source, 3);\n\t\tCardCollection discoverCards = new CardCollection();\n\n\t\tif (cardFilters != null) {\n\t\t\tfor (EntityFilter filter : cardFilters) {\n\t\t\t\tCardCollection result = new CardCollection();\n\t\t\t\tfor (Card card : cards) {\n\t\t\t\t\tif (filter == null || filter.matches(context, player, card)) {\n\t\t\t\t\t\tresult.add(card);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tif (!result.isEmpty()) {\n\t\t\t\t\tdiscoverCards.add(result.getRandom());\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tCardCollection result = new CardCollection();\n\t\t\tfor (Card card : cards) {\n\t\t\t\tif (cardFilter == null || cardFilter.matches(context, player, card)) {\n\t\t\t\t\tresult.add(card);\n\t\t\t\t}\n\t\t\t}\n\t\t\tdiscoverCards = new CardCollection();\n\t\t\t\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tif (!result.isEmpty()) {\n\t\t\t\t\tCard card = null;\n\t\t\t\t\tdo {\n\t\t\t\t\t\tcard = result.getRandom();\n\t\t\t\t\t\tresult.remove(card);\n\t\t\t\t\t} while (discoverCards.containsCard(card));\n\t\t\t\t\tif (card != null) {\n\t\t\t\t\t\tdiscoverCards.add(card);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t}\n\t\t\n\t\tif (!discoverCards.isEmpty()) {\n\t\t\tSpellUtils.castChildSpell(context, player, SpellUtils.getDiscover(context, player, desc, discoverCards).getSpell(), source, target);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DiscoverOptionSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class DiscoverOptionSpell extends Spell {\n\t\n\tLogger logger = LoggerFactory.getLogger(DiscoverOptionSpell.class);\n\t\n\tpublic static SpellDesc create(EntityReference target, SpellDesc spell) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(DiscoverOptionSpell.class);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\targuments.put(SpellArg.SPELL, spell);\n\t\treturn new SpellDesc(arguments);\n\t}\n\t\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tList<SpellDesc> spells = new ArrayList<SpellDesc>();\n\t\tSpellDesc[] spellArray = (SpellDesc[]) desc.get(SpellArg.SPELLS);\n\t\tfor (SpellDesc spell : spellArray) {\n\t\t\tspells.add(spell);\n\t\t}\n\t\t\n\t\tMap<SpellDesc, Integer> spellOrder = new HashMap<SpellDesc, Integer>();\n\t\tfor (int i = 0; i < spells.size(); i++)\n\t\t{\n\t\t    SpellDesc spell = spells.get(i);\n\t\t    spellOrder.put(spell, i);\n\t\t}\n\t\t\n\t\tint count = desc.getValue(SpellArg.HOW_MANY, context, player, target, source, 3);\n\t\tint value = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\n\t\tboolean exclusive = desc.getBool(SpellArg.EXCLUSIVE);\n\t\tList<Integer> chosenSpellInts = new ArrayList<Integer>();\n\t\tList<SpellDesc> spellsCopy = new ArrayList<SpellDesc>(spells);\n\t\tfor (int i = 0; i < value; i++) {\n\t\t\tList<SpellDesc> spellChoices = new ArrayList<SpellDesc>();\n\t\t\tfor (int j = 0; j < count; j++) {\n\t\t\t\tif (!spellsCopy.isEmpty()) {\n\t\t\t\t\tSpellDesc spell = spellsCopy.get(context.getLogic().random(spellsCopy.size()));\n\t\t\t\t\tspellChoices.add(spell);\n\t\t\t\t\tspellsCopy.remove(spell);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!spellChoices.isEmpty()) {\n\t\t\t\tSpellDesc chosenSpell = SpellUtils.getSpellDiscover(context, player, desc, spellChoices).getSpell();\n\t\t\t\tchosenSpellInts.add(spellOrder.get(chosenSpell));\n\t\t\t\tif (exclusive) {\n\t\t\t\t\tspellChoices.remove(chosenSpell);\n\t\t\t\t\tspells.remove(chosenSpell);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tCollections.sort(chosenSpellInts);\n\t\tSpellDesc[] chosenSpells = new SpellDesc[chosenSpellInts.size()];\n\t\tfor (int i = 0; i < chosenSpellInts.size(); i++) {\n\t\t\tchosenSpells[i] = spellArray[chosenSpellInts.get(i)];\n\t\t}\n\t\tif (chosenSpellInts.size() > 0) {\n\t\t\tSpellDesc metaSpell = MetaSpell.create(target != null ? target.getReference() : null, false, chosenSpells);\n\t\t\tSpellUtils.castChildSpell(context, player, metaSpell, source, target);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DiscoverRandomCardSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.spells.desc.filter.FilterArg;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class DiscoverRandomCardSpell extends Spell {\n\t\n\tpublic static SpellDesc create(EntityReference target, SpellDesc spell) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(DiscoverRandomCardSpell.class);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\targuments.put(SpellArg.SPELL, spell);\n\t\treturn new SpellDesc(arguments);\n\t}\n\t\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\n\t\tHeroClass heroClass = (HeroClass) cardFilter.getArg(FilterArg.HERO_CLASS);\n\t\tif (heroClass == null) {\n\t\t\theroClass = player.getHero().getHeroClass();\n\t\t}\n\t\tif (heroClass == HeroClass.OPPONENT) {\n\t\t\theroClass = context.getOpponent(player).getHero().getHeroClass();\n\t\t}\n\t\tif (heroClass != HeroClass.ANY && !heroClass.isBaseClass()) {\n\t\t\tCard card = context.getPendingCard();\n\t\t\theroClass = card.getHeroClass();\n\t\t\tif (heroClass == HeroClass.ANY) {\n\t\t\t\theroClass = SpellUtils.getRandomHeroClass();\n\t\t\t}\n\t\t}\n\t\tCardCollection cards = new CardCollection();\n\t\tif (heroClass == HeroClass.ANY) {\n\t\t\tCardCollection classCards = CardCatalogue.query(context.getDeckFormat());\n\t\t\tcards.addAll(classCards);\n\t\t} else {\n\t\t\tcards.addAll(CardCatalogue.query(context.getDeckFormat(), HeroClass.ANY));\n\t\t\tCardCollection classCards = CardCatalogue.query(context.getDeckFormat(), heroClass);\n\t\t\tfor (int i = 0; i < 4; i++) {\n\t\t\t\tcards.addAll(classCards);\n\t\t\t}\n\t\t}\n\t\t\n\t\tCardCollection result = new CardCollection();\n\t\tfor (Card card : cards) {\n\t\t\tif (cardFilter.matches(context, player, card)) {\n\t\t\t\tresult.add(card);\n\t\t\t}\n\t\t}\n\t\tcards = new CardCollection();\n\t\t\n\t\tint count = desc.getValue(SpellArg.HOW_MANY, context, player, target, source, 3);\n\t\tfor (int i = 0; i < count; i++) {\n\t\t\tif (!result.isEmpty()) {\n\t\t\t\tCard card = null;\n\t\t\t\tdo {\n\t\t\t\t\tcard = result.getRandom();\n\t\t\t\t\tresult.remove(card);\n\t\t\t\t} while (cards.containsCard(card));\n\t\t\t\tif (card != null) {\n\t\t\t\t\tcards.add(card);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\tif (!cards.isEmpty()) {\n\t\t\tSpellUtils.castChildSpell(context, player, SpellUtils.getDiscover(context, player, desc, cards).getSpell(), source, target);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DoubleAttackSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class DoubleAttackSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\treturn create(null);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(DoubleAttackSpell.class);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tActor targetActor = (Actor) target;\n\t\ttargetActor.modifyAttribute(Attribute.ATTACK_BONUS, targetActor.getAttributeValue(Attribute.ATTACK) + targetActor.getAttributeValue(Attribute.ATTACK_BONUS));\n\t\ttargetActor.modifyAttribute(Attribute.TEMPORARY_ATTACK_BONUS, targetActor.getAttributeValue(Attribute.TEMPORARY_ATTACK_BONUS));\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DrawCardAndDoSomethingSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.CardLocation;\n\npublic class DrawCardAndDoSomethingSpell extends Spell {\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint amount = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\n\t\tfor (int i = 0; i < amount; i++) {\n\t\t\tCard card = context.getLogic().drawCard(player.getId(), source);\n\t\t\t// card may be null (i.e. try to draw from deck, but already in\n\t\t\t// fatigue)\n\t\t\tif (card == null || card.getLocation() == CardLocation.GRAVEYARD) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tSpellDesc cardEffectSpell = (SpellDesc) desc.get(SpellArg.SPELL);\n\t\t\tcontext.setEventCard(card);\n\t\t\tSpellUtils.castChildSpell(context, player, cardEffectSpell, source, card);\n\t\t\tcontext.setEventCard(null);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DrawCardSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class DrawCardSpell extends Spell {\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint cardCount = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\n\t\tfor (int i = 0; i < cardCount; i++) {\n\t\t\tcontext.getLogic().drawCard(player.getId(), source);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/DrawCardUntilConditionSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.condition.Condition;\n\npublic class DrawCardUntilConditionSpell extends Spell {\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint cardCount = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\n\t\tCondition condition = (Condition) desc.get(SpellArg.CONDITION);\n\t\tfor (int i = 0; i < cardCount; i++) {\n\t\t\tCard card = context.getLogic().drawCard(player.getId(), source);\n\t\t\tif (card == null || (condition != null && condition.isFulfilled(context, player, source, card))) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/EitherOrSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.ISpellConditionChecker;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.condition.Condition;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class EitherOrSpell extends Spell {\n\n\tpublic static SpellDesc create(EntityReference target, SpellDesc either, SpellDesc or, ISpellConditionChecker condition) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(EitherOrSpell.class);\n\t\targuments.put(SpellArg.SPELL_1, either);\n\t\targuments.put(SpellArg.SPELL_2, or);\n\t\targuments.put(SpellArg.CONDITION, condition);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(SpellDesc either, SpellDesc or, ISpellConditionChecker condition) {\n\t\treturn create(null, either, or, condition);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tCondition condition = (Condition) desc.get(SpellArg.CONDITION);\n\t\tSpellDesc either = (SpellDesc) desc.get(SpellArg.SPELL_1);\n\t\tSpellDesc or = (SpellDesc) desc.get(SpellArg.SPELL_2);\n\n\t\tSpellDesc spellToCast = condition.isFulfilled(context, player, source, target) ? either : or;\n\t\tSpellUtils.castChildSpell(context, player, spellToCast, source, target);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/EnrageSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class EnrageSpell extends Spell {\r\n\r\n\tpublic static SpellDesc create(int attackBonus) {\r\n\t\treturn create(attackBonus, null);\r\n\t}\r\n\r\n\tpublic static SpellDesc create(int attackBonus, Attribute tag) {\r\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(EnrageSpell.class);\r\n\t\targuments.put(SpellArg.ATTACK_BONUS, attackBonus);\r\n\t\targuments.put(SpellArg.TARGET, EntityReference.SELF);\r\n\t\targuments.put(SpellArg.ATTRIBUTE, tag);\r\n\t\treturn new SpellDesc(arguments);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tint attackBonus = desc.getValue(SpellArg.ATTACK_BONUS, context, player, target, source, 0);\r\n\t\tboolean enraged = target.hasAttribute(Attribute.ENRAGED);\r\n\t\ttarget.setAttribute(Attribute.CONDITIONAL_ATTACK_BONUS, enraged ? attackBonus : 0);\r\n\t\tAttribute tag = (Attribute) desc.get(SpellArg.ATTRIBUTE);\r\n\t\tif (tag != null) {\r\n\t\t\tif (enraged) {\r\n\t\t\t\tcontext.getLogic().applyAttribute(target, tag);\r\n\t\t\t} else {\r\n\t\t\t\tcontext.getLogic().removeAttribute(target, tag);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/EquipRandomWeaponSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.cards.WeaponCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.weapons.Weapon;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class EquipRandomWeaponSpell extends Spell {\n\n\tpublic static SpellDesc create(TargetPlayer targetPlayer) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(EquipRandomWeaponSpell.class);\n\t\targuments.put(SpellArg.TARGET_PLAYER, targetPlayer);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tCardCollection allWeapons = CardCatalogue.query(context.getDeckFormat(), CardType.WEAPON);\n\t\tWeaponCard weaponCard = (WeaponCard) allWeapons.getRandom();\n\t\tWeapon weapon = weaponCard.getWeapon();\n\t\tweapon.setBattlecry(null);\n\n\t\tcontext.getLogic().equipWeapon(player.getId(), weapon);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/EquipWeaponSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.WeaponCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.weapons.Weapon;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class EquipWeaponSpell extends Spell {\n\n\tpublic static SpellDesc create(String weaponCardName) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(EquipWeaponSpell.class);\n\t\targuments.put(SpellArg.CARD, weaponCardName);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\t// if (targetPlayer != null) {\n\t\t// arguments.put(SpellArg.TARGET_PLAYER, targetPlayer);\n\t\t// }\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(TargetPlayer targetPlayer, WeaponCard weaponCard) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(EquipWeaponSpell.class);\n\t\targuments.put(SpellArg.CARD, weaponCard);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\tif (targetPlayer != null) {\n\t\t\targuments.put(SpellArg.TARGET_PLAYER, targetPlayer);\n\t\t}\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(WeaponCard weaponCard) {\n\t\treturn create(TargetPlayer.SELF, weaponCard);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tString weaponCardName = (String) desc.get(SpellArg.CARD);\n\t\tWeaponCard weaponCard = (WeaponCard) context.getCardById(weaponCardName);\n\t\tWeapon weapon = weaponCard.getWeapon();\n\t\tcontext.getLogic().equipWeapon(player.getId(), weapon);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ForceDeathPhaseSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class ForceDeathPhaseSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(ForceDeathPhaseSpell.class);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tcontext.getLogic().checkForDeadEntities();\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/FromDeckToHandSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.CardCollection;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.EntityType;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\n\r\npublic class FromDeckToHandSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tif (target != null && target.getEntityType() == EntityType.CARD) {\r\n\t\t\tCard card = (Card) target;\r\n\t\t\tif (player.getDeck().containsCard(card)) {\r\n\t\t\t\tcontext.getLogic().removeCardFromDeck(player.getId(), card);\r\n\t\t\t\tcontext.getLogic().receiveCard(player.getId(), card, source);\r\n\t\t\t}\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tint value = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\r\n\t\tString replacementCard = (String) desc.get(SpellArg.CARD);\r\n\r\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\r\n\t\tCardCollection relevantCards = null;\r\n\t\tif (cardFilter != null) {\r\n\t\t\trelevantCards = SpellUtils.getCards(player.getDeck(), card -> cardFilter.matches(context, player, card));\r\n\t\t} else {\r\n\t\t\trelevantCards = SpellUtils.getCards(player.getDeck(), null);\r\n\t\t}\r\n\t\tfor (int i = 0; i < value; i++) {\r\n\t\t\tCard card = null;\r\n\t\t\tif (!relevantCards.isEmpty()) {\r\n\t\t\t\tcard = relevantCards.getRandom();\r\n\t\t\t\trelevantCards.remove(card);\r\n\t\t\t\tcontext.getLogic().removeCardFromDeck(player.getId(), card);\r\n\t\t\t} else if (replacementCard != null) {\r\n\t\t\t\tcard = context.getCardById(replacementCard);\r\n\t\t\t}\r\n\r\n\t\t\tif (card != null) {\r\n\t\t\t\tcontext.getLogic().receiveCard(player.getId(), card);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/FumbleSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport net.demilich.metastone.game.Environment;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class FumbleSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tActor attacker = (Actor) context.resolveSingleTarget((EntityReference) context.getEnvironment().get(Environment.ATTACKER_REFERENCE));\r\n\t\tActor randomTarget = context.getLogic().getAnotherRandomTarget(context.getActivePlayer(), attacker, (Actor) target,\r\n\t\t\t\tEntityReference.ENEMY_CHARACTERS);\r\n\t\tif (randomTarget != target) {\r\n\t\t\tcontext.getEnvironment().put(Environment.TARGET_OVERRIDE, randomTarget.getReference());\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/GainManaSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class GainManaSpell extends Spell {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(GainManaSpell.class);\n\n\tpublic static SpellDesc create(int mana) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(GainManaSpell.class);\n\t\targuments.put(SpellArg.VALUE, mana);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint mana = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\n\t\tlogger.debug(\"{} gains {} mana\", player.getName(), mana);\n\t\tcontext.getLogic().modifyCurrentMana(player.getId(), mana);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/HealSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\nimport java.util.function.Predicate;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class HealSpell extends Spell {\n\n\tpublic static SpellDesc create(EntityReference target, int healing) {\n\t\treturn create(target, healing, false);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, int healing, boolean randomTarget) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(HealSpell.class);\n\t\targuments.put(SpellArg.VALUE, healing);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\tif (randomTarget) {\n\t\t\targuments.put(SpellArg.RANDOM_TARGET, true);\n\t\t\targuments.put(SpellArg.FILTER, new Predicate<Entity>() {\n\n\t\t\t\t@Override\n\t\t\t\tpublic boolean test(Entity entity) {\n\t\t\t\t\treturn ((Actor) entity).isWounded();\n\t\t\t\t}\n\n\t\t\t});\n\n\t\t}\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(int healing) {\n\t\treturn create(null, healing);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint healing = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\n\t\tcontext.getLogic().heal(player, (Actor) target, healing, source);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ICardPostProcessor.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\n\npublic interface ICardPostProcessor {\n\n\tvoid processCard(GameContext context, Player player, Card card);\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ICardProvider.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\n\npublic interface ICardProvider {\n\n\tCard getCard(GameContext context, Player player);\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/JoustSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.events.JoustEvent;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class JoustSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tJoustEvent joustEvent = context.getLogic().joust(player);\r\n\t\tif (!joustEvent.isWon()) {\r\n\t\t\tSpellDesc spell1 = (SpellDesc) desc.get(SpellArg.SPELL_1);\r\n\t\t\tif (spell1 != null) {\r\n\t\t\t\tSpellUtils.castChildSpell(context, player, spell1, source, target);\t\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tSpellDesc spell2 = (SpellDesc) desc.get(SpellArg.SPELL_2);\r\n\t\tif (spell2 != null) {\r\n\t\t\tSpellUtils.castChildSpell(context, player, spell2, source, target);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tSpellDesc spell = (SpellDesc) desc.get(SpellArg.SPELL);\r\n\t\tSpellUtils.castChildSpell(context, player, spell, source, joustEvent.getEventTarget());\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/MetaSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class MetaSpell extends Spell {\n\t\n\tpublic static SpellDesc create(EntityReference target, boolean randomTarget, SpellDesc... spells) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(MetaSpell.class);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\targuments.put(SpellArg.SPELLS, spells);\n\t\targuments.put(SpellArg.RANDOM_TARGET, randomTarget);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tfor (SpellDesc spell : (SpellDesc[]) desc.get(SpellArg.SPELLS)) {\n\t\t\tSpellUtils.castChildSpell(context, player, spell, source, target);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/MindControlSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class MindControlSpell extends Spell {\n\n\tpublic static SpellDesc create(EntityReference target, TargetPlayer targetPlayer, boolean randomTarget) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(MindControlSpell.class);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\targuments.put(SpellArg.RANDOM_TARGET, randomTarget);\n\t\targuments.put(SpellArg.TARGET_PLAYER, targetPlayer);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tMinion minion = (Minion) target;\n\t\tcontext.getLogic().mindControl(player, minion);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/MisdirectSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.Environment;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class MisdirectSpell extends Spell {\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tActor attacker = (Actor) context.resolveSingleTarget((EntityReference) context.getEnvironment().get(Environment.ATTACKER_REFERENCE));\n\t\tif (attacker.isDestroyed()) {\n\t\t\treturn;\n\t\t}\n\t\tActor randomTarget = context.getLogic().getAnotherRandomTarget(player, attacker, (Actor) target, EntityReference.ALL_CHARACTERS);\n\t\tcontext.getEnvironment().put(Environment.TARGET_OVERRIDE, randomTarget.getReference());\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/MissilesSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.List;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\n\npublic class MissilesSpell extends DamageSpell {\n\n\t@Override\n\tpublic void cast(GameContext context, Player player, SpellDesc desc, Entity source, List<Entity> targets) {\n\t\tint missiles = desc.getValue(SpellArg.HOW_MANY, context, player, null, source, 2);\n\t\tint damage = desc.getValue(SpellArg.VALUE, context, player, null, source, 1);\n\n\t\tif (damage == 1 && source.getEntityType() == EntityType.CARD && ((Card) source).getCardType().isCardType(CardType.SPELL)) {\n\t\t\tmissiles = context.getLogic().applySpellpower(player, source,  missiles);\n\t\t\tmissiles = context.getLogic().applyAmplify(player, missiles, Attribute.SPELL_AMPLIFY_MULTIPLIER);\n\t\t} else if (source.getEntityType() == EntityType.CARD && ((Card) source).getCardType().isCardType(CardType.SPELL)) {\n\t\t\tdamage = context.getLogic().applySpellpower(player, source,  damage);\n\t\t\tdamage = context.getLogic().applyAmplify(player, damage, Attribute.SPELL_AMPLIFY_MULTIPLIER);\n\t\t}\n\t\tEntityFilter filter = (EntityFilter) desc.get(SpellArg.FILTER);\n\t\tfor (int i = 0; i < missiles; i++) {\n\t\t\tList<Actor> validTargets = SpellUtils.getValidRandomTargets(SpellUtils.getValidTargets(context, player, targets, filter));\n\t\t\t\n\t\t\tif (validTargets.isEmpty()) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tActor randomTarget = SpellUtils.getRandomTarget(validTargets);\n\t\t\tcontext.getLogic().damage(player, randomTarget, damage, source, true);\n\t\t}\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ModifyAttributeSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class ModifyAttributeSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tAttribute attribute = (Attribute) desc.get(SpellArg.ATTRIBUTE);\r\n\t\tint value = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\r\n\t\ttarget.modifyAttribute(attribute, value);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ModifyDamageSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.desc.valueprovider.AlgebraicOperation;\r\n\r\npublic class ModifyDamageSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tif (context.getDamageStack().isEmpty()) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tint damage = context.getDamageStack().pop();\r\n\t\tAlgebraicOperation operation = (AlgebraicOperation) desc.get(SpellArg.OPERATION);\r\n\t\tint value = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\r\n\t\tint minDamage = desc.getInt(SpellArg.MIN_DAMAGE, 0);\r\n\t\tswitch(operation) {\r\n\t\tcase ADD:\r\n\t\t\tif ((context.resolveSingleTarget(context.getEventTargetStack().peek())).hasAttribute(Attribute.TAKE_DOUBLE_DAMAGE)) {\r\n\t\t\t\tvalue *= 2;\r\n\t\t\t}\r\n\t\t\tdamage += value;\r\n\t\t\tbreak;\r\n\t\tcase SUBTRACT:\r\n\t\t\tif ((context.resolveSingleTarget(context.getEventTargetStack().peek())).hasAttribute(Attribute.TAKE_DOUBLE_DAMAGE)) {\r\n\t\t\t\tvalue *= 2;\r\n\t\t\t}\r\n\t\t\tdamage -= value;\r\n\t\t\tdamage = Math.max(minDamage, damage);\r\n\t\t\tbreak;\r\n\t\tcase MODULO:\r\n\t\t\tif ((context.resolveSingleTarget(context.getEventTargetStack().peek())).hasAttribute(Attribute.TAKE_DOUBLE_DAMAGE)) {\r\n\t\t\t\tdamage /= 2;\r\n\t\t\t}\r\n\t\t\tdamage %= value;\r\n\t\t\tif ((context.resolveSingleTarget(context.getEventTargetStack().peek())).hasAttribute(Attribute.TAKE_DOUBLE_DAMAGE)) {\r\n\t\t\t\tdamage *= 2;\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\tcase MULTIPLY:\r\n\t\t\tdamage *= value;\r\n\t\t\tbreak;\r\n\t\tcase DIVIDE:\r\n\t\t\tdamage /= value;\r\n\t\t\tdamage = Math.max(minDamage, damage);\r\n\t\t\tbreak;\r\n\t\tcase NEGATE:\r\n\t\t\tdamage = -damage;\r\n\t\t\tbreak;\r\n\t\tcase SET:\r\n\t\t\tif ((context.resolveSingleTarget(context.getEventTargetStack().peek())).hasAttribute(Attribute.TAKE_DOUBLE_DAMAGE)) {\r\n\t\t\t\tvalue *= 2;\r\n\t\t\t}\r\n\t\t\tdamage = value;\r\n\t\t\tbreak;\r\n\t\tcase MINIMUM:\r\n\t\t\tif ((context.resolveSingleTarget(context.getEventTargetStack().peek())).hasAttribute(Attribute.TAKE_DOUBLE_DAMAGE)) {\r\n\t\t\t\tvalue *= 2;\r\n\t\t\t}\r\n\t\t\tif (damage < value) {\r\n\t\t\t\tdamage = value;\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\tcase MAXIMUM:\r\n\t\t\tif ((context.resolveSingleTarget(context.getEventTargetStack().peek())).hasAttribute(Attribute.TAKE_DOUBLE_DAMAGE)) {\r\n\t\t\t\tvalue *= 2;\r\n\t\t\t}\r\n\t\t\tif (damage > value) {\r\n\t\t\t\tdamage = value;\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\t\tdefault:\r\n\t\t\tbreak;\r\n\t\t}\r\n\t\t\r\n\t\tcontext.getDamageStack().push(damage);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ModifyDurabilitySpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class ModifyDurabilitySpell extends Spell {\n\n\tpublic static SpellDesc create(EntityReference target, int durability) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(ModifyDurabilitySpell.class);\n\t\targuments.put(SpellArg.VALUE, durability);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(int durability) {\n\t\treturn create(null, durability);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\t// if there is no weapon, just do nothing\n\t\tif (player.getHero().getWeapon() == null) {\n\t\t\treturn;\n\t\t}\n\t\tint durabilityChange = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\n\t\tcontext.getLogic().modifyDurability(player.getHero().getWeapon(), durabilityChange);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ModifyMaxManaSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class ModifyMaxManaSpell extends Spell {\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint value = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\n\t\tboolean fullManaCrystals = desc.getBool(SpellArg.FULL_MANA_CRYSTALS);\n\t\t\n\t\tcontext.getLogic().modifyMaxMana(player, value);\n\t\tif (fullManaCrystals) {\n\t\t\tcontext.getLogic().modifyCurrentMana(player.getId(), value);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/MultiTargetSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.List;\nimport java.util.Map;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\n\npublic class MultiTargetSpell extends Spell {\n\n\tpublic static SpellDesc create(int targets) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(MultiTargetSpell.class);\n\t\targuments.put(SpellArg.VALUE, targets);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tpublic void cast(GameContext context, Player player, SpellDesc desc, Entity source, List<Entity> targets) {\n\t\tint number = desc.getValue(SpellArg.VALUE, context, player, null, source, 1);\n\t\tSpellDesc spell = (SpellDesc) desc.get(SpellArg.SPELL);\n\t\tEntityFilter filter = (EntityFilter) desc.get(SpellArg.FILTER);\n\t\tfor (int i = 0; i < number; i++) {\n\t\t\tList<Actor> validTargets = SpellUtils.getValidRandomTargets(SpellUtils.getValidTargets(context, player, targets, filter));\n\t\t\t\n\t\t\tif (validTargets.isEmpty()) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tActor randomTarget = SpellUtils.getRandomTarget(validTargets);\n\t\t\tvalidTargets.remove(randomTarget);\n\t\t\tSpellUtils.castChildSpell(context, player, spell, source, randomTarget);\n\t\t}\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/NullSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class NullSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(NullSpell.class);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\t// intentionally do nothing\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/OverrideTargetSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Environment;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class OverrideTargetSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(OverrideTargetSpell.class);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tif (context.getEnvironment().get(Environment.TARGET_OVERRIDE) == null) {\n\t\t\tcontext.getEnvironment().put(Environment.TARGET_OVERRIDE, target.getReference());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/PutCopyInHandSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class PutCopyInHandSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\treturn create(1);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, int amount) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(PutCopyInHandSpell.class);\n\t\targuments.put(SpellArg.HOW_MANY, amount);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(int amount) {\n\t\treturn create(null, amount);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tMinion minion = (Minion) target;\n\n\t\tint amount = desc.getValue(SpellArg.HOW_MANY, context, player, target, source, 1);\n\t\tCard sourceCard = minion.getSourceCard();\n\t\tfor (int i = 0; i < amount; i++) {\n\t\t\tcontext.getLogic().receiveCard(player.getId(), sourceCard.getCopy());\n\t\t}\n\t}\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/PutMinionOnBoardFromDeckSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.MinionCard;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class PutMinionOnBoardFromDeckSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tMinionCard minionCard = (MinionCard) target;\r\n\t\tif (minionCard == null || !player.getDeck().contains(minionCard)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tplayer.getDeck().remove(minionCard);\r\n\t\tplayer.getSetAsideZone().add(minionCard);\r\n\t\t\r\n\t\tboolean summonSuccess = context.getLogic().summon(player.getId(), minionCard.summon());\r\n\t\t\r\n\t\t// re-add the card here if we removed it before\r\n\t\tplayer.getSetAsideZone().remove(minionCard);\r\n\t\tplayer.getDeck().add(minionCard);\r\n\t\t\r\n\t\tif (summonSuccess) {\r\n\t\t\tcontext.getLogic().removeCardFromDeck(player.getId(), minionCard);\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/PutMinionOnBoardSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.MinionCard;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class PutMinionOnBoardSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tMinionCard minionCard = (MinionCard) target;\r\n\t\t\r\n\t\tif (context.getLogic().summon(player.getId(), minionCard.summon())) {\r\n\t\t\tcontext.getLogic().removeCard(player.getId(), minionCard);\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/PutRandomMinionOnBoardSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.targeting.CardLocation;\n\npublic class PutRandomMinionOnBoardSpell extends Spell {\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\n\t\tCardLocation cardLocation = (CardLocation) desc.get(SpellArg.CARD_LOCATION);\n\t\tif (cardLocation == null) {\n\t\t\tcardLocation = CardLocation.DECK;\n\t\t}\n\t\tint numberToSummon = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\n\t\tfor (int i = 0; i < numberToSummon; i++) {\n\t\t\tputRandomMinionFromDeckOnBoard(context, player, cardFilter, cardLocation);\n\t\t}\n\t}\n\n\tprivate void putRandomMinionFromDeckOnBoard(GameContext context, Player player, EntityFilter cardFilter, CardLocation cardLocation) {\n\t\tMinionCard minionCard = null;\n\t\tCardCollection collection = cardLocation == CardLocation.HAND ? player.getHand() : player.getDeck();\n\t\tif (cardFilter == null) {\n\t\t\tminionCard = (MinionCard) collection.getRandomOfType(CardType.MINION);\n\t\t} else {\n\t\t\tminionCard = (MinionCard) SpellUtils.getRandomCard(collection, card -> cardFilter.matches(context, player, card));\n\t\t}\n\n\t\tif (minionCard == null) {\n\t\t\treturn;\n\t\t}\n\t\t\n\t\t// we need to remove the card temporarily here, because there are card interactions like Starving Buzzard + Desert Camel\n\t\t// which could result in the card being drawn while a minion is summoned\n\t\tif (cardLocation == CardLocation.DECK) {\n\t\t\tplayer.getDeck().remove(minionCard);\n\t\t\tplayer.getSetAsideZone().add(minionCard);\n\t\t}\n\t\t\n\t\tboolean summonSuccess = context.getLogic().summon(player.getId(), minionCard.summon());\n\t\t\n\t\t// re-add the card here if we removed it before\n\t\tif (cardLocation == CardLocation.DECK) {\n\t\t\tplayer.getSetAsideZone().remove(minionCard);\n\t\t\tplayer.getDeck().add(minionCard);\n\t\t}\n\t\t\n\t\tif (summonSuccess) {\n\t\t\tif (cardLocation == CardLocation.HAND) {\n\t\t\t\tcontext.getLogic().removeCard(player.getId(), minionCard);\n\t\t\t} else {\n\t\t\t\tcontext.getLogic().removeCardFromDeck(player.getId(), minionCard);\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/PutRandomSecretIntoPlaySpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.cards.SecretCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.trigger.types.Secret;\n\npublic class PutRandomSecretIntoPlaySpell extends Spell {\n\n\tprivate CardCollection findSecretCards(CardCollection cardCollection) {\n\t\tCardCollection secretCards = new CardCollection();\n\t\tfor (Card card : cardCollection) {\n\t\t\tif (card.hasAttribute(Attribute.SECRET)) {\n\t\t\t\tsecretCards.add(card);\n\t\t\t}\n\t\t}\n\t\treturn secretCards;\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint howMany = desc.getValue(SpellArg.HOW_MANY, context, player, target, source, 1);\n\t\tfor (int i = 0; i < howMany; i++) {\n\t\t\tCardCollection secretCards = findSecretCards(player.getDeck());\n\n\t\t\tif (secretCards.isEmpty()) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t\n\t\t\tsecretCards.shuffle();\n\n\t\t\tSecretCard secretCard = (SecretCard) secretCards.removeFirst();\n\t\t\twhile(!secretCards.isEmpty()) {\n\t\t\t\tif (!context.getLogic().canPlaySecret(player, secretCard)) {\n\t\t\t\t\tsecretCard = (SecretCard) secretCards.removeFirst();\t\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (secretCards.isEmpty() && !context.getLogic().canPlaySecret(player, secretCard)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tSpellDesc secretSpellDesc = secretCard.getSpell();\n\t\t\tSecret secret = (Secret) secretSpellDesc.get(SpellArg.SECRET);\n\t\t\tcontext.getLogic().playSecret(player, secret, false);\n\t\t\tcontext.getLogic().removeCardFromDeck(player.getId(), secretCard);\n\n\t\t}\n\t}\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/RandomAttackTargetSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.Environment;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.actions.PhysicalAttackAction;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class RandomAttackTargetSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tActor attacker = (Actor) context.resolveSingleTarget((EntityReference) context.getEnvironment().get(Environment.ATTACKER_REFERENCE));\r\n\t\tPhysicalAttackAction action = new PhysicalAttackAction(attacker.getReference());\r\n\t\tif (attacker.isDestroyed()) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tList<Entity> targets = context.getLogic().getValidTargets(context.getActivePlayerId(), action);\r\n\t\tEntity randomTarget = targets.get(context.getLogic().random(targets.size()));\r\n\t\tcontext.getEnvironment().put(Environment.TARGET_OVERRIDE, randomTarget.getReference());\r\n\t}\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/RandomSpellTargetSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.Environment;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.actions.GameAction;\r\nimport net.demilich.metastone.game.actions.PlayChooseOneCardAction;\r\nimport net.demilich.metastone.game.actions.PlaySpellCardAction;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.CardType;\r\nimport net.demilich.metastone.game.cards.ChooseOneCard;\r\nimport net.demilich.metastone.game.cards.SpellCard;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class RandomSpellTargetSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tif (!((Card) target).getCardType().isCardType(CardType.SPELL)) {\r\n\t\t\t// In case Yogg-Saron tries to do something silly. Which he will.\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t//SpellCard spellCard = null;\r\n\t\tGameAction action = null;\r\n\t\tif (target instanceof SpellCard) {\r\n\t\t\tSpellCard spellCard = (SpellCard) target;\r\n\t\t\taction = new PlaySpellCardAction(spellCard.getSpell(), spellCard, spellCard.getTargetRequirement());\r\n\t\t} else if (target instanceof ChooseOneCard) {\r\n\t\t\tChooseOneCard chooseOneCard = (ChooseOneCard) target;\r\n\t\t\tString chosenCardId = (String) context.getEnvironment().get(Environment.CHOOSE_ONE_CARD);\r\n\t\t\tSpellCard spellCard = (SpellCard) context.getCardById(chosenCardId);\r\n\t\t\taction = new PlayChooseOneCardAction(spellCard.getSpell(), chooseOneCard, chosenCardId, spellCard.getTargetRequirement());\r\n\t\t}\r\n\t\t\r\n\t\tList<Entity> targets = context.getLogic().getValidTargets(context.getActivePlayerId(), action);\r\n\t\tEntity randomTarget = targets.get(context.getLogic().random(targets.size()));\r\n\t\tcontext.getEnvironment().put(Environment.TARGET_OVERRIDE, randomTarget.getReference());\r\n\t}\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/RandomlyCastSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class RandomlyCastSpell extends Spell {\r\n\r\n\tpublic static SpellDesc create(EntityReference target, SpellDesc... spells) {\r\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(RandomlyCastSpell.class);\r\n\t\targuments.put(SpellArg.SPELLS, spells);\r\n\t\targuments.put(SpellArg.TARGET, target);\r\n\t\treturn new SpellDesc(arguments);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tSpellDesc[] spells = (SpellDesc[]) desc.get(SpellArg.SPELLS);\r\n\t\t\r\n\t\tint i = context.getLogic().random(spells.length);\r\n\t\tSpellUtils.castChildSpell(context, player, spells[i], source, target);\r\n\t}\r\n\r\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/RecastSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.SpellCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class RecastSpell extends Spell {\n\t\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tCard card = SpellUtils.getCard(context, desc);\n\t\tif (card == null) {\n\t\t\treturn;\n\t\t}\n\t\tif (card instanceof SpellCard) {\n\t\t\tSpellCard spell = (SpellCard) card;\n\t\t\tspell.setSpell(spell.getSpell().addArg(SpellArg.FILTER, null));\n\t\t\tSpellUtils.castChildSpell(context, player, spell.getSpell(), source, target);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ReceiveCardAndDoSomethingSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.targeting.CardLocation;\n\npublic class ReceiveCardAndDoSomethingSpell extends Spell {\n\n\tprivate void castSomethingSpell(GameContext context, Player player, SpellDesc spell, Entity source, Card card) {\n\t\tcontext.getLogic().receiveCard(player.getId(), card);\n\t\t// card may be null (i.e. try to draw from deck, but already in\n\t\t// fatigue)\n\t\tif (card == null || card.getLocation() == CardLocation.GRAVEYARD) {\n\t\t\treturn;\n\t\t}\n\t\tcontext.setEventCard(card);\n\t\tSpellUtils.castChildSpell(context, player, spell, source, card);\n\t\tcontext.setEventCard(null);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\n\t\tSpellDesc cardEffectSpell = (SpellDesc) desc.get(SpellArg.SPELL);\n\t\tint count = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\n\t\tif (cardFilter != null) {\n\t\t\tCardCollection cards = CardCatalogue.query(context.getDeckFormat());\n\t\t\tCardCollection result = new CardCollection();\n\t\t\tString replacementCard = (String) desc.get(SpellArg.CARD);\n\t\t\tfor (Card card : cards) {\n\t\t\t\tif (cardFilter.matches(context, player, card)) {\n\t\t\t\t\tresult.add(card);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tCard card = null;\n\t\t\t\tif (!result.isEmpty()) {\n\t\t\t\t\tcard = result.getRandom();\n\t\t\t\t} else if (replacementCard != null) {\n\t\t\t\t\tcard = CardCatalogue.getCardById(replacementCard);\n\t\t\t\t}\n\t\t\t\tif (card != null) {\n\t\t\t\t\tCard clone = card.clone();\n\t\t\t\t\tcastSomethingSpell(context, player, cardEffectSpell, source, clone);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor (Card card : SpellUtils.getCards(context, desc)) {\n\t\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\t\tcastSomethingSpell(context, player, cardEffectSpell, source, card);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ReceiveCardSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\n\npublic class ReceiveCardSpell extends Spell {\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\n\t\tint count = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\n\t\tif (cardFilter != null) {\n\t\t\tCardCollection cards = CardCatalogue.query(context.getDeckFormat());\n\t\t\tCardCollection result = new CardCollection();\n\t\t\tString replacementCard = (String) desc.get(SpellArg.CARD);\n\t\t\tfor (Card card : cards) {\n\t\t\t\tif (cardFilter.matches(context, player, card)) {\n\t\t\t\t\tresult.add(card);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tCard card = null;\n\t\t\t\tif (!result.isEmpty()) {\n\t\t\t\t\tcard = result.getRandom();\n\t\t\t\t} else if (replacementCard != null) {\n\t\t\t\t\tcard = context.getCardById(replacementCard);\n\t\t\t\t}\n\t\t\t\tif (card != null) {\n\t\t\t\t\tCard clone = card.clone();\n\t\t\t\t\tcontext.getLogic().receiveCard(player.getId(), clone);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor (Card card : SpellUtils.getCards(context, desc)) {\n\t\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\t\tcontext.getLogic().receiveCard(player.getId(), card);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ReceiveRandomCardSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class ReceiveRandomCardSpell extends Spell {\n\n\tpublic static SpellDesc create(TargetPlayer targetPlayer, Card... cards) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(ReceiveRandomCardSpell.class);\n\t\targuments.put(SpellArg.CARDS, cards);\n\t\targuments.put(SpellArg.TARGET_PLAYER, targetPlayer);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tCard[] cards = SpellUtils.getCards(context, desc);\n\t\tCard randomCard = cards[context.getLogic().random(cards.length)];\n\t\tcontext.getLogic().receiveCard(player.getId(), randomCard);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/RefreshHeroPowerSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class RefreshHeroPowerSpell extends Spell {\r\n\t\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tint heroPowerUsages = player.getHero().getHeroPower().hasBeenUsed();\r\n\t\tplayer.getHero().setAttribute(Attribute.HERO_POWER_USAGES, heroPowerUsages + 1);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/RemoveAttributeSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class RemoveAttributeSpell extends RevertableSpell {\n\tpublic static SpellDesc create(Attribute tag) {\n\t\treturn create(null, tag);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, Attribute tag) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(RemoveAttributeSpell.class);\n\t\targuments.put(SpellArg.ATTRIBUTE, tag);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected SpellDesc getReverseSpell(SpellDesc desc, EntityReference target) {\n\t\treturn AddAttributeSpell.create(target, (Attribute) desc.get(SpellArg.ATTRIBUTE));\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tAttribute tag = (Attribute) desc.get(SpellArg.ATTRIBUTE);\n\t\tcontext.getLogic().removeAttribute(target, tag);\n\t\tsuper.onCast(context, player, desc, source, target);\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/RemoveCardSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class RemoveCardSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tif (target == null) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tCard card = (Card) target;\r\n\t\tcontext.getLogic().removeCard(player.getId(), card);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/RenounceClassSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.heroes.powers.HeroPower;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.CardFilter;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.spells.desc.filter.FilterArg;\nimport net.demilich.metastone.game.spells.desc.filter.FilterDesc;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\nimport net.demilich.metastone.game.spells.desc.trigger.TriggerDesc;\nimport net.demilich.metastone.game.spells.desc.valueprovider.AlgebraicOperation;\nimport net.demilich.metastone.game.spells.trigger.CardDrawnTrigger;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class RenounceClassSpell extends Spell {\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\n\t\t\n\t\tHeroClass renouncedClass = (HeroClass) cardFilter.getArg(FilterArg.HERO_CLASS);\n\t\tHeroClass rebornClass = SpellUtils.getRandomHeroClassExcept(renouncedClass);\n\t\tCardCollection cards = CardCatalogue.query(context.getDeckFormat());\n\t\tCardCollection result = new CardCollection();\n\t\tfor (Card card : cards) {\n\t\t\tif (card.getHeroClass() == rebornClass) {\n\t\t\t\tresult.add(card);\n\t\t\t}\n\t\t}\n\t\t\n\t\tint manaCostModifier = desc.getValue(SpellArg.MANA_MODIFIER, context, player, target, source, 0);\n\t\t\n\t\tCardCollection heroPowers = CardCatalogue.getHeroPowers(context.getDeckFormat());\n\t\tfor (Card heroPowerCard : heroPowers) {\n\t\t\tif (heroPowerCard.getHeroClass() == rebornClass) {\n\t\t\t\tHeroPower heroPower = (HeroPower) heroPowerCard;\n\t\t\t\tplayer.getHero().setHeroPower(heroPower);\n\t\t\t}\n\t\t}\n\n\t\tCardCollection replacedCards = new CardCollection();\n\t\tfor (Card card : player.getDeck()) {\n\t\t\tif (card.getHeroClass() == renouncedClass) {\n\t\t\t\treplacedCards.add(card);\n\t\t\t}\n\t\t}\n\t\tfor (Card card : replacedCards) {\n\t\t\tCard replacement = result.getRandom().getCopy();\n\t\t\tcontext.getLogic().replaceCardInDeck(player.getId(), card, replacement);\n\t\t}\n\t\tMap<EventTriggerArg, Object> eventTriggerMap = EventTriggerDesc.build(CardDrawnTrigger.class);\n\t\teventTriggerMap.put(EventTriggerArg.TARGET_PLAYER, TargetPlayer.SELF);\n\t\tMap<FilterArg, Object> filterMap = FilterDesc.build(CardFilter.class);\n\t\tfilterMap.put(FilterArg.HERO_CLASS, rebornClass);\n\t\tCardFilter newCardFilter = new CardFilter(new FilterDesc(filterMap));\n\t\tTriggerDesc triggerDesc = new TriggerDesc();\n\t\ttriggerDesc.eventTrigger = new EventTriggerDesc(eventTriggerMap);\n\t\ttriggerDesc.spell = CardCostModifierSpell.create(EntityReference.EVENT_TARGET, AlgebraicOperation.ADD, manaCostModifier, newCardFilter);\n\t\tSpellDesc spellTriggerSpell = AddSpellTriggerSpell.create(triggerDesc);\n\t\tSpellUtils.castChildSpell(context, player, spellTriggerSpell, source, player);\n\t\t\n\t\treplacedCards = new CardCollection();\n\t\tfor (Card card : player.getHand()) {\n\t\t\tif (card.getHeroClass() == renouncedClass) {\n\t\t\t\treplacedCards.add(card);\n\t\t\t}\n\t\t}\n\t\tfor (Card card : replacedCards) {\n\t\t\tCard replacement = result.getRandom().getCopy();\n\t\t\tcontext.getLogic().replaceCard(player.getId(), card, replacement);\n\t\t\tSpellUtils.castChildSpell(context, player, CardCostModifierSpell.create(replacement.getReference(), AlgebraicOperation.ADD, manaCostModifier), source, null);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ReplaceCardLocationSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.targeting.CardLocation;\n\npublic class ReplaceCardLocationSpell extends Spell {\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\n\t\tCardCollection cards = CardCatalogue.query(context.getDeckFormat());\n\t\tCardCollection result = new CardCollection();\n\t\tString replacementCard = (String) desc.get(SpellArg.CARD);\n\t\tfor (Card card : cards) {\n\t\t\tif (cardFilter.matches(context, player, card)) {\n\t\t\t\tresult.add(card);\n\t\t\t}\n\t\t}\n\t\t\n\t\tCardLocation location = (CardLocation) desc.get(SpellArg.CARD_LOCATION);\n\t\tint count = 0;\n\t\tswitch(location) {\n\t\tdefault:\n\t\tcase DECK:\n\t\t\tcount = player.getDeck().getCount();\n\t\t\tfor (Card card : player.getDeck().toList()) {\n\t\t\t\tcontext.getLogic().removeCardFromDeck(player.getId(), card);\n\t\t\t}\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tCard card = null;\n\t\t\t\tif (!result.isEmpty()) {\n\t\t\t\t\tcard = result.getRandom();\n\t\t\t\t} else if (replacementCard != null) {\n\t\t\t\t\tcard = context.getCardById(replacementCard);\n\t\t\t\t}\n\t\t\t\tif (card != null) {\n\t\t\t\t\tcontext.getLogic().shuffleToDeck(player, card.clone());\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase HAND:\n\t\t\tcount = player.getHand().getCount();\n\t\t\tfor (Card card : player.getHand().toList()) {\n\t\t\t\tcontext.getLogic().removeCard(player.getId(), card);\n\t\t\t}\n\t\t\t\n\t\t\tint manaCostModifier = desc.getValue(SpellArg.MANA_MODIFIER, context, player, target, source, 0);\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tCard card = null;\n\t\t\t\tif (!result.isEmpty()) {\n\t\t\t\t\tcard = result.getRandom();\n\t\t\t\t} else if (replacementCard != null) {\n\t\t\t\t\tcard = context.getCardById(replacementCard);\n\t\t\t\t}\n\n\t\t\t\tif (manaCostModifier != 0) {\n\t\t\t\t\tcard.setAttribute(Attribute.MANA_COST_MODIFIER, manaCostModifier);\n\t\t\t\t}\n\t\t\t\tif (card != null) {\n\t\t\t\t\tcontext.getLogic().receiveCard(player.getId(), card.clone());\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ResurrectFromBothSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.MinionCard;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.EntityType;\r\nimport net.demilich.metastone.game.entities.minions.Minion;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\n\r\npublic class ResurrectFromBothSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tList<Minion> deadMinions = new ArrayList<>();\r\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\r\n\t\tList<Entity> bothGraveyards = new ArrayList<Entity>();\r\n\t\tbothGraveyards.addAll(player.getGraveyard());\r\n\t\tbothGraveyards.addAll(context.getOpponent(player).getGraveyard());\r\n\t\tfor (Entity deadEntity : bothGraveyards) {\r\n\t\t\tif (deadEntity.getEntityType() == EntityType.MINION) {\r\n\t\t\t\tif (cardFilter == null || cardFilter.matches(context, player, deadEntity)) {\r\n\t\t\t\t\tdeadMinions.add((Minion) deadEntity);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tint count = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\r\n\t\tfor (int i=0; i < count; i++) {\r\n\t\t\tif (deadMinions.isEmpty()) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\tMinion resurrectedMinion = deadMinions.get(context.getLogic().random(deadMinions.size()));\r\n\t\t\tMinionCard minionCard = (MinionCard) resurrectedMinion.getSourceCard();\r\n\t\t\tcontext.getLogic().summon(player.getId(), minionCard.summon());\r\n\t\t\tdeadMinions.remove(resurrectedMinion);\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ResurrectSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.MinionCard;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.EntityType;\r\nimport net.demilich.metastone.game.entities.minions.Minion;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\n\r\npublic class ResurrectSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tList<Minion> deadMinions = new ArrayList<>();\r\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\r\n\t\tList<Entity> graveyard = new ArrayList<Entity>();\r\n\t\tgraveyard.addAll(player.getGraveyard());\r\n\t\tfor (Entity deadEntity : graveyard) {\r\n\t\t\tif (deadEntity.getEntityType() == EntityType.MINION) {\r\n\t\t\t\tif (cardFilter == null || cardFilter.matches(context, player, deadEntity)) {\r\n\t\t\t\t\tdeadMinions.add((Minion) deadEntity);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tint count = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\r\n\t\tfor (int i=0; i < count; i++) {\r\n\t\t\tif (deadMinions.isEmpty()) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\tMinion resurrectedMinion = deadMinions.get(context.getLogic().random(deadMinions.size()));\r\n\t\t\tMinionCard minionCard = (MinionCard) resurrectedMinion.getSourceCard();\r\n\t\t\tcontext.getLogic().summon(player.getId(), minionCard.summon());\r\n\t\t\tdeadMinions.remove(resurrectedMinion);\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ReturnMinionToHandSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Summon;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class ReturnMinionToHandSpell extends Spell {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(ReturnMinionToHandSpell.class);\n\n\tpublic static SpellDesc create() {\n\t\treturn create(null, null, false);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, SpellDesc spell, boolean randomTarget) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(ReturnMinionToHandSpell.class);\n\t\targuments.put(SpellArg.SPELL, spell);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\targuments.put(SpellArg.RANDOM_TARGET, randomTarget);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tSpellDesc cardSpell = (SpellDesc) desc.get(SpellArg.SPELL);\n\t\tSummon summon = (Summon) target;\n\t\tPlayer owner = context.getPlayer(summon.getOwner());\n\t\tif (owner.getHand().getCount() >= GameLogic.MAX_HAND_CARDS) {\n\t\t\tlogger.debug(\"{} is destroyed because {}'s hand is full\", summon, owner.getName());\n\t\t\tcontext.getLogic().markAsDestroyed((Actor) target);\n\t\t} else {\n\t\t\tlogger.debug(\"{} is returned to {}'s hand\", summon, owner.getName());\n\t\t\tcontext.getLogic().removeSummon(summon, true);\n\t\t\tCard sourceCard = summon.getSourceCard().getCopy();\n\t\t\tcontext.getLogic().receiveCard(summon.getOwner(), sourceCard);\n\t\t\tif (cardSpell != null) {\n\t\t\t\tcontext.setEventCard(sourceCard);\n\t\t\t\tSpellUtils.castChildSpell(context, player, cardSpell, source, sourceCard);\n\t\t\t\tcontext.setEventCard(null);\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/RevertableSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\nimport net.demilich.metastone.game.spells.trigger.GameEventTrigger;\nimport net.demilich.metastone.game.spells.trigger.SpellTrigger;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic abstract class RevertableSpell extends Spell {\n\n\tprotected abstract SpellDesc getReverseSpell(SpellDesc desc, EntityReference target);\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tEventTriggerDesc triggerDesc = (EventTriggerDesc) desc.get(SpellArg.REVERT_TRIGGER);\n\n\t\tif (triggerDesc != null) {\n\t\t\tGameEventTrigger revertTrigger = triggerDesc.create();\n\t\t\tEventTriggerDesc secondTriggerDesc = (EventTriggerDesc) desc.get(SpellArg.SECOND_REVERT_TRIGGER);\n\t\t\tGameEventTrigger secondRevertTrigger = secondTriggerDesc != null ? secondTriggerDesc.create() : null;\n\t\t\tSpellDesc revert = getReverseSpell(desc, target.getReference());\n\t\t\tSpellTrigger removeTrigger = new SpellTrigger(revertTrigger, secondRevertTrigger, revert, true);\n\t\t\tcontext.getLogic().addGameEventListener(player, removeTrigger, target);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ReviveMinionSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class ReviveMinionSpell extends Spell {\n\n\tpublic static SpellDesc create(EntityReference target) {\n\t\treturn create(target, 0);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, int hpAdjustment) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(ReviveMinionSpell.class);\n\t\targuments.put(SpellArg.HP_BONUS, hpAdjustment);\n\t\targuments.put(SpellArg.TARGET, target);\n\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint hpAdjustment = desc.getValue(SpellArg.HP_BONUS, context, player, target, source, 0);\n\t\tActor targetActor = (Actor) target;\n\t\tint boardPosition = SpellUtils.getBoardPosition(context, player, desc, source);\n\t\tMinionCard minionCard = (MinionCard) targetActor.getSourceCard();\n\t\tMinion minion = minionCard.summon();\n\t\tif (hpAdjustment != 0) {\n\t\t\tminion.setHp(hpAdjustment);\n\t\t}\n\t\tcontext.getLogic().summon(player.getId(), minion, null, boardPosition, false);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SetAttackSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class SetAttackSpell extends Spell {\n\n\tpublic static SpellDesc create(int value) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(SetAttackSpell.class);\n\t\targuments.put(SpellArg.VALUE, value);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint value = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\n\t\ttarget.setAttribute(Attribute.ATTACK, value);\n\t\ttarget.removeAttribute(Attribute.TEMPORARY_ATTACK_BONUS);\n\t\ttarget.removeAttribute(Attribute.ATTACK_BONUS);\n\t\ttarget.removeAttribute(Attribute.CONDITIONAL_ATTACK_BONUS);\n\t}\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SetHeroHpSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class SetHeroHpSpell extends Spell {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(SetHeroHpSpell.class);\n\n\tpublic static SpellDesc create(int value) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(SetHeroHpSpell.class);\n\t\targuments.put(SpellArg.VALUE, value);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tActor actor = (Actor) target;\n\t\tint value = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\n\t\tif (actor.getMaxHp() < value) {\n\t\t\tactor.setMaxHp(value);\n\t\t\tlogger.debug(\"{}'s Max Hp have been set to {}\", actor, actor.getMaxHp());\n\t\t}\n\t\tactor.setHp(value);\n\t\tlogger.debug(\"{}'s Hp have been set to {}\", actor, actor.getHp());\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SetHpSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class SetHpSpell extends Spell {\n\n\tpublic static SpellDesc create(EntityReference target, int hp) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(SetHpSpell.class);\n\t\targuments.put(SpellArg.VALUE, hp);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(int hp) {\n\t\treturn create(null, hp);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint hp = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\n\t\tActor targetActor = (Actor) target;\n\t\ttargetActor.removeAttribute(Attribute.HP_BONUS);\n\t\tcontext.getLogic().modifyMaxHp(targetActor, hp);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ShuffleMinionToDeckSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.minions.Summon;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class ShuffleMinionToDeckSpell extends ShuffleToDeckSpell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tif (!target.isDestroyed()) {\r\n\t\t\tcontext.getLogic().removeSummon((Summon) target, true);\r\n\t\t}\r\n\r\n\t\tsuper.onCast(context, player, desc, source, target);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/ShuffleToDeckSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\n\npublic class ShuffleToDeckSpell extends Spell {\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tCard card = null;\n\t\tif (target != null) {\n\t\t\tcard = ((Actor) target).getSourceCard().getCopy();\n\t\t} else if (desc.contains(SpellArg.CARD_FILTER)){\n\t\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\n\t\t\tCardCollection cards = CardCatalogue.query(context.getDeckFormat());\n\t\t\tCardCollection result = new CardCollection();\n\t\t\tfor (Card cardResult : cards) {\n\t\t\t\tif (cardFilter.matches(context, player, cardResult)) {\n\t\t\t\t\tresult.add(cardResult);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcard = result.getRandom();\n\t\t} else {\n\t\t\tString cardId = (String) desc.get(SpellArg.CARD);\n\t\t\tcard = context.getCardById(cardId);\t\t\t\t\t\t\n\t\t}\n\n\t\tint howMany = desc.getValue(SpellArg.HOW_MANY, context, player, target, source, 1);\n\t\tfor (int i = 0; i < howMany; i++) {\n\t\t\tif (card != null) {\n\t\t\t\tcontext.getLogic().shuffleToDeck(player, card.clone());\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SilenceSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class SilenceSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\treturn create(null);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(SilenceSpell.class);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tcontext.getLogic().silence(player.getId(), (Minion) target);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/Spell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.List;\n\nimport net.demilich.metastone.game.Environment;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\n\npublic abstract class Spell {\n\n\tpublic void cast(GameContext context, Player player, SpellDesc desc, Entity source, List<Entity> targets) {\n\t\t// no target specified, cast the spell once with target NULL\n\t\tif (targets == null) {\n\t\t\tcastForPlayer(context, player, desc, source, null);\n\t\t\treturn;\n\t\t}\n\n\t\tEntityFilter targetFilter = desc.getEntityFilter();\n\t\tList<Entity> validTargets = SpellUtils.getValidTargets(context, player, targets, targetFilter);\n\t\t// there is at least one valid target and the RANDOM_TARGET flag is set,\n\t\t// pick one randomly\n\t\tif (validTargets.size() > 0 && desc.getBool(SpellArg.RANDOM_TARGET)) {\n\t\t\tEntity target = SpellUtils.getRandomTarget(validTargets);\n\t\t\tcastForPlayer(context, player, desc, source, target);\n\t\t} else {\n\t\t\t// there is at least one target and RANDOM_TARGET flag is not set,\n\t\t\t// cast in on all targets\n\n\t\t\tfor (Entity target : validTargets) {\n\t\t\t\tcontext.getEnvironment().put(Environment.SPELL_TARGET, target.getReference());\n\t\t\t\tcastForPlayer(context, player, desc, source, target);\n\t\t\t\tcontext.getEnvironment().remove(Environment.SPELL_TARGET);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void castForPlayer(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tTargetPlayer targetPlayer = desc.getTargetPlayer();\n\t\tif (targetPlayer == null) {\n\t\t\ttargetPlayer = TargetPlayer.SELF;\n\t\t}\n\t\tPlayer opponent = context.getOpponent(player);\n\t\tswitch (targetPlayer) {\n\t\tcase BOTH:\n\t\t\tonCast(context, player, desc, source, target);\n\t\t\tonCast(context, opponent, desc, source, target);\n\t\t\tbreak;\n\t\tcase OPPONENT:\n\t\t\tonCast(context, opponent, desc, source, target);\n\t\t\tbreak;\n\t\tcase SELF:\n\t\t\tonCast(context, player, desc, source, target);\n\t\t\tbreak;\n\t\tcase OWNER:\n\t\t\tonCast(context, context.getPlayer(target.getOwner()), desc, source, target);\n\t\t\tbreak;\n\t\tcase ACTIVE:\n\t\t\tonCast(context, context.getActivePlayer(), desc, source, target);\n\t\t\tbreak;\n\t\tcase INACTIVE:\n\t\t\tonCast(context, context.getOpponent(context.getActivePlayer()), desc, source, target);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tprotected abstract void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target);\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"[SPELL \" + getClass().getSimpleName() + \"]\";\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SpellUtils.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.function.Predicate;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.DiscoverAction;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Race;\nimport net.demilich.metastone.game.entities.minions.RelativeToSource;\nimport net.demilich.metastone.game.entities.minions.Summon;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.spells.desc.filter.Operation;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class SpellUtils {\n\n\tpublic static void castChildSpell(GameContext context, Player player, SpellDesc spell, Entity source, Entity target) {\n\t\tEntityReference sourceReference = source != null ? source.getReference() : null;\n\t\tEntityReference targetReference = spell.getTarget();\n\t\tif (targetReference == null && target != null) {\n\t\t\ttargetReference = target.getReference();\n\t\t}\n\t\tcontext.getLogic().castSpell(player.getId(), spell, sourceReference, targetReference, true);\n\t}\n\n\tpublic static boolean evaluateOperation(Operation operation, int actualValue, int targetValue) {\n\t\tswitch (operation) {\n\t\tcase EQUAL:\n\t\t\treturn actualValue == targetValue;\n\t\tcase GREATER:\n\t\t\treturn actualValue > targetValue;\n\t\tcase GREATER_OR_EQUAL:\n\t\t\treturn actualValue >= targetValue;\n\t\tcase HAS:\n\t\t\treturn actualValue > 0;\n\t\tcase LESS:\n\t\t\treturn actualValue < targetValue;\n\t\tcase LESS_OR_EQUAL:\n\t\t\treturn actualValue <= targetValue;\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic static CardCollection getCards(CardCollection source, Predicate<Card> filter) {\n\t\tCardCollection result = new CardCollection();\n\t\tfor (Card card : source) {\n\t\t\tif (filter == null || filter.test(card)) {\n\t\t\t\tresult.add(card);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\t\n\tpublic static Card getCard(GameContext context, SpellDesc spell) {\n\t\tCard card = null;\n\t\tString cardName = (String) spell.get(SpellArg.CARD);\n\t\tcard = CardCatalogue.getCardById(cardName);\n\t\tif (spell.get(SpellArg.CARD).toString().toUpperCase().equals(\"PENDING_CARD\")) {\n\t\t\tcard = (Card) context.getPendingCard();\n\t\t} else if (spell.get(SpellArg.CARD).toString().toUpperCase().equals(\"EVENT_CARD\")) {\n\t\t\tcard = (Card) context.getEventCard();\n\t\t}\n\t\treturn card;\n\t}\n\n\tpublic static Card[] getCards(GameContext context, SpellDesc spell) {\n\t\tString[] cardNames = null;\n\t\tif (spell.contains(SpellArg.CARDS)) {\n\t\t\tcardNames = (String[]) spell.get(SpellArg.CARDS);\n\t\t} else {\n\t\t\tcardNames = new String[1];\n\t\t\tcardNames[0] = (String) spell.get(SpellArg.CARD);\n\t\t}\n\t\tCard[] cards = new Card[cardNames.length];\n\t\tfor (int i = 0; i < cards.length; i++) {\n\t\t\tcards[i] = context.getCardById(cardNames[i]);\n\t\t}\n\t\treturn cards;\n\t}\n\t\n\tpublic static DiscoverAction getDiscover(GameContext context, Player player, SpellDesc desc, CardCollection cards) {\n\t\tSpellDesc spell = (SpellDesc) desc.get(SpellArg.SPELL);\n\t\tList<GameAction> discoverActions = new ArrayList<>();\n\t\tfor (Card card : cards) {\n\t\t\tSpellDesc spellClone = spell.addArg(SpellArg.CARD, card.getCardId());\n\t\t\tDiscoverAction discover = DiscoverAction.createDiscover(spellClone);\n\t\t\tdiscover.setCard(card);\n\t\t\tdiscover.setActionSuffix(card.getName());\n\t\t\tdiscoverActions.add(discover);\n\t\t}\n\t\tif (discoverActions.size() == 0) {\n\t\t\treturn null;\n\t\t}\n\t\t\n\t\tif (context.getLogic().attributeExists(Attribute.ALL_RANDOM_YOGG_ONLY_FINAL_DESTINATION)) {\n\t\t\treturn (DiscoverAction) discoverActions.get(context.getLogic().random(discoverActions.size()));\n\t\t} else {\n\t\t\treturn (DiscoverAction) player.getBehaviour().requestAction(context, player, discoverActions);\n\t\t}\n\t}\n\n\tpublic static DiscoverAction getSpellDiscover(GameContext context, Player player, SpellDesc desc, List<SpellDesc> spells) {\n\t\tList<GameAction> discoverActions = new ArrayList<>();\n\t\tfor (SpellDesc spell : spells) {\n\t\t\tDiscoverAction discover = DiscoverAction.createDiscover(spell);\n\t\t\tdiscover.setName(spell.getString(SpellArg.NAME));\n\t\t\tdiscover.setDescription(spell.getString(SpellArg.DESCRIPTION));\n\t\t\tdiscover.setActionSuffix((String) spell.get(SpellArg.NAME));\n\t\t\tdiscoverActions.add(discover);\n\t\t}\n\t\t\n\t\tif (context.getLogic().attributeExists(Attribute.ALL_RANDOM_YOGG_ONLY_FINAL_DESTINATION)) {\n\t\t\treturn (DiscoverAction) discoverActions.get(context.getLogic().random(discoverActions.size()));\n\t\t} else {\n\t\t\treturn (DiscoverAction) player.getBehaviour().requestAction(context, player, discoverActions);\n\t\t}\n\t}\n\n\tpublic static Card getRandomCard(CardCollection source, Predicate<Card> filter) {\n\t\tCardCollection result = getCards(source, filter);\n\t\tif (result.isEmpty()) {\n\t\t\treturn null;\n\t\t}\n\t\treturn result.getRandom();\n\t}\n\t\n\tpublic static HeroClass getRandomHeroClass() {\n\t\tHeroClass[] values = HeroClass.values();\n\t\tList<HeroClass> heroClasses = new ArrayList<HeroClass>();\n\t\tfor (HeroClass heroClass : values) {\n\t\t\tif (heroClass.isBaseClass()) {\n\t\t\t\theroClasses.add(heroClass);\n\t\t\t}\n\t\t}\n\t\treturn heroClasses.get(ThreadLocalRandom.current().nextInt(heroClasses.size()));\n\t}\n\t\n\tpublic static HeroClass getRandomHeroClassExcept(HeroClass... heroClassesExcluded) {\n\t\tHeroClass[] values = HeroClass.values();\n\t\tList<HeroClass> heroClasses = new ArrayList<HeroClass>();\n\t\tfor (HeroClass heroClass : values) {\n\t\t\tif (heroClass.isBaseClass()) {\n\t\t\t\theroClasses.add(heroClass);\n\t\t\t\tfor (HeroClass heroClassExcluded : heroClassesExcluded) {\n\t\t\t\t\tif (heroClassExcluded == heroClass) {\n\t\t\t\t\t\theroClasses.remove(heroClass);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn heroClasses.get(ThreadLocalRandom.current().nextInt(heroClasses.size()));\n\t}\n\n\tpublic static <T> T getRandomTarget(List<T> targets) {\n\t\tint randomIndex = ThreadLocalRandom.current().nextInt(targets.size());\n\t\treturn targets.get(randomIndex);\n\t}\n\n\tpublic static List<Actor> getValidRandomTargets(List<Entity> targets) {\n\t\tList<Actor> validTargets = new ArrayList<Actor>();\n\t\tfor (Entity entity : targets) {\n\t\t\tActor actor = (Actor) entity;\n\t\t\tif (!actor.isDestroyed() || actor.getEntityType() == EntityType.HERO) {\n\t\t\t\tvalidTargets.add(actor);\n\t\t\t}\n\n\t\t}\n\t\treturn validTargets;\n\t}\n\n\tpublic static List<Entity> getValidTargets(GameContext context, Player player, List<Entity> allTargets, EntityFilter filter) {\n\t\tif (filter == null) {\n\t\t\treturn allTargets;\n\t\t}\n\t\tList<Entity> validTargets = new ArrayList<>();\n\t\tfor (Entity entity : allTargets) {\n\t\t\tif (filter.matches(context, player, entity)) {\n\t\t\t\tvalidTargets.add(entity);\n\t\t\t}\n\t\t}\n\t\treturn validTargets;\n\t}\n\n\tpublic static int hasHowManyOfRace(Player player, Race race) {\n\t\tint count = 0;\n\t\tfor (Summon summon : player.getSummons()) {\n\t\t\tif (summon.getRace() == race) {\n\t\t\t\tcount++;\n\t\t\t}\n\t\t}\n\t\treturn count;\n\t}\n\t\n\tpublic static boolean highlanderDeck(Player player) {\n\t\tList<String> cards = new ArrayList<String>();\n\t\tfor (Card card : player.getDeck()) {\n\t\t\tif (cards.contains(card.getCardId())) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tcards.add(card.getCardId());\n\t\t}\n\t\treturn true;\n\t}\n\n\tpublic static boolean holdsCardOfType(Player player, CardType cardType) {\n\t\tfor (Card card : player.getHand()) {\n\t\t\tif (card.getCardType().isCardType(cardType)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic static boolean holdsMinionOfRace(Player player, Race race) {\n\t\tfor (Card card : player.getHand()) {\n\t\t\tif (card.getAttribute(Attribute.RACE) == race) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic static int howManyMinionsDiedThisTurn(GameContext context) {\n\t\tint currentTurn = context.getTurn();\n\t\tint count = 0;\n\t\tfor (Player player : context.getPlayers()) {\n\t\t\tfor (Entity deadEntity : player.getGraveyard()) {\n\t\t\t\tif (deadEntity.getEntityType() != EntityType.MINION) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (deadEntity.getAttributeValue(Attribute.DIED_ON_TURN) == currentTurn) {\n\t\t\t\t\tcount++;\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\t\treturn count;\n\t}\n\t\n\tpublic static int getBoardPosition(GameContext context, Player player, SpellDesc desc, Entity source) {\n\t\tfinal int UNDEFINED = -1;\n\t\tint boardPosition = desc.getInt(SpellArg.BOARD_POSITION_ABSOLUTE, UNDEFINED);\n\t\tif (boardPosition != UNDEFINED) {\n\t\t\treturn boardPosition;\n\t\t}\n\t\tRelativeToSource relativeBoardPosition = (RelativeToSource) desc.get(SpellArg.BOARD_POSITION_RELATIVE);\n\t\tif (relativeBoardPosition == null) {\n\t\t\treturn UNDEFINED;\n\t\t}\n\n\t\tint sourcePosition = context.getBoardPosition((Summon) source);\n\t\tif (sourcePosition == UNDEFINED) {\n\t\t\treturn UNDEFINED;\n\t\t}\n\t\tswitch (relativeBoardPosition) {\n\t\tcase LEFT:\n\t\t\treturn sourcePosition;\n\t\tcase RIGHT:\n\t\t\treturn sourcePosition + 1;\n\t\tdefault:\n\t\t\treturn UNDEFINED;\n\t\t}\n\t}\n\n\tprivate SpellUtils() {\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/StealRandomSecretSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.trigger.IGameEventListener;\nimport net.demilich.metastone.game.spells.trigger.types.Secret;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class StealRandomSecretSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(StealRandomSecretSpell.class);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tPlayer opponent = context.getOpponent(player);\n\t\tList<IGameEventListener> secrets = context.getLogic().getSecrets(opponent);\n\n\t\tif (secrets.isEmpty()) {\n\t\t\treturn;\n\t\t}\n\n\t\t// try to steal a secret which we do not own yet\n\t\tList<Secret> validSecrets = new ArrayList<>();\n\t\tfor (IGameEventListener trigger : secrets) {\n\t\t\tSecret secret = (Secret) trigger;\n\t\t\tif (!player.getSecrets().contains(secret.getSource().getCardId()) && player.getSecrets().size() < GameLogic.MAX_SECRETS) {\n\t\t\t\tvalidSecrets.add(secret);\n\t\t\t}\n\t\t}\n\n\t\tif (!validSecrets.isEmpty()) {\n\t\t\tSecret secret = validSecrets.get(context.getLogic().random(validSecrets.size()));\n\t\t\tsecret.setHost(player.getHero());\n\t\t\tsecret.setOwner(player.getId());\n\t\t\tplayer.getSecrets().add(secret.getSource().getCardId());\n\t\t\topponent.getSecrets().remove(secret.getSource().getCardId());\n\t\t} else {\n\t\t\t// no valid secret to steal; instead destroy one for the opponent at\n\t\t\t// least\n\t\t\tSecret secret = (Secret) secrets.get(context.getLogic().random(secrets.size()));\n\t\t\tcontext.removeTrigger(secret);\n\t\t\topponent.getSecrets().remove(secret.getSource().getCardId());\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SummonCopySpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.trigger.IGameEventListener;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class SummonCopySpell extends Spell {\n\n\tpublic static SpellDesc create(EntityReference target) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(SummonCopySpell.class);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tMinion template = (Minion) target;\n\t\tint value = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\n\t\t\tfor (int i = 0; i < value; i++) {\n\t\t\tMinion clone = template.clone();\n\t\t\tclone.clearSpellTriggers();\n\t\n\t\t\tcontext.getLogic().summon(player.getId(), clone);\n\t\t\tfor (IGameEventListener trigger : context.getTriggersAssociatedWith(template.getReference())) {\n\t\t\t\tIGameEventListener triggerClone = trigger.clone();\n\t\t\t\tcontext.getLogic().addGameEventListener(player, triggerClone, clone);\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SummonNewAttackTargetSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Environment;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class SummonNewAttackTargetSpell extends Spell {\n\n\tpublic static SpellDesc create(MinionCard minionCard) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(SummonNewAttackTargetSpell.class);\n\t\targuments.put(SpellArg.CARD, minionCard);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tMinionCard minionCard = (MinionCard) SpellUtils.getCard(context, desc);\n\t\tMinion targetMinion = minionCard.summon();\n\t\tcontext.getLogic().summon(player.getId(), targetMinion);\n\t\tif (targetMinion.getOwner() > -1) {\n\t\t\tcontext.getEnvironment().put(Environment.TARGET_OVERRIDE, targetMinion.getReference());\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SummonOneOneCopySpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.spells.desc.source.CardSource;\n\npublic class SummonOneOneCopySpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(SummonOneOneCopySpell.class);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\n\t\tCardSource cardSource = (CardSource) desc.get(SpellArg.CARD_SOURCE);\n\t\tint boardPosition = SpellUtils.getBoardPosition(context, player, desc, source);\n\t\tMinionCard minionCard = null;\n\t\tif (cardSource != null || cardFilter != null) {\n\t\t\tCardCollection relevantMinions = null;\n\t\t\tif (cardSource != null) {\n\t\t\t\tCardCollection allCards = cardSource.getCards(context, player);\n\t\t\t\trelevantMinions = new CardCollection();\n\t\t\t\tfor (Card card : allCards) {\n\t\t\t\t\tif (card.getCardType().isCardType(CardType.MINION) && (cardFilter == null || cardFilter.matches(context, player, card))) {\n\t\t\t\t\t\trelevantMinions.add(card);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tCardCollection allMinions = CardCatalogue.query(context.getDeckFormat(), CardType.MINION);\n\t\t\t\trelevantMinions = new CardCollection();\n\t\t\t\tfor (Card card : allMinions) {\n\t\t\t\t\tif (cardFilter.matches(context, player, card)) {\n\t\t\t\t\t\trelevantMinions.add(card);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tminionCard = (MinionCard) relevantMinions.getRandom();\n\t\t} else {\n\t\t\tminionCard = (MinionCard) ((Minion) target).getSourceCard();\n\t\t}\n\n\t\tif (minionCard != null) {\n\t\t\tMinion minion = minionCard.summon();\n\t\t\tif (context.getLogic().summon(player.getId(), minion, null, boardPosition, false)) {\n\t\t\t\tminion.setAttack(1);\n\t\t\t\tminion.setHp(1);\n\t\t\t\tminion.setMaxHp(1);\n\t\t\t}\n\t\t}\n\t}\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SummonRandomMinionFilteredSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.CardCatalogue;\r\nimport net.demilich.metastone.game.cards.CardCollection;\r\nimport net.demilich.metastone.game.cards.CardType;\r\nimport net.demilich.metastone.game.cards.MinionCard;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\nimport net.demilich.metastone.game.spells.desc.source.CardSource;\r\n\r\npublic class SummonRandomMinionFilteredSpell extends Spell {\r\n\r\n\tprotected static MinionCard getRandomMatchingMinionCard(GameContext context, Player player, EntityFilter cardFilter, CardSource cardSource) {\r\n\t\tCardCollection relevantMinions = null;\r\n\t\tif (cardSource != null) {\r\n\t\t\tCardCollection allCards = cardSource.getCards(context, player);\r\n\t\t\trelevantMinions = new CardCollection();\r\n\t\t\tfor (Card card : allCards) {\r\n\t\t\t\tif (card.getCardType().isCardType(CardType.MINION) && (cardFilter == null || cardFilter.matches(context, player, card))) {\r\n\t\t\t\t\trelevantMinions.add(card);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tCardCollection allMinions = CardCatalogue.query(context.getDeckFormat(), CardType.MINION);\r\n\t\t\trelevantMinions = new CardCollection();\r\n\t\t\tfor (Card card : allMinions) {\r\n\t\t\t\tif (cardFilter == null || cardFilter.matches(context, player, card)) {\r\n\t\t\t\t\trelevantMinions.add(card);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn (MinionCard) relevantMinions.getRandom();\r\n\t}\r\n\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\r\n\t\tString replacementCard = (String) desc.get(SpellArg.CARD);\r\n\t\tCardSource cardSource = (CardSource) desc.get(SpellArg.CARD_SOURCE);\r\n\r\n\t\tint boardPosition = SpellUtils.getBoardPosition(context, player, desc, source);\r\n\t\tMinionCard minionCard = getRandomMatchingMinionCard(context, player, cardFilter, cardSource);\r\n\t\tif (minionCard == null && replacementCard != null) {\r\n\t\t\tminionCard = (MinionCard) context.getCardById(replacementCard);\r\n\t\t}\r\n\t\tif (minionCard != null) {\r\n\t\t\tcontext.getLogic().summon(player.getId(), minionCard.summon(), null, boardPosition, false);\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SummonRandomNotOnBoardSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.MinionCard;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.minions.Summon;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class SummonRandomNotOnBoardSpell extends Spell {\r\n\r\n\tprivate static boolean alreadyOnBoard(List<Summon> summons, String id) {\r\n\t\tfor (Summon summon : summons) {\r\n\t\t\tif (summon.getSourceCard().getCardId().equals(id)) {\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tString[] minionCardsId = (String[]) desc.get(SpellArg.CARDS);\r\n\t\tList<String> eligibleMinions = new ArrayList<String>();\r\n\t\tfor (String minion : minionCardsId) {\r\n\t\t\tif (!alreadyOnBoard(player.getSummons(), minion)) {\r\n\t\t\t\teligibleMinions.add(minion);\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (eligibleMinions.isEmpty()) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tString randomMinionId = eligibleMinions.get(context.getLogic().random(eligibleMinions.size()));\r\n\t\tMinionCard randomMinionCard = (MinionCard) context.getCardById(randomMinionId);\r\n\t\tcontext.getLogic().summon(player.getId(), randomMinionCard.summon());\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SummonRandomSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class SummonRandomSpell extends Spell {\n\n\tpublic static SpellDesc create(MinionCard... minionCards) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(SummonRandomSpell.class);\n\t\targuments.put(SpellArg.CARDS, minionCards);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tString[] minionCardsId = (String[]) desc.get(SpellArg.CARDS);\n\t\tString randomMinionId = minionCardsId[context.getLogic().random(minionCardsId.length)];\n\t\tMinionCard randomMinionCard = (MinionCard) context.getCardById(randomMinionId);\n\t\tcontext.getLogic().summon(player.getId(), randomMinionCard.summon());\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SummonSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.SummonCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.RelativeToSource;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class SummonSpell extends Spell {\n\n\tpublic static SpellDesc create(SummonCard... summonCards) {\n\t\treturn create(TargetPlayer.SELF, summonCards);\n\t}\n\n\tpublic static SpellDesc create(RelativeToSource relativeBoardPosition, SummonCard... summonCards) {\n\t\treturn create(TargetPlayer.SELF, relativeBoardPosition, summonCards);\n\t}\n\n\tpublic static SpellDesc create(String minionCard) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(SummonSpell.class);\n\t\targuments.put(SpellArg.CARD, minionCard);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(String[] minionCards) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(SummonSpell.class);\n\t\targuments.put(SpellArg.CARDS, minionCards);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(TargetPlayer targetPlayer, SummonCard... summonCards) {\n\t\treturn create(targetPlayer, null, summonCards);\n\t}\n\n\tpublic static SpellDesc create(TargetPlayer targetPlayer, RelativeToSource relativeBoardPosition, SummonCard... summonCards) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(SummonSpell.class);\n\t\tString[] cardNames = new String[summonCards.length];\n\t\tfor (int i = 0; i < summonCards.length; i++) {\n\t\t\tcardNames[i] = summonCards[i].getCardId();\n\t\t}\n\t\targuments.put(SpellArg.CARDS, cardNames);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\targuments.put(SpellArg.TARGET_PLAYER, targetPlayer);\n\t\tif (relativeBoardPosition != null) {\n\t\t\targuments.put(SpellArg.BOARD_POSITION_RELATIVE, relativeBoardPosition);\n\t\t}\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint boardPosition = SpellUtils.getBoardPosition(context, player, desc, source);\n\t\tint count = desc.getValue(SpellArg.VALUE, context, player, target, source, 1);\n\t\tfor (Card card : SpellUtils.getCards(context, desc)) {\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tSummonCard summonCard = (SummonCard) card.clone();\n\t\t\t\tcontext.getLogic().summon(player.getId(), summonCard.summon(), null, boardPosition, false);\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SwapAttackAndHpSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class SwapAttackAndHpSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\treturn create(null);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(SwapAttackAndHpSpell.class);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tMinion minion = (Minion) target;\n\t\tint attack = minion.getAttack();\n\t\tint hp = minion.getHp();\n\t\tminion.removeAttribute(Attribute.TEMPORARY_ATTACK_BONUS);\n\t\tminion.removeAttribute(Attribute.ATTACK_BONUS);\n\t\tminion.removeAttribute(Attribute.HP_BONUS);\n\t\tminion.setAttack(hp);\n\t\tcontext.getLogic().modifyMaxHp(minion, attack);\n\t}\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SwapAttackSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.Spell;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class SwapAttackSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(SwapAttackSpell.class);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tif (context.getSummonReferenceStack().isEmpty()) {\n\t\t\treturn;\n\t\t}\n\t\tMinion sourceMinion = (Minion) context.resolveSingleTarget(context.getSummonReferenceStack().peek());\n\t\tActor targetActor = (Actor) target;\n\t\tint sourceAttack = sourceMinion.getAttack();\n\t\tint targetAttack = targetActor.getAttack();\n\t\tsource.setAttribute(Attribute.ATTACK, targetAttack);\n\t\tsourceMinion.setAttribute(Attribute.ATTACK_BONUS, 0);\n\t\tsourceMinion.setAttribute(Attribute.TEMPORARY_ATTACK_BONUS, 0);\n\t\ttargetActor.setAttribute(Attribute.ATTACK, sourceAttack);\n\t\ttargetActor.setAttribute(Attribute.ATTACK_BONUS, 0);\n\t\ttargetActor.setAttribute(Attribute.TEMPORARY_ATTACK_BONUS, 0);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SwapHpSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.Spell;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class SwapHpSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(SwapHpSpell.class);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tif (context.getSummonReferenceStack().isEmpty()) {\n\t\t\treturn;\n\t\t}\n\t\tMinion sourceMinion = (Minion) context.resolveSingleTarget(context.getSummonReferenceStack().peek());\n\t\tActor targetActor = (Actor) target;\n\t\tint sourceHp = sourceMinion.getHp();\n\t\tint targetHp = targetActor.getHp();\n\t\tcontext.getLogic().modifyMaxHp(sourceMinion, targetHp);\n\t\tsourceMinion.setAttribute(Attribute.HP_BONUS, 0);\n\t\tcontext.getLogic().modifyMaxHp(targetActor, sourceHp);\n\t\ttargetActor.setAttribute(Attribute.HP_BONUS, 0);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/SwipeSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class SwipeSpell extends Spell {\n\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint primaryDamage = desc.getValue(SpellArg.VALUE, context, player, target, source, 2);\n\t\tint secondaryDamage = desc.getValue(SpellArg.SECONDARY_VALUE, context, player, target, source, 1);\n\t\tEntityReference targetKey = (EntityReference) desc.get(SpellArg.SECONDARY_TARGET);\n\t\tif (targetKey == null) {\n\t\t\ttargetKey = EntityReference.ENEMY_CHARACTERS;\n\t\t}\n\t\tfor (Entity entity : context.resolveTarget(player, target, targetKey)) {\n\t\t\tif (entity != target) {\n\t\t\t\tcontext.getLogic().damage(player, (Actor) entity, secondaryDamage, source);\n\t\t\t}\n\t\t}\n\n\t\tcontext.getLogic().damage(player, (Actor) target, primaryDamage, source);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/TargetPlayer.java",
    "content": "package net.demilich.metastone.game.spells;\n\npublic enum TargetPlayer {\n\tSELF,\n\tOPPONENT,\n\tBOTH,\n\tOWNER,\n\tACTIVE,\n\tINACTIVE\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/TemporaryAttackSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class TemporaryAttackSpell extends Spell {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(TemporaryAttackSpell.class);\n\n\tpublic static SpellDesc create(EntityReference target, int attackBonus) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(TemporaryAttackSpell.class);\n\t\targuments.put(SpellArg.VALUE, attackBonus);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(int attackBonus) {\n\t\treturn create(null, attackBonus);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint attackBonus = desc.getValue(SpellArg.VALUE, context, player, target, source, 0);\n\n\t\tlogger.debug(\"{} gains {} attack\", target, attackBonus);\n\n\t\tActor targetActor = (Actor) target;\n\n\t\tif (attackBonus != 0) {\n\t\t\ttargetActor.modifyAttribute(Attribute.TEMPORARY_ATTACK_BONUS, +attackBonus);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/TransformCardSpell.java",
    "content": "package net.demilich.metastone.game.spells;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.targeting.CardLocation;\r\n\r\npublic class TransformCardSpell extends Spell {\r\n\r\n\tpublic static Logger logger = LoggerFactory.getLogger(TransformCardSpell.class);\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tCard card = (Card) target;\r\n\t\tif (card.getLocation() == CardLocation.HAND) {\r\n\t\t\tcontext.getLogic().removeCard(player.getId(), card);\r\n\t\t} else {\r\n\t\t\t// logger.warn(\"Trying to transform card {} in invalid location {}\",\r\n\t\t\t// card, card.getLocation());\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tString cardId = (String) desc.get(SpellArg.CARD);\r\n\t\tCard newCard = context.getCardById(cardId);\r\n\t\tcontext.getLogic().receiveCard(player.getId(), newCard);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/TransformMinionSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.SummonCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.heroes.Hero;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.entities.minions.Summon;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class TransformMinionSpell extends Spell {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(TransformMinionSpell.class);\n\n\tpublic static SpellDesc create(EntityReference target, Minion transformTarget, boolean randomTarget) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(TransformMinionSpell.class);\n\t\targuments.put(SpellArg.SECONDARY_TARGET, transformTarget);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\targuments.put(SpellArg.RANDOM_TARGET, randomTarget);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, String templateCard, boolean randomTarget) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(TransformMinionSpell.class);\n\t\targuments.put(SpellArg.CARD, templateCard);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\targuments.put(SpellArg.RANDOM_TARGET, randomTarget);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(Minion transformTarget) {\n\t\treturn create(null, transformTarget, false);\n\t}\n\n\tpublic static SpellDesc create(String templateCard) {\n\t\treturn create(null, templateCard, false);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tif (target instanceof Hero) {\n\t\t\tString heroCardName = desc.getString(SpellArg.HERO_CARD);\n\t\t\tSpellDesc changeHeroSpell = ChangeHeroSpell.create(heroCardName);\n\t\t\tSpellUtils.castChildSpell(context, context.getPlayer(target.getOwner()), changeHeroSpell, source, target);\n\t\t} else {\n\t\t\tString cardName = (String) desc.get(SpellArg.CARD);\n\t\t\tSummon summon = (Summon) target;\n\t\t\tSummon transformTarget = (Summon) desc.get(SpellArg.SECONDARY_TARGET);\n\t\t\t\n\t\t\tSummonCard templateCard = cardName != null ? (SummonCard) context.getCardById(cardName) : null;\n\t\t\tSummon newSummon = transformTarget != null ? transformTarget : templateCard.summon();\n\t\t\t\n\t\t\tlogger.debug(\"{} is transformed into a {}\", summon, newSummon);\n\t\t\tcontext.getLogic().transformMinion(summon, newSummon);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/TransformToRandomMinionSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\n\npublic class TransformToRandomMinionSpell extends TransformMinionSpell {\n\n\tpublic static SpellDesc create() {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(TransformToRandomMinionSpell.class);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tEntityFilter filter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\n\n\t\tCardCollection allMinions = CardCatalogue.query(context.getDeckFormat(), CardType.MINION);\n\t\tCardCollection filteredMinions = new CardCollection();\n\t\tfor (Card card : allMinions) {\n\t\t\tMinionCard minionCard = (MinionCard) card;\n\t\t\tif (filter == null || filter.matches(context, player, card)) {\n\t\t\t\tfilteredMinions.add(minionCard);\n\t\t\t}\n\t\t}\n\t\tMinionCard randomCard = (MinionCard) filteredMinions.getRandom();\n\n\t\tif (randomCard != null) {\n\t\t\tSpellDesc transformMinionSpell = TransformMinionSpell.create(randomCard.getCardId());\n\t\t\tsuper.onCast(context, player, transformMinionSpell, source, target);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/TriggerDeathrattleSpell.java",
    "content": "package net.demilich.metastone.game.spells;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class TriggerDeathrattleSpell extends Spell {\n\n\tpublic static SpellDesc create(EntityReference target) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(TriggerDeathrattleSpell.class);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tActor actor = (Actor) target;\n\t\tcontext.getLogic().resolveDeathrattles(player, actor);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/aura/AttributeAura.java",
    "content": "package net.demilich.metastone.game.spells.aura;\n\nimport net.demilich.metastone.game.spells.AddAttributeSpell;\nimport net.demilich.metastone.game.spells.RemoveAttributeSpell;\nimport net.demilich.metastone.game.spells.desc.aura.AuraArg;\nimport net.demilich.metastone.game.spells.desc.aura.AuraDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\n\npublic class AttributeAura extends Aura {\n\n\tpublic AttributeAura(AuraDesc desc) {\n\t\tsuper(null, AddAttributeSpell.create(desc.getAttribute()), RemoveAttributeSpell.create(desc.getAttribute()), desc.getTarget(), (EntityFilter) desc.get(AuraArg.FILTER));\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/aura/Aura.java",
    "content": "package net.demilich.metastone.game.spells.aura;\n\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.List;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.aura.AuraDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.spells.trigger.BoardChangedTrigger;\nimport net.demilich.metastone.game.spells.trigger.GameEventTrigger;\nimport net.demilich.metastone.game.spells.trigger.SpellTrigger;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class Aura extends SpellTrigger {\n\n\tprivate EntityReference targets;\n\tprivate SpellDesc applyAuraEffect;\n\tprivate SpellDesc removeAuraEffect;\n\tprivate EntityFilter entityFilter;\n\n\tprivate HashSet<Integer> affectedEntities = new HashSet<>();\n\n\tpublic Aura(AuraDesc desc) {\n\t\tthis(desc.getApplyEffect(), desc.getRemoveEffect(), desc.getTarget());\n\t\tsetEntityFilter(desc.getFilter());\n\t}\n\n\tpublic Aura(GameEventTrigger secondaryTrigger, SpellDesc applyAuraEffect, SpellDesc removeAuraEffect, EntityReference targetSelection, EntityFilter entityFilter) {\n\t\tsuper(new BoardChangedTrigger(), secondaryTrigger, applyAuraEffect, false);\n\t\tthis.applyAuraEffect = applyAuraEffect;\n\t\tthis.removeAuraEffect = removeAuraEffect;\n\t\tthis.targets = targetSelection;\n\t\tthis.entityFilter = entityFilter;\n\t}\n\n\tpublic Aura(GameEventTrigger secondaryTrigger, SpellDesc applyAuraEffect, SpellDesc removeAuraEffect, EntityReference targetSelection) {\n\t\tthis(secondaryTrigger, applyAuraEffect, removeAuraEffect, targetSelection, null);\n\t}\n\n\tpublic Aura(SpellDesc applyAuraEffect, SpellDesc removeAuraEffect, EntityReference targetSelection) {\n\t\tthis(null, applyAuraEffect, removeAuraEffect, targetSelection);\n\t}\n\n\tprotected boolean affects(GameContext context, Player player, Entity target, List<Entity> resolvedTargets) {\n\t\tif (getEntityFilter() != null && !getEntityFilter().matches(context, player, target)) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\treturn resolvedTargets.contains(target);\n\t}\n\n\t@Override\n\tpublic Aura clone() {\n\t\tAura clone = (Aura) super.clone();\n\t\tclone.targets = this.targets;\n\t\tclone.applyAuraEffect = this.applyAuraEffect.clone();\n\t\tclone.removeAuraEffect = this.removeAuraEffect.clone();\n\t\tclone.affectedEntities = new HashSet<>(this.affectedEntities);\n\t\treturn clone;\n\t}\n\n\t@Override\n\tpublic void onAdd(GameContext context) {\n\t\tsuper.onAdd(context);\n\t\taffectedEntities.clear();\n\t}\n\n\tpublic void onGameEvent(GameEvent event) {\n\t\tGameContext context = event.getGameContext();\n\t\tPlayer owner = context.getPlayer(getOwner());\n\t\tActor sourceActor = (Actor) context.resolveSingleTarget(getHostReference());\n\t\tList<Entity> resolvedTargets = context.resolveTarget(owner, sourceActor, targets);\n\t\tList<Entity> relevantTargets = new ArrayList<Entity>(resolvedTargets);\n\t\tfor (Iterator<Integer> iterator = affectedEntities.iterator(); iterator.hasNext();) {\n\t\t\tint entityId = iterator.next();\n\n\t\t\tEntityReference entityReference = new EntityReference(entityId);\n\t\t\tEntity affectedEntity = context.tryFind(entityReference);\n\t\t\tif (affectedEntity == null) {\n\t\t\t\titerator.remove();\n\t\t\t} else {\n\t\t\t\trelevantTargets.add(affectedEntity);\n\t\t\t}\n\t\t}\n\n\t\tfor (Entity target : relevantTargets) {\n\t\t\tif (affects(context, owner, target, resolvedTargets) && !affectedEntities.contains(target.getId())) {\n\t\t\t\tcontext.getLogic().castSpell(getOwner(), applyAuraEffect, getHostReference(), target.getReference(), true);\n\t\t\t\taffectedEntities.add(target.getId());\n\t\t\t\t// target is not affected anymore, remove effect\n\t\t\t} else if (!affects(context, owner, target, resolvedTargets) && affectedEntities.contains(target.getId())) {\n\t\t\t\tcontext.getLogic().castSpell(getOwner(), removeAuraEffect, getHostReference(), target.getReference(), true);\n\t\t\t\taffectedEntities.remove(target.getId());\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void onRemove(GameContext context) {\n\t\tfor (int targetId : affectedEntities) {\n\t\t\tEntityReference targetKey = new EntityReference(targetId);\n\t\t\tEntity target = context.resolveSingleTarget(targetKey);\n\t\t\tcontext.getLogic().castSpell(getOwner(), removeAuraEffect, getHostReference(), target.getReference(), true);\n\t\t}\n\t\taffectedEntities.clear();\n\t}\n\n\tpublic EntityFilter getEntityFilter() {\n\t\treturn entityFilter;\n\t}\n\n\tpublic void setEntityFilter(EntityFilter entityFilter) {\n\t\tthis.entityFilter = entityFilter;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/aura/BuffAura.java",
    "content": "package net.demilich.metastone.game.spells.aura;\n\nimport net.demilich.metastone.game.spells.AuraBuffSpell;\nimport net.demilich.metastone.game.spells.desc.aura.AuraArg;\nimport net.demilich.metastone.game.spells.desc.aura.AuraDesc;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class BuffAura extends Aura {\n\n\tpublic BuffAura(AuraDesc desc) {\n\t\tthis(desc.getInt(AuraArg.ATTACK_BONUS), desc.getInt(AuraArg.HP_BONUS), desc.getTarget(), desc.getFilter());\n\t}\n\n\tpublic BuffAura(int attackBonus, int hpBonus, EntityReference targetSelection, EntityFilter filter) {\n\t\tsuper(AuraBuffSpell.create(attackBonus, hpBonus), AuraBuffSpell.create(-attackBonus, -hpBonus), targetSelection);\n\t\tthis.setEntityFilter(filter);\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/aura/EnrageAura.java",
    "content": "package net.demilich.metastone.game.spells.aura;\r\n\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.events.GameEvent;\r\nimport net.demilich.metastone.game.events.GameEventType;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.desc.aura.AuraDesc;\r\nimport net.demilich.metastone.game.spells.trigger.EnrageChangedTrigger;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class EnrageAura extends Aura {\r\n\r\n\tprivate boolean active;\r\n\r\n\tpublic EnrageAura(AuraDesc desc) {\r\n\t\tthis(desc.getApplyEffect(), desc.getRemoveEffect(), desc.getTarget());\r\n\t}\r\n\r\n\tprivate EnrageAura(SpellDesc applyAuraEffect, SpellDesc removeAuraEffect, EntityReference targetSelection) {\r\n\t\tsuper(new EnrageChangedTrigger(), applyAuraEffect, removeAuraEffect, targetSelection);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean affects(GameContext context, Player player, Entity target, List<Entity> resolvedTargets) {\r\n\t\treturn active && super.affects(context, player, target, resolvedTargets);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void onGameEvent(GameEvent event) {\r\n\t\tif (event.getEventType() == GameEventType.ENRAGE_CHANGED) {\r\n\t\t\tactive = event.getEventTarget().hasAttribute(Attribute.ENRAGED);\r\n\t\t}\r\n\t\tsuper.onGameEvent(event);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/custom/AlarmOBotSpell.java",
    "content": "package net.demilich.metastone.game.spells.custom;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.ReturnMinionToHandSpell;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class AlarmOBotSpell extends ReturnMinionToHandSpell {\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\t// Check to see if there is a minion before returning to hand!\n\t\t// If there is no minion, do not activate!\n\t\tif (!player.getHand().hasCardOfType(CardType.MINION)) {\n\t\t\treturn;\n\t\t}\n\t\t// Summon a random minion and remove the corresponding card\n\t\t// before adding Alarm-o-bot to your hand!\n\t\tMinionCard randomMinionCard = (MinionCard) player.getHand().getRandomOfType(CardType.MINION);\n\t\tcontext.getLogic().removeCard(player.getId(), randomMinionCard);\n\t\t// return Alarm-o-bot to hand (Now it's safe and won't destroy itself!)\n\t\tsuper.onCast(context, player, desc, source, target);\n\t\t// Summon the minion, which ALSO won't destroy itself...\n\t\tcontext.getLogic().summon(player.getId(), randomMinionCard.summon());\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/custom/BetrayalSpell.java",
    "content": "package net.demilich.metastone.game.spells.custom;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Summon;\nimport net.demilich.metastone.game.spells.Spell;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class BetrayalSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(BetrayalSpell.class);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tActor attacker = (Actor) target;\n\t\tfor (Summon adjacentSummon : context.getAdjacentSummons(player, target.getReference())) {\n\t\t\tcontext.getLogic().damage(player, adjacentSummon, attacker.getAttack(), attacker);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/custom/FacelessSpell.java",
    "content": "package net.demilich.metastone.game.spells.custom;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.Environment;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.costmodifier.CardCostModifier;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.Spell;\nimport net.demilich.metastone.game.spells.SpellUtils;\nimport net.demilich.metastone.game.spells.TransformMinionSpell;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.trigger.IGameEventListener;\n\npublic class FacelessSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(FacelessSpell.class);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tMinion template = (Minion) target;\n\t\tMinion clone = template.clone();\n\t\tclone.removeAttribute(Attribute.AURA_ATTACK_BONUS);\n\t\tclone.removeAttribute(Attribute.AURA_HP_BONUS);\n\t\tclone.removeAttribute(Attribute.AURA_UNTARGETABLE_BY_SPELLS);\n\t\tclone.clearSpellTriggers();\n\t\tclone.setCardCostModifier(null);\n\n\t\tMinion sourceActor = (Minion) context.resolveSingleTarget(context.getSummonReferenceStack().peek());\n\t\tSpellDesc transformSpell = TransformMinionSpell.create(clone);\n\t\tif (context.getEnvironment().get(Environment.TRANSFORM_REFERENCE) != null) {\n\t\t\tSpellUtils.castChildSpell(context, player, transformSpell, source, sourceActor);\n\t\t\treturn;\n\t\t}\n\t\tSpellUtils.castChildSpell(context, player, transformSpell, source, sourceActor);\n\n\t\tfor (IGameEventListener trigger : context.getTriggersAssociatedWith(template.getReference())) {\n\t\t\tIGameEventListener triggerClone = trigger.clone();\n\t\t\tif (triggerClone instanceof CardCostModifier) {\n\t\t\t\tcontext.getLogic().addManaModifier(player, (CardCostModifier) triggerClone, clone);\n\t\t\t} else {\n\t\t\t\tcontext.getLogic().addGameEventListener(player, triggerClone, clone);\n\t\t\t}\n\n\t\t}\n\t}\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/custom/HeraldVolajzSpell.java",
    "content": "package net.demilich.metastone.game.spells.custom;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.Spell;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class HeraldVolajzSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(HeraldVolajzSpell.class);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tList<Entity> otherMinions = context.resolveTarget(player, source, EntityReference.OTHER_FRIENDLY_MINIONS);\n\t\tfor (Entity entity : otherMinions) {\n\t\t\tMinion minion = (Minion) entity;\n\t\t\tMinionCard minionCard = (MinionCard) minion.getSourceCard();\n\t\t\tminion = minionCard.summon();\n\t\t\tif (context.getLogic().summon(player.getId(), minion)) {\n\t\t\t\tminion.setAttack(1);\n\t\t\t\tminion.setHp(1);\n\t\t\t\tminion.setMaxHp(1);\n\t\t\t}\n\t\t}\n\t}\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/custom/HolyWrathSpell.java",
    "content": "package net.demilich.metastone.game.spells.custom;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.Spell;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\n\r\npublic class HolyWrathSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tCard drawnCard = context.getLogic().drawCard(player.getId(), source);\r\n\t\tif (drawnCard == null) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tcontext.getLogic().damage(player, (Actor) target, drawnCard.getBaseManaCost(), source);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/custom/KelThuzadSpell.java",
    "content": "package net.demilich.metastone.game.spells.custom;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.Spell;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class KelThuzadSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(KelThuzadSpell.class);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tint currentTurn = context.getTurn();\n\t\tList<Entity> graveyardSnapshot = new ArrayList<>(player.getGraveyard());\n\t\tfor (Entity deadEntity : graveyardSnapshot) {\n\t\t\tif (deadEntity.getEntityType() != EntityType.MINION) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tMinion deadMinion = (Minion) deadEntity;\n\t\t\tif (deadMinion.getAttributeValue(Attribute.DIED_ON_TURN) == currentTurn) {\n\t\t\t\tMinionCard minionCard = (MinionCard) deadMinion.getSourceCard();\n\t\t\t\tcontext.getLogic().summon(player.getId(), minionCard.summon());\n\t\t\t}\n\t\t}\n\t}\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/custom/MadamGoyaSpell.java",
    "content": "package net.demilich.metastone.game.spells.custom;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.ShuffleMinionToDeckSpell;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class MadamGoyaSpell extends ShuffleMinionToDeckSpell {\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\t// Check to see if there is a minion before returning to deck!\n\t\tif (!player.getDeck().hasCardOfType(CardType.MINION) || context.getLogic().canSummonMoreMinions(player)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Summon a random minion and remove the corresponding card\n\t\t// before adding the target to your deck!\n\t\tMinionCard randomMinionCard = (MinionCard) player.getDeck().getRandomOfType(CardType.MINION);\n\t\tcontext.getLogic().removeCardFromDeck(player.getId(), randomMinionCard);\n\t\t// return target to deck (Now it's safe and won't destroy itself!)\n\t\t\n\t\t// Summon the minion, which ALSO won't destroy itself...\n\t\tcontext.getLogic().summon(player.getId(), randomMinionCard.summon());\n\t\t\n\t\t// If there is no minion to shuffle... Idk, blame a wizard.\n\t\tsuper.onCast(context, player, desc, source, target);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/custom/MergeSpell.java",
    "content": "package net.demilich.metastone.game.spells.custom;\n\nimport java.util.List;\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.DestroySpell;\nimport net.demilich.metastone.game.spells.ForceDeathPhaseSpell;\nimport net.demilich.metastone.game.spells.Spell;\nimport net.demilich.metastone.game.spells.SpellUtils;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class MergeSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(MergeSpell.class);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tList<Entity> entities = context.resolveTarget(player, source, EntityReference.OTHER_FRIENDLY_MINIONS);\n\t\tSpellDesc destroySpell = DestroySpell.create(target.getReference());\n\t\tSpellUtils.castChildSpell(context, player, destroySpell, source, target);\n\t\tString cardId = desc.getString(SpellArg.CARD);\n\t\tfor (Entity entity : entities) {\n\t\t\tMinion minion = (Minion) entity;\n\t\t\tif (minion.getSourceCard().getCardId().equalsIgnoreCase(cardId)) {\n\t\t\t\tdestroySpell = DestroySpell.create(minion.getReference());\n\t\t\t\tSpellUtils.castChildSpell(context, player, destroySpell, source, target);\n\t\t\t\tSpellUtils.castChildSpell(context, player, ForceDeathPhaseSpell.create(), source, target);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/custom/MoatLurkerSpell.java",
    "content": "package net.demilich.metastone.game.spells.custom;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.SummonCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.AddDeathrattleSpell;\nimport net.demilich.metastone.game.spells.DestroySpell;\nimport net.demilich.metastone.game.spells.Spell;\nimport net.demilich.metastone.game.spells.SpellUtils;\nimport net.demilich.metastone.game.spells.SummonSpell;\nimport net.demilich.metastone.game.spells.TargetPlayer;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\n\npublic class MoatLurkerSpell extends Spell {\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tMinion minion = (Minion) target;\n\t\tTargetPlayer targetPlayer = TargetPlayer.SELF;\n\t\tif (minion.getOwner() != source.getOwner()) {\n\t\t\ttargetPlayer = TargetPlayer.OPPONENT;\n\t\t}\n\t\tsource.removeAttribute(Attribute.DEATHRATTLES);\n\t\tSpellDesc deathrattle = SummonSpell.create(targetPlayer, (SummonCard) minion.getSourceCard());\n\t\tSpellDesc addDeathrattleSpell = AddDeathrattleSpell.create(deathrattle);\n\t\tSpellDesc destroySpell = DestroySpell.create(target.getReference());\n\t\tSpellUtils.castChildSpell(context, player, destroySpell, source, target);\n\t\tSpellUtils.castChildSpell(context, player, addDeathrattleSpell, source, source);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/custom/PoisonSeedsSpell.java",
    "content": "package net.demilich.metastone.game.spells.custom;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.DestroySpell;\nimport net.demilich.metastone.game.spells.Spell;\nimport net.demilich.metastone.game.spells.SummonSpell;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class PoisonSeedsSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(PoisonSeedsSpell.class);\n\t\targuments.put(SpellArg.TARGET, EntityReference.NONE);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tPlayer opponent = context.getOpponent(player);\n\n\t\tEntityReference sourceReference = source != null ? source.getReference() : null;\n\t\tpoisonSeeds(context, opponent, sourceReference);\n\t\tpoisonSeeds(context, player, sourceReference);\n\t}\n\n\tprivate void poisonSeeds(GameContext context, Player player, EntityReference source) {\n\t\tint minionCount = player.getMinions().size();\n\t\tif (minionCount == 0) {\n\t\t\treturn;\n\t\t}\n\t\t\n\t\tSpellDesc destroySpell = DestroySpell.create(EntityReference.FRIENDLY_MINIONS);\n\t\tcontext.getLogic().castSpell(player.getId(), destroySpell, source, null, true);\n\t\tcontext.getLogic().checkForDeadEntities();\n\n\t\tString[] treants = new String[minionCount];\n\t\tfor (int i = 0; i < minionCount; i++) {\n\t\t\ttreants[i] = \"token_treant\";\n\t\t}\n\t\tSpellDesc summonTreants = SummonSpell.create(treants);\n\t\tcontext.getLogic().castSpell(player.getId(), summonTreants, source, EntityReference.NONE, true);\n\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/custom/PutMiniCopyInHandSpell.java",
    "content": "package net.demilich.metastone.game.spells.custom;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.CardCostModifierSpell;\nimport net.demilich.metastone.game.spells.Spell;\nimport net.demilich.metastone.game.spells.SpellUtils;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.valueprovider.AlgebraicOperation;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class PutMiniCopyInHandSpell extends Spell {\n\n\tpublic static SpellDesc create() {\n\t\treturn create(1);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target, int amount) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(PutMiniCopyInHandSpell.class);\n\t\targuments.put(SpellArg.HOW_MANY, amount);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\tpublic static SpellDesc create(int amount) {\n\t\treturn create(null, amount);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\tMinion minion = (Minion) target;\n\n\t\tint amount = desc.getValue(SpellArg.HOW_MANY, context, player, target, source, 1);\n\t\tCard sourceCard = minion.getSourceCard();\n\t\tfor (int i = 0; i < amount; i++) {\n\t\t\tCard copyCard = sourceCard.getCopy();\n\t\t\tcontext.getLogic().receiveCard(player.getId(), copyCard);\n\t\t\tcopyCard.setAttribute(Attribute.ATTACK, 1);\n\t\t\tcopyCard.setAttribute(Attribute.HP, 1);\n\t\t\tcopyCard.setAttribute(Attribute.MAX_HP, 1);\n\t\t\tSpellDesc spell = CardCostModifierSpell.create(copyCard.getReference(), AlgebraicOperation.SET, 1);\n\t\t\tSpellUtils.castChildSpell(context, player, spell, source, copyCard);\n\t\t}\n\t}\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/custom/ShadowMadnessSpell.java",
    "content": "package net.demilich.metastone.game.spells.custom;\n\nimport java.util.Map;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.MindControlSpell;\nimport net.demilich.metastone.game.spells.TargetPlayer;\nimport net.demilich.metastone.game.spells.desc.SpellArg;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\nimport net.demilich.metastone.game.spells.trigger.GameEventTrigger;\nimport net.demilich.metastone.game.spells.trigger.SilenceTrigger;\nimport net.demilich.metastone.game.spells.trigger.SpellTrigger;\nimport net.demilich.metastone.game.spells.trigger.TurnEndTrigger;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class ShadowMadnessSpell extends MindControlSpell {\n\n\tpublic static SpellDesc create() {\n\t\treturn create(null);\n\t}\n\n\tpublic static SpellDesc create(EntityReference target) {\n\t\tMap<SpellArg, Object> arguments = SpellDesc.build(ShadowMadnessSpell.class);\n\t\targuments.put(SpellArg.TARGET, target);\n\t\treturn new SpellDesc(arguments);\n\t}\n\n\t@Override\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\n\t\t// mind control minion\n\t\tsuper.onCast(context, player, desc, source, target);\n\t\t\n\t\tif (target.isDestroyed()) {\n\t\t\treturn;\n\t\t}\n\n\t\t// minion should be able to attack this turn\n\t\ttarget.removeAttribute(Attribute.SUMMONING_SICKNESS);\n\t\tcontext.getLogic().refreshAttacksPerRound(target);\n\n\t\t// mind control is terminated either when silenced or turn ends\n\t\tSpellDesc reverseMindcontrolSpell = MindControlSpell.create(EntityReference.SELF, TargetPlayer.OPPONENT, false);\n\t\tGameEventTrigger silenceTrigger = new SilenceTrigger(EventTriggerDesc.createEmpty(SilenceTrigger.class));\n\t\tGameEventTrigger turnEndTrigger = new TurnEndTrigger(EventTriggerDesc.createEmpty(TurnEndTrigger.class));\n\t\tSpellTrigger returnOnSilence = new SpellTrigger(silenceTrigger, turnEndTrigger, reverseMindcontrolSpell, true);\n\t\tcontext.getLogic().addGameEventListener(player, returnOnSilence, target);\n\t}\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/custom/ShifterZerusSpell.java",
    "content": "package net.demilich.metastone.game.spells.custom;\r\n\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.CardCatalogue;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.Spell;\r\nimport net.demilich.metastone.game.spells.SpellUtils;\r\nimport net.demilich.metastone.game.spells.TargetPlayer;\r\nimport net.demilich.metastone.game.spells.desc.SpellArg;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\nimport net.demilich.metastone.game.spells.desc.trigger.TriggerDesc;\r\nimport net.demilich.metastone.game.spells.trigger.TurnStartTrigger;\r\n\r\npublic class ShifterZerusSpell extends Spell {\r\n\r\n\t@Override\r\n\tprotected void onCast(GameContext context, Player player, SpellDesc desc, Entity source, Entity target) {\r\n\t\tCard card = (Card) target;\r\n\r\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(SpellArg.CARD_FILTER);\r\n\t\tCard newCard = SpellUtils.getRandomCard(CardCatalogue.query(context.getDeckFormat()), filterCard -> cardFilter.matches(context, player, filterCard));\r\n\t\tcontext.getLogic().replaceCard(player.getId(), card, newCard);\r\n\t\t\r\n\t\tMap<EventTriggerArg, Object> arguments = EventTriggerDesc.build(TurnStartTrigger.class);\r\n\t\targuments.put(EventTriggerArg.TARGET_PLAYER, TargetPlayer.SELF);\r\n\t\tEventTriggerDesc eventTriggerDesc = new EventTriggerDesc(arguments);\r\n\t\t\r\n\t\tTriggerDesc triggerDesc = new TriggerDesc();\r\n\t\ttriggerDesc.eventTrigger = eventTriggerDesc;\r\n\t\ttriggerDesc.spell = desc;\r\n\t\t\r\n\t\tcontext.getLogic().addGameEventListener(player, triggerDesc.create(), newCard);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/BattlecryDesc.java",
    "content": "package net.demilich.metastone.game.spells.desc;\r\n\r\nimport net.demilich.metastone.game.spells.desc.condition.ConditionDesc;\r\nimport net.demilich.metastone.game.targeting.TargetSelection;\r\n\r\npublic class BattlecryDesc {\r\n\r\n\tpublic SpellDesc spell;\r\n\tpublic TargetSelection targetSelection;\r\n\tpublic ConditionDesc condition;\r\n\tpublic String description;\r\n\r\n\tpublic TargetSelection getTargetSelection() {\r\n\t\treturn targetSelection != null ? targetSelection : TargetSelection.NONE;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/ISpellConditionChecker.java",
    "content": "package net.demilich.metastone.game.spells.desc;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic interface ISpellConditionChecker {\n\n\tboolean isFulfilled(GameContext context, Player player, Entity target);\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/SpellArg.java",
    "content": "package net.demilich.metastone.game.spells.desc;\r\n\r\npublic enum SpellArg {\r\n\r\n\tCLASS,\r\n\r\n\tARMOR_BONUS,\r\n\tATTACK_BONUS,\r\n\tATTRIBUTE,\r\n\tBOARD_POSITION_ABSOLUTE,\r\n\tBOARD_POSITION_RELATIVE,\r\n\tCANNOT_RECEIVE_OWNED,\r\n\tCARD,\r\n\tCARD_COST_MODIFIER,\r\n\tCARD_DESC_TYPE,\r\n\tCARD_FILTER,\r\n\tCARD_FILTERS,\r\n\tCARD_LOCATION,\r\n\tCARD_SOURCE,\r\n\tCARD_TYPE,\r\n\tCARDS,\r\n\tCONDITION,\r\n\tCONDITIONS,\r\n\tDESCRIPTION,\r\n\tEXCLUSIVE,\r\n\tFILTER,\r\n\tFULL_MANA_CRYSTALS,\r\n\tHERO_CARD,\r\n\tHERO_POWER,\r\n\tHOW_MANY,\r\n\tHP_BONUS,\r\n\tIGNORE_SPELL_DAMAGE,\r\n\tINCLUDE_UNCOLLECTIBLE,\r\n\tMAX_DAMAGE,\r\n\tMANA,\r\n\tMANA_MODIFIER,\r\n\tMIN_DAMAGE,\r\n\tNAME,\r\n\tOPERATION,\r\n\tOPTIONS,\r\n\tQUEST,\r\n\tPARSE_VALUE_TYPE,\r\n\tRANDOM_TARGET,\r\n\tREVERT_TRIGGER,\r\n\tSECRET,\r\n\tSECOND_REVERT_TRIGGER,\r\n\tSECONDARY_NAME,\r\n\tSECONDARY_TARGET,\r\n\tSECONDARY_VALUE,\r\n\tSPELL,\r\n\tSPELL_1,\r\n\tSPELL_2,\r\n\tSPELL_SOURCE,\r\n\tSPELLS,\r\n\tTARGET,\r\n\tTARGET_PLAYER,\r\n\tTARGET_SELECTION,\r\n\tTRIGGER,\r\n\tVALUE,\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/SpellDesc.java",
    "content": "package net.demilich.metastone.game.spells.desc;\n\nimport java.util.EnumMap;\nimport java.util.Map;\n\nimport net.demilich.metastone.game.cards.desc.Desc;\nimport net.demilich.metastone.game.logic.CustomCloneable;\nimport net.demilich.metastone.game.spells.Spell;\nimport net.demilich.metastone.game.spells.TargetPlayer;\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class SpellDesc extends Desc<SpellArg> {\n\n\tpublic SpellDesc(Map<SpellArg, Object> arguments) {\n\t\tsuper(arguments);\n\t}\n\n\tpublic static Map<SpellArg, Object> build(Class<? extends Spell> spellClass) {\n\t\tfinal Map<SpellArg, Object> arguments = new EnumMap<>(SpellArg.class);\n\t\targuments.put(SpellArg.CLASS, spellClass);\n\t\treturn arguments;\n\t}\n\n\tpublic SpellDesc addArg(SpellArg spellArg, Object value) {\n\t\tSpellDesc clone = clone();\n\t\tclone.arguments.put(spellArg, value);\n\t\treturn clone;\n\t}\n\t\n\tpublic SpellDesc removeArg(SpellArg spellArg) {\n\t\tSpellDesc clone = clone();\n\t\tclone.arguments.remove(spellArg);\n\t\treturn clone;\n\t}\n\n\t@Override\n\tpublic SpellDesc clone() {\n\t\tSpellDesc clone = new SpellDesc(build(getSpellClass()));\n\t\tfor (SpellArg spellArg : arguments.keySet()) {\n\t\t\tObject value = arguments.get(spellArg);\n\t\t\tif (value instanceof CustomCloneable) {\n\t\t\t\tCustomCloneable cloneable = (CustomCloneable) value;\n\t\t\t\tclone.arguments.put(spellArg, cloneable.clone());\n\t\t\t} else {\n\t\t\t\tclone.arguments.put(spellArg, value);\n\t\t\t}\n\t\t}\n\t\treturn clone;\n\t}\n\n\tpublic EntityFilter getEntityFilter() {\n\t\treturn (EntityFilter) get(SpellArg.FILTER);\n\t}\n\n\tpublic int getInt(SpellArg spellArg, int defaultValue) {\n\t\treturn arguments.containsKey(spellArg) ? (int) get(spellArg) : defaultValue;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic Class<? extends Spell> getSpellClass() {\n\t\treturn (Class<? extends Spell>) arguments.get(SpellArg.CLASS);\n\t}\n\n\tpublic EntityReference getTarget() {\n\t\treturn (EntityReference) arguments.get(SpellArg.TARGET);\n\t}\n\n\tpublic TargetPlayer getTargetPlayer() {\n\t\treturn (TargetPlayer) get(SpellArg.TARGET_PLAYER);\n\t}\n\n\tpublic boolean hasPredefinedTarget() {\n\t\treturn arguments.get(SpellArg.TARGET) != null;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\tString result = \"[SpellDesc arguments= {\\n\";\n\t\tfor (SpellArg spellArg : arguments.keySet()) {\n\t\t\tresult += \"\\t\" + spellArg + \": \" + arguments.get(spellArg) + \"\\n\";\n\t\t}\n\t\tresult += \"}\";\n\t\treturn result;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/SpellFactory.java",
    "content": "package net.demilich.metastone.game.spells.desc;\n\nimport net.demilich.metastone.game.spells.Spell;\n\npublic class SpellFactory {\n\n\tpublic Spell getSpell(SpellDesc spellDesc) {\n\t\tClass<? extends Spell> spellClass = spellDesc.getSpellClass();\n\t\ttry {\n\t\t\treturn spellClass.newInstance();\n\t\t} catch (InstantiationException | IllegalAccessException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\treturn null;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/aura/AuraArg.java",
    "content": "package net.demilich.metastone.game.spells.desc.aura;\r\n\r\npublic enum AuraArg {\r\n\tCLASS, FILTER, TARGET, ATTRIBUTE, ATTACK_BONUS, HP_BONUS, APPLY_EFFECT, REMOVE_EFFECT\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/aura/AuraDesc.java",
    "content": "package net.demilich.metastone.game.spells.desc.aura;\r\n\r\nimport java.lang.reflect.InvocationTargetException;\r\nimport java.util.EnumMap;\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.cards.desc.Desc;\r\nimport net.demilich.metastone.game.spells.aura.Aura;\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class AuraDesc extends Desc<AuraArg> {\r\n\r\n\tpublic static Map<AuraArg, Object> build(Class<? extends Aura> auraClass) {\r\n\t\tfinal Map<AuraArg, Object> arguments = new EnumMap<>(AuraArg.class);\r\n\t\targuments.put(AuraArg.CLASS, auraClass);\r\n\t\treturn arguments;\r\n\t}\r\n\r\n\tpublic AuraDesc(Map<AuraArg, Object> arguments) {\r\n\t\tsuper(arguments);\r\n\t}\r\n\r\n\tpublic Aura create() {\r\n\t\tClass<? extends Aura> auraClass = getAuraClass();\r\n\t\ttry {\r\n\t\t\treturn auraClass.getConstructor(AuraDesc.class).newInstance(this);\r\n\t\t} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException\r\n\t\t\t\t| NoSuchMethodException | SecurityException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\tpublic SpellDesc getApplyEffect() {\r\n\t\treturn (SpellDesc) get(AuraArg.APPLY_EFFECT);\r\n\t}\r\n\r\n\tpublic Attribute getAttribute() {\r\n\t\treturn (Attribute) get(AuraArg.ATTRIBUTE);\r\n\t}\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\tpublic Class<? extends Aura> getAuraClass() {\r\n\t\treturn (Class<? extends Aura>) get(AuraArg.CLASS);\r\n\t}\r\n\r\n\tpublic EntityFilter getFilter() {\r\n\t\treturn (EntityFilter) get(AuraArg.FILTER);\r\n\t}\r\n\r\n\tpublic SpellDesc getRemoveEffect() {\r\n\t\treturn (SpellDesc) get(AuraArg.REMOVE_EFFECT);\r\n\t}\r\n\r\n\tpublic EntityReference getTarget() {\r\n\t\treturn (EntityReference) get(AuraArg.TARGET);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/AndCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class AndCondition extends Condition {\r\n\r\n\tpublic AndCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tCondition[] conditions = (Condition[]) desc.get(ConditionArg.CONDITIONS);\r\n\t\tfor (Condition condition : conditions) {\r\n\t\t\tif (!condition.isFulfilled(context, player, source, target)) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn true;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/AttributeCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.SpellUtils;\r\nimport net.demilich.metastone.game.spells.desc.filter.Operation;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class AttributeCondition extends Condition {\r\n\r\n\tpublic AttributeCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tEntityReference entityReference = (EntityReference) desc.get(ConditionArg.TARGET);\r\n\t\tEntity entity = null;\r\n\t\tif (entityReference == null) {\r\n\t\t\tentity = target;\r\n\t\t} else {\r\n\t\t\tList<Entity> entities = context.resolveTarget(player, source, entityReference);\r\n\t\t\tif (entities == null || entities.isEmpty()) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tentity = entities.get(0);\r\n\t\t}\r\n\r\n\t\tAttribute attribute = (Attribute) desc.get(ConditionArg.ATTRIBUTE);\r\n\t\tOperation operation = (Operation) desc.get(ConditionArg.OPERATION);\r\n\t\tif (operation == null || operation == Operation.HAS) {\r\n\t\t\treturn entity.hasAttribute(attribute);\r\n\t\t}\r\n\r\n\t\tint targetValue = desc.getInt(ConditionArg.VALUE);\r\n\r\n\t\tint actualValue;\r\n\t\tif (attribute == Attribute.ATTACK) {\r\n\t\t\tif (entity instanceof Actor) {\r\n\t\t\t\tactualValue = ((Actor)entity).getAttack();\t\r\n\t\t\t} else {\r\n\t\t\t\tactualValue = entity.getAttributeValue(attribute);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t} else {\r\n\t\t\tactualValue = entity.getAttributeValue(attribute);\r\n\t\t}\r\n\r\n\t\treturn SpellUtils.evaluateOperation(operation, actualValue, targetValue);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/CardCountCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.SpellUtils;\r\nimport net.demilich.metastone.game.spells.TargetPlayer;\r\nimport net.demilich.metastone.game.spells.desc.filter.Operation;\r\n\r\npublic class CardCountCondition extends Condition {\r\n\r\n\tpublic CardCountCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tTargetPlayer targetPlayer = desc.contains(ConditionArg.TARGET_PLAYER) ? (TargetPlayer) desc.get(ConditionArg.TARGET_PLAYER)\r\n\t\t\t\t: TargetPlayer.SELF;\r\n\r\n\t\tint cardCount = 0;\r\n\t\tswitch (targetPlayer) {\r\n\t\tcase BOTH:\r\n\t\t\tcardCount = player.getHand().getCount() + context.getOpponent(player).getHand().getCount();\r\n\t\t\tbreak;\r\n\t\tcase OPPONENT:\r\n\t\t\tcardCount = context.getOpponent(player).getHand().getCount();\r\n\t\t\tbreak;\r\n\t\tcase SELF:\r\n\t\t\tcardCount = player.getHand().getCount();\r\n\t\t\tbreak;\r\n\t\tcase ACTIVE:\r\n\t\t\tcardCount = context.getActivePlayer().getHand().getCount();\r\n\t\t\tbreak;\r\n\t\tcase INACTIVE:\r\n\t\t\tcardCount = context.getOpponent(context.getActivePlayer()).getHand().getCount();\r\n\t\t\tbreak;\r\n\t\tdefault:\r\n\t\t\tbreak;\r\n\r\n\t\t}\r\n\t\tint targetValue = desc.getInt(ConditionArg.VALUE);\r\n\t\tOperation operation = (Operation) desc.get(ConditionArg.OPERATION);\r\n\t\treturn SpellUtils.evaluateOperation(operation, cardCount, targetValue);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/CardPropertyCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.CardType;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class CardPropertyCondition extends Condition {\r\n\r\n\tpublic CardPropertyCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tCard card = null;\r\n\t\tif (target instanceof Card) {\r\n\t\t\tcard = (Card) target;\r\n\t\t} else if (target instanceof Actor) {\r\n\t\t\tActor actor = (Actor) target;\r\n\t\t\tcard = actor.getSourceCard();\r\n\t\t}\r\n\r\n\t\tif (card == null) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tCardType cardType = (CardType) desc.get(ConditionArg.CARD_TYPE);\r\n\t\tif (cardType != null && !card.getCardType().isCardType(cardType)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tString cardId = (String) desc.get(ConditionArg.CARD_ID);\r\n\t\tif (cardId != null && !card.getCardId().contains(cardId)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/ComboCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class ComboCondition extends Condition {\r\n\r\n\tpublic ComboCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\treturn player.hasAttribute(Attribute.COMBO);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/ComparisonCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.SpellUtils;\r\nimport net.demilich.metastone.game.spells.desc.filter.Operation;\r\n\r\npublic class ComparisonCondition extends Condition {\r\n\r\n\tpublic ComparisonCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tint value1 = desc.getValue(ConditionArg.VALUE1, context, player, target, null, 0);\r\n\t\tint value2 = desc.getValue(ConditionArg.VALUE2, context, player, target, null, 0);\r\n\t\tOperation operation = (Operation) desc.get(ConditionArg.OPERATION);\r\n\t\treturn SpellUtils.evaluateOperation(operation, value1, value2);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/Condition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic abstract class Condition {\r\n\r\n\tprivate final ConditionDesc desc;\r\n\r\n\tpublic Condition(ConditionDesc desc) {\r\n\t\tthis.desc = desc;\r\n\t}\r\n\r\n\tprotected abstract boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target);\r\n\r\n\tpublic boolean isFulfilled(GameContext context, Player player, Entity source, Entity target) {\r\n\t\tboolean invert = desc.getBool(ConditionArg.INVERT);\r\n\t\treturn isFulfilled(context, player, desc, source, target) != invert;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/ConditionArg.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\npublic enum ConditionArg {\r\n\tCLASS,\r\n\tRACE,\r\n\tVALUE,\r\n\tVALUE1,\r\n\tVALUE2,\r\n\tTARGET,\r\n\tTARGET_PLAYER,\r\n\tOPERATION,\r\n\tATTRIBUTE,\r\n\tCARD_TYPE,\r\n\tCONDITIONS,\r\n\tCARD_ID,\r\n\tCARD_IDS,\r\n\tINVERT,\r\n\tCARD_FILTER,\r\n\tFILTER,\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/ConditionDesc.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport java.lang.reflect.InvocationTargetException;\r\nimport java.util.EnumMap;\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.cards.desc.Desc;\r\n\r\npublic class ConditionDesc extends Desc<ConditionArg> {\r\n\r\n\tpublic static Map<ConditionArg, Object> build(Class<? extends Condition> conditionClass) {\r\n\t\tfinal Map<ConditionArg, Object> arguments = new EnumMap<>(ConditionArg.class);\r\n\t\targuments.put(ConditionArg.CLASS, conditionClass);\r\n\t\treturn arguments;\r\n\t}\r\n\r\n\tpublic ConditionDesc(Map<ConditionArg, Object> arguments) {\r\n\t\tsuper(arguments);\r\n\t}\r\n\r\n\tpublic Condition create() {\r\n\t\tClass<? extends Condition> conditionClass = getConditionClass();\r\n\t\ttry {\r\n\t\t\treturn conditionClass.getConstructor(ConditionDesc.class).newInstance(this);\r\n\t\t} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException\r\n\t\t\t\t| NoSuchMethodException | SecurityException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\tpublic Class<? extends Condition> getConditionClass() {\r\n\t\treturn (Class<? extends Condition>) get(ConditionArg.CLASS);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/ControlsSecretCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class ControlsSecretCondition extends Condition {\r\n\r\n\tpublic ControlsSecretCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\treturn (player.getSecrets().size() - player.getQuests().size()) > 0;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/DeckContainsCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\n\r\npublic class DeckContainsCondition extends Condition {\r\n\r\n\tpublic DeckContainsCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(ConditionArg.CARD_FILTER);\r\n\t\tfor (Card card : player.getDeck()) {\r\n\t\t\tif (cardFilter == null || cardFilter.matches(context, player, card)) {\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/GraveyardContainsCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class GraveyardContainsCondition extends Condition {\r\n\r\n\tpublic GraveyardContainsCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tString cardId = (String) desc.get(ConditionArg.CARD_ID);\r\n\t\tfor (Entity deadEntity : player.getGraveyard()) {\r\n\t\t\tCard card = null;\r\n\t\t\tif (deadEntity instanceof Actor) {\r\n\t\t\t\tActor actor = (Actor) deadEntity;\r\n\t\t\t\tcard = actor.getSourceCard();\r\n\t\t\t} else if (deadEntity instanceof Card) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tif (card.getCardId().equals(cardId)) {\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/GraveyardCountCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.minions.Minion;\r\nimport net.demilich.metastone.game.spells.SpellUtils;\r\nimport net.demilich.metastone.game.spells.desc.filter.Operation;\r\n\r\npublic class GraveyardCountCondition extends Condition {\r\n\r\n\tpublic GraveyardCountCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tint count = 0;\r\n\t\tfor (Entity deadEntity : player.getGraveyard()) {\r\n\t\t\tif (deadEntity instanceof Minion) {\r\n\t\t\t\tcount++;\r\n\t\t\t} else if (deadEntity instanceof Card) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t}\r\n\t\tint targetValue = desc.getInt(ConditionArg.VALUE);\r\n\t\tOperation operation = (Operation) desc.get(ConditionArg.OPERATION);\r\n\t\treturn SpellUtils.evaluateOperation(operation, count, targetValue);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/HasAttackedCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class HasAttackedCondition extends Condition {\r\n\r\n\tpublic HasAttackedCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tEntityReference entityReference = (EntityReference) desc.get(ConditionArg.TARGET);\r\n\t\tEntity entity = null;\r\n\t\tif (entityReference == null) {\r\n\t\t\tentity = target;\r\n\t\t} else {\r\n\t\t\tList<Entity> entities = context.resolveTarget(player, source, entityReference);\r\n\t\t\tif (entities == null || entities.isEmpty()) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tentity = entities.get(0);\r\n\t\t}\r\n\t\tif (entity != null && entity instanceof Actor) {\r\n\t\t\tActor actor = (Actor) entity;\r\n\t\t\treturn actor.getMaxNumberOfAttacks() > actor.getAttributeValue(Attribute.NUMBER_OF_ATTACKS);\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/HasEntitiesOnBoardCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class HasEntitiesOnBoardCondition extends Condition {\r\n\r\n\tpublic HasEntitiesOnBoardCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tEntityReference entityReference = (EntityReference) desc.get(ConditionArg.TARGET);\r\n\t\tEntity entity = null;\r\n\t\tif (entityReference == null) {\r\n\t\t\tentity = target;\r\n\t\t} else {\r\n\t\t\tList<Entity> entities = context.resolveTarget(player, entity, entityReference);\r\n\t\t\tif (entities == null || entities.isEmpty()) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tentity = entities.get(0);\r\n\t\t}\r\n\t\tString[] cardNames = (String[]) desc.get(ConditionArg.CARD_IDS);\r\n\t\t\r\n\t\tList<Actor> checkedActors = new ArrayList<Actor>(player.getSummons());\r\n\t\tif (player.getHero().getWeapon() != null) {\r\n\t\t\tcheckedActors.add(player.getHero().getWeapon());\r\n\t\t}\r\n\t\tcheckedActors.add(player.getHero());\r\n\t\t\r\n\t\tfor (String cardName : cardNames) {\r\n\t\t\tboolean check = false;\r\n\t\t\tfor (Actor actor : checkedActors) {\r\n\t\t\t\tif (actor.getSourceCard().getCardId().equalsIgnoreCase(cardName)) {\r\n\t\t\t\t\tcheck = true;\r\n\t\t\t\t\tcheckedActors.remove(actor);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (!check) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn true;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/HasEntityCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class HasEntityCondition extends Condition {\r\n\r\n\tpublic HasEntityCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tEntityReference targetReference = (EntityReference) desc.get(ConditionArg.TARGET);\r\n\t\tEntityFilter filter = (EntityFilter) desc.get(ConditionArg.FILTER);\r\n\t\tfor (Entity entity : context.resolveTarget(player, source, targetReference)) {\r\n\t\t\tif (filter == null || filter.matches(context, player, entity)) {\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/HasHeroPowerCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.heroes.powers.HeroPower;\r\n\r\npublic class HasHeroPowerCondition extends Condition {\r\n\r\n\tpublic HasHeroPowerCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tHeroPower heroPower = player.getHero().getHeroPower();\r\n\t\tif (heroPower.getCardId() == null) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tString cardName = (String) desc.get(ConditionArg.CARD_ID);\r\n\t\treturn heroPower.getCardId().equalsIgnoreCase(cardName);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/HasWeaponCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.weapons.Weapon;\r\n\r\npublic class HasWeaponCondition extends Condition {\r\n\r\n\tpublic HasWeaponCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tWeapon weapon = player.getHero().getWeapon();\r\n\t\tif (weapon == null || weapon.isBroken()) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tString cardId = (String) desc.get(ConditionArg.CARD_ID);\r\n\t\tif (cardId != null && !weapon.getSourceCard().getCardId().contains(cardId)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\treturn true;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/HighlanderDeckCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.spells.SpellUtils;\n\npublic class HighlanderDeckCondition extends Condition {\n\n\tpublic HighlanderDeckCondition(ConditionDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\n\t\treturn SpellUtils.highlanderDeck(player);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/HoldsCardCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\n\r\npublic class HoldsCardCondition extends Condition {\r\n\r\n\tpublic HoldsCardCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(ConditionArg.CARD_FILTER);\r\n\t\tfor (Card card : player.getHand()) {\r\n\t\t\tif (cardFilter == null || cardFilter.matches(context, player, card)) {\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/IsDamagedCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class IsDamagedCondition extends Condition {\r\n\r\n\tpublic IsDamagedCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\treturn ((Actor) target).isWounded();\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/IsDeadCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class IsDeadCondition extends Condition {\r\n\r\n\tpublic IsDeadCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tEntityReference entityReference = (EntityReference) desc.get(ConditionArg.TARGET);\r\n\t\tEntity entity = null;\r\n\t\tif (entityReference == null) {\r\n\t\t\tentity = target;\r\n\t\t} else {\r\n\t\t\tList<Entity> entities = context.resolveTarget(player, source, entityReference);\r\n\t\t\tif (entities == null || entities.isEmpty()) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tentity = entities.get(0);\r\n\t\t}\r\n\t\treturn entity.isDestroyed();\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/ManaCostCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class ManaCostCondition extends Condition {\r\n\r\n\tpublic ManaCostCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tif (!(target instanceof Card)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tCard card = (Card) target;\r\n\t\tint value = desc.getInt(ConditionArg.VALUE);\r\n\t\treturn context.getLogic().getModifiedManaCost(player, card) == value;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/ManaMaxedCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.logic.GameLogic;\r\n\r\npublic class ManaMaxedCondition extends Condition {\r\n\r\n\tpublic ManaMaxedCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\treturn (player.getMaxMana() >= GameLogic.MAX_MANA || player.getMana() >= GameLogic.MAX_MANA);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/MinionCountCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.SpellUtils;\r\nimport net.demilich.metastone.game.spells.TargetPlayer;\r\nimport net.demilich.metastone.game.spells.desc.filter.Operation;\r\n\r\npublic class MinionCountCondition extends Condition {\r\n\r\n\tpublic MinionCountCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tTargetPlayer targetPlayer = desc.contains(ConditionArg.TARGET_PLAYER) ? (TargetPlayer) desc.get(ConditionArg.TARGET_PLAYER)\r\n\t\t\t\t: TargetPlayer.SELF;\r\n\r\n\t\tint minionCount = 0;\r\n\t\tswitch (targetPlayer) {\r\n\t\tcase BOTH:\r\n\t\t\tminionCount = context.getTotalMinionCount();\r\n\t\t\tbreak;\r\n\t\tcase OPPONENT:\r\n\t\t\tminionCount = context.getOpponent(player).getMinions().size();\r\n\t\t\tbreak;\r\n\t\tcase SELF:\r\n\t\tcase OWNER:\r\n\t\t\tminionCount = player.getMinions().size();\r\n\t\t\tbreak;\r\n\t\tdefault:\r\n\t\t\tbreak;\r\n\t\t}\r\n\t\t\r\n\t\tint targetValue = desc.getInt(ConditionArg.VALUE);\r\n\t\tOperation operation = (Operation) desc.get(ConditionArg.OPERATION);\r\n\t\treturn SpellUtils.evaluateOperation(operation, minionCount, targetValue);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/MinionOnBoardCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.minions.Summon;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\n\r\npublic class MinionOnBoardCondition extends Condition {\r\n\r\n\tpublic MinionOnBoardCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tEntityFilter cardFilter = (EntityFilter) desc.get(ConditionArg.CARD_FILTER);\r\n\t\tint value = desc.contains(ConditionArg.VALUE) ? desc.getInt(ConditionArg.VALUE) : 1;\r\n\r\n\t\tint count = 0;\r\n\t\tfor (Summon summon : player.getSummons()) {\r\n\t\t\tif ((cardFilter == null || cardFilter.matches(context, player, summon)) && !context.getSummonReferenceStack().contains(summon.getReference())) {\r\n\t\t\t\tcount++;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn count >= value;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/OrCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class OrCondition extends Condition {\r\n\r\n\tpublic OrCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tCondition[] conditions = (Condition[]) desc.get(ConditionArg.CONDITIONS);\r\n\t\tfor (Condition condition : conditions) {\r\n\t\t\tif (condition.isFulfilled(context, player, source, target)) {\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/OwnedByPlayerCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.TargetPlayer;\r\n\r\npublic class OwnedByPlayerCondition extends Condition {\r\n\r\n\tpublic OwnedByPlayerCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tTargetPlayer targetPlayer = (TargetPlayer) desc.get(ConditionArg.TARGET_PLAYER);\r\n\t\tswitch (targetPlayer) {\r\n\t\tcase ACTIVE:\r\n\t\t\treturn context.getActivePlayer().getId() == player.getId();\r\n\t\tcase INACTIVE:\r\n\t\t\treturn context.getActivePlayer().getId() != player.getId();\r\n\t\tcase BOTH:\r\n\t\t\treturn true;\r\n\t\tcase OPPONENT:\r\n\t\t\treturn target.getOwner() != player.getId();\r\n\t\tcase SELF:\r\n\t\t\treturn target.getOwner() == player.getId();\r\n\t\tdefault:\r\n\t\t\tbreak;\r\n\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/RaceCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.minions.Race;\r\n\r\n/**\r\n * \r\n * This not a programmers nightmare poured into a class; rather a condition if\r\n * the specified target is of a certain race\r\n *\r\n */\r\npublic class RaceCondition extends Condition {\r\n\r\n\tpublic RaceCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\tRace race = (Race) desc.get(ConditionArg.RACE);\r\n\t\treturn target.getAttribute(Attribute.RACE) == race;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/condition/RandomCondition.java",
    "content": "package net.demilich.metastone.game.spells.desc.condition;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class RandomCondition extends Condition {\r\n\r\n\tpublic RandomCondition(ConditionDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean isFulfilled(GameContext context, Player player, ConditionDesc desc, Entity source, Entity target) {\r\n\t\treturn context.getLogic().randomBool();\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/filter/AndFilter.java",
    "content": "package net.demilich.metastone.game.spells.desc.filter;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class AndFilter extends EntityFilter {\n\n\tpublic AndFilter(FilterDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean test(GameContext context, Player player, Entity entity) {\n\t\tEntityFilter[] filters = (EntityFilter[]) desc.get(FilterArg.FILTERS);\n\t\tfor (EntityFilter filter : filters) {\n\t\t\tif (!filter.matches(context, player, entity)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/filter/AttributeFilter.java",
    "content": "package net.demilich.metastone.game.spells.desc.filter;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.weapons.Weapon;\r\nimport net.demilich.metastone.game.spells.SpellUtils;\r\n\r\npublic class AttributeFilter extends EntityFilter {\r\n\r\n\tpublic AttributeFilter(FilterDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean test(GameContext context, Player player, Entity entity) {\r\n\t\tAttribute attribute = (Attribute) desc.get(FilterArg.ATTRIBUTE);\r\n\t\tOperation operation = (Operation) desc.get(FilterArg.OPERATION);\r\n\t\tif (operation == Operation.HAS) {\r\n\t\t\treturn entity.hasAttribute(attribute);\r\n\t\t}\r\n\r\n\t\tint targetValue = desc.getInt(FilterArg.VALUE);\r\n\t\t\r\n\t\tint actualValue = -1;\r\n\t\tif (attribute == Attribute.ATTACK) {\r\n\t\t\tif (entity instanceof Weapon) {\r\n\t\t\t\tactualValue = ((Weapon) entity).getWeaponDamage();\r\n\t\t\t} else {\r\n\t\t\t\tactualValue = ((Actor) entity).getAttack();\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tactualValue = entity.getAttributeValue(attribute);\r\n\t\t}\r\n\r\n\t\treturn SpellUtils.evaluateOperation(operation, actualValue, targetValue);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/filter/CardFilter.java",
    "content": "package net.demilich.metastone.game.spells.desc.filter;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.CardType;\r\nimport net.demilich.metastone.game.cards.Rarity;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\r\nimport net.demilich.metastone.game.entities.minions.Race;\r\nimport net.demilich.metastone.game.spells.SpellUtils;\r\n\r\npublic class CardFilter extends EntityFilter {\r\n\r\n\tpublic CardFilter(FilterDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\t\r\n\tprivate boolean heroClassTest(GameContext context, Player player, Card card, HeroClass heroClass) {\r\n\t\tif (heroClass == HeroClass.OPPONENT) {\r\n\t\t\theroClass = context.getOpponent(player).getHero().getHeroClass();\r\n\t\t} else if (heroClass == HeroClass.SELF) {\r\n\t\t\theroClass = player.getHero().getHeroClass();\r\n\t\t}\r\n\t\t\r\n\t\tif (heroClass != null && card.hasHeroClass(heroClass)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\t\r\n\t\treturn true;\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean test(GameContext context, Player player, Entity entity) {\r\n\t\tCard card = null;\r\n\t\tif (entity instanceof Card) {\r\n\t\t\tcard = (Card) entity;\r\n\t\t} else if (entity instanceof Actor) {\r\n\t\t\tActor actor = (Actor) entity;\r\n\t\t\tcard = actor.getSourceCard();\r\n\t\t} else {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tCardType cardType = (CardType) desc.get(FilterArg.CARD_TYPE);\r\n\t\tif (cardType != null && !card.getCardType().isCardType(cardType)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tRace race = (Race) desc.get(FilterArg.RACE);\r\n\t\tif (race != null && race != card.getAttribute(Attribute.RACE)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\t\r\n\t\tHeroClass[] heroClasses = (HeroClass[]) desc.get(FilterArg.HERO_CLASSES);\r\n\t\tif (heroClasses != null && heroClasses.length > 0) {\r\n\t\t\tboolean test = false;\r\n\t\t\tfor (HeroClass heroClass : heroClasses) {\r\n\t\t\t\ttest |= !heroClassTest(context, player, card, heroClass);\r\n\t\t\t}\r\n\t\t\tif (!test) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tHeroClass heroClass = (HeroClass) desc.get(FilterArg.HERO_CLASS);\r\n\t\tif (heroClass != null && heroClassTest(context, player, card, heroClass)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\t\r\n\t\tif (desc.contains(FilterArg.MANA_COST)) {\r\n\t\t\tint manaCost = desc.getValue(FilterArg.MANA_COST, context, player, null, null, 0);\r\n\t\t\tif (manaCost != card.getBaseManaCost()) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\t\tRarity rarity = (Rarity) desc.get(FilterArg.RARITY);\r\n\t\tif (rarity != null && !card.getRarity().isRarity(rarity)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\t\r\n\t\tif (desc.contains(FilterArg.ATTRIBUTE) && desc.contains(FilterArg.OPERATION)) {\r\n\t\t\tAttribute attribute = (Attribute) desc.get(FilterArg.ATTRIBUTE);\r\n\t\t\tOperation operation = (Operation) desc.get(FilterArg.OPERATION);\r\n\t\t\tif (operation == Operation.HAS || operation == null) {\r\n\t\t\t\treturn card.hasAttribute(attribute);\r\n\t\t\t}\r\n\t\r\n\t\t\tint targetValue = desc.getInt(FilterArg.VALUE);\r\n\t\t\tint actualValue = card.getAttributeValue(attribute);\r\n\t\r\n\t\t\treturn SpellUtils.evaluateOperation(operation, actualValue, targetValue);\r\n\t\t}\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/filter/DamagedFilter.java",
    "content": "package net.demilich.metastone.game.spells.desc.filter;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class DamagedFilter extends EntityFilter {\r\n\r\n\tpublic DamagedFilter(FilterDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean test(GameContext context, Player player, Entity entity) {\r\n\t\tif (entity instanceof Actor) {\r\n\t\t\treturn ((Actor) entity).isWounded();\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/filter/EntityFilter.java",
    "content": "package net.demilich.metastone.game.spells.desc.filter;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.TargetPlayer;\r\n\r\npublic abstract class EntityFilter {\r\n\r\n\tprotected final FilterDesc desc;\r\n\r\n\tpublic EntityFilter(FilterDesc desc) {\r\n\t\tthis.desc = desc;\r\n\t}\r\n\t\r\n\tpublic Object getArg(FilterArg arg) {\r\n\t\treturn desc.get(arg);\r\n\t}\r\n\t\r\n\tpublic boolean hasArg(FilterArg arg) {\r\n\t\treturn desc.contains(arg);\r\n\t}\r\n\r\n\tpublic boolean matches(GameContext context, Player player, Entity entity) {\r\n\t\tboolean invert = desc.getBool(FilterArg.INVERT);\r\n\t\tTargetPlayer targetPlayer = (TargetPlayer) desc.get(FilterArg.TARGET_PLAYER);\r\n\t\tif (targetPlayer == null) {\r\n\t\t\ttargetPlayer = TargetPlayer.SELF;\r\n\t\t}\r\n\t\tPlayer providingPlayer = null;\r\n\t\tswitch (targetPlayer) {\r\n\t\tcase ACTIVE:\r\n\t\t\tprovidingPlayer = context.getActivePlayer();\r\n\t\t\tbreak;\r\n\t\tcase BOTH:\r\n\t\t\tboolean test = false;\r\n\t\t\tfor (Player selectedPlayer : context.getPlayers()) {\r\n\t\t\t\ttest |= (this.test(context, selectedPlayer, entity) != invert);\r\n\t\t\t}\r\n\t\t\treturn test;\r\n\t\tcase INACTIVE:\r\n\t\t\tprovidingPlayer = context.getOpponent(context.getActivePlayer());\r\n\t\t\tbreak;\r\n\t\tcase OPPONENT:\r\n\t\t\tprovidingPlayer = context.getOpponent(player);\r\n\t\t\tbreak;\r\n\t\tcase OWNER:\r\n\t\t\tprovidingPlayer = context.getPlayer(entity.getOwner());\r\n\t\t\tbreak;\r\n\t\tcase SELF:\r\n\t\tdefault:\r\n\t\t\tprovidingPlayer = player;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t\treturn this.test(context, providingPlayer, entity) != invert;\r\n\t}\r\n\r\n\tprotected abstract boolean test(GameContext context, Player player, Entity entity);\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/filter/FilterArg.java",
    "content": "package net.demilich.metastone.game.spells.desc.filter;\r\n\r\npublic enum FilterArg {\r\n\tCLASS,\r\n\tTARGET_PLAYER,\r\n\tVALUE,\r\n\tRACE,\r\n\tOPERATION,\r\n\tATTRIBUTE,\r\n\tCARD_TYPE,\r\n\tRARITY,\r\n\tMANA_COST,\r\n\tHERO_CLASS,\r\n\tHERO_CLASSES,\r\n\tCARD_ID,\r\n\tFILTERS,\r\n\tINVERT,\r\n\tTARGET\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/filter/FilterDesc.java",
    "content": "package net.demilich.metastone.game.spells.desc.filter;\r\n\r\nimport java.lang.reflect.InvocationTargetException;\r\nimport java.util.EnumMap;\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.cards.desc.Desc;\r\n\r\npublic class FilterDesc extends Desc<FilterArg> {\r\n\r\n\tpublic static Map<FilterArg, Object> build(Class<? extends EntityFilter> filterClass) {\r\n\t\tfinal Map<FilterArg, Object> arguments = new EnumMap<>(FilterArg.class);\r\n\t\targuments.put(FilterArg.CLASS, filterClass);\r\n\t\treturn arguments;\r\n\t}\r\n\r\n\tpublic FilterDesc(Map<FilterArg, Object> arguments) {\r\n\t\tsuper(arguments);\r\n\t}\r\n\r\n\tpublic EntityFilter create() {\r\n\t\tClass<? extends EntityFilter> filterClass = getFilterClass();\r\n\t\ttry {\r\n\t\t\treturn filterClass.getConstructor(FilterDesc.class).newInstance(this);\r\n\t\t} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException\r\n\t\t\t\t| NoSuchMethodException | SecurityException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\tpublic Class<? extends EntityFilter> getFilterClass() {\r\n\t\treturn (Class<? extends EntityFilter>) get(FilterArg.CLASS);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/filter/HighestAttributeFilter.java",
    "content": "package net.demilich.metastone.game.spells.desc.filter;\r\n\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class HighestAttributeFilter extends EntityFilter {\r\n\r\n\tpublic HighestAttributeFilter(FilterDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean test(GameContext context, Player player, Entity entity) {\r\n\t\tAttribute attribute = (Attribute) desc.get(FilterArg.ATTRIBUTE);\r\n\t\tEntityReference targetReference = (EntityReference) desc.get(FilterArg.TARGET);\r\n\t\tList<Entity> entities = context.resolveTarget(player, entity, targetReference);\r\n\t\tint highest = getHighestInList(entities, attribute);\r\n\t\treturn getAttributeValue(entity, attribute) >= highest;\r\n\t}\r\n\r\n\tprivate static int getAttributeValue(Entity entity, Attribute attribute) {\r\n\t\tif (attribute == Attribute.ATTACK) {\r\n\t\t\treturn ((Actor) entity).getAttack();\r\n\t\t}\r\n\t\treturn entity.getAttributeValue(attribute);\r\n\t}\r\n\r\n\tprivate static int getHighestInList(List<Entity> entities, Attribute attribute) {\r\n\t\tint highest = Integer.MIN_VALUE;\r\n\t\tfor (Entity entity : entities) {\r\n\t\t\tint attributeValue = getAttributeValue(entity, attribute);\r\n\t\t\tif (attributeValue > highest) {\r\n\t\t\t\thighest = attributeValue;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn highest;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/filter/InDeckFilter.java",
    "content": "package net.demilich.metastone.game.spells.desc.filter;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class InDeckFilter extends EntityFilter {\r\n\r\n\tpublic InDeckFilter(FilterDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\t\r\n\t@Override\r\n\tprotected boolean test(GameContext context, Player player, Entity entity) {\r\n\t\tCard card = null;\r\n\t\tif (entity instanceof Card) {\r\n\t\t\tcard = (Card) entity;\r\n\t\t} else if (entity instanceof Actor) {\r\n\t\t\tActor actor = (Actor) entity;\r\n\t\t\tcard = actor.getSourceCard();\r\n\t\t} else {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\treturn player.getDeck().containsCard(card);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/filter/InHandFilter.java",
    "content": "package net.demilich.metastone.game.spells.desc.filter;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class InHandFilter extends EntityFilter {\r\n\r\n\tpublic InHandFilter(FilterDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\t\r\n\t@Override\r\n\tprotected boolean test(GameContext context, Player player, Entity entity) {\r\n\t\tCard card = null;\r\n\t\tif (entity instanceof Card) {\r\n\t\t\tcard = (Card) entity;\r\n\t\t} else if (entity instanceof Actor) {\r\n\t\t\tActor actor = (Actor) entity;\r\n\t\t\tcard = actor.getSourceCard();\r\n\t\t} else {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\treturn player.getHand().containsCard(card);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/filter/Operation.java",
    "content": "package net.demilich.metastone.game.spells.desc.filter;\r\n\r\npublic enum Operation {\r\n\tHAS,\r\n\tEQUAL,\r\n\tLESS,\r\n\tLESS_OR_EQUAL,\r\n\tGREATER,\r\n\tGREATER_OR_EQUAL\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/filter/OrFilter.java",
    "content": "package net.demilich.metastone.game.spells.desc.filter;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\n\npublic class OrFilter extends EntityFilter {\n\n\tpublic OrFilter(FilterDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean test(GameContext context, Player player, Entity entity) {\n\t\tEntityFilter[] filters = (EntityFilter[]) desc.get(FilterArg.FILTERS);\n\t\tfor (EntityFilter filter : filters) {\n\t\t\tif (filter.matches(context, player, entity)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/filter/RaceFilter.java",
    "content": "package net.demilich.metastone.game.spells.desc.filter;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Race;\n\npublic class RaceFilter extends EntityFilter {\n\n\tpublic RaceFilter(FilterDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean test(GameContext context, Player player, Entity entity) {\n\t\tRace race = (Race) desc.get(FilterArg.RACE);\n\t\treturn entity.getAttribute(Attribute.RACE) == race;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/filter/SpecificCardFilter.java",
    "content": "package net.demilich.metastone.game.spells.desc.filter;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class SpecificCardFilter extends EntityFilter {\r\n\t\r\n\tpublic SpecificCardFilter(FilterDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean test(GameContext context, Player player, Entity entity) {\r\n\t\tString cardId = null;\r\n\t\tif (entity instanceof Card) {\r\n\t\t\tcardId = ((Card) entity).getCardId();\r\n\t\t} else if (entity instanceof Actor) {\r\n\t\t\tcardId = ((Actor) entity).getSourceCard().getCardId();\r\n\t\t}\r\n\r\n\t\tString requiredCardId = desc.getString(FilterArg.CARD_ID);\r\n\t\treturn cardId.equalsIgnoreCase(requiredCardId);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/manamodifier/CardCostModifierArg.java",
    "content": "package net.demilich.metastone.game.spells.desc.manamodifier;\r\n\r\npublic enum CardCostModifierArg {\r\n\tCLASS,\r\n\tCARD_TYPE,\r\n\tREQUIRED_ATTRIBUTE,\r\n\tEXPIRATION_TRIGGER,\r\n\tMIN_VALUE,\r\n\tVALUE,\r\n\tRACE,\r\n\tTARGET_PLAYER,\r\n\tTOGGLE_ON_TRIGGER,\r\n\tTOGGLE_OFF_TRIGGER,\r\n\tTARGET,\r\n\tOPERATION,\r\n\t\r\n\t// Internal use only\r\n\tCARD_IDS,\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/manamodifier/CardCostModifierDesc.java",
    "content": "package net.demilich.metastone.game.spells.desc.manamodifier;\r\n\r\nimport java.lang.reflect.InvocationTargetException;\r\nimport java.util.EnumMap;\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.cards.costmodifier.CardCostModifier;\r\nimport net.demilich.metastone.game.cards.desc.Desc;\r\nimport net.demilich.metastone.game.logic.CustomCloneable;\r\n\r\npublic class CardCostModifierDesc extends Desc<CardCostModifierArg> {\r\n\r\n\tpublic static Map<CardCostModifierArg, Object> build(Class<? extends CardCostModifier> manaModifierClass) {\r\n\t\tfinal Map<CardCostModifierArg, Object> arguments = new EnumMap<>(CardCostModifierArg.class);\r\n\t\targuments.put(CardCostModifierArg.CLASS, manaModifierClass);\r\n\t\treturn arguments;\r\n\t}\r\n\r\n\tpublic CardCostModifierDesc(Map<CardCostModifierArg, Object> arguments) {\r\n\t\tsuper(arguments);\r\n\t}\r\n\t\r\n\tpublic CardCostModifierDesc addArg(CardCostModifierArg cardCostModififerArg, Object value) {\r\n\t\tCardCostModifierDesc clone = clone();\r\n\t\tclone.arguments.put(cardCostModififerArg, value);\r\n\t\treturn clone;\r\n\t}\r\n\t\r\n\tpublic CardCostModifierDesc removeArg(CardCostModifierArg cardCostModififerArg) {\r\n\t\tCardCostModifierDesc clone = clone();\r\n\t\tclone.arguments.remove(cardCostModififerArg);\r\n\t\treturn clone;\r\n\t}\r\n\t\r\n\t@Override\r\n\tpublic CardCostModifierDesc clone() {\r\n\t\tCardCostModifierDesc clone = new CardCostModifierDesc(build(getManaModifierClass()));\r\n\t\tfor (CardCostModifierArg cardCostModififerArg : arguments.keySet()) {\r\n\t\t\tObject value = arguments.get(cardCostModififerArg);\r\n\t\t\tif (value instanceof CustomCloneable) {\r\n\t\t\t\tCustomCloneable cloneable = (CustomCloneable) value;\r\n\t\t\t\tclone.arguments.put(cardCostModififerArg, cloneable.clone());\r\n\t\t\t} else {\r\n\t\t\t\tclone.arguments.put(cardCostModififerArg, value);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn clone;\r\n\t}\r\n\r\n\tpublic CardCostModifier create() {\r\n\t\tClass<? extends CardCostModifier> manaModifierClass = getManaModifierClass();\r\n\t\ttry {\r\n\t\t\treturn manaModifierClass.getConstructor(CardCostModifierDesc.class).newInstance(this);\r\n\t\t} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException\r\n\t\t\t\t| NoSuchMethodException | SecurityException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\tpublic Class<? extends CardCostModifier> getManaModifierClass() {\r\n\t\treturn (Class<? extends CardCostModifier>) get(CardCostModifierArg.CLASS);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String toString() {\r\n\t\tString result = \"[CardCostModifierDesc arguments= {\\n\";\r\n\t\tfor (CardCostModifierArg cardCostModififerArg : arguments.keySet()) {\r\n\t\t\tresult += \"\\t\" + cardCostModififerArg + \": \" + arguments.get(cardCostModififerArg) + \"\\n\";\r\n\t\t}\r\n\t\tresult += \"}\";\r\n\t\treturn result;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/source/CardSource.java",
    "content": "package net.demilich.metastone.game.spells.desc.source;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.CardCollection;\r\nimport net.demilich.metastone.game.spells.TargetPlayer;\r\n\r\npublic abstract class CardSource {\r\n\r\n\tprotected final SourceDesc desc;\r\n\r\n\tpublic CardSource(SourceDesc desc) {\r\n\t\tthis.desc = desc;\r\n\t}\r\n\t\r\n\tpublic Object getArg(SourceArg arg) {\r\n\t\treturn desc.get(arg);\r\n\t}\r\n\t\r\n\tpublic boolean hasArg(SourceArg arg) {\r\n\t\treturn desc.contains(arg);\r\n\t}\r\n\r\n\tpublic CardCollection getCards(GameContext context, Player player) {\r\n\t\tTargetPlayer targetPlayer = (TargetPlayer) desc.get(SourceArg.TARGET_PLAYER);\r\n\t\tif (targetPlayer == null) {\r\n\t\t\ttargetPlayer = TargetPlayer.SELF;\r\n\t\t}\r\n\t\tPlayer providingPlayer = null;\r\n\t\tswitch (targetPlayer) {\r\n\t\tcase ACTIVE:\r\n\t\t\tprovidingPlayer = context.getActivePlayer();\r\n\t\t\tbreak;\r\n\t\tcase BOTH:\r\n\t\t\tCardCollection cards = new CardCollection();\r\n\t\t\tfor (Player selectedPlayer : context.getPlayers()) {\r\n\t\t\t\tcards.addAll(this.match(context, selectedPlayer));\r\n\t\t\t}\r\n\t\t\treturn cards;\r\n\t\tcase INACTIVE:\r\n\t\t\tprovidingPlayer = context.getOpponent(context.getActivePlayer());\r\n\t\t\tbreak;\r\n\t\tcase OPPONENT:\r\n\t\t\tprovidingPlayer = context.getOpponent(player);\r\n\t\t\tbreak;\r\n\t\tcase OWNER:\r\n\t\tcase SELF:\r\n\t\tdefault:\r\n\t\t\tprovidingPlayer = player;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t\treturn this.match(context, providingPlayer);\r\n\t}\r\n\r\n\tprotected abstract CardCollection match(GameContext context, Player player);\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/source/DeckSource.java",
    "content": "package net.demilich.metastone.game.spells.desc.source;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.CardCollection;\r\n\r\npublic class DeckSource extends CardSource {\r\n\r\n\tpublic DeckSource(SourceDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected CardCollection match(GameContext context, Player player) {\r\n\t\treturn player.getDeck().clone();\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/source/DefaultSource.java",
    "content": "package net.demilich.metastone.game.spells.desc.source;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.CardCatalogue;\r\nimport net.demilich.metastone.game.cards.CardCollection;\r\n\r\npublic class DefaultSource extends CardSource {\r\n\r\n\tpublic DefaultSource(SourceDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected CardCollection match(GameContext context, Player player) {\r\n\t\treturn CardCatalogue.query(context.getDeckFormat());\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/source/HandSource.java",
    "content": "package net.demilich.metastone.game.spells.desc.source;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.CardCollection;\r\n\r\npublic class HandSource extends CardSource {\r\n\r\n\tpublic HandSource(SourceDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected CardCollection match(GameContext context, Player player) {\r\n\t\treturn player.getHand().clone();\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/source/SourceArg.java",
    "content": "package net.demilich.metastone.game.spells.desc.source;\r\n\r\npublic enum SourceArg {\r\n\tCLASS,\r\n\tTARGET_PLAYER,\r\n\tSOURCE,\r\n\tINVERT,\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/source/SourceDesc.java",
    "content": "package net.demilich.metastone.game.spells.desc.source;\r\n\r\nimport java.lang.reflect.InvocationTargetException;\r\nimport java.util.EnumMap;\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.cards.desc.Desc;\r\n\r\npublic class SourceDesc extends Desc<SourceArg> {\r\n\r\n\tpublic static Map<SourceArg, Object> build(Class<? extends CardSource> cardSourceClass) {\r\n\t\tfinal Map<SourceArg, Object> arguments = new EnumMap<>(SourceArg.class);\r\n\t\targuments.put(SourceArg.CLASS, cardSourceClass);\r\n\t\treturn arguments;\r\n\t}\r\n\r\n\tpublic SourceDesc(Map<SourceArg, Object> arguments) {\r\n\t\tsuper(arguments);\r\n\t}\r\n\r\n\tpublic CardSource create() {\r\n\t\tClass<? extends CardSource> cardSourceClass = getSourceClass();\r\n\t\ttry {\r\n\t\t\treturn cardSourceClass.getConstructor(SourceDesc.class).newInstance(this);\r\n\t\t} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException\r\n\t\t\t\t| NoSuchMethodException | SecurityException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\tpublic Class<? extends CardSource> getSourceClass() {\r\n\t\treturn (Class<? extends CardSource>) get(SourceArg.CLASS);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/trigger/EventTriggerArg.java",
    "content": "package net.demilich.metastone.game.spells.desc.trigger;\r\n\r\npublic enum EventTriggerArg {\r\n\tCLASS,\r\n\r\n\tACTION_TYPE,\r\n\tCARD_TYPE,\r\n\tFIRE_CONDITION,\r\n\tHOST_TARGET_TYPE,\r\n\tQUEUE_CONDITION,\r\n\tRACE,\r\n\tREQUIRED_ATTRIBUTE,\r\n\tSOURCE_ENTITY_TYPE,\r\n\tSOURCE_PLAYER,\r\n\tSOURCE_TYPE,\r\n\tTARGET_ENTITY_TYPE,\r\n\tTARGET_PLAYER,\r\n\tTARGET,\r\n\tVALUE,\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/trigger/EventTriggerDesc.java",
    "content": "package net.demilich.metastone.game.spells.desc.trigger;\r\n\r\nimport java.lang.reflect.InvocationTargetException;\r\nimport java.util.EnumMap;\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.cards.desc.Desc;\r\nimport net.demilich.metastone.game.spells.TargetPlayer;\r\nimport net.demilich.metastone.game.spells.trigger.GameEventTrigger;\r\n\r\npublic class EventTriggerDesc extends Desc<EventTriggerArg> {\r\n\r\n\tpublic static Map<EventTriggerArg, Object> build(Class<? extends GameEventTrigger> triggerClass) {\r\n\t\tfinal Map<EventTriggerArg, Object> arguments = new EnumMap<>(EventTriggerArg.class);\r\n\t\targuments.put(EventTriggerArg.CLASS, triggerClass);\r\n\t\treturn arguments;\r\n\t}\r\n\r\n\tpublic static EventTriggerDesc createEmpty(Class<? extends GameEventTrigger> triggerClass) {\r\n\t\treturn new EventTriggerDesc(EventTriggerDesc.build(triggerClass));\r\n\t}\r\n\r\n\tpublic EventTriggerDesc(Map<EventTriggerArg, Object> arguments) {\r\n\t\tsuper(arguments);\r\n\t}\r\n\r\n\tpublic GameEventTrigger create() {\r\n\t\tClass<? extends GameEventTrigger> triggerClass = getTriggerClass();\r\n\t\ttry {\r\n\t\t\treturn triggerClass.getConstructor(EventTriggerDesc.class).newInstance(this);\r\n\t\t} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException\r\n\t\t\t\t| NoSuchMethodException | SecurityException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\tpublic TargetPlayer getSourcePlayer() {\r\n\t\treturn (TargetPlayer) get(EventTriggerArg.SOURCE_PLAYER);\r\n\t}\r\n\r\n\tpublic TargetPlayer getTargetPlayer() {\r\n\t\treturn (TargetPlayer) get(EventTriggerArg.TARGET_PLAYER);\r\n\t}\r\n\r\n\tpublic int getTriggerCount() {\r\n\t\treturn getInt(EventTriggerArg.VALUE);\r\n\t}\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\tpublic Class<? extends GameEventTrigger> getTriggerClass() {\r\n\t\treturn (Class<? extends GameEventTrigger>) get(EventTriggerArg.CLASS);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/trigger/EventTriggerDeserializer.java",
    "content": "package net.demilich.metastone.game.spells.desc.trigger;\r\n\r\nimport java.lang.reflect.Type;\r\nimport java.util.Map;\r\n\r\nimport com.google.gson.JsonDeserializationContext;\r\nimport com.google.gson.JsonDeserializer;\r\nimport com.google.gson.JsonElement;\r\nimport com.google.gson.JsonObject;\r\nimport com.google.gson.JsonParseException;\r\n\r\nimport net.demilich.metastone.game.cards.desc.ParseUtils;\r\nimport net.demilich.metastone.game.cards.desc.ParseValueType;\r\nimport net.demilich.metastone.game.spells.trigger.GameEventTrigger;\r\n\r\npublic class EventTriggerDeserializer implements JsonDeserializer<EventTriggerDesc> {\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\t@Override\r\n\tpublic EventTriggerDesc deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\r\n\t\tif (!(json instanceof JsonObject)) {\r\n\t\t\tthrow new JsonParseException(\"Trigger parser expected an JsonObject but found \" + json + \" instead\");\r\n\t\t}\r\n\t\tJsonObject jsonData = (JsonObject) json;\r\n\t\tString triggerClassName = GameEventTrigger.class.getPackage().getName() + \".\" + jsonData.get(\"class\").getAsString();\r\n\t\tClass<? extends GameEventTrigger> triggerClass;\r\n\t\ttry {\r\n\t\t\ttriggerClass = (Class<? extends GameEventTrigger>) Class.forName(triggerClassName);\r\n\t\t} catch (ClassNotFoundException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t\tthrow new JsonParseException(\"Trigger parser encountered an invalid class: \" + triggerClassName);\r\n\t\t}\r\n\t\tMap<EventTriggerArg, Object> arguments = EventTriggerDesc.build(triggerClass);\r\n\t\tparseArgument(EventTriggerArg.ACTION_TYPE, jsonData, arguments, ParseValueType.ACTION_TYPE);\r\n\t\tparseArgument(EventTriggerArg.CARD_TYPE, jsonData, arguments, ParseValueType.CARD_TYPE);\r\n\t\tparseArgument(EventTriggerArg.FIRE_CONDITION, jsonData, arguments, ParseValueType.CONDITION);\r\n\t\tparseArgument(EventTriggerArg.HOST_TARGET_TYPE, jsonData, arguments, ParseValueType.TARGET_TYPE);\r\n\t\tparseArgument(EventTriggerArg.QUEUE_CONDITION, jsonData, arguments, ParseValueType.CONDITION);\r\n\t\tparseArgument(EventTriggerArg.RACE, jsonData, arguments, ParseValueType.RACE);\r\n\t\tparseArgument(EventTriggerArg.REQUIRED_ATTRIBUTE, jsonData, arguments, ParseValueType.ATTRIBUTE);\r\n\t\tparseArgument(EventTriggerArg.SOURCE_ENTITY_TYPE, jsonData, arguments, ParseValueType.ENTITY_TYPE);\r\n\t\tparseArgument(EventTriggerArg.SOURCE_PLAYER, jsonData, arguments, ParseValueType.TARGET_PLAYER);\r\n\t\tparseArgument(EventTriggerArg.SOURCE_TYPE, jsonData, arguments, ParseValueType.CARD_TYPE);\r\n\t\tparseArgument(EventTriggerArg.TARGET, jsonData, arguments, ParseValueType.TARGET_REFERENCE);\r\n\t\tparseArgument(EventTriggerArg.TARGET_ENTITY_TYPE, jsonData, arguments, ParseValueType.ENTITY_TYPE);\r\n\t\tparseArgument(EventTriggerArg.TARGET_PLAYER, jsonData, arguments, ParseValueType.TARGET_PLAYER);\r\n\t\tparseArgument(EventTriggerArg.VALUE, jsonData, arguments, ParseValueType.VALUE);\r\n\r\n\t\treturn new EventTriggerDesc(arguments);\r\n\t}\r\n\r\n\tprivate void parseArgument(EventTriggerArg arg, JsonObject jsonData, Map<EventTriggerArg, Object> arguments, ParseValueType valueType) {\r\n\t\tString argName = ParseUtils.toCamelCase(arg.toString());\r\n\t\tif (!jsonData.has(argName)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tObject value = ParseUtils.parse(argName, jsonData, valueType);\r\n\t\targuments.put(arg, value);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/trigger/TriggerDesc.java",
    "content": "package net.demilich.metastone.game.spells.desc.trigger;\r\n\r\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\r\nimport net.demilich.metastone.game.spells.trigger.SpellTrigger;\r\n\r\npublic class TriggerDesc {\r\n\r\n\tpublic EventTriggerDesc eventTrigger;\r\n\tpublic SpellDesc spell;\r\n\tpublic boolean oneTurn;\r\n\tpublic boolean persistentOwner;\r\n\tpublic int turnDelay;\r\n\r\n\tpublic SpellTrigger create() {\r\n\t\tSpellTrigger trigger = new SpellTrigger(eventTrigger.create(), null, spell, oneTurn, turnDelay);\r\n\t\ttrigger.setPersistentOwner(persistentOwner);\r\n\t\treturn trigger;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/AlgebraicOperation.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\npublic enum AlgebraicOperation {\r\n\r\n\tADD,\r\n\tSUBTRACT,\r\n\tMULTIPLY,\r\n\tDIVIDE,\r\n\tSET,\r\n\tNEGATE,\r\n\tMODULO,\r\n\tMINIMUM,\r\n\tMAXIMUM,\r\n\t;\r\n\r\n\tpublic int performOperation(int num1, int num2) {\r\n\t\tswitch (this) {\r\n\t\tcase ADD:\r\n\t\t\treturn num1 + num2;\r\n\t\tcase DIVIDE:\r\n\t\t\tif (num2 == 0) {\r\n\t\t\t\tnum2 = 1;\r\n\t\t\t}\r\n\t\t\treturn num1 / num2;\r\n\t\tcase MAXIMUM:\r\n\t\t\treturn num2 < num1 ? num2 : num1;\r\n\t\tcase MINIMUM:\r\n\t\t\treturn num2 > num1 ? num2 : num1;\r\n\t\tcase MODULO:\r\n\t\t\tif (num2 == 0) {\r\n\t\t\t\tnum2 = 1;\r\n\t\t\t}\r\n\t\t\treturn num1 % num2;\r\n\t\tcase MULTIPLY:\r\n\t\t\treturn num1 * num2;\r\n\t\tcase NEGATE:\r\n\t\t\treturn -num1;\r\n\t\tcase SET:\r\n\t\t\treturn num2;\r\n\t\tcase SUBTRACT:\r\n\t\t\treturn num1 - num2;\r\n\t\tdefault:\r\n\t\t\treturn num1;\r\n\t\t}\r\n\t}\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/AlgebraicValueProvider.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class AlgebraicValueProvider extends ValueProvider {\r\n\r\n\tprivate static int evaluateOperation(int value1, int value2, AlgebraicOperation operation) {\r\n\t\treturn operation.performOperation(value1, value2);\r\n\t}\r\n\r\n\tpublic AlgebraicValueProvider(ValueProviderDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected int provideValue(GameContext context, Player player, Entity target, Entity source) {\r\n\t\tint value1 = desc.getValue(ValueProviderArg.VALUE_1, context, player, target, null, 1);\r\n\t\tint value2 = desc.getValue(ValueProviderArg.VALUE_2, context, player, target, null, 1);\r\n\t\tAlgebraicOperation operation = (AlgebraicOperation) desc.get(ValueProviderArg.OPERATION);\r\n\t\treturn evaluateOperation(value1, value2, operation);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/AttributeCounter.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class AttributeCounter extends ValueProvider {\r\n\r\n\tpublic AttributeCounter(ValueProviderDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected int provideValue(GameContext context, Player player, Entity target, Entity host) {\r\n\t\tEntityReference source = desc.getSource();\r\n\t\tList<Entity> relevantEntities = context.resolveTarget(player, host, source);\r\n\t\tint count = 0;\r\n\t\tAttribute attribute = (Attribute) desc.get(ValueProviderArg.ATTRIBUTE);\r\n\t\tfor (Entity entity : relevantEntities) {\r\n\t\t\tif (entity.hasAttribute(attribute)) {\r\n\t\t\t\tcount++;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn count;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/AttributeValueProvider.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class AttributeValueProvider extends ValueProvider {\r\n\r\n\tpublic AttributeValueProvider(ValueProviderDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected int provideValue(GameContext context, Player player, Entity target, Entity host) {\r\n\t\tEntityReference sourceReference = (EntityReference) desc.get(ValueProviderArg.TARGET);\r\n\t\tAttribute attribute = (Attribute) desc.get(ValueProviderArg.ATTRIBUTE);\r\n\t\tList<Entity> entities = null;\r\n\t\tif (sourceReference != null) {\r\n\t\t\tentities = context.resolveTarget(player, host, sourceReference);\r\n\t\t} else {\r\n\t\t\tentities = new ArrayList<>();\r\n\t\t\tentities.add(target);\r\n\t\t}\r\n\t\t\r\n\t\tif (entities == null) {\r\n\t\t\treturn 0;\r\n\t\t}\r\n\t\tint value = 0;\r\n\t\tfor (Entity entity : entities) {\r\n\t\t\tif (entity instanceof Card) {\r\n\t\t\t\tCard card = (Card) entity;\r\n\t\t\t\tvalue += card.getAttributeValue(attribute);\r\n\t\t\t} else {\r\n\t\t\t\tif (entity instanceof Actor) {\r\n\t\t\t\t\tActor source = (Actor) entity;\r\n\t\t\t\t\tif (attribute == Attribute.ATTACK) {\r\n\t\t\t\t\t\tvalue += source.getAttack();\r\n\t\t\t\t\t} else if (attribute == Attribute.MAX_HP) {\r\n\t\t\t\t\t\tvalue += source.getMaxHp();\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tvalue += source.getAttributeValue(attribute);\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tvalue += entity.getAttributeValue(attribute);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn value;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/CardCounter.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class CardCounter extends ValueProvider {\r\n\r\n\tpublic CardCounter(ValueProviderDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected int provideValue(GameContext context, Player player, Entity target, Entity source) {\r\n\t\treturn player.getHand().getCount();\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/CardsPlayedValueProvider.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\n\r\npublic class CardsPlayedValueProvider extends ValueProvider {\r\n\r\n\tpublic CardsPlayedValueProvider(ValueProviderDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected int provideValue(GameContext context, Player player, Entity target, Entity host) {\r\n\t\tMap<String, Map<Integer, Integer>> cardIds = player.getStatistics().getCardsPlayed();\r\n\t\tint count = 0;\r\n\t\tEntityFilter filter = (EntityFilter) desc.get(ValueProviderArg.FILTER);\r\n\t\tfor (String cardId : cardIds.keySet()) {\r\n\t\t\tEntity entity = context.getCardById(cardId);\r\n\t\t\tif (filter == null || filter.matches(context, player, entity)) {\r\n\t\t\t\tfor (Integer turn : cardIds.get(cardId).keySet()) {\r\n\t\t\t\t\tcount += cardIds.get(cardId).get(turn);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn count;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/ConditionalValueProvider.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.condition.Condition;\r\n\r\npublic class ConditionalValueProvider extends ValueProvider {\r\n\r\n\tpublic ConditionalValueProvider(ValueProviderDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected int provideValue(GameContext context, Player player, Entity target, Entity source) {\r\n\t\tint ifTrue = desc.getInt(ValueProviderArg.IF_TRUE);\r\n\t\tint ifFalse = desc.getInt(ValueProviderArg.IF_FALSE);\r\n\r\n\t\tCondition condition = (Condition) desc.get(ValueProviderArg.CONDITION);\r\n\t\treturn condition.isFulfilled(context, player, source, target) ? ifTrue : ifFalse;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/DeadMinionsThisTurn.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.SpellUtils;\r\n\r\npublic class DeadMinionsThisTurn extends ValueProvider {\r\n\r\n\tpublic DeadMinionsThisTurn(ValueProviderDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected int provideValue(GameContext context, Player player, Entity target, Entity host) {\r\n\t\treturn SpellUtils.howManyMinionsDiedThisTurn(context);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/EntityCounter.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class EntityCounter extends ValueProvider {\r\n\r\n\tpublic EntityCounter(ValueProviderDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected int provideValue(GameContext context, Player player, Entity target, Entity host) {\r\n\t\tEntityReference source = desc.getSource();\r\n\t\tList<Entity> relevantEntities = context.resolveTarget(player, host, source);\r\n\t\tint count = 0;\r\n\t\tEntityFilter filter = (EntityFilter) desc.get(ValueProviderArg.FILTER);\r\n\t\tfor (Entity entity : relevantEntities) {\r\n\t\t\tif (filter == null || filter.matches(context, player, entity)) {\r\n\t\t\t\tcount++;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn count;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/HighestAttributeValueProvider.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.entities.Actor;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class HighestAttributeValueProvider extends ValueProvider {\r\n\r\n\tpublic HighestAttributeValueProvider(ValueProviderDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected int provideValue(GameContext context, Player player, Entity target, Entity host) {\r\n\t\tEntityReference sourceReference = (EntityReference) desc.get(ValueProviderArg.TARGET);\r\n\t\tAttribute attribute = (Attribute) desc.get(ValueProviderArg.ATTRIBUTE);\r\n\t\tList<Entity> entities = null;\r\n\t\tif (sourceReference != null) {\r\n\t\t\tentities = context.resolveTarget(player, host, sourceReference);\r\n\t\t} else {\r\n\t\t\tentities = new ArrayList<>();\r\n\t\t\tentities.add(target);\r\n\t\t}\r\n\t\tif (entities == null) {\r\n\t\t\treturn 0;\r\n\t\t}\r\n\r\n\t\tEntityFilter filter = (EntityFilter) desc.get(ValueProviderArg.FILTER);\t\t\r\n\t\tint value = 0;\r\n\t\tfor (Entity entity : entities) {\r\n\t\t\tif (filter != null && !filter.matches(context, player, entity)) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif (entity instanceof Card) {\r\n\t\t\t\tCard card = (Card) entity;\r\n\t\t\t\tif (attribute == Attribute.ATTACK) {\r\n\t\t\t\t\tvalue = Math.max(card.getAttributeValue(Attribute.ATTACK) + card.getAttributeValue(Attribute.ATTACK_BONUS), value);\r\n\t\t\t\t} else if (attribute == Attribute.MAX_HP) {\r\n\t\t\t\t\tvalue = Math.max(card.getAttributeValue(Attribute.MAX_HP) + card.getAttributeValue(Attribute.HP_BONUS), value);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tvalue = Math.max(card.getAttributeValue(attribute), value);\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tActor source = (Actor) entity;\r\n\t\t\t\tif (attribute == Attribute.ATTACK) {\r\n\t\t\t\t\tvalue = Math.max(source.getAttack(), value);\r\n\t\t\t\t} else if (attribute == Attribute.MAX_HP) {\r\n\t\t\t\t\tvalue = Math.max(source.getMaxHp(), value);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tvalue = Math.max(source.getAttributeValue(attribute), value);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn value;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/MinionSummonValueProvider.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.desc.filter.EntityFilter;\r\n\r\npublic class MinionSummonValueProvider extends ValueProvider {\r\n\r\n\tpublic MinionSummonValueProvider(ValueProviderDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected int provideValue(GameContext context, Player player, Entity target, Entity host) {\r\n\t\tMap<String, Map<Integer, Integer>> minionIds = player.getStatistics().getMinionsSummoned();\r\n\t\tint count = 0;\r\n\t\tEntityFilter filter = (EntityFilter) desc.get(ValueProviderArg.FILTER);\r\n\t\tfor (String minionId : minionIds.keySet()) {\r\n\t\t\tEntity entity = context.getCardById(minionId);\r\n\t\t\tif (filter == null || filter.matches(context, player, entity)) {\r\n\t\t\t\tfor (Integer turn : minionIds.get(minionId).keySet()) {\r\n\t\t\t\t\tcount += minionIds.get(minionId).get(turn);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn count;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/PlayerAttributeValueProvider.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\nimport net.demilich.metastone.game.Environment;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.PlayerAttribute;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.statistics.Statistic;\r\n\r\npublic class PlayerAttributeValueProvider extends ValueProvider {\r\n\r\n\tpublic PlayerAttributeValueProvider(ValueProviderDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected int provideValue(GameContext context, Player player, Entity target, Entity host) {\r\n\t\tPlayerAttribute attribute = (PlayerAttribute) desc.get(ValueProviderArg.PLAYER_ATTRIBUTE);\r\n\t\tswitch (attribute) {\r\n\t\tcase DECK_COUNT:\r\n\t\t\treturn player.getDeck().getCount();\r\n\t\tcase HAND_COUNT:\r\n\t\t\treturn player.getHand().getCount();\r\n\t\tcase HERO_POWER_USED:\r\n\t\t\treturn (int) player.getStatistics().getLong(Statistic.HERO_POWER_USED);\r\n\t\tcase LAST_MANA_COST:\r\n\t\t\treturn (int) context.getEnvironment().get(Environment.LAST_MANA_COST);\r\n\t\tcase MANA:\r\n\t\t\treturn player.getMana();\r\n\t\tcase MAX_MANA:\r\n\t\t\treturn player.getMaxMana();\r\n\t\tcase SECRET_COUNT:\r\n\t\t\treturn player.getSecrets().size() - player.getQuests().size();\r\n\t\tcase SPELLS_CAST:\r\n\t\t\treturn (int) player.getStatistics().getLong(Statistic.SPELLS_CAST);\r\n\t\tdefault:\r\n\t\t\tbreak;\r\n\t\t}\r\n\t\treturn 0;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/RandomValueProvider.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class RandomValueProvider extends ValueProvider {\r\n\r\n\tpublic RandomValueProvider(ValueProviderDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected int provideValue(GameContext context, Player player, Entity target, Entity host) {\r\n\t\tint min = desc.getInt(ValueProviderArg.MIN);\r\n\t\tint max = desc.getInt(ValueProviderArg.MAX);\r\n\t\tint diff = max - min;\r\n\t\treturn min + context.getLogic().random(diff + 1);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/ValueProvider.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.spells.TargetPlayer;\r\n\r\npublic abstract class ValueProvider {\r\n\r\n\tprotected final ValueProviderDesc desc;\r\n\r\n\tpublic ValueProvider(ValueProviderDesc desc) {\r\n\t\tthis.desc = desc;\r\n\t}\r\n\r\n\tpublic int getValue(GameContext context, Player player, Entity target, Entity host) {\r\n\t\tTargetPlayer targetPlayer = (TargetPlayer) desc.get(ValueProviderArg.TARGET_PLAYER);\r\n\t\tif (targetPlayer == null) {\r\n\t\t\ttargetPlayer = TargetPlayer.SELF;\r\n\t\t}\r\n\t\tPlayer providingPlayer = null;\r\n\t\tswitch (targetPlayer) {\r\n\t\tcase ACTIVE:\r\n\t\t\tprovidingPlayer = context.getActivePlayer();\r\n\t\t\tbreak;\r\n\t\tcase BOTH:\r\n\t\t\tint multiplier = desc.contains(ValueProviderArg.MULTIPLIER) ? desc.getInt(ValueProviderArg.MULTIPLIER) : 1;\r\n\t\t\tint offset = desc.contains(ValueProviderArg.OFFSET) ? desc.getInt(ValueProviderArg.OFFSET) : 0;\r\n\t\t\tint value = 0;\r\n\t\t\tfor (Player selectedPlayer : context.getPlayers()) {\r\n\t\t\t\tvalue += provideValue(context, selectedPlayer, target, host);\r\n\t\t\t}\r\n\t\t\tvalue = value * multiplier + offset;\r\n\t\t\treturn value;\r\n\t\tcase INACTIVE:\r\n\t\t\tprovidingPlayer = context.getOpponent(context.getActivePlayer());\r\n\t\t\tbreak;\r\n\t\tcase OPPONENT:\r\n\t\t\tprovidingPlayer = context.getOpponent(player);\r\n\t\t\tbreak;\r\n\t\tcase OWNER:\r\n\t\t\tprovidingPlayer = context.getPlayer(host.getOwner());\r\n\t\t\tbreak;\r\n\t\tcase SELF:\r\n\t\tdefault:\r\n\t\t\tprovidingPlayer = player;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t\tint multiplier = desc.contains(ValueProviderArg.MULTIPLIER) ? desc.getInt(ValueProviderArg.MULTIPLIER) : 1;\r\n\t\tint offset = desc.contains(ValueProviderArg.OFFSET) ? desc.getInt(ValueProviderArg.OFFSET) : 0;\r\n\t\tint value = provideValue(context, providingPlayer, target, host) * multiplier + offset;\r\n\t\treturn value;\r\n\t}\r\n\r\n\tprotected abstract int provideValue(GameContext context, Player player, Entity target, Entity host);\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/ValueProviderArg.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\npublic enum ValueProviderArg {\r\n\tCLASS,\r\n\tTARGET,\r\n\tATTRIBUTE,\r\n\tPLAYER_ATTRIBUTE,\r\n\tVALUE,\r\n\tOFFSET,\r\n\tMULTIPLIER,\r\n\tRACE,\r\n\tTARGET_PLAYER,\r\n\tIF_TRUE,\r\n\tIF_FALSE,\r\n\tCONDITION,\r\n\tFILTER,\r\n\tOPERATION,\r\n\tVALUE_1,\r\n\tVALUE_2,\r\n\tMIN,\r\n\tMAX\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/desc/valueprovider/ValueProviderDesc.java",
    "content": "package net.demilich.metastone.game.spells.desc.valueprovider;\r\n\r\nimport java.lang.reflect.InvocationTargetException;\r\nimport java.util.EnumMap;\r\nimport java.util.Map;\r\n\r\nimport net.demilich.metastone.game.cards.desc.Desc;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class ValueProviderDesc extends Desc<ValueProviderArg> {\r\n\r\n\tpublic static Map<ValueProviderArg, Object> build(Class<? extends ValueProvider> providerClass) {\r\n\t\tfinal Map<ValueProviderArg, Object> arguments = new EnumMap<>(ValueProviderArg.class);\r\n\t\targuments.put(ValueProviderArg.CLASS, providerClass);\r\n\t\treturn arguments;\r\n\t}\r\n\r\n\tpublic ValueProviderDesc(Map<ValueProviderArg, Object> arguments) {\r\n\t\tsuper(arguments);\r\n\t}\r\n\r\n\tpublic ValueProvider create() {\r\n\t\tClass<? extends ValueProvider> valueProviderClass = getValueProviderClass();\r\n\t\ttry {\r\n\t\t\treturn valueProviderClass.getConstructor(ValueProviderDesc.class).newInstance(this);\r\n\t\t} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException\r\n\t\t\t\t| NoSuchMethodException | SecurityException e) {\r\n\t\t\te.printStackTrace();\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\tpublic EntityReference getSource() {\r\n\t\treturn (EntityReference) get(ValueProviderArg.TARGET);\r\n\t}\r\n\r\n\t@SuppressWarnings(\"unchecked\")\r\n\tpublic Class<? extends ValueProvider> getValueProviderClass() {\r\n\t\treturn (Class<? extends ValueProvider>) get(ValueProviderArg.CLASS);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/AfterMinionPlayedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\r\n\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.events.AfterSummonEvent;\r\nimport net.demilich.metastone.game.events.GameEvent;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\n\r\npublic class AfterMinionPlayedTrigger extends AfterMinionSummonedTrigger {\r\n\tpublic AfterMinionPlayedTrigger(EventTriggerDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean fire(GameEvent event, Entity host) {\r\n\t\tAfterSummonEvent summonEvent = (AfterSummonEvent) event;\r\n\r\n\t\t// when source card is null, then this minion not played as a minion\r\n\t\t// card\r\n\t\tif (summonEvent.getSource() == null) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\treturn super.fire(summonEvent, host);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/AfterMinionSummonedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Race;\nimport net.demilich.metastone.game.events.AfterSummonEvent;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class AfterMinionSummonedTrigger extends GameEventTrigger {\n\n\tpublic AfterMinionSummonedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\tAfterSummonEvent summonEvent = (AfterSummonEvent) event;\n\t\tRace race = (Race) desc.get(EventTriggerArg.RACE);\n\t\tif (race != null && summonEvent.getMinion().getRace() != race) {\n\t\t\treturn false;\n\t\t}\n\n\t\tAttribute requiredAttribute = (Attribute) desc.get(EventTriggerArg.REQUIRED_ATTRIBUTE);\n\t\tif (requiredAttribute != null && !summonEvent.getMinion().hasAttribute(requiredAttribute)) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.AFTER_SUMMON;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/AfterPhysicalAttackTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\r\n\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.EntityType;\r\nimport net.demilich.metastone.game.events.AfterPhysicalAttackEvent;\r\nimport net.demilich.metastone.game.events.GameEvent;\r\nimport net.demilich.metastone.game.events.GameEventType;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\n\r\npublic class AfterPhysicalAttackTrigger extends GameEventTrigger {\r\n\r\n\tpublic AfterPhysicalAttackTrigger(EventTriggerDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean fire(GameEvent event, Entity host) {\r\n\t\tAfterPhysicalAttackEvent physicalAttackEvent = (AfterPhysicalAttackEvent) event;\r\n\r\n\t\tEntityType sourceEntityType = (EntityType) desc.get(EventTriggerArg.SOURCE_ENTITY_TYPE);\r\n\t\tif (sourceEntityType != null && physicalAttackEvent.getAttacker().getEntityType() != sourceEntityType) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tEntityType targetEntityType = (EntityType) desc.get(EventTriggerArg.TARGET_ENTITY_TYPE);\r\n\t\tif (targetEntityType != null && physicalAttackEvent.getDefender().getEntityType() != targetEntityType) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic GameEventType interestedIn() {\r\n\t\treturn GameEventType.AFTER_PHYSICAL_ATTACK;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/AfterSpellCastedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.events.AfterSpellCastedEvent;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class AfterSpellCastedTrigger extends GameEventTrigger {\n\n\tpublic AfterSpellCastedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\tAfterSpellCastedEvent spellCastedEvent = (AfterSpellCastedEvent) event;\n\t\t\n\t\tEntityType targetEntityType = (EntityType) desc.get(EventTriggerArg.TARGET_ENTITY_TYPE);\n\t\tif (targetEntityType != null && (spellCastedEvent.getEventTarget() == null || targetEntityType != spellCastedEvent.getEventTarget().getEntityType())) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.AFTER_SPELL_CASTED;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/ArmorGainedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class ArmorGainedTrigger extends GameEventTrigger {\n\n\tpublic ArmorGainedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.ARMOR_GAINED;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/BeforeMinionPlayedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\r\n\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.events.BeforeSummonEvent;\r\nimport net.demilich.metastone.game.events.GameEvent;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\n\r\npublic class BeforeMinionPlayedTrigger extends BeforeMinionSummonedTrigger {\r\n\tpublic BeforeMinionPlayedTrigger(EventTriggerDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean fire(GameEvent event, Entity host) {\r\n\t\tBeforeSummonEvent summonEvent = (BeforeSummonEvent) event;\r\n\r\n\t\t// when source card is null, then this minion not played as a minion\r\n\t\t// card\r\n\t\tif (summonEvent.getSource() == null) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\treturn super.fire(summonEvent, host);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/BeforeMinionSummonedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Race;\nimport net.demilich.metastone.game.events.BeforeSummonEvent;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class BeforeMinionSummonedTrigger extends GameEventTrigger {\n\n\tpublic BeforeMinionSummonedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\tBeforeSummonEvent summonEvent = (BeforeSummonEvent) event;\n\t\tRace race = (Race) desc.get(EventTriggerArg.RACE);\n\t\tif (race != null && summonEvent.getMinion().getRace() != race) {\n\t\t\treturn false;\n\t\t}\n\n\t\tAttribute requiredAttribute = (Attribute) desc.get(EventTriggerArg.REQUIRED_ATTRIBUTE);\n\t\tif (requiredAttribute != null && !summonEvent.getMinion().hasAttribute(requiredAttribute)) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.BEFORE_SUMMON;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/BoardChangedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class BoardChangedTrigger extends GameEventTrigger {\n\n\tpublic BoardChangedTrigger() {\n\t\tthis(EventTriggerDesc.createEmpty(BoardChangedTrigger.class));\n\t}\n\n\tpublic BoardChangedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.BOARD_CHANGED;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/CardDrawnTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\r\n\r\nimport net.demilich.metastone.game.cards.CardType;\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.events.DrawCardEvent;\r\nimport net.demilich.metastone.game.events.GameEvent;\r\nimport net.demilich.metastone.game.events.GameEventType;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\n\r\npublic class CardDrawnTrigger extends GameEventTrigger {\r\n\r\n\tpublic CardDrawnTrigger(EventTriggerDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean fire(GameEvent event, Entity host) {\r\n\t\tDrawCardEvent drawEvent = (DrawCardEvent) event;\r\n\t\t\r\n\t\tCardType sourceType = (CardType) desc.get(EventTriggerArg.SOURCE_TYPE);\r\n\t\tif (sourceType != null && drawEvent.getSourceType() != sourceType) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\t\r\n\t\tif (!drawEvent.isDrawn()) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic GameEventType interestedIn() {\r\n\t\treturn GameEventType.DRAW_CARD;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/CardPlayedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Race;\nimport net.demilich.metastone.game.events.CardPlayedEvent;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class CardPlayedTrigger extends GameEventTrigger {\n\n\tpublic CardPlayedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\tCardPlayedEvent cardPlayedEvent = (CardPlayedEvent) event;\n\t\tCardType cardType = (CardType) desc.get(EventTriggerArg.CARD_TYPE);\n\t\tif (cardType != null && !cardPlayedEvent.getCard().getCardType().isCardType(cardType)) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\tRace race = (Race) desc.get(EventTriggerArg.RACE);\n\t\tif (race != null && cardPlayedEvent.getCard().getAttribute(Attribute.RACE) != race) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.PLAY_CARD;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/CardReceivedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\r\n\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.events.GameEvent;\r\nimport net.demilich.metastone.game.events.GameEventType;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\n\r\npublic class CardReceivedTrigger extends GameEventTrigger {\r\n\r\n\tpublic CardReceivedTrigger(EventTriggerDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean fire(GameEvent event, Entity host) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic GameEventType interestedIn() {\r\n\t\treturn GameEventType.DRAW_CARD;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/DamageCausedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.events.DamageEvent;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class DamageCausedTrigger extends GameEventTrigger {\n\n\tpublic DamageCausedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\tDamageEvent damageEvent = (DamageEvent) event;\n\n\t\tEntityType sourceEntityType = (EntityType) desc.get(EventTriggerArg.SOURCE_ENTITY_TYPE);\n\t\tif (sourceEntityType != null && sourceEntityType != damageEvent.getSource().getEntityType()) {\n\t\t\treturn false;\n\t\t}\n\t\tEntityType targetEntityType = (EntityType) desc.get(EventTriggerArg.TARGET_ENTITY_TYPE);\n\t\tif (targetEntityType != null && targetEntityType != damageEvent.getVictim().getEntityType()) {\n\t\t\treturn false;\n\t\t}\n\t\tCardType cardType = (CardType) desc.get(EventTriggerArg.CARD_TYPE);\n\t\tif (cardType != null) {\n\t\t\tif (damageEvent.getSource() instanceof Card && !((Card) damageEvent.getSource()).getCardType().isCardType(cardType)) {\n\t\t\t\treturn false;\n\t\t\t} else if (damageEvent.getSource() instanceof Actor && !((Actor) damageEvent.getSource()).getSourceCard().getCardType().isCardType(cardType)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn damageEvent.getDamage() > 0;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.DAMAGE;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/DamageReceivedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.events.DamageEvent;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class DamageReceivedTrigger extends GameEventTrigger {\n\n\tpublic DamageReceivedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\tDamageEvent damageEvent = (DamageEvent) event;\n\n\t\tEntityType targetEntityType = (EntityType) desc.get(EventTriggerArg.TARGET_ENTITY_TYPE);\n\t\tif (targetEntityType != null && damageEvent.getVictim().getEntityType() != targetEntityType) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.DAMAGE;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/DiscardTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\r\n\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.events.DiscardEvent;\r\nimport net.demilich.metastone.game.events.GameEvent;\r\nimport net.demilich.metastone.game.events.GameEventType;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\nimport net.demilich.metastone.game.targeting.EntityReference;\r\n\r\npublic class DiscardTrigger extends GameEventTrigger {\r\n\r\n\tpublic DiscardTrigger(EventTriggerDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean fire(GameEvent event, Entity host) {\r\n\t\tDiscardEvent discardEvent = (DiscardEvent) event;\r\n\t\tEntityReference target = (EntityReference) desc.get(EventTriggerArg.TARGET);\r\n\t\tif (target == EntityReference.SELF && discardEvent.getCard() != host) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\t\r\n\t\treturn true;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic GameEventType interestedIn() {\r\n\t\treturn GameEventType.DISCARD;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/EnrageChangedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\r\n\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.events.GameEvent;\r\nimport net.demilich.metastone.game.events.GameEventType;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\n\r\npublic class EnrageChangedTrigger extends GameEventTrigger {\r\n\r\n\tpublic EnrageChangedTrigger() {\r\n\t\tthis(EventTriggerDesc.createEmpty(EnrageChangedTrigger.class));\r\n\t}\r\n\r\n\tpublic EnrageChangedTrigger(EventTriggerDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean fire(GameEvent event, Entity host) {\r\n\t\treturn event.getEventTarget() == host;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic GameEventType interestedIn() {\r\n\t\treturn GameEventType.ENRAGE_CHANGED;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/FatalDamageTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.heroes.Hero;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.PreDamageEvent;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class FatalDamageTrigger extends PreDamageTrigger {\n\t\n\tprivate static Logger logger = LoggerFactory.getLogger(FatalDamageTrigger.class);\n\n\tpublic FatalDamageTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\tif (!super.fire(event, host)) {\n\t\t\treturn false;\n\t\t} else {\n\t\t\tPreDamageEvent preDamageEvent = (PreDamageEvent) event;\n\t\t\tEntity victim = preDamageEvent.getVictim();\n\t\t\tswitch (victim.getEntityType()) {\n\t\t\tcase HERO:\n\t\t\t\tHero hero = (Hero) victim;\n\t\t\t\treturn hero.getEffectiveHp() <= event.getGameContext().getDamageStack().peek();\n\t\t\tcase MINION:\n\t\t\t\tMinion minion = (Minion) victim;\n\t\t\t\treturn minion.getHp() <= event.getGameContext().getDamageStack().peek();\n\t\t\tdefault:\n\t\t\t\tlogger.warn(\"Invalid entity type in FatalDamageTrigger: {}\", victim);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t  \n\t\t}\n\t\treturn false;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/GameEventTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.logic.CustomCloneable;\nimport net.demilich.metastone.game.spells.TargetPlayer;\nimport net.demilich.metastone.game.spells.desc.condition.Condition;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\nimport net.demilich.metastone.game.targeting.TargetType;\n\npublic abstract class GameEventTrigger extends CustomCloneable {\n\n\tprivate int owner = -1;\n\tprotected final EventTriggerDesc desc;\n\tprivate int triggerCount;\n\n\tpublic GameEventTrigger(EventTriggerDesc desc) {\n\t\tthis.desc = desc;\n\t\tthis.triggerCount = desc.getTriggerCount();\n\t}\n\n\t@Override\n\tpublic GameEventTrigger clone() {\n\t\treturn (GameEventTrigger) super.clone();\n\t}\n\n\tprotected boolean determineTargetPlayer(GameEvent event, TargetPlayer targetPlayer, Entity host, int targetPlayerId) {\n\t\tif (targetPlayerId == -1 || targetPlayer == null) {\n\t\t\treturn true;\n\t\t}\n\t\tswitch (targetPlayer) {\n\t\tcase ACTIVE:\n\t\t\treturn event.getGameContext().getActivePlayerId() == targetPlayerId;\n\t\tcase INACTIVE:\n\t\t\treturn event.getGameContext().getActivePlayerId() != targetPlayerId;\n\t\tcase BOTH:\n\t\t\treturn true;\n\t\tcase OPPONENT:\n\t\t\treturn getOwner() != targetPlayerId;\n\t\tcase OWNER:\n\t\t\treturn host.getOwner() == targetPlayerId;\n\t\tcase SELF:\n\t\t\treturn getOwner() == targetPlayerId;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprotected abstract boolean fire(GameEvent event, Entity host);\n\n\tpublic final boolean fires(GameEvent event, Entity host) {\n\t\tTargetPlayer targetPlayer = desc.getTargetPlayer();\n\t\tif (targetPlayer != null && !determineTargetPlayer(event, targetPlayer, host, event.getTargetPlayerId())) {\n\t\t\treturn false;\n\t\t}\n\t\tTargetPlayer sourcePlayer = desc.getSourcePlayer();\n\t\tif (sourcePlayer != null && !determineTargetPlayer(event, sourcePlayer, host, event.getSourcePlayerId())) {\n\t\t\treturn false;\n\t\t}\n\n\t\tTargetType hostTargetType = (TargetType) desc.get(EventTriggerArg.HOST_TARGET_TYPE);\n\t\tif (hostTargetType == TargetType.IGNORE_AS_TARGET && event.getEventTarget() == host) {\n\t\t\treturn false;\n\t\t} else if (hostTargetType == TargetType.IGNORE_AS_SOURCE && event.getEventSource() == host) {\n\t\t\treturn false;\n\t\t} else if (hostTargetType == TargetType.IGNORE_OTHER_TARGETS && event.getEventTarget() != host) {\n\t\t\treturn false;\n\t\t} else if (hostTargetType == TargetType.IGNORE_OTHER_SOURCES && event.getEventSource() != host) {\n\t\t\treturn false;\n\t\t}\n\t\tCondition condition = (Condition) desc.get(EventTriggerArg.QUEUE_CONDITION);\n\t\tPlayer owner = event.getGameContext().getPlayer(getOwner());\n\t\tif (condition != null && !condition.isFulfilled(event.getGameContext(), owner, event.getEventSource(), event.getEventTarget())) {\n\t\t\treturn false;\n\t\t}\n\t\treturn fire(event, host);\n\t}\n\n\tpublic int getOwner() {\n\t\treturn owner;\n\t}\n\n\tpublic int getTriggerCount() {\n\t\treturn triggerCount;\n\t}\n\n\tpublic void countDown() {\n\t\ttriggerCount--;\n\t\tif (triggerCount < 0) {\n\t\t\ttriggerCount = 0;\n\t\t}\n\t}\n\n\tpublic void countDown(int num) {\n\t\ttriggerCount -= num;\n\t\tif (triggerCount < 0) {\n\t\t\ttriggerCount = 0;\n\t\t}\n\t}\n\n\tpublic abstract GameEventType interestedIn();\n\n\tpublic void setOwner(int playerIndex) {\n\t\tthis.owner = playerIndex;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"[\" + getClass().getSimpleName() + \" owner:\" + owner + \"]\";\n\t}\n\t\n\tpublic boolean canFireCondition(GameEvent event) {\n\t\tCondition condition = (Condition) desc.get(EventTriggerArg.FIRE_CONDITION);\n\t\tPlayer owner = event.getGameContext().getPlayer(getOwner());\n\t\tif (condition != null && !condition.isFulfilled(event.getGameContext(), owner, event.getEventSource(), event.getEventTarget())) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/GameStartTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class GameStartTrigger extends GameEventTrigger {\n\n\tpublic GameStartTrigger() {\n\t\tthis(EventTriggerDesc.createEmpty(GameStartTrigger.class));\n\t}\n\n\tpublic GameStartTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.GAME_START;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/GameStateChangedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class GameStateChangedTrigger extends GameEventTrigger {\n\n\tpublic GameStateChangedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.ALL;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/HealingTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.events.HealEvent;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class HealingTrigger extends GameEventTrigger {\n\n\tpublic HealingTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\tHealEvent healEvent = (HealEvent) event;\n\t\t\n\t\tEntityType targetEntityType = (EntityType) desc.get(EventTriggerArg.TARGET_ENTITY_TYPE);\n\t\tif (targetEntityType != null) {\n\t\t\tif (healEvent.getTarget().getEntityType() != targetEntityType) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\t\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.HEAL;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/IGameEventListener.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic interface IGameEventListener {\n\n\tpublic IGameEventListener clone();\n\t\n\tpublic abstract boolean canFire(GameEvent event);\n\n\tpublic abstract EntityReference getHostReference();\n\n\tpublic abstract int getOwner();\n\n\tpublic abstract boolean interestedIn(GameEventType eventType);\n\n\tpublic abstract boolean isExpired();\n\n\tpublic abstract void onAdd(GameContext context);\n\n\tpublic abstract void onGameEvent(GameEvent event);\n\n\tpublic abstract void onRemove(GameContext context);\n\n\tpublic abstract void setHost(Entity host);\n\n\tpublic abstract void setOwner(int playerIndex);\n\t\n\tpublic abstract boolean hasPersistentOwner();\n\n\tpublic abstract boolean oneTurnOnly();\n\n\tpublic abstract boolean isDelayed();\n\n\tpublic abstract void delayTimeDown();\n\n\tpublic abstract boolean hasCounter();\n\n\tpublic abstract void countDown();\n\n\tpublic abstract void expire();\n\t\n\tpublic abstract boolean canFireCondition(GameEvent event);\n\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/InspireTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\r\n\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.events.GameEvent;\r\nimport net.demilich.metastone.game.events.GameEventType;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\n\r\npublic class InspireTrigger extends GameEventTrigger {\r\n\r\n\tpublic InspireTrigger(EventTriggerDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean fire(GameEvent event, Entity host) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic GameEventType interestedIn() {\r\n\t\treturn GameEventType.HERO_POWER_USED;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/MinionDeathTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.entities.minions.Race;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.events.KillEvent;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class MinionDeathTrigger extends GameEventTrigger {\n\n\tpublic MinionDeathTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\tKillEvent killEvent = (KillEvent) event;\n\t\tif (killEvent.getVictim().getEntityType() != EntityType.MINION) {\n\t\t\treturn false;\n\t\t}\n\n\t\tMinion minion = (Minion) killEvent.getVictim();\n\n\t\tRace race = (Race) desc.get(EventTriggerArg.RACE);\n\t\tif (race != null && minion.getRace() != race) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.KILL;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/MinionPlayedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\r\n\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.events.GameEvent;\r\nimport net.demilich.metastone.game.events.SummonEvent;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\n\r\npublic class MinionPlayedTrigger extends MinionSummonedTrigger {\r\n\tpublic MinionPlayedTrigger(EventTriggerDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean fire(GameEvent event, Entity host) {\r\n\t\tSummonEvent summonEvent = (SummonEvent) event;\r\n\r\n\t\t// when source card is null, then this minion not played as a minion\r\n\t\t// card\r\n\t\tif (summonEvent.getSource() == null) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\treturn super.fire(summonEvent, host);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/MinionSummonedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.minions.Race;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.events.SummonEvent;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class MinionSummonedTrigger extends GameEventTrigger {\n\n\tpublic MinionSummonedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\tSummonEvent summonEvent = (SummonEvent) event;\n\t\tRace race = (Race) desc.get(EventTriggerArg.RACE);\n\t\tif (race != null && summonEvent.getMinion().getRace() != race) {\n\t\t\treturn false;\n\t\t}\n\n\t\tAttribute requiredAttribute = (Attribute) desc.get(EventTriggerArg.REQUIRED_ATTRIBUTE);\n\t\tif (requiredAttribute != null && !summonEvent.getMinion().hasAttribute(requiredAttribute)) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.SUMMON;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/OverloadTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\r\n\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.events.GameEvent;\r\nimport net.demilich.metastone.game.events.GameEventType;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\n\r\npublic class OverloadTrigger extends GameEventTrigger {\r\n\r\n\tpublic OverloadTrigger(EventTriggerDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic boolean fire(GameEvent event, Entity host) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic GameEventType interestedIn() {\r\n\t\treturn GameEventType.OVERLOAD;\r\n\t}\r\n\r\n}"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/PhysicalAttackTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\r\n\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.entities.EntityType;\r\nimport net.demilich.metastone.game.events.GameEvent;\r\nimport net.demilich.metastone.game.events.GameEventType;\r\nimport net.demilich.metastone.game.events.PhysicalAttackEvent;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\n\r\npublic class PhysicalAttackTrigger extends GameEventTrigger {\r\n\r\n\tpublic PhysicalAttackTrigger(EventTriggerDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean fire(GameEvent event, Entity host) {\r\n\t\tPhysicalAttackEvent physicalAttackEvent = (PhysicalAttackEvent) event;\r\n\r\n\t\tEntityType sourceEntityType = (EntityType) desc.get(EventTriggerArg.SOURCE_ENTITY_TYPE);\r\n\t\tif (sourceEntityType != null && physicalAttackEvent.getAttacker().getEntityType() != sourceEntityType) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tEntityType targetEntityType = (EntityType) desc.get(EventTriggerArg.TARGET_ENTITY_TYPE);\r\n\t\tif (targetEntityType != null && physicalAttackEvent.getDefender().getEntityType() != targetEntityType) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic GameEventType interestedIn() {\r\n\t\treturn GameEventType.PHYSICAL_ATTACK;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/PreDamageTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.events.PreDamageEvent;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class PreDamageTrigger extends GameEventTrigger {\n\n\tpublic PreDamageTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\tPreDamageEvent preDamageEvent = (PreDamageEvent) event;\n\t\t\n\t\tEntityType sourceEntityType = (EntityType) desc.get(EventTriggerArg.SOURCE_ENTITY_TYPE);\n\t\tif (sourceEntityType != null && preDamageEvent.getSource().getEntityType() != sourceEntityType) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\tEntityType targetEntityType = (EntityType) desc.get(EventTriggerArg.TARGET_ENTITY_TYPE);\n\t\tif (targetEntityType != null && preDamageEvent.getVictim().getEntityType() != targetEntityType) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.PRE_DAMAGE;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/QuestPlayedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class QuestPlayedTrigger extends GameEventTrigger {\n\n\tpublic QuestPlayedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.QUEST_PLAYED;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/QuestSuccessTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class QuestSuccessTrigger extends GameEventTrigger {\n\n\tpublic QuestSuccessTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.QUEST_SUCCESSFUL;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/SecretPlayedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class SecretPlayedTrigger extends GameEventTrigger {\n\n\tpublic SecretPlayedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.SECRET_PLAYED;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/SecretRevealedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class SecretRevealedTrigger extends GameEventTrigger {\n\n\tpublic SecretRevealedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.SECRET_REVEALED;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/SilenceTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.events.SilenceEvent;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class SilenceTrigger extends GameEventTrigger {\n\n\tpublic SilenceTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\tSilenceEvent silenceEvent = (SilenceEvent) event;\n\t\treturn silenceEvent.getTarget() == host;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.SILENCE;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/SpellCastedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class SpellCastedTrigger extends GameEventTrigger {\n\n\tpublic SpellCastedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\t//SpellCastedEvent spellCastedEvent = (SpellCastedEvent) event;\n\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.SPELL_CASTED;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/SpellTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.logic.CustomCloneable;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class SpellTrigger extends CustomCloneable implements IGameEventListener {\n\tprivate final static Logger logger = LoggerFactory.getLogger(SpellTrigger.class);\n\n\tprivate GameEventTrigger primaryTrigger;\n\tprivate GameEventTrigger secondaryTrigger;\n\tprivate SpellDesc spell;\n\tprivate EntityReference hostReference;\n\tprivate final boolean oneTurn;\n\tprivate boolean expired;\n\tprivate boolean persistentOwner;\n\tprivate int turnDelay;\n\n\tpublic SpellTrigger(GameEventTrigger primaryTrigger, GameEventTrigger secondaryTrigger, SpellDesc spell, boolean oneTurn, int turnDelay) {\n\t\tthis.primaryTrigger = primaryTrigger;\n\t\tthis.secondaryTrigger = secondaryTrigger;\n\t\tthis.spell = spell;\n\t\tthis.oneTurn = oneTurn;\n\t\tthis.turnDelay = turnDelay;\n\t}\n\t\n\tpublic SpellTrigger(GameEventTrigger primaryTrigger, GameEventTrigger secondaryTrigger, SpellDesc spell, boolean oneTurn) {\n\t\tthis(primaryTrigger, secondaryTrigger, spell, oneTurn, 0);\n\t}\n\n\tpublic SpellTrigger(GameEventTrigger trigger, SpellDesc spell) {\n\t\tthis(trigger, spell, false, 0);\n\t}\n\n\tpublic SpellTrigger(GameEventTrigger trigger, SpellDesc spell, boolean oneTime, int turnDelay) {\n\t\tthis(trigger, null, spell, oneTime, turnDelay);\n\t}\n\n\t@Override\n\tpublic SpellTrigger clone() {\n\t\tSpellTrigger clone = (SpellTrigger) super.clone();\n\t\tclone.primaryTrigger = (GameEventTrigger) primaryTrigger.clone();\n\t\tif (secondaryTrigger != null) {\n\t\t\tclone.secondaryTrigger = (GameEventTrigger) secondaryTrigger.clone();\n\t\t}\n\t\tclone.spell = spell.clone();\n\t\treturn clone;\n\t}\n\n\tpublic void expire() {\n\t\texpired = true;\n\t}\n\n\t@Override\n\tpublic EntityReference getHostReference() {\n\t\treturn hostReference;\n\t}\n\n\t@Override\n\tpublic int getOwner() {\n\t\treturn primaryTrigger.getOwner();\n\t}\n\n\tprotected SpellDesc getSpell() {\n\t\treturn spell;\n\t}\n\n\t@Override\n\tpublic boolean interestedIn(GameEventType eventType) {\n\t\tboolean result = primaryTrigger.interestedIn() == eventType || primaryTrigger.interestedIn() == GameEventType.ALL;\n\t\tif (secondaryTrigger != null) {\n\t\t\tresult |= secondaryTrigger.interestedIn() == eventType || secondaryTrigger.interestedIn() == GameEventType.ALL;\n\t\t}\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic boolean isExpired() {\n\t\treturn expired;\n\t}\n\n\t@Override\n\tpublic void onAdd(GameContext context) {\n\t}\n\n\tprotected void onFire(int ownerId, SpellDesc spell, GameEvent event) {\n\t\tevent.getGameContext().getLogic().castSpell(ownerId, spell, hostReference, null, true);\n\t}\n\n\t@Override\n\tpublic void onGameEvent(GameEvent event) {\n\t\tif (expired) {\n\t\t\treturn;\n\t\t}\n\n\t\tint ownerId = primaryTrigger.getOwner();\n\n\t\t// Expire the trigger beforehand, in case of copying minion (Echoing\n\t\t// Ooze). Since this method should only be called\n\t\t// after being checked to be played, copying one-turn triggers should no\n\t\t// longer matter.\n\t\t// But let's check to make sure we don't accidentally expire something\n\t\t// that's still using it.\n\t\tif (oneTurn && (event.getEventType() == GameEventType.TURN_END || event.getEventType() == GameEventType.TURN_START)) {\n\t\t\texpire();\n\t\t}\n\t\ttry {\n\t\t\tif (event.getEventTarget() != null) {\n\t\t\t\tevent.getGameContext().getEventTargetStack().push(event.getEventTarget().getReference());\n\t\t\t} else {\n\t\t\t\tevent.getGameContext().getEventTargetStack().push(null);\n\t\t\t}\n\t\t\tonFire(ownerId, spell, event);\n\t\t\tevent.getGameContext().getEventTargetStack().pop();\n\t\t} catch (Exception e) {\n\t\t\tevent.getGameContext().printCurrentTriggers();\n\t\t\tlogger.error(\"SpellTrigger cannot be executed; GameEventTrigger: {} Spell: {}\", primaryTrigger, spell);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t@Override\n\tpublic void onRemove(GameContext context) {\n\t}\n\n\t@Override\n\tpublic void setHost(Entity host) {\n\t\tthis.hostReference = host.getReference();\n\t}\n\n\t@Override\n\tpublic void setOwner(int playerIndex) {\n\t\tprimaryTrigger.setOwner(playerIndex);\n\t\tif (secondaryTrigger != null) {\n\t\t\tsecondaryTrigger.setOwner(playerIndex);\n\t\t}\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"[SpellTrigger primaryTrigger=\" + primaryTrigger + \", secondaryTrigger=\" + secondaryTrigger + \", spell=\" + spell\n\t\t\t\t+ \", hostReference=\" + hostReference + \", oneTurn=\" + oneTurn + \", expired=\" + expired + \", persistentOwner=\"\n\t\t\t\t+ persistentOwner + \", turnDelay=\" + turnDelay + \"]\";\n\t}\n\n\t@Override\n\tpublic boolean canFire(GameEvent event) {\n\t\tif (turnDelay > 0) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\tEntity host = event.getGameContext().resolveSingleTarget(hostReference);\n\t\treturn (triggerFires(primaryTrigger, event, host) || triggerFires(secondaryTrigger, event, host));\n\t}\n\n\tprivate boolean triggerFires(GameEventTrigger trigger, GameEvent event, Entity host) {\n\t\tif (trigger == null) {\n\t\t\treturn false;\n\t\t}\n\t\tif (trigger.interestedIn() != event.getEventType() && trigger.interestedIn() != GameEventType.ALL) {\n\t\t\treturn false;\n\t\t}\n\t\treturn trigger.fires(event, host);\n\t}\n\n\tpublic boolean hasPersistentOwner() {\n\t\treturn persistentOwner;\n\t}\n\n\tpublic void setPersistentOwner(boolean persistentOwner) {\n\t\tthis.persistentOwner = persistentOwner;\n\t}\n\t\n\tpublic void delayTimeDown() {\n\t\tif (turnDelay > 0) {\n\t\t\tturnDelay--;\n\t\t}\n\t}\n\t\n\tpublic boolean isDelayed() {\n\t\treturn turnDelay > 0 ? true : false;\n\t}\n\t\n\tpublic void countDown() {\n\t\tprimaryTrigger.countDown();\n\t\tif (secondaryTrigger != null) {\n\t\t\tsecondaryTrigger.countDown();\n\t\t}\n\t}\n\t\n\tpublic void countDown(int num) {\n\t\tprimaryTrigger.countDown(num);\n\t\tif (secondaryTrigger != null) {\n\t\t\tsecondaryTrigger.countDown(num);\n\t\t}\n\t}\n\t\n\tpublic boolean hasCounter() {\n\t\tif (secondaryTrigger != null) {\n\t\t\treturn primaryTrigger.getTriggerCount() > 0 || secondaryTrigger.getTriggerCount() > 0;\n\t\t}\n\t\treturn primaryTrigger.getTriggerCount() > 0;\n\t}\n\t\n\tpublic boolean oneTurnOnly() {\n\t\treturn oneTurn;\n\t}\n\t\n\tpublic boolean canFireCondition(GameEvent event) {\n\t\tif (primaryTrigger.canFireCondition(event) || (secondaryTrigger != null && secondaryTrigger.canFireCondition(event))) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/TargetAcquisitionTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.actions.ActionType;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.events.TargetAcquisitionEvent;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerArg;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class TargetAcquisitionTrigger extends GameEventTrigger {\n\n\tpublic TargetAcquisitionTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\tTargetAcquisitionEvent targetAcquisitionEvent = (TargetAcquisitionEvent) event;\n\n\t\tActionType actionType = (ActionType) desc.get(EventTriggerArg.ACTION_TYPE);\n\t\tif (targetAcquisitionEvent.getActionType() != actionType) {\n\t\t\treturn false;\n\t\t}\n\t\tEntityType sourceEntityType = (EntityType) desc.get(EventTriggerArg.SOURCE_ENTITY_TYPE);\n\t\tif (sourceEntityType != null && sourceEntityType != targetAcquisitionEvent.getSource().getEntityType()) {\n\t\t\treturn false;\n\t\t}\n\t\tEntityType targetEntityType = (EntityType) desc.get(EventTriggerArg.TARGET_ENTITY_TYPE);\n\t\tif (targetEntityType != null && targetEntityType != targetAcquisitionEvent.getTarget().getEntityType()) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.TARGET_ACQUISITION;\n\t}\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/TriggerManager.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.aura.Aura;\nimport net.demilich.metastone.game.targeting.EntityReference;\nimport net.demilich.metastone.utils.IDisposable;\n\npublic class TriggerManager implements Cloneable, IDisposable {\n\n\tpublic static Logger logger = LoggerFactory.getLogger(TriggerManager.class);\n\n\tprivate final List<IGameEventListener> triggers = new ArrayList<IGameEventListener>();\n\n\tpublic TriggerManager() {\n\t}\n\n\tprivate TriggerManager(TriggerManager otherTriggerManager) {\n\t\tfor (IGameEventListener gameEventListener : otherTriggerManager.triggers) {\n\t\t\ttriggers.add(gameEventListener.clone());\n\t\t}\n\t}\n\n\tpublic void addTrigger(IGameEventListener trigger) {\n\t\ttriggers.add(trigger);\n\t\tif (triggers.size() > 100) {\n\t\t\tlogger.warn(\"Warning, many triggers: \" + triggers.size() + \" adding one of type: \" + trigger);\n\t\t}\n\t}\n\n\t@Override\n\tpublic TriggerManager clone() {\n\t\treturn new TriggerManager(this);\n\t}\n\n\t@Override\n\tpublic void dispose() {\n\t\ttriggers.clear();\n\t}\n\n\tpublic void fireGameEvent(GameEvent event) {\n\t\tList<IGameEventListener> eventTriggers = new ArrayList<IGameEventListener>();\n\t\tList<IGameEventListener> removeTriggers = new ArrayList<IGameEventListener>();\n\t\tfor (IGameEventListener trigger : triggers) {\n\t\t\t// In order to stop premature expiration, check\n\t\t\t// for a oneTurnOnly tag and that it isn't delayed.\n\t\t\tif (event.getEventType() == GameEventType.TURN_END) {\n\t\t\t\tif(trigger.oneTurnOnly() && !trigger.isDelayed() &&\n\t\t\t\t\t\t!trigger.interestedIn(GameEventType.TURN_START) &&\n\t\t\t\t\t\t!trigger.interestedIn(GameEventType.TURN_END)) {\n\t\t\t\t\ttrigger.expire();\n\t\t\t\t}\n\t\t\t\ttrigger.delayTimeDown();\n\t\t\t}\n\t\t\tif (trigger.isExpired()) {\n\t\t\t\tremoveTriggers.add(trigger);\n\t\t\t}\n\n\t\t\tif (!trigger.interestedIn(event.getEventType())) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (triggers.contains(trigger) && trigger.canFire(event)) {\n\t\t\t\teventTriggers.add(trigger);\n\t\t\t}\n\t\t}\n\n\t\tfor (IGameEventListener trigger : eventTriggers) {\n\t\t\tif (trigger.canFireCondition(event) && triggers.contains(trigger)) {\n\t\t\t\ttrigger.countDown();\n\t\t\t\tif (!trigger.hasCounter()) {\n\t\t\t\t\ttrigger.onGameEvent(event);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// we need to double check here if the trigger still exists;\n\t\t\t// after all, a previous trigger may have removed it (i.e. double\n\t\t\t// corruption)\n\t\t\tif (trigger.isExpired()) {\n\t\t\t\tremoveTriggers.add(trigger);\n\t\t\t}\n\t\t}\n\n\t\tfor (IGameEventListener trigger : removeTriggers) {\n\t\t\ttriggers.remove(trigger);\n\t\t}\n\t}\n\n\tprivate List<IGameEventListener> getListSnapshot(List<IGameEventListener> triggerList) {\n\t\treturn new ArrayList<IGameEventListener>(triggerList);\n\t}\n\n\tpublic List<IGameEventListener> getTriggersAssociatedWith(EntityReference entityReference) {\n\t\tList<IGameEventListener> relevantTriggers = new ArrayList<>();\n\t\tfor (IGameEventListener trigger : triggers) {\n\t\t\tif (trigger.getHostReference().equals(entityReference)) {\n\t\t\t\trelevantTriggers.add(trigger);\n\t\t\t}\n\t\t}\n\t\treturn relevantTriggers;\n\t}\n\n\tpublic void printCurrentTriggers() {\n\t\tfor (IGameEventListener trigger : triggers) {\n\t\t\tSystem.out.println();\n\t\t\tSystem.out.println(trigger.toString());\n\t\t\tSystem.out.println();\n\t\t}\n\t}\n\n\tpublic void removeTrigger(IGameEventListener trigger) {\n\t\tif (!triggers.remove(trigger)) {\n\t\t\tSystem.out.println(\"Failed to remove trigger \" + trigger);\n\t\t}\n\t}\n\n\tpublic void removeTriggersAssociatedWith(EntityReference entityReference, boolean removeAuras) {\n\t\tfor (IGameEventListener trigger : getListSnapshot(triggers)) {\n\t\t\tif (trigger.getHostReference().equals(entityReference)) {\n\t\t\t\tif (!removeAuras && trigger instanceof Aura) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\ttriggers.remove(trigger);\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/TurnEndTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class TurnEndTrigger extends GameEventTrigger {\n\n\tpublic TurnEndTrigger() {\n\t\tthis(EventTriggerDesc.createEmpty(TurnEndTrigger.class));\n\t}\n\n\tpublic TurnEndTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.TURN_END;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/TurnStartTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class TurnStartTrigger extends GameEventTrigger {\n\n\tpublic TurnStartTrigger() {\n\t\tthis(EventTriggerDesc.createEmpty(TurnStartTrigger.class));\n\t}\n\n\tpublic TurnStartTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.TURN_START;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/WeaponDestroyedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\n\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.events.GameEventType;\nimport net.demilich.metastone.game.events.WeaponDestroyedEvent;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\n\npublic class WeaponDestroyedTrigger extends GameEventTrigger {\n\n\tpublic WeaponDestroyedTrigger(EventTriggerDesc desc) {\n\t\tsuper(desc);\n\t}\n\n\t@Override\n\tprotected boolean fire(GameEvent event, Entity host) {\n\t\tWeaponDestroyedEvent weaponDestroyedEvent = (WeaponDestroyedEvent) event;\n\t\treturn weaponDestroyedEvent.getWeapon().getOwner() == host.getOwner();\n\t}\n\n\t@Override\n\tpublic GameEventType interestedIn() {\n\t\treturn GameEventType.WEAPON_DESTROYED;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/WeaponEquippedTrigger.java",
    "content": "package net.demilich.metastone.game.spells.trigger;\r\n\r\nimport net.demilich.metastone.game.entities.Entity;\r\nimport net.demilich.metastone.game.events.GameEvent;\r\nimport net.demilich.metastone.game.events.GameEventType;\r\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\r\n\r\npublic class WeaponEquippedTrigger extends GameEventTrigger {\r\n\r\n\tpublic WeaponEquippedTrigger(EventTriggerDesc desc) {\r\n\t\tsuper(desc);\r\n\t}\r\n\r\n\t@Override\r\n\tprotected boolean fire(GameEvent event, Entity host) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic GameEventType interestedIn() {\r\n\t\treturn GameEventType.WEAPON_EQUIPPED;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/types/Quest.java",
    "content": "package net.demilich.metastone.game.spells.trigger.types;\n\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.trigger.GameEventTrigger;\nimport net.demilich.metastone.game.spells.trigger.SpellTrigger;\n\npublic class Quest extends SpellTrigger {\n\n\tprivate Card source;\n\n\tpublic Quest(GameEventTrigger trigger, SpellDesc spell, Card source) {\n\t\tsuper(trigger, spell);\n\t\tthis.source = source;\n\t}\n\n\tpublic Card getSource() {\n\t\treturn source;\n\t}\n\n\t@Override\n\tprotected void onFire(int ownerId, SpellDesc spell, GameEvent event) {\n\t\tsuper.onFire(ownerId, spell, event);\n\t\tPlayer owner = event.getGameContext().getPlayer(ownerId);\n\t\tevent.getGameContext().getLogic().questTriggered(owner, this);\n\t\texpire();\n\t}\n\n\t@Override\n\tpublic void onGameEvent(GameEvent event) {\n\t\tsuper.onGameEvent(event);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/spells/trigger/types/Secret.java",
    "content": "package net.demilich.metastone.game.spells.trigger.types;\n\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.events.GameEvent;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.trigger.GameEventTrigger;\nimport net.demilich.metastone.game.spells.trigger.SpellTrigger;\n\npublic class Secret extends SpellTrigger {\n\n\tprivate Card source;\n\n\tpublic Secret(GameEventTrigger trigger, SpellDesc spell, Card source) {\n\t\tsuper(trigger, spell);\n\t\tthis.source = source;\n\t}\n\n\tpublic Card getSource() {\n\t\treturn source;\n\t}\n\n\t@Override\n\tprotected void onFire(int ownerId, SpellDesc spell, GameEvent event) {\n\t\tsuper.onFire(ownerId, spell, event);\n\t\tPlayer owner = event.getGameContext().getPlayer(ownerId);\n\t\tevent.getGameContext().getLogic().secretTriggered(owner, this);\n\t\texpire();\n\t}\n\n\t@Override\n\tpublic void onGameEvent(GameEvent event) {\n\t\tif (event.getGameContext().getActivePlayerId() == getOwner()) {\n\t\t\treturn;\n\t\t}\n\t\tsuper.onGameEvent(event);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/statistics/GameStatistics.java",
    "content": "package net.demilich.metastone.game.statistics;\n\nimport java.util.EnumMap;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.entities.minions.Permanent;\nimport net.demilich.metastone.game.entities.weapons.Weapon;\n\npublic class GameStatistics implements Cloneable {\n\n\tprivate final Map<Statistic, Object> stats = new EnumMap<Statistic, Object>(Statistic.class);\n\tprivate final Map<String, Map<Integer, Integer>> cardsPlayed = new HashMap<String, Map<Integer, Integer>>();\n\tprivate final Map<String, Map<Integer, Integer>> minionsSummoned = new HashMap<String, Map<Integer, Integer>>();\n\tprivate final Map<String, Map<Integer, Integer>> permanentsSummoned = new HashMap<String, Map<Integer, Integer>>();\n\n\tprivate void add(Statistic key, long value) {\n\t\tif (!stats.containsKey(key)) {\n\t\t\tstats.put(key, 0L);\n\t\t}\n\t\tlong newValue = getLong(key) + value;\n\t\tstats.put(key, newValue);\n\t}\n\n\tpublic void armorGained(int armor) {\n\t\tadd(Statistic.ARMOR_GAINED, armor);\n\t}\n\n\tpublic void cardDrawn() {\n\t\tadd(Statistic.CARDS_DRAWN, 1);\n\t}\n\n\tpublic void cardPlayed(Card card, int turn) {\n\t\tadd(Statistic.CARDS_PLAYED, 1);\n\n\t\tswitch (card.getCardType()) {\n\t\tcase HERO_POWER:\n\t\t\tadd(Statistic.HERO_POWER_USED, 1);\n\t\t\tbreak;\n\t\tcase MINION:\n\t\t\tadd(Statistic.MINIONS_PLAYED, 1);\n\t\t\tbreak;\n\t\tcase PERMANENT:\n\t\t\tadd(Statistic.PERMANENTS_PLAYED, 1);\n\t\t\tbreak;\n\t\tcase SPELL:\n\t\tcase CHOOSE_ONE:\n\t\t\tadd(Statistic.SPELLS_CAST, 1);\n\t\t\tbreak;\n\t\tcase WEAPON:\n\t\t\tadd(Statistic.WEAPONS_PLAYED, 1);\n\t\tcase HERO:\n\t\t\tbreak;\n\t\t}\n\t\tincreaseCardCount(card, turn);\n\t}\n\n\tpublic GameStatistics clone() {\n\t\tGameStatistics clone = new GameStatistics();\n\t\tclone.stats.putAll(stats);\n\t\tclone.getCardsPlayed().putAll(getCardsPlayed());\n\t\tclone.getMinionsSummoned().putAll(getMinionsSummoned());\n\t\tclone.getPermanentsSummoned().putAll(getPermanentsSummoned());\n\t\treturn clone;\n\t}\n\n\tpublic boolean contains(Statistic key) {\n\t\treturn stats.containsKey(key);\n\t}\n\n\tpublic void damageDealt(int damage) {\n\t\tadd(Statistic.DAMAGE_DEALT, damage);\n\t}\n\n\tpublic void equipWeapon(Weapon weapon) {\n\t\tadd(Statistic.WEAPONS_EQUIPPED, 1);\n\t}\n\n\tpublic void fatigueDamage(int fatigueDamage) {\n\t\tadd(Statistic.FATIGUE_DAMAGE, fatigueDamage);\n\t}\n\n\tpublic void gameLost() {\n\t\tadd(Statistic.GAMES_LOST, 1);\n\t\tupdateWinRate();\n\t}\n\n\tpublic void gameWon() {\n\t\tadd(Statistic.GAMES_WON, 1);\n\t\tupdateWinRate();\n\t}\n\n\tpublic Object get(Statistic key) {\n\t\treturn stats.get(key);\n\t}\n\n\tpublic Map<String, Map<Integer, Integer>> getCardsPlayed() {\n\t\treturn cardsPlayed;\n\t}\n\n\tpublic int getCardsPlayedCount(String cardId) {\n\t\tint count = 0;\n\t\tfor (String card : cardsPlayed.keySet()) {\n\t\t\tfor (int turn : cardsPlayed.get(card).keySet()) {\n\t\t\t\tcount += cardsPlayed.get(card).get(turn);\n\t\t\t}\n\t\t}\n\t\treturn count;\n\t}\n\t\n\tpublic Map<String, Map<Integer, Integer>> getMinionsSummoned() {\n\t\treturn minionsSummoned;\n\t}\n\t\n\tpublic Map<String, Map<Integer, Integer>> getPermanentsSummoned() {\n\t\treturn permanentsSummoned;\n\t}\n\n\tpublic double getDouble(Statistic key) {\n\t\treturn stats.containsKey(key) ? (double) stats.get(key) : 0.0;\n\t}\n\n\tpublic long getLong(Statistic key) {\n\t\treturn stats.containsKey(key) ? (long) stats.get(key) : 0L;\n\t}\n\n\tpublic void heal(int healing) {\n\t\tadd(Statistic.HEALING_DONE, healing);\n\t}\n\t\n\tprivate void increaseCardCount(Card card, int turn) {\n\t\tif (card.getCardType().isCardType(CardType.HERO_POWER)) {\n\t\t\treturn;\n\t\t}\n\t\tString cardId = card.getCardId();\n\t\tif (!getCardsPlayed().containsKey(cardId)) {\n\t\t\tgetCardsPlayed().put(cardId, new HashMap<Integer, Integer>());\n\t\t}\n\t\tif (!getCardsPlayed().get(cardId).containsKey(turn)) {\n\t\t\tgetCardsPlayed().get(cardId).put(turn, 0);\n\t\t}\n\t\tgetCardsPlayed().get(cardId).put(turn, getCardsPlayed().get(cardId).get(turn) + 1);\n\t}\n\t\n\tprivate void increaseMinionCount(Minion minion, int turn) {\n\t\tString cardId = minion.getSourceCard().getCardId();\n\t\tif (!getMinionsSummoned().containsKey(cardId)) {\n\t\t\tgetMinionsSummoned().put(cardId, new HashMap<Integer, Integer>());\n\t\t}\n\t\tif (!getMinionsSummoned().get(cardId).containsKey(turn)) {\n\t\t\tgetMinionsSummoned().get(cardId).put(turn, 0);\n\t\t}\n\t\tgetMinionsSummoned().get(cardId).put(turn, getMinionsSummoned().get(cardId).get(turn) + 1);\n\t}\n\t\n\tprivate void increasePermanentCount(Permanent permanent, int turn) {\n\t\tString cardId = permanent.getSourceCard().getCardId();\n\t\tif (!getPermanentsSummoned().containsKey(cardId)) {\n\t\t\tgetPermanentsSummoned().put(cardId, new HashMap<Integer, Integer>());\n\t\t}\n\t\tif (!getPermanentsSummoned().get(cardId).containsKey(turn)) {\n\t\t\tgetPermanentsSummoned().get(cardId).put(turn, 0);\n\t\t}\n\t\tgetPermanentsSummoned().get(cardId).put(turn, getPermanentsSummoned().get(cardId).get(turn) + 1);\n\t}\n\n\tpublic void manaSpent(int mana) {\n\t\tadd(Statistic.MANA_SPENT, mana);\n\t}\n\n\tpublic void merge(GameStatistics otherStatistics) {\n\t\tfor (Statistic stat : otherStatistics.stats.keySet()) {\n\t\t\tObject value = get(stat);\n\t\t\tif (value != null) {\n\t\t\t\tif (value instanceof Long) {\n\t\t\t\t\tadd(stat, otherStatistics.getLong(stat));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tstats.put(stat, otherStatistics.get(stat));\n\t\t\t}\n\t\t}\n\t\tfor (String cardId : otherStatistics.getCardsPlayed().keySet()) {\n\t\t\tif (!getCardsPlayed().containsKey(cardId)) {\n\t\t\t\tgetCardsPlayed().put(cardId, new HashMap<Integer, Integer>());\n\t\t\t}\n\t\t\tfor (int turn : otherStatistics.getCardsPlayed().get(cardId).keySet()) {\n\t\t\t\tif (!getCardsPlayed().get(cardId).containsKey(turn)) {\n\t\t\t\t\tgetCardsPlayed().get(cardId).put(turn, 0);\n\t\t\t\t}\n\t\t\t\tgetCardsPlayed().get(cardId).put(turn, getCardsPlayed().get(cardId).get(turn) + otherStatistics.getCardsPlayed().get(cardId).get(turn));\n\t\t\t}\n\t\t}\n\t\tupdateWinRate();\n\t}\n\t\n\tpublic void minionSummoned(Minion minion, int turn) {\n\t\tadd(Statistic.MINIONS_PLAYED, 1);\n\n\t\tincreaseMinionCount(minion, turn);\n\t}\n\t\n\tpublic void permanentSummoned(Permanent permanent, int turn) {\n\t\tadd(Statistic.PERMANENTS_PLAYED, 1);\n\n\t\tincreasePermanentCount(permanent, turn);\n\t}\n\n\tpublic void set(Statistic key, Object value) {\n\t\tstats.put(key, value);\n\t}\n\n\tpublic void startTurn() {\n\t\tadd(Statistic.TURNS_TAKEN, 1);\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\tStringBuilder builder = new StringBuilder(\"[GameStatistics]\\n\");\n\t\tfor (Statistic stat : stats.keySet()) {\n\t\t\tbuilder.append(stat);\n\t\t\tbuilder.append(\": \");\n\t\t\tbuilder.append(stats.get(stat));\n\t\t\tbuilder.append(\"\\n\");\n\t\t}\n\t\treturn builder.toString();\n\t}\n\n\tprivate void updateWinRate() {\n\t\tdouble winRate = getLong(Statistic.GAMES_WON) / (double) (getLong(Statistic.GAMES_WON) + getLong(Statistic.GAMES_LOST));\n\t\tset(Statistic.WIN_RATE, winRate);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/statistics/Statistic.java",
    "content": "package net.demilich.metastone.game.statistics;\n\npublic enum Statistic {\n\t// meta statistics\n\tWIN_RATE,\n\t// AVG_TURNS_TAKEN,\n\n\tGAMES_WON,\n\tGAMES_LOST,\n\tDAMAGE_DEALT,\n\tHEALING_DONE,\n\tMANA_SPENT,\n\tCARDS_PLAYED,\n\tTURNS_TAKEN,\n\tARMOR_GAINED,\n\tCARDS_DRAWN,\n\tFATIGUE_DAMAGE,\n\tMINIONS_PLAYED,\n\tPERMANENTS_PLAYED,\n\tSPELLS_CAST,\n\tHERO_POWER_USED,\n\tWEAPONS_EQUIPPED,\n\tWEAPONS_PLAYED,\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/targeting/CardLocation.java",
    "content": "package net.demilich.metastone.game.targeting;\n\npublic enum CardLocation {\n\tGRAVEYARD,\n\tHAND,\n\tDECK,\n\tSET_ASIDE_ZONE,\n\tHERO_POWER,\n\tPENDING,\n\tEVENT,\n\tCATALOGUE\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/targeting/CardReference.java",
    "content": "package net.demilich.metastone.game.targeting;\n\npublic class CardReference {\n\n\tprivate final int playerId;\n\tprivate final CardLocation location;\n\tprivate final int cardId;\n\tprivate final String cardName;\n\n\tpublic CardReference(int playerId, CardLocation location, int cardId, String cardName) {\n\t\tthis.playerId = playerId;\n\t\tthis.location = location;\n\t\tthis.cardId = cardId;\n\t\tthis.cardName = cardName;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object obj) {\n\t\tif (!(obj instanceof CardReference)) {\n\t\t\treturn false;\n\t\t}\n\t\tCardReference cardReference = (CardReference) obj;\n\t\treturn cardReference.getCardId() == getCardId() && cardReference.getPlayerId() == cardReference.getPlayerId();\n\t}\n\n\tpublic int getCardId() {\n\t\treturn cardId;\n\t}\n\n\tpublic String getCardName() {\n\t\treturn cardName;\n\t}\n\n\tpublic CardLocation getLocation() {\n\t\treturn location;\n\t}\n\n\tpublic int getPlayerId() {\n\t\treturn playerId;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn String.format(\"[CardReference playerId:%d cardName:%s cardLocation:%s cardId:%d]\", playerId, cardName, location.toString(),\n\t\t\t\tcardId);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/targeting/EntityReference.java",
    "content": "package net.demilich.metastone.game.targeting;\r\n\r\nimport net.demilich.metastone.game.entities.Entity;\r\n\r\npublic class EntityReference {\r\n\tpublic static final EntityReference NONE = new EntityReference(-1);\r\n\tpublic static final EntityReference ENEMY_CHARACTERS = new EntityReference(-2);\r\n\tpublic static final EntityReference ENEMY_MINIONS = new EntityReference(-3);\r\n\tpublic static final EntityReference ENEMY_HERO = new EntityReference(-4);\r\n\tpublic static final EntityReference FRIENDLY_CHARACTERS = new EntityReference(-5);\r\n\tpublic static final EntityReference FRIENDLY_MINIONS = new EntityReference(-6);\r\n\tpublic static final EntityReference OTHER_FRIENDLY_MINIONS = new EntityReference(-7);\r\n\tpublic static final EntityReference ADJACENT_MINIONS = new EntityReference(-8);\r\n\tpublic static final EntityReference FRIENDLY_HERO = new EntityReference(-9);\r\n\tpublic static final EntityReference ALL_MINIONS = new EntityReference(-10);\r\n\tpublic static final EntityReference ALL_CHARACTERS = new EntityReference(-11);\r\n\tpublic static final EntityReference ALL_OTHER_CHARACTERS = new EntityReference(-12);\r\n\tpublic static final EntityReference ALL_OTHER_MINIONS = new EntityReference(-13);\r\n\tpublic static final EntityReference FRIENDLY_WEAPON = new EntityReference(-14);\r\n\tpublic static final EntityReference ENEMY_WEAPON = new EntityReference(-15);\r\n\tpublic static final EntityReference FRIENDLY_HAND = new EntityReference(-16);\r\n\tpublic static final EntityReference ENEMY_HAND = new EntityReference(-17);\r\n\tpublic static final EntityReference OPPOSITE_MINIONS = new EntityReference(-18);\r\n\tpublic static final EntityReference LEFTMOST_FRIENDLY_MINION = new EntityReference(-19);\r\n\tpublic static final EntityReference LEFTMOST_ENEMY_MINION = new EntityReference(-20);\r\n\tpublic static final EntityReference FRIENDLY_PLAYER = new EntityReference(-21);\r\n\tpublic static final EntityReference ENEMY_PLAYER = new EntityReference(-22);\r\n\tpublic static final EntityReference MINIONS_TO_LEFT = new EntityReference(-23);\r\n\tpublic static final EntityReference MINIONS_TO_RIGHT = new EntityReference(-24);\r\n\r\n\tpublic static final EntityReference TARGET = new EntityReference(-30);\r\n\tpublic static final EntityReference SPELL_TARGET = new EntityReference(-31);\r\n\r\n\tpublic static final EntityReference EVENT_TARGET = new EntityReference(-40);\r\n\tpublic static final EntityReference SELF = new EntityReference(-41);\r\n\tpublic static final EntityReference KILLED_MINION = new EntityReference(-42);\r\n\tpublic static final EntityReference ATTACKER_REFERENCE = new EntityReference(-43);\r\n\tpublic static final EntityReference PENDING_CARD = new EntityReference(-44);\r\n\tpublic static final EntityReference EVENT_CARD = new EntityReference(-45);\r\n\r\n\tpublic static EntityReference pointTo(Entity entity) {\r\n\t\tif (entity == null) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t\treturn new EntityReference(entity.getId());\r\n\t}\r\n\r\n\tprivate final int key;\r\n\r\n\tpublic EntityReference(int key) {\r\n\t\tthis.key = key;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic boolean equals(Object obj) {\r\n\t\tif (!(obj instanceof EntityReference)) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tEntityReference entityReference = (EntityReference) obj;\r\n\t\treturn entityReference.getId() == getId();\r\n\t}\r\n\r\n\tpublic int getId() {\r\n\t\treturn key;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic int hashCode() {\r\n\t\treturn new Integer(key).hashCode();\r\n\t}\r\n\r\n\tpublic boolean isTargetGroup() {\r\n\t\treturn key < 0;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String toString() {\r\n\t\treturn String.format(\"[EntityReference id:%d]\", key);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/targeting/IdFactory.java",
    "content": "package net.demilich.metastone.game.targeting;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.logic.CustomCloneable;\n\npublic class IdFactory extends CustomCloneable {\n\n\tpublic static final int UNASSIGNED = -1;\n\tpublic static final int PLAYER_1 = GameContext.PLAYER_1;\n\tpublic static final int PLAYER_2 = GameContext.PLAYER_2;\n\n\tprivate int id;\n\n\tpublic IdFactory() {\n\t\tid = PLAYER_2 + 1;\n\t}\n\n\tprivate IdFactory(int resumeId) {\n\t\tthis.id = resumeId;\n\t}\n\n\t@Override\n\tpublic IdFactory clone() {\n\t\treturn new IdFactory(id);\n\t}\n\n\tpublic int generateId() {\n\t\treturn ++id;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/targeting/TargetSelection.java",
    "content": "package net.demilich.metastone.game.targeting;\n\npublic enum TargetSelection {\n\t// DO NOT CHANGE THIS ORDERING!\n\t// TODO: Allow the order to be changed.\n\tNONE,\n\tAUTO,\n\tANY,\n\tMINIONS,\n\tENEMY_CHARACTERS,\n\tFRIENDLY_CHARACTERS,\n\tENEMY_MINIONS,\n\tFRIENDLY_MINIONS,\n\tHEROES,\n\tENEMY_HERO,\n\tFRIENDLY_HERO,\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/targeting/TargetType.java",
    "content": "package net.demilich.metastone.game.targeting;\r\n\r\npublic enum TargetType {\r\n\tIGNORE_AS_TARGET,\r\n\tIGNORE_AS_SOURCE,\r\n\tIGNORE_OTHER_SOURCES,\r\n\tIGNORE_OTHER_TARGETS,\r\n}\r\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/utils/GameTagUtils.java",
    "content": "package net.demilich.metastone.game.utils;\n\nimport net.demilich.metastone.game.Attribute;\n\npublic class GameTagUtils {\n\n\tpublic static String getTagName(Attribute tag) {\n\t\treturn tag.toString();\n\t}\n\n\tpublic static TagValueType getTagValueType(Attribute tag) {\n\t\tswitch (tag) {\n\t\tcase ARMOR:\n\t\tcase ATTACK:\n\t\tcase ATTACK_BONUS:\n\t\tcase TEMPORARY_ATTACK_BONUS:\n\t\tcase AURA_ATTACK_BONUS:\n\t\tcase AURA_HP_BONUS:\n\t\tcase HP:\n\t\tcase HP_BONUS:\n\t\tcase COMBO:\n\t\tcase DIED_ON_TURN:\n\t\tcase EXTRA_ATTACKS:\n\t\tcase FATIGUE:\n\t\tcase MAX_HP:\n\t\tcase NUMBER_OF_ATTACKS:\n\t\tcase OPPONENT_SPELL_DAMAGE:\n\t\tcase OVERLOAD:\n\t\tcase SPELL_AMPLIFY_MULTIPLIER:\n\t\tcase HEAL_AMPLIFY_MULTIPLIER:\n\t\tcase SPELL_DAMAGE:\n\t\t\treturn TagValueType.INTEGER;\n\t\tcase ATTACK_EQUALS_HP:\n\t\tcase CANNOT_ATTACK:\n\t\tcase CANNOT_ATTACK_HEROES:\n\t\tcase CANNOT_REDUCE_HP_BELOW_1:\n\t\tcase CHARGE:\n\t\tcase COUNTERED:\n\t\tcase DIVINE_SHIELD:\n\t\tcase DOUBLE_DEATHRATTLES:\n\t\tcase DOUBLE_BATTLECRIES:\n\t\tcase BOTH_CHOOSE_ONE_OPTIONS:\n\t\tcase ENRAGED:\n\t\tcase FROZEN:\n\t\tcase IMMUNE:\n\t\tcase IMMUNE_WHILE_ATTACKING:\n\t\tcase INVERT_HEALING:\n\t\tcase QUEST:\n\t\tcase SECRET:\n\t\tcase STEALTH:\n\t\tcase SUMMONING_SICKNESS:\n\t\tcase TAUNT:\n\t\tcase UNLIMITED_ATTACKS:\n\t\tcase UNTARGETABLE_BY_SPELLS:\n\t\tcase AURA_UNTARGETABLE_BY_SPELLS:\n\t\tcase WINDFURY:\n\t\t\treturn TagValueType.BOOLEAN;\n\t\tcase BASE_ATTACK:\n\t\tcase BASE_HP:\n\t\tcase BATTLECRY:\n\t\tcase DEATHRATTLES:\n\t\tcase DEBUG:\n\t\tcase MANA_COST_MODIFIER:\n\t\tcase RACE:\n\t\t\treturn TagValueType.OTHER;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t\treturn TagValueType.OTHER;\n\t}\n\n\tprivate GameTagUtils() {\n\n\t}\n\n}\n"
  },
  {
    "path": "game/src/main/java/net/demilich/metastone/game/utils/TagValueType.java",
    "content": "package net.demilich.metastone.game.utils;\n\npublic enum TagValueType {\n\tINTEGER, BOOLEAN, OTHER\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/AdvancedMechanicTests.java",
    "content": "package net.demilich.metastone.tests;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.testng.Assert;\nimport org.testng.annotations.Test;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.actions.PhysicalAttackAction;\nimport net.demilich.metastone.game.behaviour.Behaviour;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.IChooseOneCard;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.cards.SpellCard;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.spells.SetHpSpell;\nimport net.demilich.metastone.game.spells.SilenceSpell;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class AdvancedMechanicTests extends BasicTests {\n\n\t@Test\n\tpublic void testChooseOne() {\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\n\t\tcontext.endTurn();\n\t\tTestMinionCard minionCard = new TestMinionCard(1, 4);\n\t\tplayCard(context, opponent, minionCard);\n\t\tcontext.endTurn();\n\n\t\tplayer.getHero().getHeroPower().markUsed();\n\t\tfor (Card card : player.getHand().toList()) {\n\t\t\tcontext.getLogic().removeCard(player.getId(), card);\n\t\t}\n\t\tCard wrath = CardCatalogue.getCardById(\"spell_wrath\");\n\t\tIChooseOneCard wrathChooseOne = (IChooseOneCard) wrath;\n\t\tcontext.getLogic().receiveCard(player.getId(), wrath);\n\t\tplayer.setMana(wrath.getBaseManaCost() + 1);\n\t\tList<GameAction> validActions = context.getLogic().getValidActions(player.getId());\n\t\tAssert.assertEquals(player.getHand().getCount(), 1);\n\t\t// player should have 3 valid actions: two from 'Choose One' card and 1 'End Turn'\n\t\tAssert.assertEquals(validActions.size(), 3);\n\n\t\tGameAction playWrath = wrathChooseOne.playOptions()[0];\n\t\tplayWrath.setTarget(getSingleMinion(opponent.getMinions()));\n\t\tcontext.getLogic().performGameAction(player.getId(), playWrath);\n\n\t\tvalidActions = context.getLogic().getValidActions(player.getId());\n\t\t// This time it should just be the 'End Turn'\n\t\tAssert.assertEquals(validActions.size(), 1);\n\t\tAssert.assertEquals(player.getHand().getCount(), 0);\n\t}\n\n\t@Test\n\tpublic void testCopyCards() {\n\t\tGameContext context = createContext(HeroClass.PRIEST, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\t\tplayer.getHand().removeAll();\n\n\t\tint cardsInHand = player.getHand().getCount();\n\t\tint cardsInOpponentsDeck = opponent.getDeck().getCount();\n\t\tCard thoughtsteal = CardCatalogue.getCardById(\"spell_thoughtsteal\");\n\t\tcontext.getLogic().receiveCard(player.getId(), thoughtsteal);\n\t\tcontext.getLogic().performGameAction(player.getId(), thoughtsteal.play());\n\t\tAssert.assertEquals(opponent.getDeck().getCount(), cardsInOpponentsDeck);\n\t\tAssert.assertEquals(player.getHand().getCount(), cardsInHand + 2);\n\t}\n\n\t@Test\n\tpublic void testDivineShield() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer mage = context.getPlayer1();\n\t\tmage.setMana(10);\n\t\tPlayer warrior = context.getPlayer2();\n\t\twarrior.setMana(10);\n\n\t\tMinionCard minionCard1 = new TestMinionCard(2, 2, Attribute.DIVINE_SHIELD);\n\t\tcontext.getLogic().receiveCard(mage.getId(), minionCard1);\n\t\tcontext.getLogic().performGameAction(mage.getId(), minionCard1.play());\n\n\t\tMinionCard minionCard2 = new TestMinionCard(5, 5);\n\t\tcontext.getLogic().receiveCard(warrior.getId(), minionCard2);\n\t\tcontext.getLogic().performGameAction(warrior.getId(), minionCard2.play());\n\n\t\tActor attacker = getSingleMinion(mage.getMinions());\n\t\tActor defender = getSingleMinion(warrior.getMinions());\n\n\t\tGameAction attackAction = new PhysicalAttackAction(attacker.getReference());\n\t\tattackAction.setTarget(defender);\n\n\t\tcontext.getLogic().performGameAction(mage.getId(), attackAction);\n\t\tAssert.assertEquals(attacker.getHp(), attacker.getMaxHp());\n\t\tAssert.assertEquals(defender.getHp(), defender.getMaxHp() - attacker.getAttack());\n\t\tAssert.assertEquals(attacker.isDestroyed(), false);\n\n\t\tcontext.getLogic().performGameAction(mage.getId(), attackAction);\n\t\tAssert.assertEquals(attacker.getHp(), attacker.getMaxHp() - defender.getAttack());\n\t\tAssert.assertEquals(defender.getHp(), defender.getMaxHp() - attacker.getAttack() * 2);\n\t\tAssert.assertEquals(attacker.isDestroyed(), true);\n\t}\n\n\t@Test\n\tpublic void testEnrage() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.PRIEST);\n\t\tPlayer mage = context.getPlayer1();\n\t\tmage.setMana(10);\n\t\tPlayer priest = context.getPlayer2();\n\t\tpriest.setMana(10);\n\n\t\tfinal int BASE_ATTACK = 2;\n\t\tfinal int ENRAGE_ATTACK_BONUS = 3;\n\t\tplayCard(context, priest, CardCatalogue.getCardById(\"minion_amani_berserker\"));\n\n\t\tplayCard(context, mage, new TestMinionCard(1, 10));\n\n\t\tEntity attacker = getSingleMinion(mage.getMinions());\n\t\tActor defender = getSingleMinion(priest.getMinions());\n\n\t\tAssert.assertEquals(defender.getAttack(), BASE_ATTACK);\n\t\tAssert.assertEquals(defender.hasAttribute(Attribute.ENRAGED), false);\n\n\t\t// attack once, should apply the enrage attack bonus\n\t\tGameAction attackAction = new PhysicalAttackAction(attacker.getReference());\n\t\tattackAction.setTarget(defender);\n\t\tcontext.getLogic().performGameAction(mage.getId(), attackAction);\n\t\tAssert.assertEquals(defender.getAttack(), BASE_ATTACK + ENRAGE_ATTACK_BONUS);\n\t\tAssert.assertEquals(defender.hasAttribute(Attribute.ENRAGED), true);\n\t\t// attack second time, enrage bonus should not increase\n\t\tcontext.getLogic().performGameAction(mage.getId(), attackAction);\n\t\tAssert.assertEquals(defender.getAttack(), BASE_ATTACK + ENRAGE_ATTACK_BONUS);\n\n\t\t// heal - enrage attack bonus should be gone\n\t\tGameAction healAction = priest.getHero().getHeroPower().play();\n\t\thealAction.setTarget(defender);\n\t\tcontext.getLogic().performGameAction(priest.getId(), healAction);\n\t\tAssert.assertEquals(defender.getAttack(), BASE_ATTACK);\n\t\tAssert.assertEquals(defender.hasAttribute(Attribute.ENRAGED), false);\n\t\t\n\t\t// attack once more - should enrage again\n\t\tcontext.getLogic().performGameAction(mage.getId(), attackAction);\n\t\tAssert.assertEquals(defender.getAttack(), BASE_ATTACK + ENRAGE_ATTACK_BONUS);\n\t\tAssert.assertEquals(defender.hasAttribute(Attribute.ENRAGED), true);\n\t\t\n\t\t// attack should be set to 1\n\t\tplayCardWithTarget(context, mage, CardCatalogue.getCardById(\"spell_humility\"), defender);\n\t\tAssert.assertEquals(defender.getAttack(), 1);\n\t\tAssert.assertEquals(defender.hasAttribute(Attribute.ENRAGED), true);\n\t}\n\n\t@Test\n\tpublic void testOverload() {\n\t\tGameContext context = createContext(HeroClass.SHAMAN, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tint playerId = player.getId();\n\n\t\tcontext.getLogic().startTurn(playerId);\n\t\tAssert.assertEquals(player.getMana(), 1);\n\t\tcontext.getLogic().endTurn(playerId);\n\t\tcontext.getLogic().startTurn(playerId);\n\t\tAssert.assertEquals(player.getMana(), 2);\n\n\t\tCard overloadCard = new TestMinionCard(1, 1);\n\t\toverloadCard.setAttribute(Attribute.OVERLOAD, 2);\n\t\tcontext.getLogic().receiveCard(playerId, overloadCard);\n\t\tcontext.getLogic().performGameAction(playerId, overloadCard.play());\n\t\tcontext.getLogic().endTurn(playerId);\n\t\tcontext.getLogic().startTurn(playerId);\n\t\tAssert.assertEquals(player.getMana(), 1);\n\n\t\tcontext.getLogic().endTurn(playerId);\n\t\tcontext.getLogic().startTurn(playerId);\n\t\tAssert.assertEquals(player.getMana(), 4);\n\t}\n\n\t@Test\n\tpublic void testSetHpPlusSilence() {\n\t\tGameContext context = createContext(HeroClass.HUNTER, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\n\t\tint baseHp = 5;\n\t\t// summon a minion and check the base hp\n\t\tplayCard(context, opponent, new TestMinionCard(4, baseHp));\n\t\tActor minion = getSingleMinion(opponent.getMinions());\n\t\tAssert.assertEquals(minion.getHp(), baseHp);\n\n\t\tint modifiedHp = 1;\n\t\t// cast a spell on the minion which modifies the hp\n\t\tSpellDesc setHpSpell = SetHpSpell.create(modifiedHp);\n\t\tSpellCard spellCard = new TestSpellCard(setHpSpell);\n\t\tspellCard.setTargetRequirement(TargetSelection.MINIONS);\n\t\tcontext.getLogic().receiveCard(player.getId(), spellCard);\n\t\tGameAction playSpellCard = spellCard.play();\n\t\tplaySpellCard.setTarget(minion);\n\t\tcontext.getLogic().performGameAction(player.getId(), playSpellCard);\n\t\tAssert.assertEquals(minion.getHp(), modifiedHp);\n\t\tAssert.assertEquals(minion.getMaxHp(), modifiedHp);\n\n\t\t// silence the creature - hp should be back to original value\n\t\tSpellDesc silenceSpell = SilenceSpell.create();\n\t\tspellCard = new TestSpellCard(silenceSpell);\n\t\tspellCard.setTargetRequirement(TargetSelection.MINIONS);\n\t\tcontext.getLogic().receiveCard(player.getId(), spellCard);\n\t\tplaySpellCard = spellCard.play();\n\t\tplaySpellCard.setTarget(minion);\n\t\tcontext.getLogic().performGameAction(player.getId(), playSpellCard);\n\t\tAssert.assertEquals(minion.getHp(), baseHp);\n\t}\n\n\t@Test\n\tpublic void testShorttermBuffs() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer mage = context.getPlayer1();\n\t\tmage.setMana(10);\n\t\tPlayer warrior = context.getPlayer2();\n\t\twarrior.setMana(10);\n\n\t\tint baseAttack = 1;\n\t\tmage.setBehaviour(new Behaviour() {\n\n\t\t\t@Override\n\t\t\tpublic String getName() {\n\t\t\t\treturn \"Select-First\";\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic List<Card> mulligan(GameContext context, Player player, List<Card> cards) {\n\t\t\t\treturn new ArrayList<Card>();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic GameAction requestAction(GameContext context, Player player, List<GameAction> validActions) {\n\t\t\t\treturn validActions.get(0);\n\t\t\t}\n\n\t\t});\n\n\t\tplayCard(context, mage, new TestMinionCard(baseAttack, 1));\n\t\tActor testSubject = getSingleMinion(mage.getMinions());\n\t\tAssert.assertEquals(testSubject.getAttack(), baseAttack);\n\n\t\tplayCard(context, mage, CardCatalogue.getCardById(\"minion_abusive_sergeant\"));\n\t\tAssert.assertEquals(testSubject.getAttack(), baseAttack + 2);\n\t\tcontext.getLogic().endTurn(mage.getId());\n\t\tAssert.assertEquals(testSubject.getAttack(), baseAttack);\n\t}\n\n\t@Test\n\tpublic void testSpellpower() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer mage = context.getPlayer1();\n\t\tmage.setMana(10);\n\t\tPlayer warrior = context.getPlayer2();\n\t\twarrior.setMana(10);\n\n\t\tAssert.assertEquals(warrior.getHero().getHp(), warrior.getHero().getMaxHp());\n\t\tCard damageSpell = CardCatalogue.getCardById(\"spell_mind_blast\");\n\t\tint mindBlastDamage = 5;\n\t\tcontext.getLogic().receiveCard(mage.getId(), damageSpell);\n\n\t\tcontext.getLogic().performGameAction(mage.getId(), damageSpell.play());\n\t\tAssert.assertEquals(warrior.getHero().getHp(), warrior.getHero().getMaxHp() - mindBlastDamage);\n\n\t\tMinionCard spellPowerMinionCard = (MinionCard) CardCatalogue.getCardById(\"minion_kobold_geomancer\");\n\t\tcontext.getLogic().receiveCard(mage.getId(), spellPowerMinionCard);\n\t\tcontext.getLogic().performGameAction(mage.getId(), spellPowerMinionCard.play());\n\t\tcontext.getLogic().receiveCard(mage.getId(), damageSpell);\n\t\tcontext.getLogic().performGameAction(mage.getId(), damageSpell.play());\n\t\tint spellPower = getSingleMinion(mage.getMinions()).getAttributeValue(Attribute.SPELL_DAMAGE);\n\t\tAssert.assertEquals(warrior.getHero().getHp(), warrior.getHero().getMaxHp() - 2 * mindBlastDamage - spellPower);\n\t\t\n\t\tint opponentHp = warrior.getHero().getHp();\n\t\tGameAction useHeroPower = mage.getHero().getHeroPower().play();\n\t\tuseHeroPower.setTarget(warrior.getHero());\n\t\tcontext.getLogic().performGameAction(mage.getId(), useHeroPower);\n\t\t\n\t\t// mage hero power should not be affected by SPELL_DAMAGE, and thus deal 1 damage\n\t\tAssert.assertEquals(warrior.getHero().getHp(), opponentHp - 1);\n\t}\n\t\n\t@Test\n\tpublic void testBuffWithBoardWipe() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.PRIEST);\n\t\tPlayer mage = context.getPlayer1();\n\t\tmage.setMana(10);\n\t\tPlayer priest = context.getPlayer2();\n\t\tpriest.setMana(10);\n\t\t\n\t\tCard darkCultist = CardCatalogue.getCardById(\"minion_dark_cultist\");\n\t\tplayCard(context, priest, darkCultist);\n\t\tCard darkIronDwarf = CardCatalogue.getCardById(\"minion_dark_iron_dwarf\");\n\t\tplayCard(context, priest, darkIronDwarf);\n\t\t\n\t\tAssert.assertEquals(priest.getMinions().size(), 2);\n\t\t\n\t\tCard flamestrike = CardCatalogue.getCardById(\"spell_flamestrike\");\n\t\tplayCard(context, mage, flamestrike);\n\t\t\n\t\t// there should be no minions left after the Flamestrike\n\t\t// the Dark Cultist Deathrattle shouldn't have any effect, as both minions are removed simultaneously\n\t\tAssert.assertEquals(priest.getMinions().size(), 0);\n\n\t}\n\t\n\t\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/AuraTests.java",
    "content": "package net.demilich.metastone.tests;\n\nimport org.testng.Assert;\nimport org.testng.annotations.Test;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.actions.PhysicalAttackAction;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.cards.SpellCard;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.DestroySpell;\nimport net.demilich.metastone.game.spells.aura.BuffAura;\nimport net.demilich.metastone.game.targeting.EntityReference;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class AuraTests extends BasicTests {\n\n\t@Test\n\tpublic void testAdjacentAura() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\n\t\tTestMinionCard minionCard = new TestMinionCard(1, 1);\n\t\tMinion testMinion1 = playMinionCard(context, player, minionCard);\n\n\t\tMinionCard direWolfCard = (MinionCard) CardCatalogue.getCardById(\"minion_dire_wolf_alpha\");\n\t\tMinion direWolf = playMinionCard(context, player, direWolfCard);\n\n\t\tminionCard = new TestMinionCard(5, 5);\n\t\tMinion testMinion2 = playMinionCard(context, player, minionCard);\n\t\tminionCard = new TestMinionCard(5, 5);\n\t\tMinion testMinion3 = playMinionCard(context, player, minionCard);\n\n\t\tAssert.assertEquals(direWolf.getAttack(), 2);\n\t\tAssert.assertEquals(testMinion1.getAttack(), 2);\n\t\tAssert.assertEquals(testMinion2.getAttack(), 6);\n\t\tAssert.assertEquals(testMinion3.getAttack(), 5);\n\n\t\tSpellCard destroyCard = new TestSpellCard(DestroySpell.create());\n\t\tdestroyCard.setTargetRequirement(TargetSelection.ANY);\n\t\tcontext.getLogic().receiveCard(player.getId(), destroyCard);\n\t\tGameAction destroyAction = destroyCard.play();\n\t\tdestroyAction.setTarget(testMinion2);\n\t\tcontext.getLogic().performGameAction(player.getId(), destroyAction);\n\t\tAssert.assertEquals(testMinion1.getAttack(), 2);\n\t\tAssert.assertEquals(direWolf.getAttack(), 2);\n\t\tAssert.assertEquals(testMinion3.getAttack(), 6);\n\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"spell_hellfire\"));\n\t\tAssert.assertEquals(direWolf.getAttack(), 2);\n\t\tAssert.assertEquals(testMinion3.getAttack(), 5);\n\t}\n\n\t@Test\n\tpublic void testAura() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\n\t\tTestMinionCard minionCard = new TestMinionCard(1, 1);\n\t\tminionCard.getMinion().addSpellTrigger(new BuffAura(1, 1, EntityReference.OTHER_FRIENDLY_MINIONS, null));\n\t\tplayCard(context, player, minionCard);\n\n\t\tActor minion1 = getSingleMinion(player.getMinions());\n\t\tAssert.assertEquals(minion1.getAttack(), 1);\n\n\t\tminionCard = new TestMinionCard(1, 1);\n\t\tminionCard.getMinion().addSpellTrigger(new BuffAura(1, 1, EntityReference.OTHER_FRIENDLY_MINIONS, null));\n\t\tActor minion2 = playMinionCard(context, player, minionCard);\n\n\t\tAssert.assertNotEquals(minion1, minion2);\n\t\tAssert.assertEquals(minion1.getAttack(), 2);\n\t\tAssert.assertEquals(minion2.getAttack(), 2);\n\n\t\tTestMinionCard minionCardOpponent = new TestMinionCard(3, 3);\n\t\tActor enemyMinion = playMinionCard(context, opponent, minionCardOpponent);\n\t\tAssert.assertEquals(enemyMinion.getAttack(), 3);\n\n\t\tAssert.assertEquals(minion1.getAttack(), 2);\n\t\tAssert.assertEquals(minion2.getAttack(), 2);\n\t\tPhysicalAttackAction attackAction = new PhysicalAttackAction(enemyMinion.getReference());\n\t\tattackAction.setTarget(minion2);\n\t\tcontext.getLogic().performGameAction(opponent.getId(), attackAction);\n\t\tAssert.assertEquals(minion1.getAttack(), 1);\n\n\t\tminionCard = new TestMinionCard(1, 1);\n\t\tminion2 = playMinionCard(context, player, minionCard);\n\t\tAssert.assertEquals(minion1.getAttack(), 1);\n\t\tAssert.assertEquals(minion2.getAttack(), 2);\n\t}\n\n\t@Test\n\tpublic void testAuraPlusFaceless() {\n\t\tGameContext context = createContext(HeroClass.PRIEST, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\n\t\tMinion murloc = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_bluegill_warrior\"));\n\t\tAssert.assertEquals(murloc.getAttack(), 2);\n\t\tAssert.assertEquals(murloc.getHp(), 1);\n\n\t\tMinion warleader = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_murloc_warleader\"));\n\t\tAssert.assertEquals(murloc.getAttack(), 4);\n\t\tAssert.assertEquals(murloc.getHp(), 2);\n\t\tAssert.assertEquals(warleader.getAttack(), 3);\n\t\tAssert.assertEquals(warleader.getHp(), 3);\n\n\t\tTestBehaviour behaviour = (TestBehaviour) player.getBehaviour();\n\t\tbehaviour.setTargetPreference(warleader.getReference());\n\n\t\tCard facelessCard = CardCatalogue.getCardById(\"minion_faceless_manipulator\");\n\t\tcontext.getLogic().receiveCard(player.getId(), facelessCard);\n\t\tGameAction action = facelessCard.play();\n\t\taction.setTarget(warleader);\n\t\tcontext.getLogic().performGameAction(player.getId(), action);\n\t\tAssert.assertEquals(murloc.getAttack(), 6);\n\t\tAssert.assertEquals(murloc.getHp(), 3);\n\t}\n\n\t@Test\n\tpublic void testAuraPlusMindControl() {\n\t\tGameContext context = createContext(HeroClass.PRIEST, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\n\t\tcontext.getLogic().endTurn(player.getId());\n\n\t\tTestMinionCard minionCard = new TestMinionCard(1, 1);\n\t\tminionCard.getMinion().addSpellTrigger(new BuffAura(1, 1, EntityReference.FRIENDLY_MINIONS, null));\n\t\tMinion auraMinion = playMinionCard(context, opponent, minionCard);\n\t\tMinion opponentMinion = playMinionCard(context, opponent, new TestMinionCard(1, 1));\n\t\tAssert.assertEquals(opponentMinion.getAttack(), 2);\n\t\tcontext.getLogic().endTurn(opponent.getId());\n\n\t\tminionCard = new TestMinionCard(1, 1);\n\t\tActor minion1 = playMinionCard(context, player, minionCard);\n\t\tAssert.assertEquals(minion1.getAttack(), 1);\n\n\t\tCard mindControlCard = CardCatalogue.getCardById(\"spell_mind_control\");\n\t\tcontext.getLogic().receiveCard(player.getId(), mindControlCard);\n\t\tGameAction mindControl = mindControlCard.play();\n\t\tmindControl.setTarget(auraMinion);\n\t\tcontext.getLogic().performGameAction(player.getId(), mindControl);\n\n\t\tAssert.assertEquals(auraMinion.getOwner(), player.getId());\n\t\tAssert.assertEquals(minion1.getAttack(), 2);\n\t\tAssert.assertEquals(opponentMinion.getAttack(), 1);\n\t}\n\t\n\t@Test\n\tpublic void testOpponentAuraPlusFaceless() {\n\t\tGameContext context = createContext(HeroClass.PRIEST, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\n\t\tMinion wolf = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_dire_wolf_alpha\"));\n\t\tAssert.assertEquals(wolf.getAttack(), 2);\n\t\tAssert.assertEquals(wolf.getHp(), 2);\n\n\t\tMinion dummy = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_target_dummy\"));\n\t\tAssert.assertEquals(dummy.getAttack(), 1);\n\t\tAssert.assertEquals(dummy.getHp(), 2);\n\t\tAssert.assertEquals(dummy.hasAttribute(Attribute.AURA_UNTARGETABLE_BY_SPELLS), false);\n\t\t\n\t\tplayMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_wee_spellstopper\"));\n\t\tAssert.assertEquals(dummy.hasAttribute(Attribute.AURA_UNTARGETABLE_BY_SPELLS), true);\n\t\t\n\t\tcontext.getLogic().endTurn(player.getId());\n\n\t\tTestBehaviour behaviour = (TestBehaviour) opponent.getBehaviour();\n\t\tbehaviour.setTargetPreference(dummy.getReference());\n\n\t\tCard facelessCard = CardCatalogue.getCardById(\"minion_faceless_manipulator\");\n\t\tcontext.getLogic().receiveCard(opponent.getId(), facelessCard);\n\t\tGameAction action = facelessCard.play();\n\t\taction.setTarget(dummy);\n\t\tcontext.getLogic().performGameAction(opponent.getId(), action);\n\t\t\n\t\tMinion facelessCopy = getSummonedMinion(opponent.getMinions());\n\t\tAssert.assertEquals(facelessCopy.hasAttribute(Attribute.AURA_UNTARGETABLE_BY_SPELLS), false);\n\t\tAssert.assertEquals(facelessCopy.getAttack(), 0);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/BasicTests.java",
    "content": "package net.demilich.metastone.tests;\n\nimport org.testng.Assert;\nimport org.testng.annotations.Test;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.BattlecryAction;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.actions.PhysicalAttackAction;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.spells.BuffHeroSpell;\nimport net.demilich.metastone.game.spells.DamageSpell;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class BasicTests extends TestBase {\n\n\tprivate Card getTheCoin(CardCollection cards) {\n\t\tfor (Card card : cards) {\n\t\t\tif (card.getCardId().equalsIgnoreCase(\"spell_the_coin\")) {\n\t\t\t\treturn card;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Test\n\tpublic void testBattlecry() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer mage = context.getPlayer1();\n\t\tmage.setMana(10);\n\t\tPlayer warrior = context.getPlayer2();\n\t\twarrior.setMana(10);\n\n\t\tTestMinionCard devMonster = new TestMinionCard(3, 3);\n\t\tSpellDesc damageSpell = DamageSpell.create(EntityReference.ENEMY_HERO, 3);\n\t\tBattlecryAction testBattlecry = BattlecryAction.createBattlecry(damageSpell);\n\t\ttestBattlecry.setTarget(warrior.getHero());\n\t\tdevMonster.getMinion().setBattlecry(testBattlecry);\n\t\tcontext.getLogic().receiveCard(mage.getId(), devMonster);\n\t\tcontext.getLogic().performGameAction(mage.getId(), devMonster.play());\n\n\t\tAssert.assertEquals(warrior.getHero().getHp(), warrior.getHero().getMaxHp() - 3);\n\t}\n\n\t@Test\n\tpublic void testHeroAttack() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.DRUID);\n\t\tPlayer mage = context.getPlayer1();\n\t\tmage.setMana(10);\n\t\tPlayer druid = context.getPlayer2();\n\t\tdruid.setMana(10);\n\n\t\tint damage = 1;\n\t\tTestMinionCard devMonsterCard = new TestMinionCard(damage, 2);\n\t\tplayCard(context, mage, devMonsterCard);\n\n\t\tSpellDesc heroBuffSpell = BuffHeroSpell.create(EntityReference.FRIENDLY_HERO, damage, 0);\n\t\tcontext.getLogic().castSpell(druid.getId(), heroBuffSpell, druid.getHero().getReference(), null, false);\n\t\tcontext.getLogic().endTurn(druid.getId());\n\n\t\tActor devMonster = getSingleMinion(mage.getMinions());\n\t\tGameAction minionAttackAction = new PhysicalAttackAction(devMonster.getReference());\n\t\tminionAttackAction.setTarget(druid.getHero());\n\t\tcontext.getLogic().performGameAction(mage.getId(), minionAttackAction);\n\t\t// monster attacked; it should not be damaged by the hero\n\t\tAssert.assertEquals(druid.getHero().getHp(), druid.getHero().getMaxHp() - damage);\n\t\tAssert.assertEquals(devMonster.getHp(), devMonster.getMaxHp());\n\t\tcontext.getLogic().endTurn(mage.getId());\n\n\t\tcontext.getLogic().castSpell(druid.getId(), heroBuffSpell, druid.getHero().getReference(), null, false);\n\t\tGameAction heroAttackAction = new PhysicalAttackAction(druid.getHero().getReference());\n\t\theroAttackAction.setTarget(devMonster);\n\t\tcontext.getLogic().performGameAction(mage.getId(), heroAttackAction);\n\t\t// hero attacked; both entities should be damaged\n\t\tAssert.assertEquals(druid.getHero().getHp(), druid.getHero().getMaxHp() - 2 * damage);\n\t\tAssert.assertEquals(devMonster.getHp(), devMonster.getMaxHp() - damage);\n\t}\n\n\t@Test\n\tpublic void testMinionAttack() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer mage = context.getPlayer1();\n\t\tmage.setMana(10);\n\t\tPlayer warrior = context.getPlayer2();\n\t\twarrior.setMana(10);\n\n\t\tMinionCard minionCard1 = new TestMinionCard(5, 5);\n\t\tcontext.getLogic().receiveCard(mage.getId(), minionCard1);\n\t\tcontext.getLogic().performGameAction(mage.getId(), minionCard1.play());\n\n\t\tMinionCard minionCard2 = new TestMinionCard(1, 1);\n\t\tcontext.getLogic().receiveCard(warrior.getId(), minionCard2);\n\t\tcontext.getLogic().performGameAction(warrior.getId(), minionCard2.play());\n\n\t\tAssert.assertEquals(mage.getMinions().size(), 1);\n\t\tAssert.assertEquals(warrior.getMinions().size(), 1);\n\n\t\tActor attacker = getSingleMinion(mage.getMinions());\n\t\tActor defender = getSingleMinion(warrior.getMinions());\n\n\t\tGameAction attackAction = new PhysicalAttackAction(attacker.getReference());\n\t\tattackAction.setTarget(defender);\n\t\tcontext.getLogic().performGameAction(mage.getId(), attackAction);\n\n\t\tAssert.assertEquals(attacker.getHp(), attacker.getMaxHp() - defender.getAttack());\n\t\tAssert.assertEquals(defender.getHp(), defender.getMaxHp() - attacker.getAttack());\n\t\tAssert.assertEquals(defender.isDestroyed(), true);\n\n\t\tAssert.assertEquals(mage.getMinions().size(), 1);\n\t\tAssert.assertEquals(warrior.getMinions().size(), 0);\n\t}\n\n\t@Test\n\tpublic void testSummon() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer mage = context.getPlayer1();\n\t\tfor (Card card : mage.getHand().toList()) {\n\t\t\tcontext.getLogic().removeCard(mage.getId(), card);\n\t\t}\n\t\tMinionCard devMonster = new TestMinionCard(1, 1);\n\t\tcontext.getLogic().receiveCard(mage.getId(), devMonster);\n\t\tAssert.assertEquals(mage.getHand().getCount(), 1);\n\t\tcontext.getLogic().performGameAction(mage.getId(), devMonster.play());\n\t\tAssert.assertEquals(mage.getHand().isEmpty(), true);\n\t\tActor minion = getSingleMinion(mage.getMinions());\n\t\tAssert.assertEquals(minion.getName(), devMonster.getName());\n\t\tAssert.assertEquals(minion.getAttack(), 1);\n\t\tAssert.assertEquals(minion.getHp(), 1);\n\t\tAssert.assertEquals(minion.isDestroyed(), false);\n\n\t\tMinionCard devMonster2 = new TestMinionCard(2, 2);\n\t\tcontext.getLogic().receiveCard(mage.getId(), devMonster2);\n\t\tGameAction summonAction = devMonster2.play();\n\t\tsummonAction.setTarget(minion);\n\t\tcontext.getLogic().performGameAction(mage.getId(), summonAction);\n\n\t\tAssert.assertEquals(mage.getMinions().size(), 2);\n\t\tActor left = mage.getMinions().get(0);\n\t\tActor right = mage.getMinions().get(1);\n\t\tAssert.assertEquals(left.getAttack(), 2);\n\t\tAssert.assertEquals(right.getAttack(), 1);\n\t}\n\n\t@Test\n\tpublic void testTheCoin() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer mage = context.getPlayer1();\n\t\tPlayer warrior = context.getPlayer2();\n\n\t\tCard theCoin = getTheCoin(mage.getHand());\n\t\tAssert.assertEquals(theCoin, null);\n\t\ttheCoin = getTheCoin(warrior.getHand());\n\t\tAssert.assertNotEquals(theCoin, null);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/BlackrockMountainTests.java",
    "content": "package net.demilich.metastone.tests;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.cards.SpellCard;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.DamageSpell;\nimport net.demilich.metastone.game.targeting.EntityReference;\nimport org.testng.Assert;\nimport org.testng.annotations.Test;\n\n\npublic class BlackrockMountainTests extends BasicTests {\n\n\t@Test\n\tpublic void testAxeFlinger() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\n\t\tplayMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_axe_flinger\"));\n\t\tplayMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_axe_flinger\"));\n\t\t\n\t\tcontext.getLogic().endTurn(player.getId());\n\t\tPlayer opponent = context.getOpponent(player);\n\t\t\n\t\tSpellCard damageCard = new TestSpellCard(DamageSpell.create(EntityReference.ENEMY_CHARACTERS, 1));\n\t\tplayCard(context, opponent, damageCard);\n\t\tAssert.assertEquals(player.getHero().getHp(), player.getHero().getMaxHp() - 1);\n\t\tAssert.assertEquals(opponent.getHero().getHp(), opponent.getHero().getMaxHp() - 4);\n\t}\n\t\n\t@Test\n\tpublic void testBlackwingCorruptor() {\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.HUNTER);\n\t\tPlayer player = context.getPlayer1();\n\t\tplayer.getHand().removeAll();\n\t\t\n\t\tTestBehaviour behaviour = (TestBehaviour) player.getBehaviour();\n\t\tbehaviour.setTargetPreference(player.getHero().getReference());\n\t\t\n\t\tplayMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_blackwing_corruptor\"));\n\t\tAssert.assertEquals(player.getHero().getHp(), player.getHero().getMaxHp());\n\t\t\n\t\tcontext.getLogic().receiveCard(player.getId(), CardCatalogue.getCardById(\"minion_azure_drake\"));\n\t\tplayMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_blackwing_corruptor\"));\n\t\tAssert.assertEquals(player.getHero().getHp(), player.getHero().getMaxHp() - 3);\n\t}\n\t\n\t@Test\n\tpublic void testBlackwingTechnician() {\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.HUNTER);\n\t\tPlayer player = context.getPlayer1();\n\t\tcontext.getLogic().removeAllCards(player.getId());\n\t\t\n\t\tMinion blackwingTechnician = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_blackwing_technician\"));\n\t\tAssert.assertEquals(blackwingTechnician.getHp(), blackwingTechnician.getBaseHp());\n\t\tAssert.assertEquals(blackwingTechnician.getAttack(), blackwingTechnician.getBaseAttack());\n\t\t\n\t\tcontext.getLogic().receiveCard(player.getId(), CardCatalogue.getCardById(\"minion_azure_drake\"));\n\t\tblackwingTechnician = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_blackwing_technician\"));\n\t\tAssert.assertEquals(blackwingTechnician.getHp(), blackwingTechnician.getBaseHp() + 1);\n\t\tAssert.assertEquals(blackwingTechnician.getAttack(), blackwingTechnician.getBaseAttack() + 1);\n\t}\n\t\n\t@Test\n\tpublic void testChromaggus() {\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.HUNTER);\n\t\tPlayer player = context.getPlayer1();\n\t\tplayer.getHand().removeAll();\n\t\t\n\t\tAssert.assertEquals(player.getHand().getCount(), 0);\n\t\t\n\t\tplayMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_chromaggus\"));\n\t\tcontext.getLogic().drawCard(player.getId(), player.getHero());\n\t\tAssert.assertEquals(player.getHand().getCount(), 2);\n\t\t\n\t\tplayer.getHand().removeAll();\n\t\t\n\t\tAssert.assertEquals(player.getHand().getCount(), 0);\n\t\t\n\t\tplayMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_chromaggus\"));\n\t\tcontext.getLogic().drawCard(player.getId(), player.getHero());\n\t\tAssert.assertEquals(player.getHand().getCount(), 3);\n\t}\n\t\n\t@Test\n\tpublic void testCoreRager() {\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.HUNTER);\n\t\tPlayer player = context.getPlayer1();\n\t\tplayer.getHand().removeAll();\n\t\t\n\t\tMinion coreRager = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_core_rager\"));\n\t\tAssert.assertEquals(coreRager.getAttack(), coreRager.getBaseAttack() + 3);\n\t\tAssert.assertEquals(coreRager.getHp(), coreRager.getBaseHp() + 3);\n\t\t\n\t\tcontext.getLogic().drawCard(player.getId(), player.getHero());\n\t\t\n\t\tcoreRager = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_core_rager\"));\n\t\tAssert.assertEquals(coreRager.getAttack(), coreRager.getBaseAttack());\n\t\tAssert.assertEquals(coreRager.getHp(), coreRager.getBaseHp());\n\t}\n\t\n\t@Test\n\tpublic void testDarkIronSkulker() {\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.HUNTER);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getOpponent(player);\n\t\t\n\t\tMinion testMinion1 = playMinionCard(context, player, new TestMinionCard(3, 3, 0));\n\t\tMinion injuredBlademaster = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_injured_blademaster\"));\n\t\tMinion testMinion2 = playMinionCard(context, player, new TestMinionCard(3, 3, 0));\n\t\tAssert.assertEquals(testMinion1.getHp(), testMinion1.getMaxHp());\n\t\tAssert.assertEquals(injuredBlademaster.getHp(), injuredBlademaster.getMaxHp() - 4);\n\t\tAssert.assertEquals(testMinion2.getHp(), testMinion2.getMaxHp());\n\t\t\n\t\tcontext.getLogic().endTurn(player.getId());\n\t\t\n\t\tMinion testMinionOpponent = playMinionCard(context, opponent, new TestMinionCard(3, 3, 0));\n\t\tMinion injuredBlademasterOpponent = playMinionCard(context, opponent, (MinionCard) CardCatalogue.getCardById(\"minion_injured_blademaster\"));\n\t\tAssert.assertEquals(testMinionOpponent.getHp(), testMinionOpponent.getMaxHp());\n\t\tAssert.assertEquals(injuredBlademasterOpponent.getHp(), injuredBlademasterOpponent.getMaxHp() - 4);\n\t\t\n\t\tMinion darkIronSkulker = playMinionCard(context, opponent, (MinionCard) CardCatalogue.getCardById(\"minion_dark_iron_skulker\"));\n\t\tAssert.assertEquals(darkIronSkulker.getHp(), darkIronSkulker.getMaxHp());\n\t\t\n\t\tAssert.assertEquals(testMinionOpponent.getHp(), testMinionOpponent.getMaxHp());\n\t\tAssert.assertEquals(injuredBlademasterOpponent.getHp(), injuredBlademasterOpponent.getMaxHp() - 4);\n\t\t\n\t\tAssert.assertEquals(testMinion1.getHp(), testMinion1.getMaxHp() - 2);\n\t\tAssert.assertEquals(injuredBlademaster.getHp(), injuredBlademaster.getMaxHp() - 4);\n\t\tAssert.assertEquals(testMinion2.getHp(), testMinion2.getMaxHp() - 2);\n\t}\n\t\n\t@Test\n\tpublic void testDragonConsort() {\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.HUNTER);\n\t\tPlayer player = context.getPlayer1();\n\t\t\n\t\tfinal int MANA_REDUCTION = 2;\n\t\t\n\t\tMinionCard dragonConsort = (MinionCard) CardCatalogue.getCardById(\"minion_dragon_consort\");\n\t\tcontext.getLogic().receiveCard(player.getId(), dragonConsort);\n\t\tAssert.assertEquals(dragonConsort.getManaCost(context, player), dragonConsort.getBaseManaCost());\n\n\t\tplayMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_dragon_consort\"));\n\t\tAssert.assertEquals(context.getLogic().getModifiedManaCost(player, dragonConsort), dragonConsort.getBaseManaCost() - MANA_REDUCTION);\n\t}\n\t\n\t@Test\n\tpublic void testDragonEgg() {\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.HUNTER);\n\t\tPlayer player = context.getPlayer1();\n\t\t\n\t\tfinal String TOKEN = \"token_black_whelp\";\n\t\t\n\t\tMinion dragonEgg = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_dragon_egg\"));\n\t\tAssert.assertEquals(getSummonedMinion(player.getMinions()), dragonEgg);\n\n\t\tplayCardWithTarget(context, player, CardCatalogue.getCardById(\"spell_fireball\"), dragonEgg);\n\t\tAssert.assertEquals(getSummonedMinion(player.getMinions()).getSourceCard().getCardId(), TOKEN);\n\t\t\n\t}\n\t\n\t@Test\n\tpublic void testDragonkinSorceror() {\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.HUNTER);\n\t\tPlayer player = context.getPlayer1();\n\t\t\n\t\tfinal int ATTACK_BONUS = 1;\n\t\tfinal int HP_BONUS = 1;\n\t\t\n\t\tMinion dragonkin1 = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_dragonkin_sorcerer\"));\n\t\tMinion dragonkin2 = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_dragonkin_sorcerer\"));\n\t\tAssert.assertEquals(dragonkin1.getAttack(), dragonkin2.getAttack());\n\t\tAssert.assertEquals(dragonkin1.getHp(), dragonkin2.getHp());\n\t\t\n\t\tplayCardWithTarget(context, player, CardCatalogue.getCardById(\"spell_gang_up\"), dragonkin1);\n\t\tAssert.assertEquals(dragonkin1.getAttack(), dragonkin2.getAttack() + ATTACK_BONUS);\n\t\tAssert.assertEquals(dragonkin1.getHp(), dragonkin2.getHp() + HP_BONUS);\n\t}\n\t\n\t@Test\n\tpublic void testDrakonidCrusher() {\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.HUNTER);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\t\t\n\t\tfinal int ATTACK_BONUS = 3;\n\t\tfinal int HP_BONUS = 3;\n\t\t\n\t\tMinion drakonid = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_drakonid_crusher\"));\n\t\tAssert.assertEquals(drakonid.getAttack(), drakonid.getBaseAttack());\n\t\tAssert.assertEquals(drakonid.getHp(), drakonid.getBaseHp());\n\t\t\n\t\topponent.getHero().setHp(15);\n\t\t\n\t\tdrakonid = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_drakonid_crusher\"));\n\t\tAssert.assertEquals(drakonid.getAttack(), drakonid.getBaseAttack() + ATTACK_BONUS);\n\t\tAssert.assertEquals(drakonid.getHp(), drakonid.getBaseHp() + HP_BONUS);\n\t}\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/CardInteractionTests.java",
    "content": "package net.demilich.metastone.tests;\n\nimport org.testng.Assert;\nimport org.testng.annotations.Test;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.cards.SpellCard;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.entities.minions.Race;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.game.spells.SilenceSpell;\nimport net.demilich.metastone.game.spells.SwapAttackAndHpSpell;\nimport net.demilich.metastone.game.spells.TemporaryAttackSpell;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class CardInteractionTests extends TestBase {\n\n\t@Test\n\tpublic void testAttackBuffStacking() {\n\t\tGameContext context = createContext(HeroClass.HUNTER, HeroClass.WARRIOR);\n\t\tPlayer hunter = context.getPlayer1();\n\n\t\t// summon Ghaz'rilla\n\t\tMinionCard gahzrillaCard = (MinionCard) CardCatalogue.getCardById(\"minion_gahzrilla\");\n\t\tMinion gahzrilla = playMinionCard(context, hunter, gahzrillaCard);\n\t\tAssert.assertEquals(gahzrilla.getAttack(), 6);\n\t\tAssert.assertEquals(gahzrilla.getHp(), 9);\n\n\t\t// buff it with 'Abusive Sergeant' spell\n\t\t// This temporary Attack boost should be doubled and removed after the turn\n\t\tCard abusiveSergeant = CardCatalogue.getCardById(\"minion_abusive_sergeant\");\n\t\tcontext.getLogic().receiveCard(hunter.getId(), abusiveSergeant);\n\t\tGameAction action = abusiveSergeant.play();\n\t\taction.setTarget(gahzrilla);\n\t\tcontext.getLogic().performGameAction(hunter.getId(), action);\n\t\tAssert.assertEquals(gahzrilla.getAttack(), 8);\n\t\tAssert.assertEquals(gahzrilla.getHp(), 9);\n\n\t\tcontext.getLogic().destroy((Actor) find(context, \"minion_abusive_sergeant\"));\n\n\t\t// buff it with 'Cruel Taskmaster' spell\n\t\tCard cruelTaskmasterCard = CardCatalogue.getCardById(\"minion_cruel_taskmaster\");\n\t\tcontext.getLogic().receiveCard(hunter.getId(), cruelTaskmasterCard);\n\t\taction = cruelTaskmasterCard.play();\n\t\taction.setTarget(gahzrilla);\n\t\tcontext.getLogic().performGameAction(hunter.getId(), action);\n\t\tAssert.assertEquals(gahzrilla.getAttack(), 20);\n\t\tAssert.assertEquals(gahzrilla.getHp(), 8);\n\n\t\tcontext.getLogic().destroy((Actor) find(context, \"minion_cruel_taskmaster\"));\n\n\t\t// buff it again with 'Abusive Sergeant' spell\n\t\tabusiveSergeant = CardCatalogue.getCardById(\"minion_abusive_sergeant\");\n\t\tcontext.getLogic().receiveCard(hunter.getId(), abusiveSergeant);\n\t\taction = abusiveSergeant.play();\n\t\taction.setTarget(gahzrilla);\n\t\tcontext.getLogic().performGameAction(hunter.getId(), action);\n\t\tAssert.assertEquals(gahzrilla.getAttack(), 22);\n\t\tAssert.assertEquals(gahzrilla.getHp(), 8);\n\n\t\tcontext.endTurn();\n\t\tcontext.endTurn();\n\t\tAssert.assertEquals(gahzrilla.getAttack(), 16);\n\t\tAssert.assertEquals(gahzrilla.getHp(), 8);\n\t}\n\n\t@Test\n\tpublic void testKnifeJugglerPlusStealth() {\n\t\tGameContext context = createContext(HeroClass.ROGUE, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\n\t\tMinion knifeJuggler = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_knife_juggler\"));\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"spell_conceal\"));\n\t\t// knife juggler should be stealthed\n\t\tAssert.assertTrue(knifeJuggler.hasAttribute(Attribute.STEALTH));\n\t\t// knife juggler should be unstealthed as soon as another minion is\n\t\t// played and his trigger fires\n\t\tplayCard(context, player, new TestMinionCard(1, 1));\n\t\tAssert.assertFalse(knifeJuggler.hasAttribute(Attribute.STEALTH));\n\t}\n\n\t@Test\n\tpublic void testSilenceWithBuffs() {\n\t\tGameContext context = createContext(HeroClass.WARLOCK, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\n\t\t// summon attack target\n\t\tcontext.endTurn();\n\t\tPlayer opponent = context.getPlayer2();\n\t\tplayCard(context, opponent, new TestMinionCard(4, 4, 0));\n\t\tcontext.endTurn();\n\n\t\t// summon test minion\n\t\tplayer.setMana(10);\n\t\tTestMinionCard minionCard = new TestMinionCard(6, 6, 0);\n\t\tplayCard(context, player, minionCard);\n\n\t\tActor minion = getSingleMinion(player.getMinions());\n\n\t\t// buff test minion\n\t\tSpellCard buffSpellCard = (SpellCard) CardCatalogue.getCardById(\"spell_bananas\");\n\t\tcontext.getLogic().receiveCard(player.getId(), buffSpellCard);\n\t\tGameAction action = buffSpellCard.play();\n\t\taction.setTarget(minion);\n\t\tcontext.getLogic().performGameAction(player.getId(), action);\n\n\t\tAssert.assertEquals(minion.getAttack(), 7);\n\t\tAssert.assertEquals(minion.getHp(), 7);\n\n\t\t// attack target to get test minion wounded\n\t\tattack(context, player, minion, getSingleMinion(opponent.getMinions()));\n\t\tAssert.assertEquals(minion.getAttack(), 7);\n\t\tAssert.assertEquals(minion.getHp(), 3);\n\n\t\t// swap hp and attack of wounded test minion\n\t\tSpellDesc swapHpAttackSpell = SwapAttackAndHpSpell.create(EntityReference.FRIENDLY_MINIONS);\n\t\tSpellCard swapSpellCard = new TestSpellCard(swapHpAttackSpell);\n\t\tbuffSpellCard.setTargetRequirement(TargetSelection.NONE);\n\t\tplayCard(context, player, swapSpellCard);\n\t\tAssert.assertEquals(minion.getAttack(), 3);\n\t\tAssert.assertEquals(minion.getHp(), 7);\n\n\t\t// silence minion and check if it regains original stats\n\t\tSpellDesc silenceSpell = SilenceSpell.create(EntityReference.FRIENDLY_MINIONS);\n\t\tSpellCard silenceSpellCard = new TestSpellCard(silenceSpell);\n\t\tsilenceSpellCard.setTargetRequirement(TargetSelection.NONE);\n\t\tplayCard(context, player, silenceSpellCard);\n\t\tAssert.assertEquals(minion.getAttack(), 6);\n\t\tAssert.assertEquals(minion.getHp(), 6);\n\t}\n\n\t@Test\n\tpublic void testSwapWithBuffs() {\n\t\tGameContext context = createContext(HeroClass.WARLOCK, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\n\t\t// summon test minion\n\t\tplayer.setMana(10);\n\t\tTestMinionCard minionCard = new TestMinionCard(1, 3, 0);\n\t\tplayCard(context, player, minionCard);\n\n\t\t// buff test minion with temporary buff\n\t\tSpellDesc buffSpell = TemporaryAttackSpell.create(EntityReference.FRIENDLY_MINIONS, +4);\n\t\tSpellCard buffSpellCard = new TestSpellCard(buffSpell);\n\t\tbuffSpellCard.setTargetRequirement(TargetSelection.NONE);\n\t\tplayCard(context, player, buffSpellCard);\n\n\t\tActor minion = getSingleMinion(player.getMinions());\n\t\tAssert.assertEquals(minion.getAttack(), 5);\n\t\tAssert.assertEquals(minion.getHp(), 3);\n\n\t\t// swap hp and attack of wounded test minion\n\t\tSpellDesc swapHpAttackSpell = SwapAttackAndHpSpell.create(EntityReference.FRIENDLY_MINIONS);\n\t\tSpellCard swapSpellCard = new TestSpellCard(swapHpAttackSpell);\n\t\tbuffSpellCard.setTargetRequirement(TargetSelection.NONE);\n\t\tplayCard(context, player, swapSpellCard);\n\t\tAssert.assertEquals(minion.getAttack(), 3);\n\t\tAssert.assertEquals(minion.getHp(), 5);\n\n\t\t// end turn; temporary buff wears off, but stats should still be the\n\t\t// same\n\t\tcontext.endTurn();\n\t\tAssert.assertEquals(minion.getAttack(), 3);\n\t\tAssert.assertEquals(minion.getHp(), 5);\n\t}\n\n\t@Test\n\tpublic void testBloodsailRaider() {\n\t\tGameContext context = createContext(HeroClass.WARRIOR, HeroClass.MAGE);\n\t\tPlayer warrior = context.getPlayer1();\n\t\twarrior.setMana(10);\n\n\t\tplayCard(context, warrior, CardCatalogue.getCardById(\"weapon_arcanite_reaper\"));\n\t\tplayCard(context, warrior, new TestMinionCard(2, 1, 0));\n\n\t\tMinion bloodsailRaider = playMinionCard(context, warrior, (MinionCard) CardCatalogue.getCardById(\"minion_bloodsail_raider\"));\n\t\tAssert.assertEquals(bloodsailRaider.getAttack(), 7);\n\t}\n\n\t@Test\n\tpublic void testWildPyroPlusEquality() {\n\t\tGameContext context = createContext(HeroClass.PALADIN, HeroClass.WARRIOR);\n\t\tPlayer paladin = context.getPlayer1();\n\t\tplayCard(context, paladin, new TestMinionCard(3, 2, 0));\n\t\tplayCard(context, paladin, new TestMinionCard(4, 4, 0));\n\t\tcontext.getLogic().endTurn(paladin.getId());\n\n\t\tPlayer warrior = context.getPlayer2();\n\t\tplayCard(context, warrior, new TestMinionCard(5, 5, 0));\n\t\tplayCard(context, warrior, new TestMinionCard(1, 2, 0));\n\t\tplayCard(context, warrior, new TestMinionCard(8, 8, 0));\n\t\tplayCard(context, warrior, new TestMinionCard(2, 1, 0));\n\t\tcontext.getLogic().endTurn(warrior.getId());\n\n\t\tAssert.assertEquals(paladin.getMinions().size(), 2);\n\t\tAssert.assertEquals(warrior.getMinions().size(), 4);\n\n\t\tplayCard(context, paladin, CardCatalogue.getCardById(\"minion_wild_pyromancer\"));\n\t\tplayCard(context, paladin, CardCatalogue.getCardById(\"spell_equality\"));\n\n\t\t// wild pyromancer + equality should wipe the board if there no\n\t\t// deathrattles\n\t\tAssert.assertEquals(paladin.getMinions().size(), 0);\n\t\tAssert.assertEquals(warrior.getMinions().size(), 0);\n\t}\n\t\n\t@Test\n\tpublic void testLordJaraxxus() {\n\t\tGameContext context = createContext(HeroClass.WARLOCK, HeroClass.PALADIN);\n\t\tPlayer warlock = context.getPlayer1();\n\t\tCard jaraxxus = CardCatalogue.getCardById(\"minion_lord_jaraxxus\");\n\t\t// first, just play Jaraxxus on an empty board\n\t\tplayCard(context, warlock, jaraxxus);\n\t\tAssert.assertEquals(warlock.getHero().getRace(), Race.DEMON);\n\t\tAssert.assertEquals(warlock.getHero().getHp(), 15);\n\t\tAssert.assertNotNull(warlock.getHero().getWeapon());\n\t\t\n\t\t// start a new game\n\t\tcontext = createContext(HeroClass.WARLOCK, HeroClass.PALADIN);\n\t\t// opponent plays Repentance, which triggers on Lord Jaraxxus play\n\t\tPlayer paladin = context.getPlayer2();\n\t\tCard repentance = CardCatalogue.getCardById(\"secret_repentance\");\n\t\tplayCard(context, paladin, repentance);\n\t\t\n\t\tcontext.getLogic().endTurn(paladin.getId());\n\t\t\n\t\twarlock = context.getPlayer1();\n\t\tjaraxxus = CardCatalogue.getCardById(\"minion_lord_jaraxxus\");\n\t\tplayCard(context, warlock, jaraxxus);\n\t\tAssert.assertEquals(warlock.getHero().getRace(), Race.DEMON);\n\t\t// Jaraxxus should be affected by Repentance, bringing him down to 1 hp\n\t\tAssert.assertEquals(warlock.getHero().getHp(), 1);\n\t\tAssert.assertNotNull(warlock.getHero().getWeapon());\n\t}\n\t\n\t@Test\n\tpublic void testBlessingOfWisdomMindControl() {\n\t\tGameContext context = createContext(HeroClass.PALADIN, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\t\t\n\t\tint cardCount = player.getHand().getCount();\n\t\t\n\t\tMinion minion = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_chillwind_yeti\"));\n\t\tplayCardWithTarget(context, player, CardCatalogue.getCardById(\"spell_blessing_of_wisdom\"), minion);\n\t\tAssert.assertEquals(cardCount, player.getHand().getCount());\n\t\t\n\t\tattack(context, opponent, minion, opponent.getHero());\n\t\tAssert.assertEquals(player.getHand().getCount(), cardCount + 1);\n\t\t\n\t\tcontext.getLogic().mindControl(opponent, minion);\n\t\tattack(context, opponent, minion, player.getHero());\n\t\tAssert.assertEquals(player.getHand().getCount(), cardCount + 2);\n\t}\n\t\n\t@Test\n\tpublic void testImpFlamestrike() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARLOCK);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\t\t\n\t\tcontext.endTurn();\n\t\tfor (int i = 0; i < GameLogic.MAX_MINIONS; i++) {\n\t\t\tplayMinionCard(context, opponent, (MinionCard) CardCatalogue.getCardById(\"minion_imp_gang_boss\"));\t\n\t\t}\n\t\t\n\t\tAssert.assertEquals(opponent.getMinions().size(), GameLogic.MAX_MINIONS);\n\t\tcontext.endTurn();\n\t\t\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"spell_flamestrike\"));\n\t\tAssert.assertEquals(opponent.getMinions().size(), 0);\n\t}\n\t\n\t@Test\n\tpublic void testHarvestGolemFlamestrike() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARLOCK);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\t\t\n\t\tcontext.endTurn();\n\t\tfor (int i = 0; i < GameLogic.MAX_MINIONS; i++) {\n\t\t\tplayMinionCard(context, opponent, (MinionCard) CardCatalogue.getCardById(\"minion_harvest_golem\"));\t\n\t\t}\n\t\t\n\t\tAssert.assertEquals(opponent.getMinions().size(), GameLogic.MAX_MINIONS);\n\t\tcontext.endTurn();\n\t\t\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"spell_flamestrike\"));\n\t\tAssert.assertEquals(opponent.getMinions().size(), 7);\n\t\t\n\t}\n\t\n\t@Test\n\tpublic void testGrimPatrons() {\n\t\tGameContext context = createContext(HeroClass.PALADIN, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\t\t\n\t\tcontext.endTurn();\n\t\tfor (int i = 0; i < 4; i++) {\n\t\t\tplayMinionCard(context, opponent, (MinionCard) CardCatalogue.getCardById(\"minion_grim_patron\"));\t\n\t\t}\n\t\t\n\t\tAssert.assertEquals(opponent.getMinions().size(), 4);\n\t\tplayCard(context, opponent, CardCatalogue.getCardById(\"spell_whirlwind\"));\n\t\tAssert.assertEquals(opponent.getMinions().size(), 7);\n\t\tcontext.endTurn();\n\t\t\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"spell_consecration\"));\n\t\tAssert.assertEquals(opponent.getMinions().size(), 3);\n\t\t\n\t}\n\t\n\t@Test\n\tpublic void testWobblingRunts() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\t\t\n\t\tcontext.endTurn();\n\t\tplayMinionCard(context, opponent, (MinionCard) CardCatalogue.getCardById(\"minion_wobbling_runts\"));\n\t\tfor (int i = 0; i < GameLogic.MAX_MINIONS - 1; i++) {\n\t\t\tplayMinionCard(context, opponent, (MinionCard) CardCatalogue.getCardById(\"minion_wisp\"));\t\n\t\t}\n\t\t\n\t\tAssert.assertEquals(opponent.getMinions().size(), GameLogic.MAX_MINIONS);\n\t\tcontext.endTurn();\n\t\t\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"minion_malygos\"));\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"spell_flamestrike\"));\n\t\tAssert.assertEquals(opponent.getMinions().size(), 3);\n\t\t\n\t}\n\t\n\t@Test\n\tpublic void testHauntedCreeperHarvestGolem() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\t\t\n\t\tcontext.endTurn();\n\t\tplayMinionCard(context, opponent, (MinionCard) CardCatalogue.getCardById(\"minion_haunted_creeper\"));\n\t\tplayMinionCard(context, opponent, (MinionCard) CardCatalogue.getCardById(\"minion_harvest_golem\"));\n\t\tAssert.assertEquals(opponent.getMinions().size(), 2);\n\t\tcontext.endTurn();\n\t\t\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"spell_flamestrike\"));\n\t\tAssert.assertEquals(opponent.getMinions().size(), 3);\n\t\tfinal int HARVEST_GOLEM = 1;\n\t\tfor (int i = 0; i < opponent.getMinions().size(); i++) {\n\t\t\tMinion minion = opponent.getMinions().get(i);\n\t\t\tif (i == HARVEST_GOLEM) {\n\t\t\t\tAssert.assertEquals(minion.getAttack(), 2);\n\t\t\t\tAssert.assertEquals(minion.getHp(), 1);\n\t\t\t\tAssert.assertEquals(minion.getRace(), Race.MECH);\n\t\t\t} else {\n\t\t\t\tAssert.assertEquals(minion.getAttack(), 1);\n\t\t\t\tAssert.assertEquals(minion.getHp(), 1);\n\t\t\t}\n\t\t}\n\t}\n\t\n\t@Test\n\tpublic void testImpGangBossConeOfCold() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\t\t\n\t\tcontext.endTurn();\n\t\tMinion firstYeti = playMinionCard(context, opponent, (MinionCard) CardCatalogue.getCardById(\"minion_chillwind_yeti\"));\n\t\tMinion impGangBoss = playMinionCard(context, opponent, (MinionCard) CardCatalogue.getCardById(\"minion_imp_gang_boss\"));\n\t\tMinion secondYeti = playMinionCard(context, opponent, (MinionCard) CardCatalogue.getCardById(\"minion_chillwind_yeti\"));\n\t\tAssert.assertEquals(opponent.getMinions().size(), 3);\n\t\tcontext.endTurn();\n\t\t\n\t\tplayCardWithTarget(context, player, CardCatalogue.getCardById(\"spell_cone_of_cold\"), impGangBoss);\n\t\tAssert.assertEquals(opponent.getMinions().size(), 4);\n\t\tAssert.assertTrue(firstYeti.hasAttribute(Attribute.FROZEN));\n\t\tAssert.assertTrue(impGangBoss.hasAttribute(Attribute.FROZEN));\n\t\tAssert.assertFalse(secondYeti.hasAttribute(Attribute.FROZEN));\n\t}\n\t\n\t@Test\n\tpublic void testSummoningStonePrep() {\n\t\tGameContext context = createContext(HeroClass.ROGUE, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\t\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"minion_summoning_stone\"));\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"spell_preparation\"));\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"secret_ice_block\"));\n\t\t\n\t\tAssert.assertEquals(player.getMinions().size(), 3);\n\t\tfor (Minion minion : player.getMinions()) {\n\t\t\tif (minion.getSourceCard().getCardId().equalsIgnoreCase(\"minion_summoning_stone\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t\n\t\t\tAssert.assertEquals(minion.getSourceCard().getBaseManaCost(), 0);\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/CloningTest.java",
    "content": "package net.demilich.metastone.tests;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.testng.Assert;\nimport org.testng.annotations.Test;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.behaviour.PlayRandomBehaviour;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardCollection;\nimport net.demilich.metastone.game.cards.CardSet;\nimport net.demilich.metastone.game.cards.SpellCard;\nimport net.demilich.metastone.game.decks.DeckFactory;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.game.gameconfig.PlayerConfig;\n\npublic class CloningTest extends TestBase {\n\n\tprivate static Logger logger = LoggerFactory.getLogger(CloningTest.class);\n\n\tprivate void compareCardCollections(CardCollection collection1, CardCollection collection2) {\n\t\tAssert.assertEquals(collection1.getCount(), collection2.getCount());\n\t\tAssert.assertNotSame(collection1, collection2);\n\t\tfor (int j = 0; j < collection1.getCount(); j++) {\n\t\t\tCard originalCard = collection1.get(j);\n\t\t\tlogger.debug(\"Original card: \" + originalCard);\n\t\t\tCard cloneCard = collection2.get(j);\n\t\t\tlogger.debug(\"Clone card: \" + cloneCard);\n\t\t\tAssert.assertNotSame(originalCard, cloneCard);\n\t\t\tif (originalCard instanceof SpellCard) {\n\t\t\t\tAssert.assertTrue(cloneCard instanceof SpellCard, \"cloneCard is instanceof \" + cloneCard.getClass().getSimpleName());\n\t\t\t\tSpellCard originalSpellCard = (SpellCard) originalCard;\n\t\t\t\tSpellCard cloneSpellCard = (SpellCard) cloneCard;\n\t\t\t\tAssert.assertNotSame(originalSpellCard.getSpell(), cloneSpellCard.getSpell());\n\t\t\t}\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testCloneSpellCard() {\n\t\tCard original = CardCatalogue.getCardById(\"spell_polymorph\");\n\t\tCard clone = original.clone();\n\t\tAssert.assertNotSame(original, clone);\n\t\tSpellCard originalSpellCard = (SpellCard) original;\n\t\tSpellCard cloneSpellCard = (SpellCard) clone;\n\t\tAssert.assertNotSame(originalSpellCard.getSpell(), cloneSpellCard.getSpell());\n\t}\n\n\t@Test\n\tpublic void testCloning() {\n\t\tDeckFormat deckFormat = new DeckFormat();\n\t\tfor (CardSet set : CardSet.values()) {\n\t\t\tdeckFormat.addSet(set);\n\t\t}\n\t\tfor (int i = 0; i < 100; i++) {\n\t\t\tPlayerConfig player1Config = new PlayerConfig(DeckFactory.getRandomDeck(HeroClass.MAGE, deckFormat), new PlayRandomBehaviour());\n\t\t\tplayer1Config.setName(\"Player 1\");\n\t\t\tplayer1Config.setHeroCard(getHeroCardForClass(HeroClass.MAGE));\n\t\t\tPlayer player1 = new Player(player1Config);\n\n\t\t\tPlayerConfig player2Config = new PlayerConfig(DeckFactory.getRandomDeck(HeroClass.WARRIOR, deckFormat), new PlayRandomBehaviour());\n\t\t\tplayer2Config.setName(\"Player 2\");\n\t\t\tplayer2Config.setHeroCard(getHeroCardForClass(HeroClass.WARRIOR));\n\t\t\tPlayer player2 = new Player(player2Config);\n\n\t\t\t\n\n\t\t\tGameContext original = new GameContext(player1, player2, new GameLogic(), deckFormat);\n\t\t\tTestMinionCard minionCard = new TestMinionCard(3, 3);\n\t\t\toriginal.getTempCards().add(minionCard);\n\t\t\toriginal.getLogic().receiveCard(player1.getId(), minionCard);\n\t\t\toriginal.getLogic().performGameAction(player1.getId(), minionCard.play());\n\t\t\tActor testMinion = minionCard.getMinion();\n\n\t\t\tGameContext clone = original.clone();\n\n\t\t\tAssert.assertNotSame(original, clone);\n\t\t\tAssert.assertNotSame(original.getPlayer1(), clone.getPlayer1());\n\n\t\t\tlogger.debug(\"Comparing hands\");\n\t\t\tcompareCardCollections(original.getPlayer1().getHand(), clone.getPlayer1().getHand());\n\t\t\tlogger.debug(\"Comparing decks\");\n\t\t\tcompareCardCollections(original.getPlayer1().getDeck(), clone.getPlayer1().getDeck());\n\n\t\t\tGameContext cloneOfClone = clone.clone();\n\t\t\tcompareCardCollections(clone.getPlayer1().getHand(), cloneOfClone.getPlayer1().getHand());\n\t\t\tcompareCardCollections(clone.getPlayer1().getDeck(), cloneOfClone.getPlayer1().getDeck());\n\n\t\t\tAssert.assertNotSame(original.getPlayer2().getMinions(), clone.getPlayer2().getMinions());\n\t\t\tActor originalMinion = getSingleMinion(original.getPlayer1().getMinions());\n\t\t\tActor cloneMinion = getSingleMinion(clone.getPlayer1().getMinions());\n\t\t\tAssert.assertNotSame(originalMinion, cloneMinion);\n\t\t\tAssert.assertEquals(original.getPlayer2().getMinions().size(), clone.getPlayer2().getMinions().size());\n\t\t\tAssert.assertEquals(original.getPlayer1().getMana(), clone.getPlayer1().getMana());\n\n\t\t\tclone.play();\n\t\t\tlogger.info(\"\");\n\t\t\tlogger.info(\"********ORIGINAL********\");\n\t\t\tlogger.info(original.toString());\n\t\t\tlogger.info(\"\");\n\t\t\tlogger.info(\"********CLONE********\");\n\t\t\tlogger.info(clone.toString());\n\t\t\tAssert.assertNotEquals(original.getTurn(), clone.getTurn());\n\t\t\tAssert.assertEquals(testMinion.getHp(), 3);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/DebugContext.java",
    "content": "package net.demilich.metastone.tests;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.logic.GameLogic;\n\npublic class DebugContext extends GameContext {\n\n\tpublic DebugContext(Player player1, Player player2, GameLogic logic, DeckFormat deckFormat) {\n\t\tsuper(player1, player2, logic, deckFormat);\n\t}\n\n\t@Override\n\tpublic void init() {\n\t\tactivePlayer = getPlayer(PLAYER_1).getId();\n\t\tgetLogic().init(activePlayer, true);\n\t\tgetLogic().init(getOpponent(getActivePlayer()).getId(), false);\n\t}\n\n\tpublic void setActivePlayer(int playerId) {\n\t\tthis.activePlayer = playerId;\n\t}\n\t\n\tpublic void endTurn() {\n\t\tsuper.endTurn();\n\t\tstartTurn(activePlayer);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/HeroPowerTest.java",
    "content": "package net.demilich.metastone.tests;\n\nimport org.testng.Assert;\nimport org.testng.annotations.Test;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.entities.heroes.Hero;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.game.spells.DamageSpell;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class HeroPowerTest extends TestBase {\n\n\t@Test\n\tpublic void testArmorUp() {\n\t\tGameContext context = createContext(HeroClass.WARRIOR, HeroClass.MAGE);\n\t\tfinal Hero warrior = context.getPlayer1().getHero();\n\n\t\tAssert.assertEquals(warrior.getHp(), GameLogic.MAX_HERO_HP);\n\n\t\tGameAction armorUp = warrior.getHeroPower().play();\n\t\tcontext.getLogic().performGameAction(context.getPlayer1().getId(), armorUp);\n\n\t\tint armorUpBonus = 2;\n\t\tAssert.assertEquals(warrior.getHp(), GameLogic.MAX_HERO_HP);\n\t\tAssert.assertEquals(warrior.getArmor(), armorUpBonus);\n\n\t\tSpellDesc damage = DamageSpell.create(EntityReference.FRIENDLY_HERO, 2 * armorUpBonus);\n\t\tplayCard(context, context.getPlayer1(), new TestSpellCard(damage));\n\t\tAssert.assertEquals(warrior.getHp(), GameLogic.MAX_HERO_HP - armorUpBonus);\n\t\tAssert.assertEquals(warrior.getArmor(), 0);\n\n\t\t// there was a bug where armor actually increased the hp of the hero\n\t\t// when\n\t\t// the damage dealt was less than the total armor. Following test\n\t\t// covers that scenario\n\t\tcontext.getLogic().performGameAction(context.getPlayer1().getId(), armorUp);\n\t\tdamage = DamageSpell.create(EntityReference.FRIENDLY_HERO, armorUpBonus / 2);\n\t\tplayCard(context, context.getPlayer1(), new TestSpellCard(damage));\n\n\t\tAssert.assertEquals(warrior.getHp(), GameLogic.MAX_HERO_HP - armorUpBonus);\n\t\tAssert.assertEquals(warrior.getArmor(), armorUpBonus / 2);\n\t}\n\n\t@Test\n\tpublic void testFireblast() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tHero mage = context.getPlayer1().getHero();\n\t\tHero victim = context.getPlayer2().getHero();\n\t\tAssert.assertEquals(victim.getHp(), GameLogic.MAX_HERO_HP);\n\n\t\tGameAction fireblast = mage.getHeroPower().play();\n\t\tfireblast.setTarget(victim);\n\t\tfinal int fireblastDamage = 1;\n\t\tcontext.getLogic().performGameAction(context.getPlayer1().getId(), fireblast);\n\t\tAssert.assertEquals(victim.getHp(), GameLogic.MAX_HERO_HP - fireblastDamage);\n\t}\n\n\t@Test\n\tpublic void testLesserHeal() {\n\t\tGameContext context = createContext(HeroClass.PRIEST, HeroClass.WARRIOR);\n\t\tHero priest = context.getPlayer1().getHero();\n\n\t\tint lesserHealing = 2;\n\t\tpriest.setHp(GameLogic.MAX_HERO_HP - lesserHealing);\n\t\tAssert.assertEquals(priest.getHp(), GameLogic.MAX_HERO_HP - lesserHealing);\n\n\t\tGameAction lesserHeal = priest.getHeroPower().play();\n\t\tlesserHeal.setTarget(priest);\n\t\tcontext.getLogic().performGameAction(context.getPlayer1().getId(), lesserHeal);\n\t\tAssert.assertEquals(priest.getHp(), GameLogic.MAX_HERO_HP);\n\t\tcontext.getLogic().performGameAction(context.getPlayer1().getId(), lesserHeal);\n\t\tAssert.assertEquals(priest.getHp(), GameLogic.MAX_HERO_HP);\n\t}\n\n\t@Test\n\tpublic void testLifeTap() {\n\t\tGameContext context = createContext(HeroClass.WARLOCK, HeroClass.WARRIOR);\n\t\tPlayer warlockPlayer = context.getPlayer1();\n\t\tHero warlock = warlockPlayer.getHero();\n\n\t\tAssert.assertEquals(warlock.getHp(), GameLogic.MAX_HERO_HP);\n\n\t\tint cardCount = warlockPlayer.getHand().getCount();\n\t\tGameAction lifetap = warlock.getHeroPower().play();\n\t\tcontext.getLogic().performGameAction(warlockPlayer.getId(), lifetap);\n\n\t\tfinal int lifeTapDamage = 2;\n\t\tAssert.assertEquals(warlock.getHp(), GameLogic.MAX_HERO_HP - lifeTapDamage);\n\t\tAssert.assertEquals(warlockPlayer.getHand().getCount(), cardCount + 1);\n\t}\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/ManaTests.java",
    "content": "package net.demilich.metastone.tests;\r\n\r\nimport org.testng.Assert;\r\nimport org.testng.annotations.Test;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.CardCatalogue;\r\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\r\n\r\npublic class ManaTests extends TestBase {\r\n\t\r\n\t@Test\r\n\tpublic void testDarnassusAspirant() {\r\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.WARRIOR);\r\n\t\tPlayer player = context.getPlayer1();\r\n\t\tint playerId = player.getId();\r\n\r\n\t\tcontext.getLogic().startTurn(playerId);\r\n\t\tplayer.setMana(4);\r\n\t\tplayer.setMaxMana(4);\r\n\t\tAssert.assertEquals(player.getMana(), 4);\r\n\t\tAssert.assertEquals(player.getMaxMana(), 4);\r\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"minion_darnassus_aspirant\"));\r\n\t\tAssert.assertEquals(player.getMana(), 2);\r\n\t\tAssert.assertEquals(player.getMaxMana(), 5);\r\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"minion_doomsayer\"));\r\n\t\tAssert.assertEquals(player.getMana(), 0);\r\n\t\tAssert.assertEquals(player.getMaxMana(), 5);\r\n\t\tcontext.getLogic().endTurn(playerId);\r\n\t\t\r\n\t\t// start turn - Doomsayer triggers and kills Darnassus Aspirant\r\n\t\tcontext.getLogic().startTurn(playerId);\r\n\t\t// player should loose a full mana crystal in this case\r\n\t\tAssert.assertEquals(player.getMana(), 5);\r\n\t\tAssert.assertEquals(player.getMaxMana(), 5);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/MassTest.java",
    "content": "package net.demilich.metastone.tests;\r\n\r\nimport java.util.concurrent.ThreadLocalRandom;\r\n\r\nimport org.slf4j.LoggerFactory;\r\nimport org.testng.Assert;\r\nimport org.testng.annotations.BeforeTest;\r\nimport org.testng.annotations.Test;\r\n\r\nimport ch.qos.logback.classic.Level;\r\nimport ch.qos.logback.classic.Logger;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.behaviour.PlayRandomBehaviour;\r\nimport net.demilich.metastone.game.cards.CardSet;\r\nimport net.demilich.metastone.game.decks.DeckFactory;\r\nimport net.demilich.metastone.game.decks.DeckFormat;\r\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\r\nimport net.demilich.metastone.game.logic.GameLogic;\r\nimport net.demilich.metastone.game.gameconfig.PlayerConfig;\r\n\r\npublic class MassTest extends TestBase {\r\n\r\n\tprivate static HeroClass getRandomClass() {\r\n\t\tHeroClass randomClass = HeroClass.ANY;\r\n\t\tHeroClass[] values = HeroClass.values();\r\n\t\twhile (!randomClass.isBaseClass()) {\r\n\t\t\trandomClass = values[ThreadLocalRandom.current().nextInt(values.length)];\r\n\t\t}\r\n\t\treturn randomClass;\r\n\t}\r\n\r\n\t@BeforeTest\r\n\tprivate void loggerSetup() {\r\n\t\tLogger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);\r\n\t\troot.setLevel(Level.INFO);\r\n\t}\r\n\r\n\t@Test(threadPoolSize = 16, invocationCount = 1000)\r\n\tpublic void testRandomMassPlay() {\r\n\t\tDeckFormat deckFormat = new DeckFormat();\r\n\t\tfor (CardSet set : CardSet.values()) {\r\n\t\t\tdeckFormat.addSet(set);\r\n\t\t}\r\n\t\tHeroClass heroClass1 = getRandomClass();\r\n\t\tPlayerConfig player1Config = new PlayerConfig(DeckFactory.getRandomDeck(heroClass1, deckFormat), new PlayRandomBehaviour());\r\n\t\tplayer1Config.setName(\"Player 1\");\r\n\t\tplayer1Config.setHeroCard(getHeroCardForClass(heroClass1));\r\n\t\tPlayer player1 = new Player(player1Config);\r\n\r\n\t\tHeroClass heroClass2 = getRandomClass();\r\n\t\tPlayerConfig player2Config = new PlayerConfig(DeckFactory.getRandomDeck(heroClass2, deckFormat), new PlayRandomBehaviour());\r\n\t\tplayer2Config.setName(\"Player 2\");\r\n\t\tplayer2Config.setHeroCard(getHeroCardForClass(heroClass2));\r\n\t\tPlayer player2 = new Player(player2Config);\r\n\t\tGameContext context = new GameContext(player1, player2, new GameLogic(), deckFormat);\r\n\t\ttry {\r\n\t\t\tcontext.play();\r\n\t\t\tcontext.dispose();\r\n\t\t} catch (Exception e) {\r\n\t\t\tAssert.fail(\"Exception occured\", e);\r\n\t\t}\r\n\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/PoisonSeedsTests.java",
    "content": "package net.demilich.metastone.tests;\r\n\r\nimport org.testng.Assert;\r\nimport org.testng.annotations.Test;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.actions.GameAction;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.CardCatalogue;\r\nimport net.demilich.metastone.game.cards.MinionCard;\r\nimport net.demilich.metastone.game.cards.SpellCard;\r\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\r\nimport net.demilich.metastone.game.entities.minions.Minion;\r\nimport net.demilich.metastone.game.logic.GameLogic;\r\n\r\npublic class PoisonSeedsTests extends TestBase {\r\n\r\n\t@Test\r\n\tpublic void testPoisonSeeds() {\r\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.ROGUE);\r\n\t\tPlayer druid = context.getPlayer1();\r\n\t\tPlayer rogue = context.getPlayer2();\r\n\t\tMinionCard chillwindYeti = (MinionCard) CardCatalogue.getCardById(\"minion_chillwind_yeti\");\r\n\r\n\t\tfor (int i = 0; i < GameLogic.MAX_MINIONS; i++) {\r\n\t\t\tplayMinionCard(context, druid, chillwindYeti);\r\n\t\t}\r\n\r\n\t\tMinionCard nerubianEgg = (MinionCard) CardCatalogue.getCardById(\"minion_nerubian_egg\");\r\n\t\tfor (int i = 0; i < 3; i++) {\r\n\t\t\tplayMinionCard(context, rogue, nerubianEgg);\r\n\t\t}\r\n\r\n\t\tAssert.assertEquals(druid.getMinions().size(), GameLogic.MAX_MINIONS);\r\n\t\tAssert.assertEquals(rogue.getMinions().size(), 3);\r\n\r\n\t\tSpellCard poisonSeeds = (SpellCard) CardCatalogue.getCardById(\"spell_poison_seeds\");\r\n\t\tplayCard(context, druid, poisonSeeds);\r\n\r\n\t\tAssert.assertEquals(druid.getMinions().size(), GameLogic.MAX_MINIONS);\r\n\t\tAssert.assertEquals(rogue.getMinions().size(), 6);\r\n\t\tfor (Minion minion : druid.getMinions()) {\r\n\t\t\tAssert.assertEquals(minion.getSourceCard().getCardId(), \"token_treant\");\r\n\t\t}\r\n\t}\r\n\r\n\t@Test\r\n\tpublic void testPoisonSeedsAuchenai() {\r\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.PRIEST);\r\n\t\tPlayer druid = context.getPlayer1();\r\n\t\tPlayer priest = context.getPlayer2();\r\n\r\n\t\tMinionCard zombieChow = (MinionCard) CardCatalogue.getCardById(\"minion_zombie_chow\");\r\n\t\tplayMinionCard(context, priest, zombieChow);\r\n\t\tplayMinionCard(context, priest, zombieChow);\r\n\r\n\t\tMinionCard auchenaiSoulpriest = (MinionCard) CardCatalogue.getCardById(\"minion_auchenai_soulpriest\");\r\n\t\tplayMinionCard(context, priest, auchenaiSoulpriest);\r\n\r\n\t\tCard pyroblast = CardCatalogue.getCardById(\"spell_pyroblast\");\r\n\t\tcontext.getLogic().receiveCard(druid.getId(), pyroblast);\r\n\t\tGameAction gameAction = pyroblast.play();\r\n\t\tgameAction.setTarget(druid.getHero());\r\n\t\tcontext.getLogic().performGameAction(druid.getId(), gameAction);\r\n\r\n\t\tAssert.assertEquals(druid.getHero().getHp(), GameLogic.MAX_HERO_HP - 10);\r\n\r\n\t\tSpellCard poisonSeeds = (SpellCard) CardCatalogue.getCardById(\"spell_poison_seeds\");\r\n\t\tplayCard(context, druid, poisonSeeds);\r\n\r\n\t\tAssert.assertEquals(druid.getHero().getHp(), GameLogic.MAX_HERO_HP);\r\n\t}\r\n\r\n\t@Test\r\n\tpublic void testPoisonSeedsHauntedCreeper() {\r\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.ROGUE);\r\n\t\tPlayer druid = context.getPlayer1();\r\n\t\tMinionCard hauntedCreeper = (MinionCard) CardCatalogue.getCardById(\"minion_haunted_creeper\");\r\n\r\n\t\tfor (int i = 0; i < 4; i++) {\r\n\t\t\tplayMinionCard(context, druid, hauntedCreeper);\r\n\t\t}\r\n\t\tAssert.assertEquals(druid.getMinions().size(), 4);\r\n\r\n\t\tSpellCard poisonSeeds = (SpellCard) CardCatalogue.getCardById(\"spell_poison_seeds\");\r\n\t\tplayCard(context, druid, poisonSeeds);\r\n\r\n\t\tAssert.assertEquals(druid.getMinions().size(), GameLogic.MAX_MINIONS);\r\n\r\n\t\tfor (Minion minion : druid.getMinions()) {\r\n\t\t\tAssert.assertEquals(minion.getSourceCard().getCardId(), \"token_spectral_spider\");\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/SecretTest.java",
    "content": "package net.demilich.metastone.tests;\n\nimport org.testng.Assert;\nimport org.testng.annotations.Test;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.cards.SecretCard;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.logic.GameLogic;\n\npublic class SecretTest extends TestBase {\n\n\t@Test\n\tpublic void testKillingStopsAttack() {\n\t\tDebugContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer mage = context.getPlayer1();\n\t\tmage.setMana(10);\n\t\tPlayer warrior = context.getPlayer2();\n\t\twarrior.setMana(10);\n\n\t\tfinal int SECRET_DAMAGE = 2;\n\t\tplayCard(context, mage, new TestSecretCard(SECRET_DAMAGE));\n\t\tplayCard(context, warrior, new TestMinionCard(2, 3));\n\n\t\tcontext.setActivePlayer(warrior.getId());\n\t\tActor minion = getSingleMinion(warrior.getMinions());\n\t\tattack(context, warrior, minion, mage.getHero());\n\t\tAssert.assertEquals(mage.getHero().getHp(), mage.getHero().getMaxHp() - minion.getAttack());\n\t\tAssert.assertEquals(minion.getHp(), minion.getMaxHp() - SECRET_DAMAGE);\n\n\t\tplayCard(context, mage, new TestSecretCard(SECRET_DAMAGE));\n\t\tattack(context, warrior, minion, mage.getHero());\n\t\tAssert.assertTrue(minion.isDestroyed());\n\t\tAssert.assertEquals(mage.getHero().getHp(), mage.getHero().getMaxHp() - minion.getAttack());\n\t}\n\n\t@Test\n\tpublic void testNewSpellTarget() {\n\t\tDebugContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer mage = context.getPlayer1();\n\t\tmage.setMana(10);\n\t\tPlayer warrior = context.getPlayer2();\n\t\twarrior.setMana(10);\n\n\t\tint fullHp = 10;\n\t\tplayCard(context, warrior, new TestMinionCard(2, fullHp));\n\n\t\tActor minion = getSingleMinion(warrior.getMinions());\n\t\tcontext.getLogic().endTurn(mage.getId());\n\n\t\tfor (int i = 0; i < 2; i++) {\n\t\t\tplayCard(context, mage, CardCatalogue.getCardById(\"secret_spellbender\"));\n\t\t\tAssert.assertEquals(mage.getSecrets().size(), 1);\n\n\t\t\tCard testSpellCard = CardCatalogue.getCardById(\"spell_frostbolt\");\n\t\t\tcontext.getLogic().receiveCard(warrior.getId(), testSpellCard);\n\t\t\tGameAction spellAttackAction = testSpellCard.play();\n\t\t\tspellAttackAction.setTarget(minion);\n\n\t\t\tcontext.setActivePlayer(warrior.getId());\n\t\t\tcontext.getLogic().performGameAction(warrior.getId(), spellAttackAction);\n\n\t\t\tAssert.assertEquals(minion.getHp(), fullHp);\n\t\t\tAssert.assertEquals(warrior.getMinions().size(), 1);\n\n\t\t\tattack(context, warrior, minion, mage.getHero());\n\t\t}\n\n\t}\n\n\t@Test\n\tpublic void testPlayOnlyOnce() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer mage = context.getPlayer1();\n\t\tmage.setMana(10);\n\t\tPlayer warrior = context.getPlayer2();\n\t\twarrior.setMana(10);\n\n\t\tSecretCard secretCard = new TestSecretCard();\n\t\tcontext.getLogic().receiveCard(mage.getId(), secretCard);\n\t\tAssert.assertTrue(context.getLogic().canPlaySecret(mage, secretCard));\n\t\tcontext.getLogic().performGameAction(mage.getId(), secretCard.play());\n\n\t\tSecretCard secretCard2 = new TestSecretCard();\n\t\tcontext.getLogic().receiveCard(mage.getId(), secretCard2);\n\t\tAssert.assertFalse(context.getLogic().canPlaySecret(mage, secretCard2));\n\n\t\tSecretCard otherSecret = (SecretCard) CardCatalogue.getCardById(\"secret_explosive_trap\");\n\t\tcontext.getLogic().receiveCard(mage.getId(), otherSecret);\n\t\tAssert.assertTrue(context.getLogic().canPlaySecret(mage, otherSecret));\n\t}\n\n\t@Test\n\tpublic void testDuplicate() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"secret_duplicate\"));\n\n\t\tMinion novice = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_novice_engineer\"));\n\t\twhile (player.getHand().getCount() < GameLogic.MAX_HAND_CARDS) {\n\t\t\tplayCard(context, player, CardCatalogue.getCardById(\"minion_novice_engineer\"));\n\t\t}\n\t\tAssert.assertEquals(player.getHand().getCount(), GameLogic.MAX_HAND_CARDS);\n\t\tcontext.endTurn();\n\t\tplayCard(context, opponent, CardCatalogue.getCardById(\"weapon_fiery_war_axe\"));\n\n\t\tattack(context, opponent, opponent.getHero(), novice);\n\t\t// player has full hand, therefor Duplicate should not have triggered\n\t\tAssert.assertEquals(player.getSecrets().size(), 1);\n\t}\n\n\t@Test\n\tpublic void testExplosivePlusFreezingTrap() {\n\t\tGameContext context = createContext(HeroClass.WARRIOR, HeroClass.HUNTER);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\n\t\tMinionCard minionCard = (MinionCard) CardCatalogue.getCardById(\"minion_wisp\");\n\t\tMinion minion = playMinionCard(context, player, minionCard);\n\t\tcontext.endTurn();\n\n\t\tCard explosiveTrap = CardCatalogue.getCardById(\"secret_explosive_trap\");\n\t\tplayCard(context, opponent, explosiveTrap);\n\t\tCard freezingTrap = CardCatalogue.getCardById(\"secret_freezing_trap\");\n\t\tplayCard(context, opponent, freezingTrap);\n\t\tcontext.endTurn();\n\n\t\tAssert.assertEquals(player.getMinions().size(), 1);\n\t\tAssert.assertEquals(opponent.getSecrets().size(), 2);\n\n\t\tattack(context, player, minion, opponent.getHero());\n\t\tAssert.assertEquals(player.getMinions().size(), 0);\n\t\tAssert.assertEquals(opponent.getSecrets().size(), 1);\n\t}\n\n\t@Test\n\tpublic void testFreezingPlusBearTrap() {\n\t\tGameContext context = createContext(HeroClass.WARRIOR, HeroClass.HUNTER);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\n\t\tMinionCard minionCard = (MinionCard) CardCatalogue.getCardById(\"minion_wisp\");\n\t\tMinion minion = playMinionCard(context, player, minionCard);\n\t\tcontext.endTurn();\n\n\t\tCard freezingTrap = CardCatalogue.getCardById(\"secret_freezing_trap\");\n\t\tplayCard(context, opponent, freezingTrap);\n\t\tCard explosiveTrap = CardCatalogue.getCardById(\"secret_bear_trap\");\n\t\tplayCard(context, opponent, explosiveTrap);\n\n\t\tcontext.endTurn();\n\n\t\tAssert.assertEquals(player.getMinions().size(), 1);\n\t\tAssert.assertEquals(opponent.getSecrets().size(), 2);\n\n\t\tattack(context, player, minion, opponent.getHero());\n\t\tAssert.assertEquals(player.getMinions().size(), 0);\n\t\tAssert.assertEquals(opponent.getSecrets().size(), 1);\n\t}\n\n\t@Test\n\tpublic void testIceBlockWithArmor() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\n\t\tplayer.getHero().setHp(3);\n\t\tplayer.getHero().setAttribute(Attribute.ARMOR, 10);\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"secret_ice_block\"));\n\t\tcontext.endTurn();\n\n\t\tplayCardWithTarget(context, opponent, CardCatalogue.getCardById(\"spell_bash\"), player.getHero());\n\t\t// Ice block should not have triggered, as the Mage had enough armor to\n\t\t// prevent fatal damage\n\t\tAssert.assertEquals(player.getSecrets().size(), 1);\n\t\tAssert.assertFalse(player.getHero().hasAttribute(Attribute.IMMUNE));\n\t}\n\n\t@Test\n\tpublic void testAvenge() {\n\t\tGameContext context = createContext(HeroClass.PALADIN, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getPlayer2();\n\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"secret_avenge\"));\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"minion_murloc_raider\"));\n\t\tMinion minion = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_murloc_raider\"));\n\t\tAssert.assertEquals(player.getSecrets().size(), 1);\n\t\tcontext.endTurn();\n\t\tplayCardWithTarget(context, opponent, CardCatalogue.getCardById(\"spell_bash\"), minion);\n\n\t\tAssert.assertEquals(player.getSecrets().size(), 0);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/SpecialCardTests.java",
    "content": "package net.demilich.metastone.tests;\n\nimport java.util.List;\n\nimport org.testng.Assert;\nimport org.testng.annotations.Test;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.actions.PhysicalAttackAction;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.cards.SpellCard;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.heroes.Hero;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.DamageSpell;\nimport net.demilich.metastone.game.spells.DestroySpell;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.EntityReference;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class SpecialCardTests extends TestBase {\n\n\t@Test\n\tpublic void testFaerieDragon() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer mage = context.getPlayer1();\n\t\tmage.setMana(10);\n\t\tPlayer warrior = context.getPlayer2();\n\t\twarrior.setMana(10);\n\n\t\tMinionCard faerieDragonCard = (MinionCard) CardCatalogue.getCardById(\"minion_faerie_dragon\");\n\t\tcontext.getLogic().receiveCard(warrior.getId(), faerieDragonCard);\n\t\tcontext.getLogic().performGameAction(warrior.getId(), faerieDragonCard.play());\n\n\t\tMinionCard devMonsterCard = new TestMinionCard(1, 1);\n\t\tcontext.getLogic().receiveCard(mage.getId(), devMonsterCard);\n\t\tcontext.getLogic().performGameAction(mage.getId(), devMonsterCard.play());\n\n\t\tEntity attacker = getSingleMinion(mage.getMinions());\n\t\tActor elusiveOne = getSingleMinion(warrior.getMinions());\n\n\t\tGameAction attackAction = new PhysicalAttackAction(attacker.getReference());\n\t\tList<Entity> validTargets = context.getLogic().getValidTargets(warrior.getId(), attackAction);\n\t\t// should be two valid targets: enemy hero and faerie dragon\n\t\tAssert.assertEquals(validTargets.size(), 2);\n\n\t\tGameAction useFireblast = mage.getHero().getHeroPower().play();\n\t\tvalidTargets = context.getLogic().getValidTargets(mage.getId(), useFireblast);\n\t\t// should be three valid targets, both heroes + minion which is not the\n\t\t// faerie dragon\n\t\tAssert.assertEquals(validTargets.size(), 3);\n\t\tAssert.assertFalse(validTargets.contains(elusiveOne));\n\n\t\tCard arcaneExplosionCard = CardCatalogue.getCardById(\"spell_arcane_explosion\");\n\t\tcontext.getLogic().receiveCard(mage.getId(), arcaneExplosionCard);\n\t\tint faerieDragonHp = elusiveOne.getHp();\n\t\tcontext.getLogic().performGameAction(mage.getId(), arcaneExplosionCard.play());\n\t\t// hp should been affected after playing area of effect spell\n\t\tAssert.assertNotEquals(faerieDragonHp, elusiveOne.getHp());\n\t}\n\n\t@Test\n\tpublic void testGurubashiBerserker() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer mage = context.getPlayer1();\n\t\tmage.setMana(10);\n\t\tPlayer warrior = context.getPlayer2();\n\t\twarrior.setMana(10);\n\n\t\tfinal int BASE_ATTACK = 2;\n\t\tfinal int ATTACK_BONUS = 3;\n\n\t\tMinionCard gurubashiBerserkerCard = (MinionCard) CardCatalogue.getCardById(\"minion_gurubashi_berserker\");\n\t\tplayCard(context, warrior, gurubashiBerserkerCard);\n\n\t\tMinionCard oasisSnapjawCard = (MinionCard) CardCatalogue.getCardById(\"minion_oasis_snapjaw\");\n\t\tplayCard(context, mage, oasisSnapjawCard);\n\n\t\tActor attacker = getSingleMinion(mage.getMinions());\n\t\tActor defender = getSingleMinion(warrior.getMinions());\n\n\t\t// Gurubashi Berserker should start with just his base attack\n\t\tAssert.assertEquals(defender.getAttack(), BASE_ATTACK);\n\n\t\t// first attack, Gurubashi Berserker should have increased attack\n\t\tGameAction attackAction = new PhysicalAttackAction(attacker.getReference());\n\t\tattackAction.setTarget(defender);\n\t\tcontext.getLogic().performGameAction(mage.getId(), attackAction);\n\n\t\tAssert.assertEquals(attacker.getHp(), attacker.getMaxHp() - BASE_ATTACK);\n\t\tAssert.assertEquals(defender.getHp(), defender.getMaxHp() - attacker.getAttack());\n\t\tAssert.assertEquals(defender.getAttack(), BASE_ATTACK + ATTACK_BONUS);\n\n\t\t// second attack, Gurubashi Berserker should become even stronger\n\t\tcontext.getLogic().performGameAction(mage.getId(), attackAction);\n\t\tAssert.assertEquals(attacker.getHp(), attacker.getMaxHp() - 2 * BASE_ATTACK - ATTACK_BONUS);\n\t\tAssert.assertEquals(defender.getHp(), defender.getMaxHp() - 2 * attacker.getAttack());\n\t\tAssert.assertEquals(defender.getAttack(), BASE_ATTACK + 2 * ATTACK_BONUS);\n\t}\n\n\t@Test\n\tpublic void testSavageRoar() {\n\t\tGameContext context = createContext(HeroClass.DRUID, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tHero druid = player.getHero();\n\n\t\tplayer.setMana(10);\n\t\tPlayer warrior = context.getPlayer2();\n\t\twarrior.setMana(10);\n\n\t\tMinionCard devMonsterCard = new TestMinionCard(1, 1);\n\t\tcontext.getLogic().receiveCard(player.getId(), devMonsterCard);\n\t\tcontext.getLogic().performGameAction(player.getId(), devMonsterCard.play());\n\n\t\tActor minion = getSingleMinion(player.getMinions());\n\n\t\tcontext.getLogic().performGameAction(player.getId(), druid.getHeroPower().play());\n\t\tAssert.assertEquals(druid.getAttack(), 1);\n\t\tAssert.assertEquals(minion.getAttack(), 1);\n\n\t\tCard savageRoar = CardCatalogue.getCardById(\"spell_savage_roar\");\n\t\tcontext.getLogic().receiveCard(player.getId(), savageRoar);\n\t\tcontext.getLogic().performGameAction(player.getId(), savageRoar.play());\n\t\tAssert.assertEquals(druid.getAttack(), 3);\n\t\tAssert.assertEquals(minion.getAttack(), 3);\n\n\t\tcontext.getLogic().endTurn(player.getId());\n\t\tAssert.assertEquals(druid.getAttack(), 0);\n\t\tAssert.assertEquals(minion.getAttack(), 1);\n\n\t\tcontext.getLogic().endTurn(player.getId());\n\t\tAssert.assertEquals(druid.getAttack(), 0);\n\t\tAssert.assertEquals(minion.getAttack(), 1);\n\t}\n\n\t@Test\n\tpublic void testSpitefulSmith() {\n\t\tGameContext context = createContext(HeroClass.WARRIOR, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tplayer.setMana(10);\n\n\t\tCard fieryWarAxe = CardCatalogue.getCardById(\"weapon_fiery_war_axe\");\n\t\tplayCard(context, player, fieryWarAxe);\n\n\t\tAssert.assertTrue(player.getHero().getWeapon() != null);\n\t\tAssert.assertEquals(player.getHero().getWeapon().getWeaponDamage(), 3);\n\n\t\tMinionCard spitefulSmithCard = (MinionCard) CardCatalogue.getCardById(\"minion_spiteful_smith\");\n\t\tMinion spitefulSmith = playMinionCard(context, player, spitefulSmithCard);\n\t\t// Smith has been played, but is not enraged yet, so weapon damage\n\t\t// should still be unaltered\n\t\tAssert.assertEquals(player.getHero().getWeapon().getWeaponDamage(), 3);\n\n\t\tSpellCard damageSpell = new TestSpellCard(DamageSpell.create(1));\n\t\tdamageSpell.setTargetRequirement(TargetSelection.ANY);\n\t\tcontext.getLogic().receiveCard(player.getId(), damageSpell);\n\t\tGameAction spellAction = damageSpell.play();\n\t\tspellAction.setTarget(spitefulSmith);\n\t\tcontext.getLogic().performGameAction(player.getId(), spellAction);\n\n\t\t// Smith is damaged now, so weapon should be buffed\n\t\tAssert.assertEquals(player.getHero().getWeapon().getWeaponDamage(), 5);\n\n\t\t// equip a new weapon; this one should get buffed too\n\t\tfieryWarAxe = CardCatalogue.getCardById(\"weapon_fiery_war_axe\");\n\t\tplayCard(context, player, fieryWarAxe);\n\t\tAssert.assertEquals(player.getHero().getWeapon().getWeaponDamage(), 5);\n\n\t\t// wipe everything\n\t\tSpellDesc wipeSpell = DestroySpell.create(EntityReference.ALL_MINIONS);\n\t\tSpellCard wipe = new TestSpellCard(wipeSpell);\n\t\tplayCard(context, player, wipe);\n\n\t\t// Smith is destroyed, weapon power should be back to normal\n\t\tAssert.assertEquals(player.getHero().getWeapon().getWeaponDamage(), 3);\n\t}\n\n\t@Test\n\tpublic void testSummoningPortal() {\n\t\tGameContext context = createContext(HeroClass.WARLOCK, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tplayer.setMana(10);\n\n\t\tCard summoningPortal1 = CardCatalogue.getCardById(\"minion_summoning_portal\");\n\t\tcontext.getLogic().receiveCard(player.getId(), summoningPortal1);\n\t\tCard summoningPortal2 = CardCatalogue.getCardById(\"minion_summoning_portal\");\n\t\tcontext.getLogic().receiveCard(player.getId(), summoningPortal2);\n\n\t\tMinionCard testMinionCard = new TestMinionCard(1, 1, 4);\n\t\tcontext.getLogic().receiveCard(player.getId(), testMinionCard);\n\t\tAssert.assertEquals(player.getMana(), 10);\n\n\t\t// first summoning portal costs full 4 mana\n\t\tcontext.getLogic().performGameAction(player.getId(), summoningPortal1.play());\n\t\tAssert.assertEquals(player.getMana(), 6);\n\n\t\t// second summoning portal affected by first one, costs only 2 mana\n\t\tcontext.getLogic().performGameAction(player.getId(), summoningPortal2.play());\n\t\tAssert.assertEquals(player.getMana(), 4);\n\n\t\t// base cost of minion card is 4, reduced by both summoning portals, but\n\t\t// not below 1\n\t\tcontext.getLogic().performGameAction(player.getId(), testMinionCard.play());\n\t\tAssert.assertEquals(player.getMana(), 3);\n\n\t}\n\n\t@Test\n\tpublic void testWildPyromancer() {\n\t\tGameContext context = createContext(HeroClass.PRIEST, HeroClass.WARRIOR);\n\t\tPlayer warrior = context.getPlayer2();\n\t\tCard hauntedCreeper = CardCatalogue.getCardById(\"minion_haunted_creeper\");\n\t\tplayCard(context, warrior, hauntedCreeper);\n\n\t\tPlayer priest = context.getPlayer1();\n\t\tCard wildPyromancer = CardCatalogue.getCardById(\"minion_wild_pyromancer\");\n\t\tplayCard(context, priest, wildPyromancer);\n\n\t\tAssert.assertEquals(warrior.getMinions().size(), 1);\n\n\t\tCard holyNova = CardCatalogue.getCardById(\"spell_holy_nova\");\n\t\tplayCard(context, priest, holyNova);\n\n\t\t// the warriors board should be completely wiped, as the Holy Nova\n\t\t// should kill the\n\t\t// first body of Haunted Creeper, the Deathrattle resolves and then Wild\n\t\t// Pyromancer\n\t\t// triggers, clearing the two 1/1 Spectral Spiders\n\t\tAssert.assertEquals(warrior.getMinions().size(), 0);\n\t}\n\n\t@Test\n\tpublic void testBetrayal() {\n\t\tGameContext context = createContext(HeroClass.PALADIN, HeroClass.ROGUE);\n\t\tPlayer paladin = context.getPlayer1();\n\n\t\tMinionCard adjacentMinionCard1 = new TestMinionCard(1, 5, 0);\n\t\tMinion adjacentMinion1 = playMinionCard(context, paladin, adjacentMinionCard1);\n\n\t\tMinionCard targetMinionCard = new TestMinionCard(3, 1, 0);\n\t\tMinion targetMinion = playMinionCard(context, paladin, targetMinionCard);\n\n\t\tMinionCard adjacentMinionCard2 = new TestMinionCard(1, 5, 0);\n\t\tMinion adjacentMinion2 = playMinionCard(context, paladin, adjacentMinionCard2);\n\n\t\tcontext.getLogic().endTurn(paladin.getId());\n\n\t\tAssert.assertEquals(paladin.getMinions().size(), 3);\n\n\t\tPlayer rogue = context.getPlayer2();\n\t\tCard betrayal = CardCatalogue.getCardById(\"spell_betrayal\");\n\n\t\tcontext.getLogic().receiveCard(rogue.getId(), betrayal);\n\t\tGameAction action = betrayal.play();\n\t\taction.setTarget(targetMinion);\n\t\tcontext.getLogic().performGameAction(rogue.getId(), action);\n\t\tAssert.assertEquals(targetMinion.getAttack(), 3);\n\t\tAssert.assertEquals(targetMinion.getHp(), 1);\n\n\t\tAssert.assertEquals(adjacentMinion1.getHp(), 2);\n\t\tAssert.assertEquals(adjacentMinion2.getHp(), 2);\n\n\t\tAssert.assertEquals(paladin.getMinions().size(), 3);\n\t}\n\n\t@Test\n\tpublic void testBetrayalNotAffectedBySpellDamage() {\n\t\tGameContext context = createContext(HeroClass.PALADIN, HeroClass.ROGUE);\n\t\tPlayer paladin = context.getPlayer1();\n\n\t\tMinionCard adjacentMinionCard1 = new TestMinionCard(1, 5, 0);\n\t\tMinion adjacentMinion1 = playMinionCard(context, paladin, adjacentMinionCard1);\n\n\t\tMinionCard targetMinionCard = new TestMinionCard(3, 1, 0);\n\t\tMinion targetMinion = playMinionCard(context, paladin, targetMinionCard);\n\n\t\tMinionCard adjacentMinionCard2 = new TestMinionCard(1, 5, 0);\n\t\tMinion adjacentMinion2 = playMinionCard(context, paladin, adjacentMinionCard2);\n\n\t\tcontext.getLogic().endTurn(paladin.getId());\n\n\t\tPlayer rogue = context.getPlayer2();\n\n\t\tMinionCard azureDrakeCard = (MinionCard) CardCatalogue.getCardById(\"minion_azure_drake\");\n\t\tplayMinionCard(context, rogue, azureDrakeCard);\n\n\t\tCard betrayal = CardCatalogue.getCardById(\"spell_betrayal\");\n\n\t\tcontext.getLogic().receiveCard(rogue.getId(), betrayal);\n\t\tGameAction action = betrayal.play();\n\t\taction.setTarget(targetMinion);\n\t\tcontext.getLogic().performGameAction(rogue.getId(), action);\n\t\tAssert.assertEquals(targetMinion.getAttack(), 3);\n\t\tAssert.assertEquals(targetMinion.getHp(), 1);\n\n\t\tAssert.assertEquals(adjacentMinion1.getHp(), 2);\n\t\tAssert.assertEquals(adjacentMinion2.getHp(), 2);\n\t}\n\n\t@Test\n\tpublic void testBetrayalOnEmperorCobraDestroysAdjacentMinions() {\n\t\tGameContext context = createContext(HeroClass.PALADIN, HeroClass.ROGUE);\n\t\tPlayer paladin = context.getPlayer1();\n\n\t\tMinionCard adjacentMinionCard1 = new TestMinionCard(1, 5, 0);\n\t\tplayMinionCard(context, paladin, adjacentMinionCard1);\n\n\t\tMinionCard targetMinionCard = (MinionCard) CardCatalogue.getCardById(\"minion_emperor_cobra\");\n\t\tMinion targetMinion = playMinionCard(context, paladin, targetMinionCard);\n\n\t\tMinionCard adjacentMinionCard2 = new TestMinionCard(1, 5, 0);\n\t\tplayMinionCard(context, paladin, adjacentMinionCard2);\n\n\t\tcontext.getLogic().endTurn(paladin.getId());\n\n\t\tAssert.assertEquals(paladin.getMinions().size(), 3);\n\n\t\tPlayer rogue = context.getPlayer2();\n\n\t\tCard betrayal = CardCatalogue.getCardById(\"spell_betrayal\");\n\n\t\tcontext.getLogic().receiveCard(rogue.getId(), betrayal);\n\t\tGameAction action = betrayal.play();\n\t\taction.setTarget(targetMinion);\n\t\tcontext.getLogic().performGameAction(rogue.getId(), action);\n\n\t\tAssert.assertEquals(paladin.getMinions().size(), 1);\n\t}\n\n\t@Test\n\tpublic void testEydisDarkbane() {\n\t\tGameContext context = createContext(HeroClass.PRIEST, HeroClass.WARRIOR);\n\t\tPlayer priest = context.getPlayer1();\n\t\tPlayer warrior = context.getPlayer2();\n\n\t\tMinionCard eydisDarkbaneCard = (MinionCard) CardCatalogue.getCardById(\"minion_eydis_darkbane\");\n\t\tMinion eydisDarkbane = playMinionCard(context, priest, eydisDarkbaneCard);\n\n\t\tCard testSpellCard = CardCatalogue.getCardById(\"spell_power_word_shield\");\n\t\tcontext.getLogic().receiveCard(priest.getId(), testSpellCard);\n\t\tGameAction spellAction = testSpellCard.play();\n\t\tspellAction.setTarget(eydisDarkbane);\n\t\tcontext.getLogic().performGameAction(priest.getId(), spellAction);\n\n\t\t// priest casted a spell on Eydis - warrior should be wounded\n\t\tAssert.assertEquals(warrior.getHero().getHp(), warrior.getHero().getMaxHp() - 3);\n\n\t\ttestSpellCard = CardCatalogue.getCardById(\"spell_shield_slam\");\n\t\tcontext.getLogic().receiveCard(warrior.getId(), testSpellCard);\n\t\tspellAction = testSpellCard.play();\n\t\tspellAction.setTarget(eydisDarkbane);\n\t\tcontext.getLogic().performGameAction(warrior.getId(), spellAction);\n\n\t\t// warrior casted a spell on Eydis - nothing should happen\n\t\tAssert.assertEquals(warrior.getHero().getHp(), warrior.getHero().getMaxHp() - 3);\n\t}\n\n\t@Test\n\tpublic void testBetrayalOnBurlyRockjawTroggDeals5Damage() {\n\t\tGameContext context = createContext(HeroClass.PALADIN, HeroClass.ROGUE);\n\t\tPlayer paladin = context.getPlayer1();\n\n\t\tMinionCard adjacentMinionCard1 = new TestMinionCard(1, 5, 0);\n\t\tplayMinionCard(context, paladin, adjacentMinionCard1);\n\n\t\tMinionCard targetMinionCard = (MinionCard) CardCatalogue.getCardById(\"minion_burly_rockjaw_trogg\");\n\t\tMinion targetMinion = playMinionCard(context, paladin, targetMinionCard);\n\n\t\tMinionCard adjacentMinionCard2 = new TestMinionCard(1, 5, 0);\n\t\tplayMinionCard(context, paladin, adjacentMinionCard2);\n\n\t\tcontext.getLogic().endTurn(paladin.getId());\n\n\t\tAssert.assertEquals(paladin.getMinions().size(), 3);\n\n\t\tPlayer rogue = context.getPlayer2();\n\n\t\tCard betrayal = CardCatalogue.getCardById(\"spell_betrayal\");\n\n\t\tcontext.getLogic().receiveCard(rogue.getId(), betrayal);\n\t\tGameAction action = betrayal.play();\n\t\taction.setTarget(targetMinion);\n\t\tcontext.getLogic().performGameAction(rogue.getId(), action);\n\n\t\tAssert.assertEquals(paladin.getMinions().size(), 1);\n\t}\n\n\t@Test\n\tpublic void testRallyingBlade() {\n\t\tGameContext context = createContext(HeroClass.PALADIN, HeroClass.ROGUE);\n\t\tPlayer player = context.getPlayer1();\n\t\tMinionCard argentSquireCard = (MinionCard) CardCatalogue.getCardById(\"minion_argent_squire\");\n\t\tMinion argentSquire = playMinionCard(context, player, argentSquireCard);\n\t\tAssert.assertEquals(argentSquire.getAttack(), 1);\n\t\tAssert.assertEquals(argentSquire.getHp(), 1);\n\n\t\tCard rallyingBladeCard = CardCatalogue.getCardById(\"weapon_rallying_blade\");\n\t\tplayCard(context, player, rallyingBladeCard);\n\t\tAssert.assertEquals(argentSquire.getAttack(), 2);\n\t\tAssert.assertEquals(argentSquire.getHp(), 2);\n\t}\n\n\t@Test\n\tpublic void testCurseOfRafaam() {\n\t\tGameContext context = createContext(HeroClass.WARRIOR, HeroClass.WARLOCK);\n\n\t\tPlayer player = context.getPlayer1();\n\t\tCard koboldGeomancerCard = CardCatalogue.getCardById(\"minion_kobold_geomancer\");\n\t\tplayCard(context, player, koboldGeomancerCard);\n\t\tcontext.endTurn();\n\n\t\tPlayer opponent = context.getPlayer2();\n\t\tCard curseOfRafaamCard = CardCatalogue.getCardById(\"spell_curse_of_rafaam\");\n\t\tplayCard(context, opponent, curseOfRafaamCard);\n\t\tcontext.endTurn();\n\n\t\tfinal int CURSE_OF_RAFAAM_DAMAGE = 2;\n\t\t// first player should take exactly 2 damage (NOT 3, because the spell\n\t\t// damage should not be applied)\n\t\tAssert.assertEquals(player.getHero().getHp(), player.getHero().getMaxHp() - CURSE_OF_RAFAAM_DAMAGE);\n\n\t}\n\n\t@Test\n\tpublic void testEmperorThaurissanEmptyHand() {\n\t\tGameContext context = createContext(HeroClass.WARRIOR, HeroClass.WARLOCK);\n\n\t\tPlayer player = context.getPlayer1();\n\t\tMinionCard emperorThaurissanCard = (MinionCard) CardCatalogue.getCardById(\"minion_emperor_thaurissan\");\n\t\tMinion emperorThaurissan = playMinionCard(context, player, emperorThaurissanCard);\n\t\tfor (Card card : player.getHand().toList()) {\n\t\t\tcontext.getLogic().removeCard(player.getId(), card);\n\n\t\t}\n\t\tAssert.assertTrue(player.getHand().isEmpty());\n\t\tcontext.endTurn();\n\n\t\tPlayer opponent = context.getPlayer2();\n\t\tCard assassinateCard = CardCatalogue.getCardById(\"spell_assassinate\");\n\t\tplayCardWithTarget(context, opponent, assassinateCard, emperorThaurissan);\n\t\tcontext.getLogic().receiveCard(player.getId(), CardCatalogue.getCardById(\"minion_chillwind_yeti\"));\n\t\tcontext.endTurn();\n\n\t\tCard card = player.getHand().peekFirst();\n\t\tint modifiedCost = context.getLogic().getModifiedManaCost(player, card);\n\t\tSystem.out.println(\"Card [\" + card.getName() + \"] has baseManaCost of \" + card.getBaseManaCost()\n\t\t\t\t+ \" and current actual manacost of \" + modifiedCost);\n\t\tAssert.assertEquals(card.getBaseManaCost(), modifiedCost);\n\n\t}\n\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/TargetingTests.java",
    "content": "package net.demilich.metastone.tests;\n\nimport java.util.List;\n\nimport org.testng.Assert;\nimport org.testng.annotations.Test;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.actions.PhysicalAttackAction;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.EntityType;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\n\npublic class TargetingTests extends TestBase {\n\n\t@Test\n\tpublic void testTaunt() {\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\n\t\tPlayer mage = context.getPlayer1();\n\t\tPlayer victim = context.getPlayer2();\n\n\t\tMinionCard tauntCard = (MinionCard) CardCatalogue.getCardById(\"minion_shieldbearer\");\n\t\tcontext.getLogic().receiveCard(victim.getId(), tauntCard);\n\n\t\tMinionCard attackerCard = new TestMinionCard(1, 1, 0);\n\t\tcontext.getLogic().receiveCard(mage.getId(), attackerCard);\n\n\t\tcontext.getLogic().performGameAction(victim.getId(), tauntCard.play());\n\t\tcontext.getLogic().performGameAction(mage.getId(), attackerCard.play());\n\n\t\tEntity attacker = getSingleMinion(mage.getMinions());\n\t\tEntity defender = getSingleMinion(victim.getMinions());\n\t\tAssert.assertEquals(defender.hasAttribute(Attribute.TAUNT), true);\n\n\t\tList<Entity> validTargets;\n\n\t\tGameAction attackAction = new PhysicalAttackAction(attacker.getReference());\n\t\tvalidTargets = context.getLogic().getValidTargets(mage.getId(), attackAction);\n\t\tAssert.assertEquals(validTargets.size(), 1);\n\n\t\tGameAction fireblast = mage.getHero().getHeroPower().play();\n\t\tvalidTargets = context.getLogic().getValidTargets(mage.getId(), fireblast);\n\t\tAssert.assertEquals(validTargets.size(), 4);\n\n\t\tdefender.removeAttribute(Attribute.TAUNT);\n\n\t\tvalidTargets = context.getLogic().getValidTargets(mage.getId(), attackAction);\n\t\tAssert.assertEquals(validTargets.size(), 2);\n\n\t\tvalidTargets = context.getLogic().getValidTargets(mage.getId(), fireblast);\n\t\tAssert.assertEquals(validTargets.size(), 4);\n\n\t\t// taunt should be ignored when the minion is stealthed\n\t\tdefender.setAttribute(Attribute.TAUNT);\n\t\tdefender.setAttribute(Attribute.STEALTH);\n\t\tvalidTargets = context.getLogic().getValidTargets(mage.getId(), attackAction);\n\t\tAssert.assertEquals(validTargets.size(), 1);\n\t\tAssert.assertEquals(validTargets.get(0).getEntityType(), EntityType.HERO);\n\n\t}\n\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/TechnicalTests.java",
    "content": "package net.demilich.metastone.tests;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.testng.Assert;\nimport org.testng.annotations.Test;\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.spells.DamageSpell;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class TechnicalTests extends TestBase {\n\n\t@Test\n\tpublic void testDoubleCorruption() {\n\t\tGameContext context = createContext(HeroClass.WARLOCK, HeroClass.WARRIOR);\n\t\tPlayer warlock = context.getPlayer1();\n\t\twarlock.setMana(10);\n\t\tPlayer warrior = context.getPlayer2();\n\t\twarrior.setMana(10);\n\n\t\tTestMinionCard victimCard = new TestMinionCard(1, 10);\n\t\tcontext.getLogic().receiveCard(warrior.getId(), victimCard);\n\t\tcontext.getLogic().performGameAction(warrior.getId(), victimCard.play());\n\n\t\tCard corruption1 = CardCatalogue.getCardById(\"spell_corruption\");\n\t\tCard corruption2 = CardCatalogue.getCardById(\"spell_corruption\");\n\t\tcontext.getLogic().receiveCard(warlock.getId(), corruption1);\n\t\tcontext.getLogic().receiveCard(warlock.getId(), corruption2);\n\n\t\tEntity victim = getSingleMinion(warrior.getMinions());\n\t\tGameAction playCorruption1 = corruption1.play();\n\t\tplayCorruption1.setTarget(victim);\n\t\tGameAction playCorruption2 = corruption2.play();\n\t\tplayCorruption2.setTarget(victim);\n\n\t\tcontext.getLogic().performGameAction(warlock.getId(), playCorruption1);\n\t\tcontext.getLogic().performGameAction(warlock.getId(), playCorruption2);\n\n\t\tcontext.getLogic().endTurn(GameContext.PLAYER_1);\n\t\tcontext.getLogic().startTurn(GameContext.PLAYER_2);\n\t\tcontext.getLogic().endTurn(GameContext.PLAYER_2);\n\t\tcontext.getLogic().startTurn(GameContext.PLAYER_1);\n\t}\n\n\t@Test\n\tpublic void testTriplePyromancer() {\n\t\tGameContext context = createContext(HeroClass.PRIEST, HeroClass.WARRIOR);\n\t\tPlayer player1 = context.getPlayer1();\n\t\tplayer1.setMana(10);\n\n\t\tfor (int i = 0; i < 3; i++) {\n\t\t\tplayCard(context, player1, CardCatalogue.getCardById(\"minion_wild_pyromancer\"));\n\t\t}\n\n\t\tList<Minion> copyOfMinionList = new ArrayList<Minion>();\n\t\tcopyOfMinionList.addAll(player1.getMinions());\n\t\tAssert.assertEquals(copyOfMinionList.size(), 3);\n\t\t// three pyromancers summoned, all should be at max HP\n\t\tfor (Minion minion : copyOfMinionList) {\n\t\t\tAssert.assertEquals(minion.getHp(), minion.getMaxHp());\n\t\t}\n\n\t\tplayCard(context, player1, new TestSpellCard(DamageSpell.create(EntityReference.ENEMY_HERO, 1)));\n\t\t// after playing a spell:\n\t\t// all three pyromancers should have triggered, even though all are dead\n\t\t// after the first two\n\t\t// this net.demilich.metastone.tests that minions are only removed from board after all effects\n\t\t// are resolved\n\t\tfor (Minion minion : copyOfMinionList) {\n\t\t\tAssert.assertEquals(minion.getHp(), -1);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/TestAction.java",
    "content": "package net.demilich.metastone.tests;\n\nimport net.demilich.metastone.game.actions.GameAction;\n\npublic abstract class TestAction extends GameAction {\n\n\t@Override\n\tpublic String getPromptText() {\n\t\treturn \"[Test Action]\";\n\t}\n\n\t@Override\n\tpublic boolean isSameActionGroup(GameAction anotherAction) {\n\t\treturn false;\n\t}\n\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/TestBase.java",
    "content": "package net.demilich.metastone.tests;\n\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.slf4j.LoggerFactory;\n\nimport ch.qos.logback.classic.Level;\nimport ch.qos.logback.classic.Logger;\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.actions.GameAction;\nimport net.demilich.metastone.game.actions.PhysicalAttackAction;\nimport net.demilich.metastone.game.behaviour.Behaviour;\nimport net.demilich.metastone.game.cards.Card;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.CardParseException;\nimport net.demilich.metastone.game.cards.CardSet;\nimport net.demilich.metastone.game.cards.HeroCard;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.decks.DeckFactory;\nimport net.demilich.metastone.game.decks.DeckFormat;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.Entity;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport net.demilich.metastone.game.gameconfig.PlayerConfig;\nimport net.demilich.metastone.game.logic.GameLogic;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class TestBase {\n\n\tprotected static class TestBehaviour extends Behaviour {\n\n\t\tprivate EntityReference targetPreference;\n\n\t\t@Override\n\t\tpublic String getName() {\n\t\t\treturn \"Null Behaviour\";\n\t\t}\n\n\t\tpublic EntityReference getTargetPreference() {\n\t\t\treturn targetPreference;\n\t\t}\n\n\t\t@Override\n\t\tpublic List<Card> mulligan(GameContext context, Player player, List<Card> cards) {\n\t\t\treturn new ArrayList<Card>();\n\t\t}\n\n\t\t@Override\n\t\tpublic GameAction requestAction(GameContext context, Player player, List<GameAction> validActions) {\n\t\t\tif (targetPreference != null) {\n\t\t\t\tfor (GameAction action : validActions) {\n\t\t\t\t\tif (action.getTargetKey().equals(targetPreference)) {\n\t\t\t\t\t\treturn action;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn validActions.get(0);\n\t\t}\n\n\t\tpublic void setTargetPreference(EntityReference targetPreference) {\n\t\t\tthis.targetPreference = targetPreference;\n\t\t}\n\n\t}\n\n\tstatic {\n\t\tLogger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);\n\t\troot.setLevel(Level.DEBUG);\n\n\t\ttry {\n\t\t\tCardCatalogue.loadLocalCards();\n\t\t} catch (IOException e) {\n\t\t\te.printStackTrace();\n\t\t} catch (URISyntaxException e) {\n\t\t\te.printStackTrace();\n\t\t} catch (CardParseException e) {\n\t\t\tSystem.err.println(e.getMessage());\n\t\t}\n\t}\n\n\tprotected static void attack(GameContext context, Player player, Entity attacker, Entity target) {\n\t\tPhysicalAttackAction physicalAttackAction = new PhysicalAttackAction(attacker.getReference());\n\t\tphysicalAttackAction.setTarget(target);\n\t\tcontext.getLogic().performGameAction(player.getId(), physicalAttackAction);\n\t}\n\n\tprotected static DebugContext createContext(HeroClass hero1, HeroClass hero2) {\n\t\tDeckFormat deckFormat = new DeckFormat();\n\t\tfor (CardSet set : CardSet.values()) {\n\t\t\tdeckFormat.addSet(set);\n\t\t}\n\t\tPlayerConfig player1Config = new PlayerConfig(DeckFactory.getRandomDeck(hero1, deckFormat), new TestBehaviour());\n\t\tplayer1Config.setName(\"Player 1\");\n\t\tplayer1Config.setHeroCard(getHeroCardForClass(hero1));\n\t\tPlayer player1 = new Player(player1Config);\n\n\t\tPlayerConfig player2Config = new PlayerConfig(DeckFactory.getRandomDeck(hero2, deckFormat), new TestBehaviour());\n\t\tplayer2Config.setName(\"Player 2\");\n\t\tplayer2Config.setHeroCard(getHeroCardForClass(hero2));\n\t\tPlayer player2 = new Player(player2Config);\n\n\t\tGameLogic logic = new GameLogic();\n\t\tDebugContext context = new DebugContext(player1, player2, logic, deckFormat);\n\t\tlogic.setContext(context);\n\t\tcontext.init();\n\t\treturn context;\n\t}\n\n\tprotected static Entity find(GameContext context, String cardId) {\n\t\tfor (Player player : context.getPlayers()) {\n\t\t\tfor (Minion minion : player.getMinions()) {\n\t\t\t\tif (minion.getSourceCard().getCardId().equals(cardId)) {\n\t\t\t\t\treturn minion;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprotected static HeroCard getHeroCardForClass(HeroClass heroClass) {\n\t\tfor (Card card : CardCatalogue.getHeroes()) {\n\t\t\tHeroCard heroCard = (HeroCard) card;\n\t\t\tif (heroCard.getHeroClass() == heroClass) {\n\t\t\t\treturn heroCard;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprotected static Actor getSingleMinion(List<Minion> minions) {\n\t\tfor (Actor minion : minions) {\n\t\t\tif (minion == null) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\treturn minion;\n\t\t}\n\t\treturn null;\n\t}\n\n\tprotected static Minion getSummonedMinion(List<Minion> minions) {\n\t\tList<Minion> minionList = new ArrayList<>(minions);\n\t\tCollections.sort(minionList, (m1, m2) -> Integer.compare(m1.getId(), m2.getId()));\n\t\treturn minionList.get(minionList.size() - 1);\n\t}\n\n\tprotected static void playCard(GameContext context, Player player, Card card) {\n\t\tcontext.getLogic().receiveCard(player.getId(), card);\n\t\tcontext.getLogic().performGameAction(player.getId(), card.play());\n\t}\n\n\tprotected static void playCardWithTarget(GameContext context, Player player, Card card, Entity target) {\n\t\tcontext.getLogic().receiveCard(player.getId(), card);\n\t\tGameAction action = card.play();\n\t\taction.setTarget(target);\n\t\tcontext.getLogic().performGameAction(player.getId(), action);\n\t}\n\n\tprotected static Minion playMinionCard(GameContext context, Player player, MinionCard minionCard) {\n\t\tcontext.getLogic().receiveCard(player.getId(), minionCard);\n\t\tcontext.getLogic().performGameAction(player.getId(), minionCard.play());\n\t\treturn getSummonedMinion(player.getMinions());\n\t}\n\n\tprotected static void target(Player player, Entity target) {\n\t\tTestBehaviour testBehaviour = (TestBehaviour) player.getBehaviour();\n\t\ttestBehaviour.setTargetPreference(target != null ? target.getReference() : null);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/TestMinionCard.java",
    "content": "package net.demilich.metastone.tests;\n\nimport java.util.EnumMap;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.cards.Rarity;\nimport net.demilich.metastone.game.cards.desc.MinionCardDesc;\nimport net.demilich.metastone.game.entities.Actor;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Minion;\n\npublic class TestMinionCard extends MinionCard {\n\n\tprivate static int id = 1;\n\n\tprivate static MinionCardDesc getDesc(int attack, int hp, Attribute... attributes) {\n\t\tMinionCardDesc desc = new MinionCardDesc();\n\t\tdesc.name = \"Test monster \" + ++id;\n\t\tdesc.rarity = Rarity.FREE;\n\t\tdesc.baseAttack = attack;\n\t\tdesc.baseHp = hp;\n\t\tdesc.type = CardType.MINION;\n\t\tdesc.heroClass = HeroClass.ANY;\n\t\tdesc.attributes = new EnumMap<Attribute, Object>(Attribute.class);\n\t\tfor (Attribute gameTag : attributes) {\n\t\t\tdesc.attributes.put(gameTag, true);\n\t\t}\n\t\treturn desc;\n\t}\n\n\tprivate final Minion minion;\n\n\tpublic TestMinionCard(int baseAttack, int baseHp, Attribute... tags) {\n\t\tsuper(getDesc(baseAttack, baseHp, tags));\n\t\tsetCollectible(false);\n\n\t\tthis.minion = createMinion();\n\t\tfor (Attribute attribute : tags) {\n\t\t\tminion.setAttribute(attribute);\n\t\t}\n\t}\n\n\tpublic TestMinionCard(int baseAttack, int baseHp, int manaCost) {\n\t\tsuper(getDesc(baseAttack, baseHp));\n\t\tsetCollectible(false);\n\t\tthis.minion = createMinion();\n\t}\n\n\t@Override\n\tpublic String getCardId() {\n\t\treturn \"minion_test\";\n\t}\n\n\tpublic Actor getMinion() {\n\t\treturn minion;\n\t}\n\n\t@Override\n\tpublic Minion summon() {\n\t\treturn minion.clone();\n\t}\n\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/TestSecretCard.java",
    "content": "package net.demilich.metastone.tests;\n\nimport java.util.EnumMap;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.cards.Rarity;\nimport net.demilich.metastone.game.cards.SecretCard;\nimport net.demilich.metastone.game.cards.desc.SecretCardDesc;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.spells.DamageSpell;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.spells.desc.trigger.EventTriggerDesc;\nimport net.demilich.metastone.game.spells.trigger.PhysicalAttackTrigger;\nimport net.demilich.metastone.game.spells.trigger.TurnEndTrigger;\nimport net.demilich.metastone.game.spells.trigger.types.Secret;\nimport net.demilich.metastone.game.targeting.EntityReference;\n\npublic class TestSecretCard extends SecretCard {\n\n\tprivate static SecretCardDesc getDesc() {\n\t\tSecretCardDesc desc = new SecretCardDesc();\n\t\tdesc.name = \"Trap\";\n\t\tdesc.rarity = Rarity.FREE;\n\t\tdesc.type = CardType.SPELL;\n\t\tdesc.heroClass = HeroClass.ANY;\n\t\tdesc.attributes = new EnumMap<Attribute, Object>(Attribute.class);\n\t\tdesc.trigger = EventTriggerDesc.createEmpty(TurnEndTrigger.class);\n\t\treturn desc;\n\t}\n\n\tpublic TestSecretCard() {\n\t\tthis(1);\n\t}\n\n\tpublic TestSecretCard(int damage) {\n\t\tsuper(getDesc());\n\t\tsetDescription(\"Secret for unit testing. Deals \" + damage + \" damage to all enemies\");\n\t\tsetCollectible(false);\n\n\t\tSpellDesc damageSpell = DamageSpell.create(EntityReference.ENEMY_CHARACTERS, damage);\n\t\tsetSecret(new Secret(new PhysicalAttackTrigger(EventTriggerDesc.createEmpty(PhysicalAttackTrigger.class)), damageSpell, this));\n\t}\n\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/TestSpellCard.java",
    "content": "package net.demilich.metastone.tests;\n\nimport java.util.EnumMap;\n\nimport net.demilich.metastone.game.Attribute;\nimport net.demilich.metastone.game.cards.CardType;\nimport net.demilich.metastone.game.cards.Rarity;\nimport net.demilich.metastone.game.cards.SpellCard;\nimport net.demilich.metastone.game.cards.desc.SpellCardDesc;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.spells.desc.SpellDesc;\nimport net.demilich.metastone.game.targeting.TargetSelection;\n\npublic class TestSpellCard extends SpellCard {\n\n\tprivate static SpellCardDesc getDesc() {\n\t\tSpellCardDesc desc = new SpellCardDesc();\n\t\tdesc.name = \"Unit Test Spell\";\n\t\tdesc.rarity = Rarity.FREE;\n\t\tdesc.type = CardType.SPELL;\n\t\tdesc.heroClass = HeroClass.ANY;\n\t\tdesc.attributes = new EnumMap<Attribute, Object>(Attribute.class);\n\t\treturn desc;\n\t}\n\n\tpublic TestSpellCard(SpellDesc spell) {\n\t\tsuper(getDesc());\n\t\tsetDescription(\"This spell can have various effects and should only be used in the context of unit net.demilich.metastone.tests.\");\n\t\tsetCollectible(false);\n\n\t\tsetSpell(spell);\n\t\tsetTargetRequirement(TargetSelection.NONE);\n\t}\n\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/TheOldGodsTests.java",
    "content": "package net.demilich.metastone.tests;\n\n\nimport net.demilich.metastone.game.GameContext;\nimport net.demilich.metastone.game.Player;\nimport net.demilich.metastone.game.cards.CardCatalogue;\nimport net.demilich.metastone.game.cards.MinionCard;\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\nimport net.demilich.metastone.game.entities.minions.Minion;\nimport org.testng.Assert;\nimport org.testng.annotations.Test;\n\npublic class TheOldGodsTests extends TestBase {\n\n\t@Test\n\tpublic void testCallInTheFinishers() {\n\t\tGameContext context = createContext(HeroClass.SHAMAN, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"spell_call_in_the_finishers\"));\n\n\t\tfor (Minion minion : player.getMinions()) {\n\t\t\tAssert.assertEquals(minion.getSourceCard().getCardId(), \"token_murloc_razorgill\");\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testDarkshireCoucilman() {\n\t\tGameContext context = createContext(HeroClass.SHAMAN, HeroClass.WARRIOR);\n\t\tPlayer player = context.getPlayer1();\n\t\tPlayer opponent = context.getOpponent(player);\n\n\t\tMinion darkshireCouncilman = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_darkshire_councilman\"));\n\t\tAssert.assertEquals(darkshireCouncilman.getAttack(), darkshireCouncilman.getBaseAttack());\n\n\t\tMinion darkshireCouncilman2 = playMinionCard(context, player, (MinionCard) CardCatalogue.getCardById(\"minion_darkshire_councilman\"));\n\t\tAssert.assertEquals(darkshireCouncilman.getAttack(), darkshireCouncilman.getBaseAttack() + 1);\n\t\tAssert.assertEquals(darkshireCouncilman2.getAttack(), darkshireCouncilman2.getBaseAttack());\n\n\t\tcontext.getLogic().endTurn(player.getId());\n\t\tMinion opponentMinion = playMinionCard(context, opponent, (MinionCard) CardCatalogue.getCardById(\"minion_darkshire_councilman\"));\n\n\t\tAssert.assertEquals(darkshireCouncilman.getAttack(), darkshireCouncilman.getBaseAttack() + 1);\n\t\tAssert.assertEquals(darkshireCouncilman2.getAttack(), darkshireCouncilman2.getBaseAttack());\n\t\tAssert.assertEquals(opponentMinion.getAttack(), opponentMinion.getBaseAttack());\n\t}\n\n}\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/WeaponTests.java",
    "content": "package net.demilich.metastone.tests;\r\n\r\nimport org.testng.Assert;\r\nimport org.testng.annotations.Test;\r\n\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.CardCatalogue;\r\nimport net.demilich.metastone.game.cards.WeaponCard;\r\nimport net.demilich.metastone.game.entities.heroes.Hero;\r\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\r\n\r\npublic class WeaponTests extends TestBase {\r\n\t\r\n\t@Test\r\n\tpublic void testWeapon() {\r\n\t\tDebugContext context = createContext(HeroClass.WARRIOR, HeroClass.WARRIOR);\r\n\t\tPlayer player = context.getPlayer1();\r\n\t\tHero warrior = player.getHero();\r\n\r\n\t\tWeaponCard weaponCard = (WeaponCard) CardCatalogue.getCardById(\"weapon_battle_axe\");\r\n\r\n\t\tcontext.setActivePlayer(player.getId());\r\n\t\tcontext.getLogic().startTurn(player.getId());\r\n\t\tAssert.assertEquals(warrior.getAttack(), 0);\r\n\t\tcontext.getLogic().receiveCard(player.getId(), weaponCard);\r\n\t\tcontext.getLogic().performGameAction(player.getId(), weaponCard.play());\r\n\t\tAssert.assertEquals(warrior.getAttack(), 2);\r\n\t\tAssert.assertEquals(warrior.getWeapon().getDurability(), 2);\r\n\r\n\t\tattack(context, player, warrior, context.getPlayer2().getHero());\r\n\t\tAssert.assertEquals(warrior.getWeapon().getDurability(), 1);\r\n\t}\r\n\t\r\n\t@Test\r\n\tpublic void testKingsDefenderAttendee() {\r\n\t\tDebugContext context = createContext(HeroClass.WARRIOR, HeroClass.WARRIOR);\r\n\t\tPlayer player = context.getPlayer1();\r\n\t\tHero hero = player.getHero();\r\n\t\t\r\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"weapon_deaths_bite\"));\r\n\t\tAssert.assertEquals(hero.getWeapon().getAttack(), 4);\r\n\t\tAssert.assertEquals(hero.getWeapon().getDurability(), 2);\r\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"minion_tournament_attendee\"));\r\n\t\tAssert.assertEquals(player.getMinions().size(), 1);\r\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"weapon_kings_defender\"));\r\n\t\tAssert.assertEquals(hero.getWeapon().getAttack(), 3);\r\n\t\tAssert.assertEquals(hero.getWeapon().getDurability(), 3);\r\n\t\tAssert.assertEquals(player.getMinions().size(), 0);\r\n\t}\r\n\t\r\n\t@Test\r\n\tpublic void testKingsDefenderHogger() {\r\n\t\tDebugContext context = createContext(HeroClass.WARRIOR, HeroClass.WARRIOR);\r\n\t\tPlayer player = context.getPlayer1();\r\n\t\tHero hero = player.getHero();\r\n\t\t\r\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"weapon_deaths_bite\"));\r\n\t\tAssert.assertEquals(hero.getWeapon().getAttack(), 4);\r\n\t\tAssert.assertEquals(hero.getWeapon().getDurability(), 2);\r\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"minion_hogger_doom_of_elwynn\"));\r\n\t\tAssert.assertEquals(player.getMinions().size(), 1);\r\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"weapon_kings_defender\"));\r\n\t\tAssert.assertEquals(hero.getWeapon().getAttack(), 3);\r\n\t\tAssert.assertEquals(hero.getWeapon().getDurability(), 2);\r\n\t\tAssert.assertEquals(player.getMinions().size(), 2);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/allcards/ClassicMageCards.java",
    "content": "package net.demilich.metastone.tests.allcards;\r\n\r\nimport org.testng.Assert;\r\nimport org.testng.annotations.Test;\r\n\r\nimport net.demilich.metastone.game.Attribute;\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.CardCatalogue;\r\nimport net.demilich.metastone.game.cards.MinionCard;\r\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\r\nimport net.demilich.metastone.game.entities.minions.Minion;\r\nimport net.demilich.metastone.tests.TestBase;\r\n\r\npublic class ClassicMageCards extends TestBase {\r\n\t\r\n\t@Test\r\n\tpublic void testBlizzard() {\r\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARLOCK);\r\n\t\tPlayer player = context.getPlayer1();\r\n\t\tPlayer opponent = context.getPlayer2();\r\n\t\t\r\n\t\tcontext.endTurn();\r\n\t\tMinion impGangBoss = playMinionCard(context, opponent, (MinionCard) CardCatalogue.getCardById(\"minion_imp_gang_boss\"));\r\n\t\tcontext.endTurn();\r\n\t\t\r\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"spell_blizzard\"));\r\n\t\t\r\n\t\tAssert.assertEquals(impGangBoss.getHp(), impGangBoss.getMaxHp() - 2);\r\n\t\tfor (Minion minion : opponent.getMinions()) {\r\n\t\t\tAssert.assertEquals(minion.hasAttribute(Attribute.FROZEN), true);\t\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "game/src/test/java/net/demilich/metastone/tests/allcards/ClassicNeutralCards.java",
    "content": "package net.demilich.metastone.tests.allcards;\r\n\r\nimport org.testng.Assert;\r\nimport org.testng.annotations.Test;\r\n\r\nimport net.demilich.metastone.game.GameContext;\r\nimport net.demilich.metastone.game.Player;\r\nimport net.demilich.metastone.game.cards.Card;\r\nimport net.demilich.metastone.game.cards.CardCatalogue;\r\nimport net.demilich.metastone.game.cards.MinionCard;\r\nimport net.demilich.metastone.game.entities.heroes.HeroClass;\r\nimport net.demilich.metastone.game.logic.GameLogic;\r\nimport net.demilich.metastone.tests.TestBase;\r\n\r\npublic class ClassicNeutralCards extends TestBase {\r\n\r\n\t@Test\r\n\tpublic void testElvenArcher() {\r\n\t\tGameContext context = createContext(HeroClass.WARRIOR, HeroClass.SHAMAN);\r\n\t\tPlayer player = context.getPlayer1();\r\n\t\tPlayer opponent = context.getPlayer2();\r\n\r\n\t\tAssert.assertEquals(opponent.getHero().getHp(), GameLogic.MAX_HERO_HP);\r\n\t\tMinionCard elvenArcherCard = (MinionCard) CardCatalogue.getCardById(\"minion_elven_archer\");\r\n\t\tplayCardWithTarget(context, player, elvenArcherCard, opponent.getHero());\r\n\t\tAssert.assertEquals(opponent.getHero().getHp(), GameLogic.MAX_HERO_HP - 1);\r\n\t}\r\n\r\n\t@Test\r\n\tpublic void testNoviceEngineer() {\r\n\t\tGameContext context = createContext(HeroClass.WARRIOR, HeroClass.SHAMAN);\r\n\t\tPlayer player = context.getPlayer1();\r\n\r\n\t\tint cardCount = player.getHand().getCount();\r\n\t\tAssert.assertEquals(player.getHand().getCount(), cardCount);\r\n\t\tCard noviceEngineerCard = CardCatalogue.getCardById(\"minion_novice_engineer\");\r\n\t\tplayCard(context, player, noviceEngineerCard);\r\n\t\tAssert.assertEquals(player.getHand().getCount(), cardCount + 1);\r\n\t}\r\n\r\n\t@Test\r\n\tpublic void testKoboldGeomancer() {\r\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\r\n\t\tPlayer player = context.getPlayer1();\r\n\t\tPlayer opponent = context.getPlayer2();\r\n\r\n\t\tAssert.assertEquals(opponent.getHero().getHp(), GameLogic.MAX_HERO_HP);\r\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"spell_arcane_missiles\"));\r\n\t\tAssert.assertEquals(opponent.getHero().getHp(), GameLogic.MAX_HERO_HP - 3);\r\n\t\tplayCardWithTarget(context, player, CardCatalogue.getCardById(\"spell_fireball\"), opponent.getHero());\r\n\t\tAssert.assertEquals(opponent.getHero().getHp(), GameLogic.MAX_HERO_HP - 3 - 6);\r\n\r\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"minion_kobold_geomancer\"));\r\n\r\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"spell_arcane_missiles\"));\r\n\t\tAssert.assertEquals(opponent.getHero().getHp(), GameLogic.MAX_HERO_HP - 3 - 6 - 4);\r\n\t\tplayCardWithTarget(context, player, CardCatalogue.getCardById(\"spell_fireball\"), opponent.getHero());\r\n\t\tAssert.assertEquals(opponent.getHero().getHp(), GameLogic.MAX_HERO_HP - 3 - 6 - 4 - 7);\r\n\t}\r\n\r\n\t@Test\r\n\tpublic void testAcidicSwampOoze() {\r\n\t\tGameContext context = createContext(HeroClass.MAGE, HeroClass.WARRIOR);\r\n\t\tPlayer player = context.getPlayer1();\r\n\t\tPlayer opponent = context.getPlayer2();\r\n\r\n\t\tcontext.endTurn();\r\n\t\tplayCard(context, opponent, CardCatalogue.getCardById(\"weapon_fiery_war_axe\"));\r\n\t\tAssert.assertNotNull(opponent.getHero().getWeapon());\r\n\t\tcontext.endTurn();\r\n\r\n\t\tplayCard(context, player, CardCatalogue.getCardById(\"minion_acidic_swamp_ooze\"));\r\n\t\tAssert.assertNull(opponent.getHero().getWeapon());\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Sun Apr 17 04:40:36 PDT 2016\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-2.9-all.zip\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "settings.gradle",
    "content": "rootProject.name = 'metastone'\ninclude 'shared', 'game', 'cards', 'app'"
  },
  {
    "path": "shared/build.gradle",
    "content": "buildscript {\r\n\trepositories {\r\n\t\tmaven {\r\n\t\t\turl 'https://plugins.gradle.org/m2/'\r\n\t\t}\r\n\t}\r\n\tdependencies {\r\n\t\tclasspath 'gradle.plugin.de.fuerstenau:BuildConfigPlugin:1.1.4'\r\n\t}\r\n}\r\n\r\napply plugin: 'de.fuerstenau.buildconfig'\r\n\r\n// will generate a BuildConfig.java file in 'build/gen/buildconfig/src/main'\r\nbuildConfig {\r\n\tpackageName = 'net.demilich.metastone'\r\n\tversion = project.version\r\n\tappName = rootProject.name\r\n\tbuildConfigField 'int', 'CARD_REVISION', '15'\r\n\tbuildConfigField 'boolean', 'DEV_BUILD', project.hasProperty('DEV_BUILD').toString()\r\n\tbuildConfigField 'int', 'DEFAULT_SLEEP_DELAY', '100'\r\n}\r\n\r\next {\r\n\tbuildConfigSrcDir = file(\"$buildDir/gen/buildconfig/src/main\")\r\n}\r\n\r\nsourceSets.main.java {\r\n\tsrcDir buildConfigSrcDir\r\n}\r\n\r\ndependencies {\r\n\tcompile files('lib/nitty-gritty-mvc.jar')\r\n\tcompile group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.3'\r\n}\r\n\r\ntask compileBuildConfigFirst (dependsOn: ['compileBuildConfig'])\r\neclipseProject.dependsOn(compileBuildConfigFirst)\r\nideaModule.dependsOn(compileBuildConfigFirst)\r\n\r\nidea.module {\r\n\tgeneratedSourceDirs += file(buildConfigSrcDir)\r\n}\r\n"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/GameNotification.java",
    "content": "package net.demilich.metastone;\n\npublic enum GameNotification {\n\tAPPLICATION_STARTUP,\n\tCANVAS_CREATED,\n\tSHOW_VIEW,\n\tCHECK_FOR_UPDATE,\n\tNEW_VERSION_AVAILABLE,\n\t\n\tCARD_PARSE_ERROR,\n\n\tSTART_GAME,\n\tPLAY_GAME,\n\tGAME_STATE_UPDATE,\n\tGAME_STATE_LATE_UPDATE,\n\tGAME_ACTION_PERFORMED,\n\tGAME_OVER,\n\n\tSIMULATE_GAMES,\n\tSIMULATION_PROGRESS_UPDATE,\n\tSIMULATION_RESULT,\n\n\tHUMAN_PROMPT_FOR_MULLIGAN,\n\tHUMAN_PROMPT_FOR_ACTION,\n\tHUMAN_PROMPT_FOR_TARGET,\n\n\tSHOW_MODAL_DIALOG,\n\tSHOW_USER_DIALOG,\n\n\tMAIN_MENU,\n\tPLAY_MODE_SELECTED,\n\tDECK_BUILDER_SELECTED,\n\tSIMULATION_MODE_SELECTED,\n\tSANDBOX_MODE_SELECTED,\n\tTRAINING_MODE_SELECTED,\n\tBATTLE_OF_DECKS_SELECTED,\n\n\tTRAINING_PROGRESS_UPDATE,\n\tSTART_TRAINING,\n\n\tBATTLE_OF_DECKS_PROGRESS_UPDATE,\n\n\tREQUEST_DECKS,\n\tREQUEST_DECK_FORMATS,\n\tREPLY_DECKS,\n\tREPLY_DECK_FORMATS,\n\tCOMMIT_PLAYMODE_CONFIG,\n\tCOMMIT_SIMULATIONMODE_CONFIG,\n\tCOMMIT_TRAININGMODE_CONFIG,\n\tCOMMIT_SANDBOXMODE_CONFIG,\n\tCOMMIT_BATTLE_OF_DECKS_CONFIG,\n\n\tLOAD_DECKS,\n\tLOAD_DECK_FORMATS,\n\tDECKS_LOADED,\n\tDECK_FORMATS_LOADED,\n\tCREATE_NEW_DECK,\n\tSET_ACTIVE_DECK,\n\tEDIT_DECK,\n\tCHANGE_DECK_NAME,\n\tACTIVE_DECK_CHANGED,\n\tFILTERED_CARDS,\n\tFILTER_CARDS,\n\tADD_CARD_TO_DECK,\n\tREMOVE_CARD_FROM_DECK,\n\tFILL_DECK_WITH_RANDOM_CARDS,\n\tSAVE_ACTIVE_DECK,\n\tIMPORT_DECK_FROM_URL,\n\tINVALID_DECK_NAME,\n\tDUPLICATE_DECK_NAME,\n\tADD_DECK_TO_META_DECK,\n\tREMOVE_DECK_FROM_META_DECK,\n\tDELETE_DECK,\n\n\t// sandbox notifications\n\tSELECT_PLAYER,\n\tSELECT_TARGET,\n\tMODIFY_PLAYER_HAND,\n\tMODIFY_PLAYER_DECK,\n\tSPAWN_MINION,\n\tPERFORM_ACTION,\n\tUPDATE_SANDBOX_STATE,\n\tCREATE_NEW_SANDBOX,\n\tSTART_PLAY_SANDBOX,\n\tSTOP_PLAY_SANDBOX,\n\n\tANIMATION_STARTED,\n\tANIMATION_COMPLETED,\n\n\tSAVE_TRAINING_DATA,\n\tREQUEST_TRAINING_DATA,\n}\n"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/NotificationProxy.java",
    "content": "package net.demilich.metastone;\n\nimport net.demilich.nittygrittymvc.interfaces.INotification;\nimport net.demilich.nittygrittymvc.interfaces.INotifier;\n\n\npublic class NotificationProxy {\n\n    private static INotifier<GameNotification> SUBJECT;\n\n    public static void init(INotifier<GameNotification> subject) {\n        SUBJECT = subject;\n    }\n\n    public static void notifyObservers(INotification<GameNotification> notification) {\n        if (SUBJECT == null) {\n            throw new RuntimeException(\"NotificationProxy must first be initialized!\");\n        }\n        SUBJECT.notifyObservers(notification);\n    }\n\n    public static void sendNotification(GameNotification notification) {\n        if (SUBJECT == null) {\n            throw new RuntimeException(\"NotificationProxy must first be initialized!\");\n        }\n        SUBJECT.sendNotification(notification);\n    }\n\n    public static void sendNotification(GameNotification notification, Object data) {\n        if (SUBJECT == null) {\n            throw new RuntimeException(\"NotificationProxy must first be initialized!\");\n        }\n        SUBJECT.sendNotification(notification, data);\n    }\n}"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/game/behaviour/threat/FeatureVector.java",
    "content": "package net.demilich.metastone.game.behaviour.threat;\n\nimport java.util.EnumMap;\nimport java.util.Map;\n\npublic class FeatureVector implements Cloneable {\n\n\tpublic static FeatureVector getDefault() {\n\t\tFeatureVector defaultVector = new FeatureVector();\n\t\tdefaultVector.set(WeightedFeature.RED_MODIFIER, -50);\n\t\tdefaultVector.set(WeightedFeature.YELLOW_MODIFIER, -10);\n\t\tdefaultVector.set(WeightedFeature.OWN_HP_FACTOR, 1);\n\t\tdefaultVector.set(WeightedFeature.OPPONENT_HP_FACTOR, -1);\n\t\tdefaultVector.set(WeightedFeature.OWN_CARD_COUNT, 3);\n\t\tdefaultVector.set(WeightedFeature.OPPONENT_CARD_COUNT, -3);\n\t\tdefaultVector.set(WeightedFeature.MINION_INTRINSIC_VALUE, 1);\n\t\tdefaultVector.set(WeightedFeature.MINION_ATTACK_FACTOR, 1);\n\t\tdefaultVector.set(WeightedFeature.MINION_HP_FACTOR, 1);\n\t\tdefaultVector.set(WeightedFeature.MINION_RED_TAUNT_MODIFIER, 8);\n\t\tdefaultVector.set(WeightedFeature.MINION_YELLOW_TAUNT_MODIFIER, 4);\n\t\tdefaultVector.set(WeightedFeature.MINION_DEFAULT_TAUNT_MODIFIER, 2);\n\t\tdefaultVector.set(WeightedFeature.MINION_WINDFURY_MODIFIER, 1.5);\n\t\tdefaultVector.set(WeightedFeature.MINION_DIVINE_SHIELD_MODIFIER, 1.5);\n\t\tdefaultVector.set(WeightedFeature.MINION_SPELL_POWER_MODIFIER, 1);\n\t\tdefaultVector.set(WeightedFeature.MINION_STEALTHED_MODIFIER, 1);\n\t\tdefaultVector.set(WeightedFeature.MINION_UNTARGETABLE_BY_SPELLS_MODIFIER, 1.5);\n\t\tdefaultVector.set(WeightedFeature.HARD_REMOVAL_VALUE, 2);\n\t\treturn defaultVector;\n\t}\n\n\tpublic static FeatureVector getFittest() {\n\t\tFeatureVector defaultVector = new FeatureVector();\n\t\tdefaultVector.set(WeightedFeature.RED_MODIFIER, -43);\n\t\tdefaultVector.set(WeightedFeature.YELLOW_MODIFIER, -17);\n\t\tdefaultVector.set(WeightedFeature.OWN_HP_FACTOR, 0.214);\n\t\tdefaultVector.set(WeightedFeature.OPPONENT_HP_FACTOR, -1.115);\n\t\tdefaultVector.set(WeightedFeature.OWN_CARD_COUNT, 3.572);\n\t\tdefaultVector.set(WeightedFeature.OPPONENT_CARD_COUNT, 0);\n\t\tdefaultVector.set(WeightedFeature.MINION_INTRINSIC_VALUE, 1.181);\n\t\tdefaultVector.set(WeightedFeature.MINION_ATTACK_FACTOR, 2.419);\n\t\tdefaultVector.set(WeightedFeature.MINION_HP_FACTOR, 3);\n\t\tdefaultVector.set(WeightedFeature.MINION_RED_TAUNT_MODIFIER, 10.1);\n\t\tdefaultVector.set(WeightedFeature.MINION_YELLOW_TAUNT_MODIFIER, 7.1);\n\t\tdefaultVector.set(WeightedFeature.MINION_DEFAULT_TAUNT_MODIFIER, 0.671);\n\t\tdefaultVector.set(WeightedFeature.MINION_WINDFURY_MODIFIER, 15.71);\n\t\tdefaultVector.set(WeightedFeature.MINION_DIVINE_SHIELD_MODIFIER, 6.1);\n\t\tdefaultVector.set(WeightedFeature.MINION_SPELL_POWER_MODIFIER, 3.841);\n\t\tdefaultVector.set(WeightedFeature.MINION_STEALTHED_MODIFIER, 1.281);\n\t\tdefaultVector.set(WeightedFeature.MINION_UNTARGETABLE_BY_SPELLS_MODIFIER, 0);\n\t\tdefaultVector.set(WeightedFeature.HARD_REMOVAL_VALUE, 2);\n\t\treturn defaultVector;\n\t}\n\n\tprivate final Map<WeightedFeature, Double> values = new EnumMap<WeightedFeature, Double>(WeightedFeature.class);\n\n\tpublic FeatureVector() {\n\t\tfor (WeightedFeature feature : WeightedFeature.values()) {\n\t\t\tset(feature, 0);\n\t\t}\n\t}\n\n\t@Override\n\tpublic FeatureVector clone() {\n\t\tFeatureVector clone = new FeatureVector();\n\t\tfor (WeightedFeature feature : getValues().keySet()) {\n\t\t\tclone.set(feature, get(feature));\n\t\t}\n\t\treturn clone;\n\t}\n\n\tpublic double get(WeightedFeature param) {\n\t\treturn values.get(param);\n\t}\n\n\tpublic Map<WeightedFeature, Double> getValues() {\n\t\treturn values;\n\t}\n\n\tpublic void set(WeightedFeature param, double value) {\n\t\tgetValues().put(param, value);\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\tStringBuilder builder = new StringBuilder(\"[FeatureVector] Values:\\n\");\n\t\tfor (WeightedFeature feature : getValues().keySet()) {\n\t\t\tbuilder.append(\"\\t\");\n\t\t\tbuilder.append(feature.toString());\n\t\t\tbuilder.append(\": \");\n\t\t\tbuilder.append(String.valueOf(getValues().get(feature)));\n\t\t\tbuilder.append(\"\\n\");\n\t\t}\n\t\treturn builder.toString();\n\t}\n\n}\n"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/game/behaviour/threat/WeightedFeature.java",
    "content": "package net.demilich.metastone.game.behaviour.threat;\n\npublic enum WeightedFeature {\n\tRED_MODIFIER, YELLOW_MODIFIER, OWN_HP_FACTOR, OPPONENT_HP_FACTOR, OWN_CARD_COUNT, OPPONENT_CARD_COUNT, MINION_INTRINSIC_VALUE, MINION_ATTACK_FACTOR, MINION_HP_FACTOR, MINION_RED_TAUNT_MODIFIER, MINION_YELLOW_TAUNT_MODIFIER, MINION_DEFAULT_TAUNT_MODIFIER, MINION_WINDFURY_MODIFIER, MINION_DIVINE_SHIELD_MODIFIER, MINION_SPELL_POWER_MODIFIER, MINION_STEALTHED_MODIFIER, MINION_UNTARGETABLE_BY_SPELLS_MODIFIER, HARD_REMOVAL_VALUE\n}\n"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/trainingmode/ITrainingDataListener.java",
    "content": "package net.demilich.metastone.trainingmode;\n\nimport net.demilich.metastone.trainingmode.TrainingData;\n\npublic interface ITrainingDataListener {\n\n\tvoid answerTrainingData(TrainingData trainingData);\n}\n"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/trainingmode/RequestTrainingDataNotification.java",
    "content": "package net.demilich.metastone.trainingmode;\n\nimport net.demilich.nittygrittymvc.Notification;\nimport net.demilich.metastone.GameNotification;\n\npublic class RequestTrainingDataNotification extends Notification<GameNotification> {\n\n\tprivate final String deckName;\n\tprivate final ITrainingDataListener listener;\n\n\tpublic RequestTrainingDataNotification(String deckName, ITrainingDataListener listener) {\n\t\tsuper(GameNotification.REQUEST_TRAINING_DATA);\n\t\tthis.deckName = deckName;\n\t\tthis.listener = listener;\n\t}\n\n\tpublic String getDeckName() {\n\t\treturn deckName;\n\t}\n\n\tpublic ITrainingDataListener getListener() {\n\t\treturn listener;\n\t}\n\n}\n"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/trainingmode/TrainingData.java",
    "content": "package net.demilich.metastone.trainingmode;\n\nimport net.demilich.metastone.game.behaviour.threat.FeatureVector;\n\npublic class TrainingData {\n\n\tprivate final String deckName;\n\tprivate final FeatureVector featureVector;\n\n\tpublic TrainingData(String deckName, FeatureVector featureVector) {\n\t\tthis.deckName = deckName;\n\t\tthis.featureVector = featureVector;\n\t}\n\n\tpublic String getDeckName() {\n\t\treturn deckName;\n\t}\n\n\tpublic FeatureVector getFeatureVector() {\n\t\treturn featureVector;\n\t}\n\n}\n"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/utils/ICallback.java",
    "content": "package net.demilich.metastone.utils;\n\npublic interface ICallback {\n\n\tvoid call(Object result);\n\n}\n"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/utils/IDisposable.java",
    "content": "package net.demilich.metastone.utils;\n\npublic interface IDisposable {\n\n\tpublic void dispose();\n\n}\n"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/utils/MathUtils.java",
    "content": "package net.demilich.metastone.utils;\n\npublic class MathUtils {\n\n\tpublic static double clamp(double value, double min, double max) {\n\t\tif (value < min) {\n\t\t\treturn min;\n\t\t} else if (value > max) {\n\t\t\treturn max;\n\t\t}\n\t\treturn value;\n\t}\n\n\tpublic static int clamp(int value, int min, int max) {\n\t\tif (value < min) {\n\t\t\treturn min;\n\t\t} else if (value > max) {\n\t\t\treturn max;\n\t\t}\n\t\treturn value;\n\t}\n\n\tpublic static double clamp01(double value) {\n\t\treturn clamp(value, 0.0, 1.0);\n\t}\n\n\tpublic static double levy(double xMin, double alpha) {\n\t\tdouble u = Math.random();\n\t\treturn xMin * Math.pow(u, -1 / alpha);\n\t}\n\n\tprivate MathUtils() {\n\t}\n\n}\n"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/utils/MetastoneProperties.java",
    "content": "package net.demilich.metastone.utils;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.util.Properties;\n\n/**\n * Utility class to read and write from the Metastone properties file.\n */\npublic class MetastoneProperties {\n    private static final String METASTONE_PROPERTIES_FILE = UserHomeMetastone.getPath() + File.separator + \"metastone.properties\";\n    private static MetastoneProperties INSTANCE;\n    private static Logger logger = LoggerFactory.getLogger(MetastoneProperties.class);\n    private final File propertiesFile;\n    private final Properties prop = new Properties();\n\n    private  MetastoneProperties(String propertiesFilePath) throws IOException {\n        propertiesFile = new File(propertiesFilePath);\n        if (!propertiesFile.exists()) {\n            // ensure that the metastone.properties file exists\n            try {\n                propertiesFile.createNewFile();\n            } catch (IOException e) {\n                logger.error(\"Could not create property file: \" +  propertiesFilePath);\n                e.printStackTrace();\n                throw e;\n            }\n        }\n    }\n\n    /**\n     * Searches for the property with the specified key within the Metastone properties file.\n     * If the key is not found in the property file, the method returns {@code null}.\n     *\n     * @param   key   the property key.\n     * @return  the value in the Metastone properties file with the specified key value.\n     * @throws  IOException if the Metastone properties file cannot be loaded.\n     */\n    public static String getProperty(String key) throws IOException {\n        if (INSTANCE == null) {\n            INSTANCE = new MetastoneProperties(METASTONE_PROPERTIES_FILE);\n        }\n        // ensure we have the latest properties values loaded\n        INSTANCE.load();\n        return INSTANCE.prop.getProperty(key);\n    }\n\n    /**\n     * Convenience method to parse the value at the given key to a boolean value\n     * by calling Boolean.parseBoolean(getProperty(key))\n     *\n     * @param key the property key\n     * @return the boolean value in the Metastone properties file with the specified key.\n     * @throws IOException if the Metastone properties file cannot be loaded.\n     */\n    public static boolean getBoolean(String key) throws IOException {\n        return Boolean.parseBoolean(getProperty(key));\n    }\n    \n    /**\n     * Convenience method to parse the value at the given key to a int value\n     * by calling Integer.parseInt(getProperty(key))\n     *\n     * @param key the property key\n     * @param defaultValue the value which is returned when the key is not present\n     * @return the int value in the Metastone properties file with the specified key.\n     * @throws IOException if the Metastone properties file cannot be loaded.\n     */\n    public static int getInt(String key, int defaultValue) throws IOException {\n    \tString propertyValue = getProperty(key);\n    \tif (propertyValue == null) {\n    \t\treturn defaultValue;\n    \t}\n    \treturn Integer.parseInt(propertyValue);\n    }\n    \n    /**\n     * Set the value for the given property key.\n     *\n     * @param key the key to be placed into this property list.\n     * @param value the value corresponding to <tt>key</tt>.\n     * @return     the previous value of the specified key in the Metastone properties file\n     *             or {@code null} if it did not have one.\n     * @throws  IOException if the Metastone properties file cannot be written to.\n     */\n    public static synchronized Object setProperty(String key, String value) throws IOException {\n        if (INSTANCE == null) {\n            INSTANCE = new MetastoneProperties(METASTONE_PROPERTIES_FILE);\n        }\n\n        // ensure we have the latest properties values loaded\n        INSTANCE.load();\n        // set the new properties value for the given key\n        Object previousValue = INSTANCE.prop.setProperty(key, value);\n        // write out the properties file to disk\n        INSTANCE.store();\n\n        return previousValue;\n    }\n\n    /**\n     * Convenience method to set the boolean value for the given property key.\n     *\n     * @param key the key to be placed into this property list.\n     * @param value the boolean value corresponding to <tt>key</tt>.\n     * @return     the previous value of the specified key in the Metastone properties file\n     *             or {@code null} if it did not have one.\n     * @throws  IOException if the Metastone properties file cannot be written to.\n     */\n    public static Object setBoolean(String key, boolean value) throws IOException {\n        return setProperty(key, Boolean.valueOf(value).toString());\n    }\n\n    private void load() throws IOException {\n        FileInputStream input = new FileInputStream(propertiesFile);\n\n        // load properties file\n        prop.load(input);\n        input.close();\n    }\n\n    private void store() throws IOException {\n        FileOutputStream output = new FileOutputStream(propertiesFile);\n\n        // write properties file\n        prop.store(output, null);\n        output.close();\n    }\n}\n"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/utils/ResourceInputStream.java",
    "content": "package net.demilich.metastone.utils;\n\nimport java.io.InputStream;\n\n/**\n * Data object that holds a filename, inputstream and a boolean flag to indicate\n * if the file is on the filesystem or in the applications resource bundled dir.\n */\npublic class ResourceInputStream {\n\n    public final String fileName;\n    public final InputStream inputStream;\n    public final boolean fromFilesystem;\n\n    public ResourceInputStream(String fileName, InputStream inputStream, boolean filesytem) {\n        this.fileName = fileName;\n        this.inputStream = inputStream;\n        this.fromFilesystem = filesytem;\n    }\n}"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/utils/ResourceLoader.java",
    "content": "package net.demilich.metastone.utils;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.net.URLClassLoader;\nimport java.nio.file.FileSystem;\nimport java.nio.file.FileSystemNotFoundException;\nimport java.nio.file.FileSystems;\nimport java.nio.file.FileVisitResult;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.SimpleFileVisitor;\nimport java.nio.file.StandardCopyOption;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.stream.Stream;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class ResourceLoader {\n\n\t// the number of dirs levels to traverse on the given path\n\tprivate static final int DIR_LEVELS = 5;\n\n\tprivate static Logger logger = LoggerFactory.getLogger(ResourceLoader.class);\n\n\t/**\n\t * Loads all the json files from the given rootDir into a collection of\n\t * ResourceInputStreams\n\t * \n\t * @param rootDir\n\t *            the root dir from where to start traversing to load the json\n\t *            files\n\t * @param fromFileSystem\n\t *            True if the rootDir is on the filesystem, False if the rootDir\n\t *            is in the Resources dir\n\t * @return Collections of ResourceInputStreams pointing to the json files\n\t * @throws URISyntaxException\n\t * @throws IOException\n\t */\n\tpublic static Collection<ResourceInputStream> loadJsonInputStreams(String rootDir, boolean fromFileSystem)\n\t\t\tthrows URISyntaxException, IOException {\n\t\tif (rootDir == null) {\n\t\t\tthrow new RuntimeException(\"rootDir cannot be null\");\n\t\t}\n\n\t\tPathReference pathReference;\n\t\tif (fromFileSystem) {\n\t\t\tpathReference = new PathReference(Paths.get(rootDir), false);\n\t\t} else { // from resources\n\t\t\tpathReference = getPathFromResources(rootDir);\n\t\t}\n\n\t\tCollection<ResourceInputStream> inputStreams = new ArrayList<>();\n\n\t\tPath filePath;\n\t\tStream<Path> walk = Files.walk(pathReference.path, DIR_LEVELS);\n\t\tfor (Iterator<Path> it = walk.iterator(); it.hasNext();) {\n\t\t\tfilePath = it.next();\n\n\t\t\t// skip over non-json files and directories\n\t\t\tif (!filePath.toString().endsWith(\"json\"))\n\t\t\t\tcontinue;\n\n\t\t\tInputStream inputStream;\n\t\t\tif (pathReference.fromJar) {\n\t\t\t\tinputStream = Object.class.getResourceAsStream(filePath.toString());\n\t\t\t} else {\n\t\t\t\tinputStream = new FileInputStream(new File(filePath.toString()));\n\t\t\t}\n\n\t\t\tinputStreams.add(new ResourceInputStream(filePath.getFileName().toString(), inputStream, fromFileSystem));\n\t\t}\n\t\twalk.close();\n\t\treturn inputStreams;\n\t}\n\n\t/**\n\t * Utility method to get a PathReference from a given sourceDir that's in\n\t * the Resources dir or a Jar file.\n\t * \n\t * @param sourceDir\n\t *            the dir of interest in the Resources dir or Jar file\n\t * @return a PathReference which contains a Path and boolean indicating the\n\t *         path is in a Jar fle.\n\t * @throws URISyntaxException\n\t * @throws IOException\n\t */\n\tprivate static PathReference getPathFromResources(String sourceDir) throws URISyntaxException, IOException {\n\t\tURI uri;\n\t\ttry {\n\t\t\turi = Object.class.getResource(\"/\" + sourceDir).toURI();\n\t\t} catch (NullPointerException ex) {\n\t\t\tlogger.error(sourceDir + \" directory not found in resources\");\n\t\t\tthrow new RuntimeException(sourceDir + \" directory not found in resources\");\n\t\t}\n\n\t\t// handle case where resources are on the filesystem instead of jar. ie:\n\t\t// running form within IntelliJ\n\t\tboolean fromJar = uri.getScheme().equals(\"jar\");\n\t\tPath path;\n\t\tFileSystem fileSystem;\n\t\tif (fromJar) { // from jar file on the classpath\n\t\t\ttry {\n\t\t\t\tfileSystem = FileSystems.getFileSystem(uri);\n\t\t\t} catch (FileSystemNotFoundException ex) {\n\t\t\t\tfileSystem = FileSystems.newFileSystem(uri, Collections.<String, Object> emptyMap());\n\t\t\t}\n\t\t\tpath = fileSystem.getPath(sourceDir);\n\t\t} else { // from resources folder on the filesystem\n\t\t\tpath = Paths.get(uri);\n\t\t}\n\n\t\treturn new PathReference(path, fromJar);\n\t}\n\n\t/**\n\t * Copy all files from the Resources sourceDir subfolder to the targetDir on\n\t * the filesystem.\n\t * \n\t * @param sourceDir\n\t *            path to dir who's contents to copy\n\t * @param targetdir\n\t *            path to dir where we want to copy the files to\n\t * @throws URISyntaxException\n\t * @throws IOException\n\t */\n\tpublic static void copyFromResources(final String sourceDir, final String targetdir) throws URISyntaxException, IOException {\n\n\t\tClassLoader cl = ClassLoader.getSystemClassLoader();\n\t\tURL[] urls = ((URLClassLoader) cl).getURLs();\n\t\tURL cardsUrl = null;\n\t\tString jarFileName = null;\n\t\tfor (URL url : urls) {\n\t\t\tjarFileName = new File(url.toURI()).getName();\n\t\t\tif (jarFileName.startsWith(\"cards\")) {\n\t\t\t\tcardsUrl = url;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tfinal String cardsJarFile = jarFileName;\n\t\tfinal PathReference sourcePathReference = getPathFromResources(sourceDir);\n\t\tfinal Path targetDirPath = Paths.get(targetdir);\n\n\t\tlogger.info(\"Copying resources from \" + cardsUrl + \" to \" + targetDirPath);\n\n\t\tFiles.walkFileTree(sourcePathReference.path, new SimpleFileVisitor<Path>() {\n\n\t\t\t@Override\n\t\t\tpublic FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {\n\t\t\t\t//Path currentTargetDir = Paths.get(targetDirPath.toString() + File.separator + dir.getFileName());\n\t\t\t\tString relativePath = dir.toString().replace(sourcePathReference.path.toString(), \"\");\n\t\t\t\tPath currentTargetDir = Paths.get(targetDirPath + relativePath);\n\t\t\t\tFiles.createDirectories(currentTargetDir);\n\t\t\t\treturn FileVisitResult.CONTINUE;\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {\n\t\t\t\tString relativePath = file.toString().replace(sourcePathReference.path.toString(), \"\");\n\t\t\t\tPath currentTargetFile = Paths.get(targetDirPath + relativePath);\n\n\t\t\t\tlogger.info(cardsJarFile + \"!\" + file + \"  -->  \" + currentTargetFile);\n\t\t\t\tFiles.copy(file, currentTargetFile, StandardCopyOption.REPLACE_EXISTING);\n\t\t\t\treturn FileVisitResult.CONTINUE;\n\t\t\t}\n\n\t\t});\n\t}\n\n\t/**\n\t * Data tuple which holds a path and boolean flag indicating that the path\n\t * is from a jar resource file.\n\t */\n\tprivate static class PathReference {\n\t\tfinal Path path;\n\t\tfinal boolean fromJar;\n\n\t\tpublic PathReference(Path path, boolean fromJar) {\n\t\t\tthis.path = path;\n\t\t\tthis.fromJar = fromJar;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/utils/Tuple.java",
    "content": "package net.demilich.metastone.utils;\n\npublic class Tuple<T1, T2> {\n\n\tprivate final T1 first;\n\tprivate final T2 second;\n\n\tpublic Tuple(T1 first, T2 second) {\n\t\tthis.first = first;\n\t\tthis.second = second;\n\t}\n\n\tpublic T1 getFirst() {\n\t\treturn first;\n\t}\n\n\tpublic T2 getSecond() {\n\t\treturn second;\n\t}\n}\n"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/utils/UserHomeMetastone.java",
    "content": "package net.demilich.metastone.utils;\n\nimport java.io.File;\nimport java.util.logging.Logger;\n\nimport javax.swing.filechooser.FileSystemView;\n\nimport net.demilich.metastone.BuildConfig;\n\n/**\n * Singleton data class that holds the platform specific path to the metastone user home dir.\n */\npublic class UserHomeMetastone {\n\n    private static UserHomeMetastone INSTANCE;\n    private static String ENV_VAR_NAME = \"USER_HOME_METASTONE\";\n\n    static {\n        String metastoneHomeDirPath = System.getenv(ENV_VAR_NAME);\n        \n        // if we dont have an ENV variable set for USER_HOME_METASTONE, then use the default user directory\n        if ((metastoneHomeDirPath == null || metastoneHomeDirPath.isEmpty())) {\n            metastoneHomeDirPath = (FileSystemView.getFileSystemView().getDefaultDirectory().getPath()\n                    + File.separator + BuildConfig.NAME).replace(\"\\\\\", \"\\\\\\\\\");\n        }\n\n    \tUserHomeMetastone.init(metastoneHomeDirPath);\n    }\n    \n    \n    private String dirPath;\n\n    private UserHomeMetastone(String path) {\n        dirPath = path;\n    }\n\n    private static void init(String path) {\n        if(path == null) {\n            throw new NullPointerException(\"UserHomeMetastone.init(path) cannot be initialized with null!\");\n        }\n\n        if (INSTANCE == null) {\n            INSTANCE = new UserHomeMetastone(path);\n        } else {\n            INSTANCE.dirPath = path;\n        }\n    }\n\n    public static String getPath() {\n        if (INSTANCE == null) {\n            throw new RuntimeException(\"UserHomeMetastone must first be initialized!\");\n        }\n\n        return INSTANCE.dirPath;\n    }\n}"
  },
  {
    "path": "shared/src/main/java/net/demilich/metastone/utils/VersionInfo.java",
    "content": "package net.demilich.metastone.utils;\r\n\r\npublic class VersionInfo {\r\n\r\n\tprivate static final int MAJOR_INDEX = 0;\r\n\tprivate static final int MINOR_INDEX = 1;\r\n\tprivate static final int REVESION_INDEX = 2;\r\n\r\n\tpublic String version;\r\n\tpublic String[] whatsNew;\r\n\r\n\tpublic boolean isNewerVersionAvailable(String localVersionStr) {\r\n\t\treturn updateRequired(localVersionStr, version);\r\n\t\t\r\n\t}\r\n\t\r\n\tpublic static boolean updateRequired(String version1, String version2) {\r\n\t\tInteger[] localVersion = parseVersionString(version1);\r\n\t\tInteger[] remoteVersion = parseVersionString(version2);\r\n\t\t\r\n\t\tif (remoteVersion[MAJOR_INDEX] > localVersion[MAJOR_INDEX]) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\tif (remoteVersion[MAJOR_INDEX] == localVersion[MAJOR_INDEX] && remoteVersion[MINOR_INDEX] > localVersion[MINOR_INDEX]) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\tif (remoteVersion[MAJOR_INDEX] == localVersion[MAJOR_INDEX] && remoteVersion[MINOR_INDEX] == localVersion[MINOR_INDEX]\r\n\t\t\t\t&& remoteVersion[REVESION_INDEX] > localVersion[REVESION_INDEX]) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n\tprivate static Integer[] parseVersionString(String rawVersionString) {\r\n\t\tString versionString = rawVersionString.replaceAll(\"[^\\\\d+|^\\\\.]\", \"\");\r\n\t\tString[] parts = versionString.split(\"\\\\.\");\r\n\t\tInteger[] versions = new Integer[parts.length];\r\n\t\tfor (int i = 0; i < versions.length; i++) {\r\n\t\t\tversions[i] = Integer.parseInt(parts[i]);\r\n\t\t}\r\n\t\treturn versions;\r\n\t}\r\n}\r\n"
  }
]