* Added UI elements for commander mode in NewGame screen.
- still missing random pool of commander candidates sharing a color with each selected color identity.
* Added rudimentary commander choice with preview image.
For now, it is 5 suggestions per color identity with the requirement that the chosen color is part of the commander's identity.
* Commander card is now added to the player inventory and cannot be sold.
The save game adds a flag whether the game is in commander mode, which will be important for the future changes.
* Commander is now auto-added to the initial deck. Commander cannot be removed. Commander is properly saved and loaded.
* Duels now feature the command zone and all rules
* The chosen commander is now set to every deck if the game mode is commander.
* Only cards can be added to the deck that fulfill the color identity.
* heuristically add a pile of singleton cards to function as the starter for the commander's journey.
* Fix land color identity bug in commander pile
* Items are put on autosell if they do not match commander identity.
* Autosell is now properly set with the card flip timing rules. When visiting a shop, the deck conformaty message does not show anymore.
* Remove strict necessity for a fixed commander. This way, a player may choose to switch to a different legendary creature they find.
* 15 Streamlined commander choices without having to swap colors individually.
* Clean up to remove now redundant i18n label. More focus on creatures for starter commander decks.
* Refactor adventure commander deck generation into the DeckgenUtil class
* Refactorings as suggested by @Jetz72.
* Remove custom deck generator
* handle DeckFormat logic through AdventureDeckEditor.
Fix bugs, where partner commanders would be overwritten when using the stock DeckgenUtil.
* Remove random new line
* Code cleanup
* Fix unused import error
* Fix unused import error
* Revert "organise import" changes and random space linebreak changes.
* Removed another import package.* statement
* Implement fixed commander deck to start with. Removed UI edits and random commander choice.
* Add commander-specific cards and a two white overrun effects
* Revert to prior string formatting.
* Remove import *
* Formatting nitpicking to change as little original code as possible.
---------
Co-authored-by: Agetian <stavdev@mail.ru>
* Make getAllFaces return nonnull list
* Optimize Predicates
* CardDB and script syntax changes
* Apply syntax changes
* In-game support for flavor names
* Add display names to PaperCards
* Support searching by flavor names
* Remove some WIP stuff
* Update PaperCard translation key.
* Update capitalization
* Auto-map to variants when edition entry uses a flavor name
* Consolidate display name logic.
* Added syntax for generating flavor named variants in edition files.
* Some examples of new syntax.
* Ignore flavored oracle text when searching rules text
* add hasFlavorName
* Add image key
* Get correct variant from card requests with flavor names.
* turn CardChangedName into record
* Add CardLists.getDifferentNamesCount
* CostDiscard: rename to +WithDifferentNames
* CostSacrifice: use getDifferentNamesCount
* Refactor DifferentCardNames_
* add ChangeTypeDesc for basic land
* Search N is up to
* add some more ChangeTypeDesc
* add some more ChangeTypeDesc
* add some more ChangeTypeDesc
* Update panorama
* update landscape
* Update Clan Monuments
* Small Monument to Perfection change
* update capenna fetchers
* remove ChangeNum from basic Land searchers
* better desc for fetch lands
* remove uneeded ChangeTypeDesc
* Some cleanup.
* Expanded/fixed basic land set functions for quest and adventure.
* Get land sets from unlocked planes in conquest mode.
* Add importer for Adventure, Quest, and Conquest.
* Remove unused import
* Remove redundant override
* Deprecate hasBasicLands predicate.
* Delete getManaNameAndSymbol
---------
Co-authored-by: Jetz <Jetz722@gmail.com>
The project moved to GitHub almost a year ago.
Having duplicated, unmaintained templates will generate problems in the
future. If needed, they can always be retrieved from the commit history.
This bumps `izpack-maven-plugin`.
Tested with JDK 25.
Similar fix for another project:
https://github.com/lsc-project/lsc/pull/385
Original Error message:
`[ERROR] Failed to execute goal
org.codehaus.izpack:izpack-maven-plugin:5.2.3:izpack
(standard-installer) on project forge-installer: Execution
standard-installer
of goal org.codehaus.izpack:izpack-maven-plugin:5.2.3:izpack failed:
java.lang.ArrayIndexOutOfBoundsException: Index 70131 out of bounds for
length 22674 -> [Help 1]`
* Import from CubeCobra feature for Draft and Sealed
* adjust comment for cube Import
* CubeImporter refactoring and generalisation
* Allow hyphens in Cube ID validation
* remove unused imports
* make parseFromURL private
* add LAST_IMPORTED_CUBE_ID to ForgePreferences
---------
Co-authored-by: Antonio <mart@gmail.com>
- Generate CHANGES.txt in forge-gui-desktop/target/ instead of source tree
- Update installer to copy from target directory for all build profiles
- Add CHANGES.txt to .gitignore since it's generated
- Remove hardcoded fromRef to use latest tag automatically
- Remove maven-release-plugin exclusion for untracked file
* - Add achievement for EOE/EOC by Marek14.
* - Add AI logic for Krumar Initiate
* - Tweak AI logic for Krumar Initiate
* Update EndureAi.java
only when doing X
---------
Co-authored-by: Hans Mackowiak <hanmac@gmx.de>
* improve resource finding procedure and add android-dev-build
Added the `android-dev-build` profile to trigger building a developer
apk.
Developer apk is at application id forge.app.dev, and has name
'Forge (dev)'. It is installable in parallel with the official release
app allowing developers to test the android build on their phones
without interfering with their existing installation.
Updated how android resources are found to accomodate changes in
application id at runtime. This method doesn't rely on the 'R' package.
* Use all arguments of getIdentifier
Use all arguments of getIdentifier instead of using a fully qualified
resource name
* Gwen and Miles (Transformation to be added later)
* Create miles_morales_ultimate_spider_man.txt
* Update miles_morales_ultimate_spider_man.txt
* Update miles_morales_ultimate_spider_man.txt
* Add GamesInMatch combo box selection to booster draft page
Also updated combo box default to be seeded with the stored preferences
* Working comboboxes for desktop version
* Working linked buttons on Mobile
* Add binder classes for preferences and other Model components
* Move to pref binders for mobile GUI
* Allow search by type and cmc
* Allow colon and added aliases to cmc
* Support negate
* Extract parser function into its own file
* Support for "!"
* Colors test
* Color search
* Oracle text
* Numeric p/t search
* Set search + fixes
* Typo
* Rarity check
* Loyalty
* Cleanup
* Case ignore for kw
* Support for "or"
* Rename method
* Support parentheses
* Add cases for l
* Cleanup
* Use PaperCardPredicates.printedInSet
* Fix in set to remove conjured cards
* Use func
* Remove redundant check
When I put in the code to delete quest items on a new game+ I forgot to re-add the colorless rune in case you start without the main quest (you get it as part of the main quest). This fixes that.
* LF
* Support for Code2 and Aliases in sheets
* Support for Code2 and Aliases in sheets
* TryConvertingName
* Revert changes
* Update CardEdition.java
* Wrong check
* New sprites that fit established aesthetic
* Adjustments
* Remove downscale + fix wrong pixel on Sorin
---------
Co-authored-by: Agetian <stavdev@mail.ru>
* Cleaning up the file structure to move all Innistrad maps under the Innistrad folder, as well as some minor logic change to Shandalar Old Border's config.json to make maintenance easier. (By setting the desired sets in allowedEditions, we no longer have to restrict every new edition as it comes in.)
Finally, added block info for Alchemy: Innistrad, so it should start being available in events as well.
* Cleaning up the file structure to move all Innistrad maps under the Innistrad folder, as well as some minor logic change to Shandalar Old Border's config.json to make maintenance easier. (By setting all the desired sets to be viewed in allowedEditions, it will only shows those, and we no longer have to restrict every new edition as it comes in.) Also fixed a bug making the "Ghost Town" not work in Shandalar Old Border
Finally, added block info for Alchemy: Innistrad, so it should start being available in events as well.
* Update Adventure - Guardian Gladiolus.dck
---------
Co-authored-by: Hans Mackowiak <hanmac@gmx.de>
* Added: utility method for adding an integer selecting combo-box.
* Added: Adventure now supports dynamic amount of decks
* Tweaked: lowered max deck count to 20.
* Added: dynamic deck count can't ever be higher than the maximum in current version.
* Fixing an issue in which touching the space the map occupies outside of the world map does not allow the player to move (very relevant on maps with content in the top left corner)
* Fixing a bug in which the transition screen's non-blocking of the start match button can be clicked multiple times, which results in a crash when the match ends
When the "Visually Alert on Receipt of Priority" feature is enabled,
the `showTab()` call at the beginning of `forge.gui.framework.SDisplayUtil.remind()`
steals focus from the primary button whenever anything goes on the
stack.
This change, previously suggested by pfps as probably not necessary, is
indeed necessary after all to prevent that focus stealing by restoring
focus to the previous owner only if the owner was an `FButton`.
fixes https://github.com/Card-Forge/forge/issues/7660
* Fixing an issue in which touching the space the map occupies outside of the world map does not allow the player to move (very relevant on maps with content in the top left corner)
* Fixing several text based references that looked created in bulk with similar problems. Kiora and Teferi don't require going back to the town that issued the quest, and the others reference the wrong location when arriving there for where the reputation goes
* Fixing an issue in which touching the space the map occupies outside of the world map does not allow the player to move (very relevant on maps with content in the top left corner)
* Adventure Mode: Fix Quest_APortalToNowhere, in which the file path got moved but the point of interest json was not updated
* Manually altering points_of_interest.json instead of using the adventure editor tool
* Actual change was removed during synchronization with remote head, re-applying actual fix
* Add files via upload
* Update TypeLists.txt
* Create g_1_1_frog.txt
* Update quina_qu_gourmet.txt
I realized that I forgot to set the amount of tokens.
There's something strange with Phantom Train's ability where its P/T display doesn't update properly after getting the counter -- maybe because the counter is gained before animating?
Added monochrome icon pngs for each mipmap size and added them to the xml configs for the icons. The monochrome icons were made by editing the respective icon in paint.net.
* Fixing an issue in which touching the space the map occupies outside of the world map does not allow the player to move (very relevant on maps with content in the top left corner)
* Fix a misspelling in the name of a Merfolk Warrior when being previewed before a battle
Note from Scyfall:
Dominaria United series of 26 tokens
Dominaria United comes with two sets of tokens: a series of 26 bearing either DMU or DMC set codes, and a separate series of 12 tokens with exclusively the DMC set code. We don't know why they did things this way. We've opted to file the entire series of 26 here regardless of set code to avoid archival conflicts.
* Fixing an issue in which touching the space the map occupies outside of the world map does not allow the player to move (very relevant on maps with content in the top left corner)
* Attempt to band-aid two problems relating to showing a dialog to return to the main map, one in which some patrolling enemies ignore the menu restriction and another in which such enemies touching a player who is transitioning out of a dungeon causing a soft lock upon returning to the map
* Fixing an issue in which touching the space the map occupies outside of the world map does not allow the player to move (very relevant on maps with content in the top left corner)
* Fix an edge case where the On The Hunt quest can end up failing to ever spawn an enemy to hunt
---------
Co-authored-by: Agetian <stavdev@mail.ru>
* CardEdition: add collector number for other
* EditionEntry record
* Add getOtherImageKey
* Update StaticData.java
* use getOtherImageKey in getFacedownImageKey
* Update CardEdition.java
Remove findOther in favor of getOtherSet
* Update CardEdition.java
return findOther, but with Aggregates.random
* ~ move more helper images to ImageKeys
* HostPort now properly returns -1 when no port was specified
* refactored the URL parsing to now attempt a DNS lookup in cases where URI parsing fails
This now allows local computer names and localhost etc
Re wrote the URL parsing logic again, but due to increased complexity put it into a utility class
Fixed desktop version opening up a lobby even when it did not connect to a server
Wired up the new error message to mobile, and both error messages to Desktop
* First draft of the castle main quest bug workaround
Adds a possibility to start the game without the main quest and changes all castles to only let you into the gate if you either don't have a main quest active or are actively on the quest to defeat the castles.
* Rewrote main quest stuff
Wanted to decouple the main quest from the start of the game a little bit to allow the player to start without a main quest.
* Fully tested solution for main quest bug
Castles should now behave as expected.
Need to fix one minor bug still.
* Fixing the starting portal and wizard
This seems to have completely fixed the issues I have found with not starting with a main quest. Hopefully I have found all of the things that might break.
* Refactor - Unknown set code to constant
* Refactor - Support for multiple initial selections for getChoices
* Covert noSell and marked color identities into serializable flags
* Fix cards in deck not being converted to newer noSell format
* unused imports
* Fix NPE
* Cleanup card filter
* Remove 14-year-old check that shouldn't be possible anymore
* CRLF -> LF
---------
Co-authored-by: Jetz <Jetz722@gmail.com>
* Initial commit of network improvements
Seperated server properties into their own file, this will eventually help facilitate a headless server.
Fixed the localhost ip mapping to give the correct IP Address instead of failing and defaulting to "localhost"
Fixed UPnP as well as added some additional options and choices regarding UPnP
Added localization strings to all language files. (Translators will need to translate these, but the current English string is there for easy reference so they dont have to search the en-US file)
* Initial commit of network improvements
Seperated server properties into their own file, this will eventually help facilitate a headless server.
Fixed the localhost ip mapping to give the correct IP Address instead of failing and defaulting to "localhost"
Fixed UPnP as well as added some additional options and choices regarding UPnP
Added localization strings to all language files. (Translators will need to translate these, but the current English string is there for easy reference so they dont have to search the en-US file)
* Fixed properties file reference
* Refactored server address parsing logic to use the Java URI class for improved readability and robustness.
Extracted reusable code into separate functions to enhance modularity and maintainability.
General code cleanup to improve structure and readability.
* Fixed a potential issue if a protocol was already specified in the connection url.
* Removed logger implementation as changing loggers is out of scope for this PR
Reverted to JUPnP as its implementation is fixed in #7367
Made some of the new localization strings generic as they can be used elsewhere
Added a server.preferences.example file
removed the server port from the old location (forge.progile.properties.example)
Added a server port back into ForgeConstants as it doesnt make sense for the prefered hosting port of the user to override the default Forge connection port.
* resolve conflicts between this branch and master
* Implemented a parent class for all preference Enums so they can be passed into a function regardless of type using IPref, necessary since I separated server settings into its own FNetPref file
Added server preferences section to the preferences Desktop GUI
Added a port preference setting and a UPnP preference setting to the aforementioned server preferences section
Added a localizedComboBox and localizedComboBoxListener so that localized strings can be used in combobox dropdowns in the server preferences section.
TODO: (In scope)
The new server preferences section needs to be added to Android and IOS perhaps?
TODO: (out of scope)
GamePlayerUtil has a bunch on non localized english strings that should be converted to localized
* Fixed unused import
* Resolved merge conflicts
Added server settings to the reset to defaults function
This removes quest items from a NG+ run in adventure mode. It might not remove all when a file has multiple NG+'s already, but multiple resets will remove all of the items.
I decided to remove the quest item tag from the teleport runes, since they are not added by a quest but can be bought in the store instead. Keeping them seems a good move for NG+.
Now the script works with arrays, much cleaner. I have not been able to test the controller support, it should work but I don't have the means to test it.
* Add command zone effect displaying speed
* Remove enum counter type for speed.
* Make Start Your Engines an SBA.
* LifeLost -> LifeLostAll per speed rules.
* Use same game event for all speed changes.
* Fix keyword not appearing in detail text
* Cleanup extra createSpeedEffect.
* Add support for arbitrary overlay text. Remove fake counters.
* Text styling.
* Remove extra SBA check.
* Remove speed from PlayerView; localization support.
---------
Co-authored-by: Jetz <Jetz722@gmail.com>
* AnimateSubAbility rework
* Remove old fix code, it will no longer be needed
* Add zone ordering
* Add labels
* Clean up
* Fix test
* Tweak logic
* Fix Gilraen
* Refactor with counter
* Clean up
* Cleanup effect if card can't ETB
* Update semesters_end.txt
Format abilities
* Return as Aura trickery
* Apply effect before RE
* Update scripts
* Fix order after creating it earlier
* Fix test
---------
Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.60>
Co-authored-by: Hans Mackowiak <hanmac@gmx.de>
* added PromisedGift as xCount
* SpellAbilityAi: move chooseOptionalCosts
* SpellAbilityAi: chooseOptionalCosts check for invalid targets
* Give API logic access to castSA
* ~ add BaseSpell for PromiseGift
* Unearth: remove unneeded Param
* PromiseGift: uses AITgts to stop AI from using Gift when not needed
---------
Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.60>
Some quests would notify the player of Reputation Rewards that were not actually awarded. Likewise some dialogue choices or upon declining some quests would notify the player of a loss of reputation that wasn't actually deducted. And in one case, the listed reputation change was different than the actual change. This change should ensure all displayed reputation rewards and penalties are applied. (Only applies to Shandalar)
* Updates se.bjurr.gitchangelog to v2.2.0. POM changes to skip "GenerateGitChangelog" in android test and debug builds.
* Remove jitpack.io repo
* Update pom.xml
* Start of contraptions
* More contraption work
* More contraption stuff.
Steamflogger Boss implementation
* More Contraption things
* Use AI's preferred target for mandatory animate effects
* Possible fix for Boomflinger's empty target prompt
* Remove nullable low-definition mana pool icon.
* Contraption Predicates
* Desktop extra zone menu
* Remember cranked contraptions
* AnimateAi fallback to AITgts; Bee-Bee Gun
* Fix AI paying optional costs to assemble zero contraptions.
* Override predicted damage for DealDamage effects with random amounts
* Fix double prompt when reassembling other players' contraptions
* All remaining contraptions and support cards!
* Remove unused imports
* Some cleanup
* Implement non-assembled contraption rule.
Fix blank line prefix on card detail text.
---------
Co-authored-by: Jetz <Jetz722@gmail.com>
* chore: move files to be alphabetically sorted
* feat: make the alphabetically stored cube contents match the new file names
* fix: cube name typo in MTGO Legacy Cube 2017-01
---------
Co-authored-by: Agetian <stavdev@mail.ru>
Replace copyonwritearray to a custom implementation for concurrent modification exception. A little bit slower but supports iterator operations.
Added FCollectionTest
should display error for null icons on VPlayerPanel Infotab. If pointer is still null then the icon seems to be disposed (probably on android) or just deliberately missing.
The original purpose which is to create a copy of linkedlist to iterate to avoid concurrent modification, but we use copyonwritearray design and it is already thread safe and iteration while modification is handled internally.
Don't know why this input is needed but it seems it's not taken into account even with default value to true when the cron job triggers, we have already test build workflow for android apk along with debug signing for checking if build and signing succeeded. Check discord reports on missing/cannot update reports.
removed method to generate borderless card since we used shader as default method
Texture, TextureRegion and other LibGDX objects that are disposable should be nonstatic
show Morph image on FChoiceList
- Update install.xml to set 775 permissions for .command files in Script pack
- Add .command files to target directory copy tasks in pom.xml
- Allow macOS users to run Forge apps without manual chmod
This change ensures .command files receive proper executable permissions
during installation, matching the behavior of .sh files on Unix/Linux.
* - Improve AI for Canopy lands.
- Improve AI for Ugin's Labyrinth.
* - Pyrite Spellbomb: also don't sac immediately.
* - Improve AI for Eladamri, Korvecdal
* - Improve AI decision for Zuran Orb.
* - Cleaner implementation for Eladamri AI
- More generic implementation for SacToDraw AI that doesn't need a logic parameter
* - Fix imports
* - Cleaner implementation for Eladamri AI
* - Suggested code tweak
The main issues is that getAllCards returns a card list that is unique
by name. That menas each card only has a single rarity. In this case,
cards like "Forbidden Orchard" and "Choke" de-deuplicate to a version of
the card with 'S' rarity. This means that when filtering by rarity, the
'S' rarity will be filtered out.
Another similar issue is that when a card exists in multiple sets with
different rarities, you might filter to a set in which the card is 'R',
but the de-duped version in allCards has rarity 'U', so using the cardDB
to check the rarity to filter on doesn't work.
When filtering by a specific set, the solution is to use the rarity of
the card in that set.
When not filtering by a set but filtering by rarity, we have to address
the root cause of the de-duping.
WHen de-duping, I changed the logic to ignore 'S' rarities. This fixes
the rarity filter when not filtering by an edition (although if there
are multiple rarities you still have to kind of guess).
This has the side-effect of making the card image used something more
legible. Before 'Choke' de-duped to the MPS_AHK set which has illegible
text. By ignoring 'S' rarities, it not de-dups to 8ED.
More work could probably be done to improve de-duping, but perfect
correctness would probably involve de-duping only after filtering, which
is likely too slow.
Remove open confirmation and disable leaving inventory scene when opening boosters.
Change Booster Pack Inventory Name
Old:
Booster Pack: WAR
New:
War of the Sparks Booster
Final
Tweak
Corrected "[enviroment]" to "[environment]" in [README.md].
This pull request addresses a minor typo found in repository. The typo has been corrected to improve clarity and maintain the quality of the documentation.
This change is purely cosmetic and does not affect functionality.
* Added Your Plans Mean Nothing
Tested with teammates to ensure that if a teammate is targeted they do not get to draw cards but they do discard their hand.
* Update your_plans_mean_nothing.txt
* Support for icons in dialogs.
Mana abilities show chosen mana via icons.
Thread safety for showOptionDialog
* Ensure EventDispatchThread for showInputDialog
---------
Co-authored-by: Jetz <Jetz722@gmail.com>
* Make Myriad optional for each player again
* Fix scripts
* Fix NPE due to not removing empty shards
---------
Co-authored-by: TRT <>
Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.59>
Fixed Trial of Agony which wasn't dealing the damage, and Disturbing Mirth which was only allowing you to sacrifice another enchantment (not a creature).
* 19 DSK cards
Ghost Vacuum has a weird bug: if AI uses the second ability, I get a window where I can order how the cards should enter the battlefield.
* Add files via upload
* Update grievous_wound.txt
* Update meathook_massacre_ii.txt
* Update say_its_name.txt
* Update say_its_name.txt
* Update drag_to_the_roots.txt
* Update patchwork_beastie.txt
---------
Co-authored-by: tool4ever <therealtoolkit@hotmail.com>
* Fix self-replacement scripts
* Fix scripts to use current value on resolve
* Clean up
* Try to get right controller when ordering
* Fix corner case
* Fix script
* Revert for now
---------
Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.60>
* Unknown Philadelphia 2023 and Minenapolis 2023
* Barcelona 2023 what I could
* A few of the las vegas ones
* Questing role give to creature instead of being on aura
* Changed prizes to promo, should fix phila
And also put the you make the card on a special sheet, should fix a future issue if I got it right
* Fix the etb tapped from bloomburrow patch
Kept old wording for now cause those cards don't have oracle texts, might change if asked
* Las Vegas 2023 (what I could)
* Shortened set codes as suggested
* Fix edition change on las vegas ymtc
* GameActionUtil: use Multikicker as KeywordExtraCost
* ~ remove getMultiKickerManaCost
* ~ remove Announce$ Multikicker
* some AI tweaks
* Fix MultiKicker for AI
* Update ComputerUtilMana.java
* Update ComputerUtil.java
* GamePieceType and isCollectible.
Can no longer ante conjured cards.
Helper set of ZoneTypes that are part of the command zone.
* Faster GamePieceType evaluation
---------
Co-authored-by: Jetz <Jetz722@gmail.com>
I think it produces one mana of EACH color (I had AI use it to pay {2} UnlessCost). But I don't know how to fix that, so I have at least corrected the text.
Refactored functional variants to be merged in the card face rather than the card factory
Some variant characteristics now show in deck builder columns
Fixed loading progress amount
"[card] has neither ManaCost nor Color" warning now actually displays when these are missing.
Co-authored-by: Jetz <Jetz722@gmail.com>
Co-authored-by: tool4ever <therealtoolkit@hotmail.com>
* Add Forage Cost
* ~ fix PaymentDecision for now
* TriggerForage: add Forage trigger
* CostForage: Add First Part of Human Sacrifice Food Logic
* Human ready, first Forage card
* AiCostDecision: Basic AI for Forage
Attempting to add UNF's Nearby Planet using the Omo effect from M3C (and to a lesser extent Planar Nexus from MH3) as a reference.
Tested card, seems to work as intended. The typeline is messier than with the Mistform Ultimus/changeling effect for creatures (Mistform displays "(All)" whereas the Omo version spells out the types), but it is consistent with the referenced Modern Horizons cards.
Both goldLoss and lifeLoss where not being set on creation + update difficulty. So regardless of difficulty chosen, on defeat the default value on the difficultyData class for the properties (0.2f) was being used
I tried to assign the right image numbers, as the first image for most of cards is the extended version.
Since Overclocked Electromancer is still unimplemented, Creative Energy won't work yet.
- your favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
- Java JDK 17 or later
- Git
- Git client (optional)
- Maven
- GitHub account
- Libgdx (optional: familiarity with this library is helpful for mobile platform development)
- Android SDK (optional: for Android releases)
- RoboVM (optional: for iOS releases) (TBD: Current status of support by libgdx)
## Project Quick Setup
- Login into GitHub with your user account and fork the project
- Clone your forked project to your local machine
- Go to the project location on your machine. Run Maven to download all dependencies and build a snapshot.
- Example for Windows & Linux: `mvn -U -B clean -P windows-linux install`
## IntelliJ
IntelliJ is the recommended IDE for Forge development. Quick start guide for [setting up the Forge project within IntelliJ](https://github.com/Card-Forge/forge/wiki/IntelliJ-setup).
## Eclipse
Eclipse includes Maven integration so a separate install is not necessary.
Google no longer supports Android SDK releases for Eclipse.
## Windows
TBD
## Linux / Mac OSX
TBD
### Android Platform
In IntelliJ, if the SDK Manager is not already running, go to Tools > Android > Android SDK Manager. Install the following options / versions:
- Android SDK Build-tools 35.0.0
- Android 15 (API 35) SDK Platform
### Proguard update
Standalone Proguard 7.6.0 is included with the project (proguard.jar) under forge-gui-android > tools and supports up to Java 23 (latest android uses Java 17).
## Card Scripting
Visit [this page](https://github.com/Card-Forge/forge/wiki/Card-scripting-API) for information on scripting.
Card scripting resources are found in the forge-gui/res/ path.
## General Notes
### Project Hierarchy
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:
- forge-ai
- forge-core
- forge-game
- forge-gui
The platform-specific projects are:
- forge-gui-android
- forge-gui-desktop
- forge-gui-ios
- forge-gui-mobile
- forge-gui-mobile-dev
#### forge-ai
The forge-ai project contains the computer opponent logic for gameplay. It includes decision-making algorithms for specific abilities, cards and turn phases.
#### forge-core
The forge-core project contains the core game engine, card mechanics, rules engine, and fundamental game logic. It includes the implementation of Magic: The Gathering rules, card interactions, and the game state management system.
#### forge-game
The forge-game project handles the game session management, player interactions, and game flow control. It includes implementations for multiplayer support, game modes, matchmaking, and game state persistence. This module bridges the core game engine with the user interface and networking components.
#### forge-gui
The forge-gui project contains the user interface components and rendering logic for the game. It includes the main game window, card displays, player interactions, and the scripting resource definitions in the res/ path.
#### forge-gui-android
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
#### forge-gui-desktop
Java Swing based GUI targeting desktop machines.
Screen layout and game logic revolving around the GUI is found here. For example, the overlay arrows (when enabled) that indicate attackers and blockers, or the targets of the stack are defined and drawn by this.
#### forge-gui-ios
Libgdx-based backend targeting iOS. Relies on forge-gui-mobile for GUI logic.
#### forge-gui-mobile
Mobile GUI game logic utilizing [libgdx](https://libgdx.badlogicgames.com/) library. Screen layout and game logic revolving around the GUI for the mobile platforms is found here.
#### forge-gui-mobile-dev
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
- you favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
- Java JDK 8 or later (some IDEs such as Eclipse require JDK11+, whereas the Android build currently only works with JDK8)
- Git
- Git client (optional)
- Maven
- GitHub account
- Libgdx (optional: familiarity with this library is helpful for mobile platform development)
- Android SDK (optional: for Android releases)
- RoboVM (optional: for iOS releases) (TBD: Current status of support by libgdx)
## ✨ Introduction
## Project Quick Setup
**Forge** is a dynamic and open-source **Rules Engine** tailored for **Magic: The Gathering** enthusiasts. Developed by a community of passionate programmers, Forge allows players to explore the rich universe of MTG through a flexible, engaging platform.
- Login into GitHub with your user account and fork the project.
**Note:** Forge operates independently and is not affiliated with Wizards of the Coast.
- Clone your forked project to your local machine
---
- Go to the project location on your machine. Run Maven to download all dependencies and build a snapshot. Example for Windows & Linux: `mvn -U -B clean -P windows-linux install`
## 🌟 Key Features
## Eclipse
- **🌐 Cross-Platform Support:** Play on **Windows, Mac, Linux,** and **Android**.
- **🔧 Extensible Architecture:** Built in **Java**, Forge encourages developers to contribute by adding features and cards.
- **🎮 Versatile Gameplay:** Dive into single-player modes or challenge opponents online!
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
---
### Project Setup
## 🛠️ Installation Guide
- Follow the instructions for cloning from GitHub. You'll need to setup an account and your SSH key.
### 📥 Desktop Installation
1.**Latest Releases:** Download the latest version [here](https://github.com/Card-Forge/forge/releases/latest).
2.**Snapshot Build:** For the latest development version, grab the `forge-gui-desktop` tarball from our [Snapshot Build](https://github.com/Card-Forge/forge/releases/tag/daily-snapshots).
- **Tip:** Extract to a new folder to prevent version conflicts.
3.**User Data Management:** Previous players’ data is preserved during upgrades.
4.**Java Requirement:** Ensure you have **Java 17 or later** installed.
If you are on a Windows machine you can use Putty with TortoiseGit for SSH keys. Run puttygen.exe to generate the key -- save the private key and export
the OpenSSH public key. If you just leave the dialog open, you can copy and paste the key from it to your GitHub profile under
"SSH keys". Run pageant.exe and add the private key generated earlier. TortoiseGit will use this for accessing GitHub.
### 📱 Android Installation
- _(Note: **Android 11** is the minimum requirement with at least **6GB RAM** to run smoothly. You need to enable **"Install unknown apps"** for Forge to initialize and update itself)_
- Download the **APK** from the [Snapshot Build](https://github.com/Card-Forge/forge/releases/tag/daily-snapshots). On the first launch, Forge will automatically download all necessary assets.
- Fork the Forge git repo to your GitHub account.
---
- Clone your forked repo to your local machine.
## 🎮 Modes of Play
- Make sure the Java SDK is installed -- not just the JRE. Java 8 or newer required. If you execute `java -version` at the shell or command prompt, it should report version 1.8 or later.
Forge offers various exciting gameplay options:
- Install Eclipse 2018-12 or later for Java. Launch it.
### 🌍 Adventure Mode
Embark on a thrilling single-player journey where you can:
- Explore an overworld map.
- Challenge diverse AI opponents.
- Collect cards and items to boost your abilities.
- Create a workspace. Go to the workbench. Right-click inside of Package Explorer > Import... > Maven > Existing Maven Projects > Navigate to root path of the local forge repo and
- Let Eclipse run through building the project. You may be prompted for resolving any missing Maven plugins -- accept the ones offered. You may see errors appear in the "Problems" tab. These should
be automatically resolved as plug-ins are installed and Eclipse continues the build process. If this is the first time for some plug-in installs, Eclipse may prompt you to restart. Do so. Be patient
for this first time through.
### 🔍 Quest Modes
Engage in focused gameplay without the overworld exploration—perfect for quick sessions!
- Once everything builds, all errors should disappear. You can now advance to Project launch.
Download the following archived version of the Android SDK: http://dl-ssl.google.com/android/repository/tools_r25.2.3-windows.zip. Install it somewhere on your machine. This is referenced
in the following instructions as your 'Android SDK Install' path.
---
##### Linux / Mac OSX
TBD
#### Android Plugin for Eclipse
Google's last plugin release does not work completely with target's running Android 7.0 or later. Download the ADT-24.2.0-20160729.zip plugin
from: https://github.com/khaledev/ADT/releases
In Eclipse go to: Help > Install New Software... > Add > Name: ADT Update, Click on the "Archive:" button and navigate to the downloaded ADT-24.2.0-20160729.zip file > Add. Install all "Developer Tools". Eclipse
should restart and prompt you to run the SDK Manager. Launch it and continue to the next steps below.
#### Android Platform
In Eclipse, if the SDK Manager is not already running, go to Window > Android SDK Manager. Install the following options / versions:
- Android SDK Build-tools 26.0.1
- Android 8.0.0 (API 26) SDK Platform
- Google USB Driver (in case your phone is not detected by ADB)
Note that this will populate additional tools in the Android SDK install path extracted above.
#### Proguard update
The Proguard included with the Android SDK Build-tools is outdated and does not work with Java 1.8. Download Proguard 6.0.3 or later (last tested with 7.0.1) from https://github.com/Guardsquare/proguard
- Go to the Android SDK install path. Rename the tools/proguard/ path to tools/proguard-4.7/.
- Extract your Proguard version to the Android SDK install path under tools/. You will need to either rename the dir proguard-<your-version> to proguard/ or, if your filesystem supports it, use a symbolic link (the later is highly recommended), such as `ln -s proguard proguard-<your-version>`.
#### Android Build
The Eclipse plug-ins do NOT support building things for Android. They do however allow you to use the debugger so you can still set breakpoints and trace
things out. The steps below show how to generate a debug Android build.
1) Create a Maven build for the forge top-level project. Right-click on the forge project. Run as.. > Maven build...
- On the Main tab, set Goals: clean install
2) Run forge Maven build. If everything built, you should see "BUILD SUCCESS" in the Console View.
3) Right-click on the forge-gui-android project. Run as.. > Maven build...
- On the Main tab, set Goals: install, Profiles: android-debug
- On the Environment tab, you may need to define the variable ANDROID_HOME with the value containing the path to your Android SDK installation. For example, Variable: ANDROID_HOME, Value: Your Android SDK install path here.
4) Run the forge-gui-android Maven build. This may take a few minutes. If everything worked, you should see "BUILD SUCCESS" in the Console View.
Assuming you got this far, you should have an Android forge-android-[version].apk in the forge-gui-android/target path.
#### Android Deploy
You'll need to have the Android SDK install path platform-tools/ path in your command search path to easily deploy builds.
- Open a command prompt. Navigate to the forge-gui-android/target/ path.
- Connect your Android device to your dev machine.
- Ensure the device is visible using `adb devices`
- Remove the old Forge install if present: `adb uninstall forge.app`
- Install the new apk: `adb install forge-android-[version].apk`
#### Android Debugging
Assuming the apk is installed, launch it from the device.
In Eclipse, launch the DDMS. Window > Perspective > Open Perspective > Other... > DDMS. You should see the forge app in the list. Highlight the app, click on the green debug button and a
green debug button should appear next to the app's name. You can now set breakpoints and step through the source code.
### Windows / Linux SNAPSHOT build
SNAPSHOT builds can be built via the Maven integration in Eclipse.
1) Create a Maven build for the forge top-level project. Right-click on the forge project. Run as.. > Maven build...
- On the Main tab, set Goals: clean install, set Profiles: windows-linux
2) Run forge Maven build. If everything built, you should see "BUILD SUCCESS" in the Console View.
The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT
## IntelliJ
Quick start guide for [setting up the Forge project within IntelliJ](https://github.com/Card-Forge/forge/wiki/IntelliJ-setup).
## Card Scripting
Visit [this page](https://github.com/Card-Forge/forge/wiki/Card-scripting-API) for information on scripting.
Card scripting resources are found in the forge-gui/res/ path.
## General Notes
### Project Hierarchy
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:
- forge-ai
- forge-core
- forge-game
- forge-gui
The platform-specific projects are:
- forge-gui-android
- forge-gui-desktop
- forge-gui-ios
- forge-gui-mobile
- forge-gui-mobile-dev
#### forge-ai
#### forge-core
#### forge-game
#### forge-gui
The forge-gui project includes the scripting resource definitions in the res/ path.
#### forge-gui-android
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
#### forge-gui-desktop
Java Swing based GUI targeting desktop machines.
Screen layout and game logic revolving around the GUI is found here. For example, the overlay arrows (when enabled) that indicate attackers and blockers, or the targets of the stack are defined and drawn by this.
#### forge-gui-ios
Libgdx-based backend targeting iOS. Relies on forge-gui-mobile for GUI logic.
#### forge-gui-mobile
Mobile GUI game logic utilizing [libgdx](https://libgdx.badlogicgames.com/) library. Screen layout and game logic revolving around the GUI for the mobile platforms is found here.
#### forge-gui-mobile-dev
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
The AI is *not* "trained". It uses basic rules and can be easy to overcome knowing its weaknesses.
The AI is:
- Best with Aggro and midrange decks
- Poor to Ok in control decks
- Pretty bad for most combo decks
If you want to train a model for the AI, please do. We would love to see something like that implemented in Forge.
# AI Matches from Command Line
The AI can battle itself in the command line, allowing the tests to be performed on headless servers or on computers that have poor graphic performance, and when you just don't need to see the match. This can be useful if you want to script testing of decks, test a large tournament, or just bash 100's of games out to see how well a deck performs.
Please understand, the AI is still the AI, and it's limitations exist even against itself. Games can lag and become almost unbearably long when the AI has a lot to think about, and you can't see what's on the table for it to play against. It's best if you set up the tournament and walk away, you can analyze logs later, results are printed at the end.
In linux and mac, command line arguments are not currently passed through the sh script, please call `java -jar` manually, instead of the exe.
-`sim` - "Simulation Mode" forces Forge to not start the GUI and automatically runs the AI matches in command line. Enables all other switches for simulation mode.
-`-d <deck1[.dck]> ... <deckX[.dck]>` - Space separated list of deck files, in `-f` game type path. (For example; If `-f` is set to Commander, decks from `<userdata>/decks/commander/` will be searched. If `-f` is not set then default is `<userdata>/decks/constructed/`.) Names must use quote marks when they contain spaces.
-`deck1.dck` - Literal deck file name, when the value has ".dck" extension.
-`deck` - A meta deck name of a deck file.
-`-D [path]` - [path] is absolute directory path to load decks from. (Overrides path for `-d`.)
-`-n [N]` - [N] number of games, just flat test the AI multiple times. Default is 1.
-`-m [M]` - [M] number of matches, best of [M] matches. (Overrides -n) Recommended 1, 3, or 5. Default is 1.
-`-f [F]` - Runs [F] format of game. Default is "constructed" (other options may not work, list extracted from code)
-`Commander`
-`Oathbreaker`
-`TinyLeaders`
-`Brawl`
-`MomirBasic`
-`Vanguard`
-`MoJhoSto`
-`-t [T]` - for Tournament Mode, [T] for type of tournament.
-`Bracket` - See wikipedia for [Bracket Tournament](https://en.wikipedia.org/wiki/Bracket_(tournament))
-`RoundRobin` - See wikipedia for [Round Robin Tournaments](https://en.wikipedia.org/wiki/Round-robin_tournament)
-`Swiss` - See wikipedia for [Swiss Pairing Tournaments](https://en.wikipedia.org/wiki/Swiss-system_tournament)
-`-p [P]` - [P] number of players paired, only used in tournament mode. Default is 2.
-`-q` - Quiet Mode, only prints the result not the entire log.
## Examples
In linux and macos you must run forge by evoking java and calling the jar, currently command line parameters are not passed through the script. The forge jar filename is truncated in these examples from `forge-whatever-version-youre-on.jar` to `forge.jar`.
In Windows, if you use the EXE file as described below, the simulation runs in the background and output is sent to the forge log file only. If you want to have output to the console, please use the `java -jar` evocation of forge.
To simulate a basic three games of two decks (deck1 and deck2 must be meta deck names of decks in `<userdata>\decks\constructed\`):
Forge implements many ways to help you find the cards you want in your ever growing collection. One of them uses a [Scryfall-like syntax](https://scryfall.com/docs/syntax) in the collection search bar.
## Additional information
If no operators are passed between tokens, Forge will assume it is joined by `and`. For example, `t:cat t:warrior t:creature` will search for "creatures that are a cat **and** a warrior". Make sure to use `|` or `or` for your queries, as well as parentheses `( )` when needed.
Keywords can be negated by prefixing a minus sign `-`. For example, `t:creature -t:goblin` will search for "creatures that aren't goblins".
If no keywords are used, Forge will search in their name, type and oracle text for the passed values. For exemple, `(cat | warrior)` will search for cards that has `cat` or `warrior` anywhere in their name, type, or oracle text. Not that it is not bounrd, so it will also match on "catastrophe". This type of search can be negated too. For exemple, `lightning -bolt` will search for card with "lightning and not bolt in their name, types, or oracle text", or `(t:cat | t:warrior) -(orc | angel | phyrexian)` will search for "cat or warrior cards that don't have orc, angel, or phyrexian in their name, types, or oracle text.
## Implemented keywords
### Colors
#### Keyword(s): `color`, `c`
You can find cards that are a certain color using the `c:` or `color:` keyword. Both keywords accepts full color names like blue or the abbreviated color letters `w`, `u`, `r`, `b` and `g`.
You can use many nicknames for color sets: all guild names (e.g. `azorius`), all shard names (e.g. `bant`), all college names (e.g., `quandrix`), all wedge names (e.g. `abzan`), and the four-color nicknames `chaos`, `aggression`, `altruism`, `growth`, `artifice` are supported.
Use `c` or `colorless` to match colorless cards, and `m`, `multi`, or `multicolor` to match multicolor cards.
You can use comparison expressions (`>`, `<`, `>=`, `<=`, and `!=`) to check against ranges of colors.
*Exemples:*
`c:rg` - Cards that are at least red and green
`c!gruul` - Cards that exclusively red and green
`color>=uw -c:red` - Cards that are at least white and blue, but not red
### Card Types
#### Keyword(s): `type:`, `t:`
Find cards of a certain card type with the `t:` or `type:` keywords. You can search for any supertype, card type, or subtype.
Using only partial words is allowed.
*Exemples:*
`t:merfolk t:legend` - Legendary merfolk cards
`t:goblin -t:creature` - Goblin cards that aren't creatures
### Card Text
#### Keyword(s): `oracle:`, `o:`
Use the `o:` or `oracle:` keywords to find cards that have specific phrases in their text box.
You must put quotes `" "` around text with punctuation or spaces.
*Exemples:*
`o:"enters tapped"` - Cards that enter the battlefield tapped
#### Keyword(s): `keyword:`, `kw:`
You can use `keyword:` or `kw:` to search for cards with a specific keyword ability.
> Note: Known to be buggy. You can search by oracle text instead.
*Exemples:*
`kw:flying -t:creature` - Noncreatures that have the flying keyword
#### Keyword(s): `name:`
You can find cards with certain words in their name using `name`.
Supports `!` (exact search), `!=` (doesn't contain), and `:` or `=` (contains).
*Exemples:*
`name!Fire` - The card Fire
`name:Phyrexian`- Cards that contain Phyrexian in their name
#### Keyword(s): `is:vanilla`
Find vanilla cratures (Creatures with no abilities).
### Mana Costs
#### Keyword(s): `manavalue`, `mv`, `cmc`
You can find cards of a specific mana value with `manavalue`, `mv`, or `cmc`, comparing with a numeric expression (>, <, =, >=, <=, and !=).
*Exemples:*
`c:u mv=5` - Blue cards with mana value 5
### Power, Toughness, and Loyalty
#### Keyword(s): `power`, `pow`
You can use numeric expressions (>, <, =, >=, <=, and !=) to find cards with certain power using `power` or `pow`.
*Exemples:*
`pow>=8` - Cards with 8 or more power
`pow>tou c:w t:creature` - White creatures that are top-heavy
#### Keyword(s): `toughness`, `tou`
You can use numeric expressions (>, <, =, >=, <=, and !=) to find cards with certain toughness using `toughness` or `tou`.
*Exemples:*
`tou<=4` - Cards with 4 or less thoughness
#### Keyword(s): `loyalty`, `loy`
You can use numeric expressions (`>`, `<`, `=`, `>=`, `<=`, and `!=`) to find cards with certain starting loyalty using `loyalty` or `loy`.
*Exemples:*
`t:planeswalker loy=3` - Planeswalkers that start at 3 loyalty
### Sets (Editions)
#### Keyword(s): `set:`, `s:`, `edition:`, `e`
Use `s:`, `e:`, `set:`, or `edition:` to find cards using their Magic set code.
Examples:
`e:war` - Cards from War of the Spark
#### Keyword(s): `in:` (set)
The `in:` keyword finds cards that appeared in given set code.
Examples:
`in:lea` - Find cards that once appeared in Alpha.
### Rarity
#### Keyword(s): `rarity:`, `r:`
Use `r:` or `rarity:` to find cards by their print rarity. You can search for `land` (`l`) (usually only basic lands), `common` (`c`), `uncommon` (`u`), `rare` (`r`), `mythic` (`m`), and `special` (`s`). You can also use comparison operators like `<` and `>=.`
Examples:
`r:common t:artifact` - Common artifacts
`r>=r`- Cards at rare rarity or above (rares and mythics)
#### Keyword(s): `in:` (rarity)
You can find cards that have been printed in a given rarity using `in:`
Adventure mode is a work-in-progress game mode where you explore the ever-changing landscape of Shandalar, duel creatures to earn gold and new cards to battle the various bosses. You can visit towns to buy equipment and cards, and crawl through dungeons to find artifacts and loot to help you on your journey. Adventure mode is an awesome reimagining of the original "Shandalar" 1997 PC Game in Forge, even though the scope of adventure is much broader and we don't constrain ourselves to the lore of the plane of Shandalar. You can play Shandalar on Desktop (Linux, Windows, IOS) or on Android devices.
# How to run?
1. Step 1: Download the latest version of Forge https://downloads.cardforge.org/dailysnapshots/
2. Step 2: Make sure you have the required version of the JDK (https://www.oracle.com/be/java/technologies/downloads/)
3. Step 3: Launch the adventure mode from the "adventure.exe" file included in the forge version (on android, the adventure version should be embedded into the main Forge program)
4. Step 4: In order to have pictures show up make sure you enable "automatically download missing card images" in the settings. This should automatically download the picture of the card as you encounter them in game.
Logical screen with/height, changing this would require to change all ui elements and won't increase resolution.
## **skin**
path to the used skin for adventure
## **playerBaseSpeed**
base speed of player character
## **minDeckSize**
minimum deck size for matches, decks with lesser cards will be filled with wastes.
## **starterDecks**
string list of all starter decks
## **restrictedCards**
string list of restricted cards, those cards won't appear in random shops or rewards but it it still possible to get those cards if the plane specifically drops it.
## **restrictedEditions**
string list of restricted editions, behaves the same as restricedCards but with editions.
## **difficulties**
list of DifficultyData
## **legalCards**
RewardData for legal cards, behaves similar as restrictedCards only as white list and not black ist.
Also it is defined as RewardData see [Create-Rewards](https://github.com/Card-Forge/forge/wiki/Create-Rewards) for syntax
In order to edit which sets will show up in random rewards and shop you will need to edit the config file. You can find it here "forgefolder"\res\adventure\common\config.json. Add the sets you want to have restricted to the "restricted edition" section
Forge provides an in-game console in adventure mode.
You can access (and close) the console while exploring by pressing F9 (or Fn-F9).
To scroll the console window, click and drag the text box.
## Available commands
| Command Example | Description |
| -- | -- |
| resetMapQuests | Resets the map quests, resulting in all side-quest progress being lost and all side-quest types being re-picked |
| give gold 1000 | Give 1000 gold |
| give shards 1000 | Give 1000 shards |
| give print lea 232 | Add an alpha (LEA set code) black lotus (232 collector number) |
| give item <itemnameorcode?> | Adds an in game item such as leather boots |
| give set sld | Give 4 copies of every card in the Secret Lair Drop (set code SLD), flagged as having no sell value |
| give nosell card forest | Gives a forest with no sell value |
| give boosters leb | Add a booster from beta (LEB set code) |
| give quest 123 | Add the quest by its number ID |
| give life 10 | Add 10 life to yourself |
| give card forest | Adds a forest to your inventory |
| debug collision | Displays bounding boxes around entities |
| debug map | TODO |
| debug off | Turns off previously enable debugging |
| teleport to 6000 5000 | Moves you 6000 tiles east and 5000 tiles north from the left bottom corner |
| fullHeal | Returns your health back to baseline |
| sprint 100 | Increases your speed for 100 seconds |
| setColorId R | Sets the player color identity; Probably used for testing and shops |
| clearnosell | Clears the no sell value flag from all cards you own that are not used in a deck |
| remove enemy abc | Remove the enemy from the map with the map ID abc |
| remove enemy all | Remove all the enemies from the map |
| dumpEnemyDeckList | Print the enemy deck lists to terminal output stream |
| getShards amount 100 | Similar to give shards command; Gives 100 shards to the player |
| resetQuests | Resets the world quests. In progress quests are not abandonned or reset including dungeons. Does not reroll abandoned quests. Not really sure what this does. |
| hide 100 | Enemies do not chase you for 100 seconds |
| fly 100 | You can walk over obstacles for 100 seconds |
| crack | Cracks a random item you are wearing |
| spawn enemy Sliver | Spawns a Sliver on your screen |
| listPOI | Prints all locations in terminal output stream as ID-type pairings |
| leave | Gets you out of the current town/dungeon/cave |
| dumpEnemyColorIdentity | Prints all enemies, their colour affinity and deck name to terminal output |
| heal | Recover your full health |
| dumpEnemyDeckColors | Prints all decks available to enemies and their affinities |
All Enemies are stored under `res/<AdventureName>/world/enemies.json`
Enemies spawned on the overworld map or on map stages will use this exact template to define their base behavior. These values can be modified or added to with additional settings on an individual enemy basis, details of which can be found within [map instance](Create-new-Maps.md).
Some ideas for custom enemy cards:
- basic (CR or at least Alchemy conform) effects for normal mobs to add flavor
- advanced + all UN stuff. but their mechanics could be combined in new ways not seen in print
- boss/rare ones that are balanced around meta mechanics (=quests)
The Json file contains an Array of Objects and each Object is one enemy.
Name of the enemy, every time an other object will use an enemy, it will refer to this name.
## **nameOverride**
String - If provided, this will be displayed in any references to this enemy in the game. If not provided, Name will be used.
## **sprite**
String - Path to the sprite atlas for the enemy (from `res/<AdventureName>`)
In fights against the enemy, the sprite under "Avatar" will be used as avatar picture.
Every sprite under
"Idle","Walk","Attack","Hit","Death"
will be used as animation for the corresponding action.
direction can be added to alter the animation depending on the direction like "IdleRight"
Supported directions are "Right","Left","Up","Down","RightDown","LeftDown","LeftUp","RightUp"
## **deck**
Array of strings containing paths to the decks used for this enemy (from `res/<AdventureName>`)
If no decks are defined then the enemy will act like a treasure chest and give the rewards without a fight.
(only for enemies in dungeons)
The format for the deck file can be the normal forge *.dck syntax or a json file that will behave like a collection of [rewards](Create-Rewards.md) to get a random generated deck.
## **randomizeDeck**
Boolean - if true then the enemy deck will be randomly selected from the deck array. If false, an algorithm will select a deck in sequential order based on the player's prior win/loss ratio against that opponent (discouraged and currently unused due to wild swings in ratio at low game count).
## **ai**
String - Currently unused, this appears to be intended to allow different playstyles to be associated with this enemy.
## **boss**
Boolean - Not used to any great extent at this time, but a value of true in this field indicates that this is a boss-level enemy for which the match presentation can be changed if desired. Currently, this causes capable Android devices to vibrate for a longer period on collision with the enemy sprite.
## **flying**
Boolean - If true, this enemy ignores terrain collisions and can travel freely in their intended movement direction.
## **spawnRate**
Decimal - Relative frequency with which this enemy will be picked to spawn in appropriate biomes (which are set in the biome json file). Existing values range from 0 to 1.0.
## **difficulty**
Decimal - Relative estimated difficulty associated with this enemy. Currently unused, but will likely be factored in as a part of filtering enemies into early/late game appropriate opponents. Existing values range from 0 to 1.0.
## **speed**
Integer - Movement speed of this enemy in overworld or on a [map instance](Create-new-Maps.md). For comparison, the player's base speed is set at a value of 32 (before any equipment / ability modifiers).
## **scale**
Decimal - Default 1.0. For enemies whose sprites are too large or small for their intended usage, this serves as multiplier for the enemy's visual dimensions & collision area. By default, we work with 16x16 pixel sprites for most entities - this can be replicated with a more detailed 32x32 sprite by setting a scale of 0.5 for the enemy entry.
## **life**
Integer - Base starting life total. This is modified universally by a value determined by the player's chosen difficulty, and can be adjusted further at the enemy object level on [map instances](Create-new-Maps.md).
## **rewards**
Array - A collection of the rewards to be granted for defeating the enemy.
see [Create Rewards](Create-Rewards.md) for the syntax.
## **equipment**
Array - A collection of strings representing [equipment items](adventure-items) normally intended for player use that this enemy will have. Not used widely, usually when an enemy will drop that [equipment](adventure-items) and it does not use [mana shards](mana-shards).
## **colors**
String - Any combination of "B" (Black), "U" (Blue), "C" (Colorless), "G" (Green), "R" Red, and "W" (White). Used to display color identity alongside the sprite when an active ability allows it.
## **questTags**
Array- A collection of strings associated with this entity for filtering in regards to quests.
Rewards represent anything that can be obtained by the player in Adventure Mode. Cards are the most common type of reward, but rewards can also represent a pieces of equipment, gold, mana shards, maximum life increases, keys, or generic items to be interacted with via dialogs and quests.
Rewards are associated with...
* Enemies - The loot they drop when defeated.
* Lootable items - Treasure chests, piles of gold, spellbooks, or mana shards on dungeon maps.
* Shops - The items available for purchase.
* Dialog - Items given to the player during a dialog sequence OR required in order to have access to an option.
* Quests - Rewards for completion of a quest.
* Events - Rewards for completion of an event.
As a multipurpose concept, Reward data has a large number of fields, but almost all are optional and no one reward will use all of the fields.
The expected fields and behavior for the rewards are set based on the reward's `type` value. This is the one field that all reward data must contain, as it tells the game what other fields to expect data in and how to process them.
The simplest types are `gold` (give the player X gold), `shards` (give the player X [mana shards](mana-shards), and `life` (increase the player's current and maximum life total by this amount [given very sparingly]). Beyond their types, these three items only require a `count` field. Optional fields for these types include `probability` and `addMaxCount`, (see full list at bottom). An example of a `gold` reward that will grant the player 50 gold follows:
```json
{
"type":"gold",
"count":50
}
```
`Item` rewards are slightly more specific, but still relatively simple, requiring `type`, `count`, and EITHER `itemName` or `itemNames`, examples of both follow. Optional parameters again include `probability` and `addMaxCount`. If `itemName` is specified, then `count` copies of the named item will be granted to the player. If `itemNames` is specified, `count` copies of items randomly selected from the list of named items provided will be granted instead.
`Card` rewards are potentially the most complex from a definition standpoint, but almost all of the fields are optional. A simple and a complex example follow below.
Granting the player one completely random card that is legal for use in Adventure:
```json
{
"type":"card",
"count":1
}
```
As a contrasting and complicated example that will return no matching results, consider the card reward data below. 80% of the time, this will try to grant the player 1-9 (modified further by difficulty) cards that are rare, multicolor, contain both red and blue in their color identity, are from M21 or M22 editions, are either instants or creatures, have the Elf subtype, contain the word "dragon" in the card name, contain the word "destroy" and/or "exile" in the card text. Details on the individual fields can be found in the reference list at the bottom of this page.
```json
{
"type":"card",
"probability":0.8,
"count":1,
"addMaxCount":8,
"colors":["red","blue"],
"rarity":["rare"],
"editions":["M22","M21"],
"cardTypes":["Instant","Creature"],
"colorType":"MultiColor",
"subTypes":["Elf"],
"superTypes":["Legendary"]
"cardName":"dragon",
"cardText":"destroy|exile"
}
```
`Union` reward types are a purely structural element. This reward type does nothing on its own, but it is used as wrappers for multiple `card` instances that has distinctly separate parameters. Required elements are `type` (Union), `count`, and `cardUnion` (which contains additional `card` reward definitions). Optional parameters are, once again `addMaxCount` and `probability`. As an example, the following would award the player with a single red dragon from M21 OR a single green sorcery from M22, but never a red sorcery, M22 dragon, or so on. The individual card pools are conjoined, giving an equal chance of all possible cards across the pools.
```json
{
"count":1,
"type":"Union",
"cardUnion":[
{
"count":1,
"editions":["M21"],
"subTypes":["Dragon"],
"colors":["red"]
},
{
"count":1,
"editions":["M22"],
"cardTypes":["Sorcery"],
"colors":["green"]
}
]
}
```
# Fields:
## **type**
Defines the type of reward.
Valid options are:
*`gold` will reward the player with gold.
*`life` will increase the maximum life of the player.
*`shards` will reward the player with [mana shards](mana-shards).
*`item` will give items to be added to the player's inventory.
*`card` will create one or more cards matching a detailed set of filters to follow.
*`union` is a wrapper for multiple `card` instances that can have mutually exclusive filters.
*`deckCard` is only used with rewards from [enemies](Create-Enemies.md), this functions as a `card` reward that is limited to cards found in that enemy's deck.
`{"type": "card", ...}`
## **probability**
The probability of this reward being given out, on a decimal scale from 0 to 1. (Defaults to 1 if not provided)
`{..., "probability": 0.5, ...}`
## **count**
If given at all (see `probability`, above), get at least this many of the reward.
`{..., "count": 10, ...}`
## **addMaxCount**
If given at all, an additional "addMaxCount" instances of the reward will be added. A pre-set multiplier based on the chosen difficulty is applied to this parameter. On normal difficulty, you will get the reward "count" to "addMaxCount" times, `{"type": gold", "count":10, "addMaxCount":5"}` would give anywhere from 10 to 15 gold. (Defaults to 0 if not provided)
`{..., "addMaxCount": 5, ...}`
## **colors**
An array of the possible colors for `card` and `deckCard`.
`{..., "colors": ["red", "black"], ...}`
## **rarity**
An array of the possible raritys for `card` and `deckCard`.
A regular expression defining text that must appear on the card. A sequence of plain text alphanumeric (A-Z, 0-9) characters can function here if you are unfamiliar with regular expressions, but many special characters will change the functionality. Useful for denoting keywords or identifying helper cards that are associated with but do not actually have a given type.
`{..., "cardText": "reveal the top card of", ...}`
`{..., "cardText": "cast (an instant|a sorcery) from your hand", ...}`
## **deckNeeds**
This is a functional but partially implemented concept at best, as the result set is currently limited. Card scripts can be tagged as having certain attributes that are used for creating synergies in decks constructed by the game. If/when this feature is expanded to more of our script library, the using those tags will become more and more useful to use for defining rewards so as to allow "concepts" to be awarded as opposed to specific card names or text contents.
`{..., "deckNeeds": ["Ability$Graveyard"], ...}`
## **cardPack**
This element should not be seen or used in JSON definitions of reward data. `cardPack` rewards are a type that represent a collection of cards whose contents have been pre-determined in game logic (such as a drafted deck that can be kept after an event), but that were not pre-determined at the time when the reward data was written. To manually implement granting specific or randomized cards we should use one or more `card` rewards, detailed above. When granted, this will create an item in the player's inventory that can subsequently be opened by the player to award the individual card contents of the associated deck.
This will allow you to edit the maps and tile sets.
To interact with the player, objects needs to be added to the Objects layer.
Objects templates are stored in the "obj" folder, but are not necessary.
Impotent are the types of the object and his properties.
## Object types
# enemy
will spawn an Enemy on the map. On collide with the player a magic duel will be started.
If the player win, the enemy will be removed from the map and the player will get the reward.
If the player loose, then the player will move 1 step back and receive the standard penalty.
Loot is also defined as enemy without a deck, then the player will receive the reward right away.
Properties:
`enemy` name of the enemies
# shop
Will spawn an shop on the map. On collide the player will enter the shop.
Properties:
`shopList` List of possible shop, leave it empty for all shops.
`signXOffset` x offset for the shop sign.
`signYOffset` y offset for the shop sign.
# inn
Will spawn an inn the map. On collide the player will enter the inn.
Properties:
# entry
Will be used as the map entry and exit. On collide the player will be teleported to an other map or the over world.
Properties:
`direction` the position where to spawn. up means the player will be teleported to the upper edge of the object rectangle.
`teleport` The map where the player gets teleported. If the property is empty, then the player will be teleported to the over world.
`teleportObjectId` the object id where the player will be teleported. If empty then it will search for an entry object, that would teleport the player back to the source map.
You can add lands by clicking the triple dots icon in the right top of the deck building interface.
Initially you only have access to jumpstart basic land arts - to get more, you need to purchase the landscape sketch books from the basic land shop (The Cartographers Guild).
# 40-card deck recommendation
40-card decks give you a much more predictable curve.
In a 40-card deck, each individual card has a 2.5% chance of being drawn.
In a 60-card deck, each individual card has a 1.6% chance of being drawn.
When you use a smaller deck, the significance of each individual card is much higher and will give you more predictable performance.
# Autosell
When you click a card that is not part of any decks, you get the option to move it to auto-sell.
The numbers in the interface indicate how many copies you have.
To sell the cards you have marked, go to any town and enter the Inn.
In the Inn, the middle icon of the coin called Sell (E) will sell all your cards you have marked to sell.
Individual card values vary by rarity and reputation with the town you sell them in.
Since dying will cause you to lose a percentage of your gold, you may want to not sell all your cards immediately.
In Adventure Mode, you can select different planes (AKA worlds) to experience. The default experience is the Shandalar plane, and is the basis of gameplay found in all the others. The other planes are community projects, and are in various states of completion. Below there is a list of each plane in the game (as of this writing), and it's status.
To change planes, open up Adventure Mode. On the main screen where you can either load a game or start a new one. Go to the Settings menu, and you will find a drop down on the top right with a list of the planes currently available. When changing planes, you will need to restart Forge to apply the change. Save files are unique to each plane, and will load back in when you change to the appropriate plane.
## Amonkhet
A plane set in the world of Amonkhet, and is intended to have new quests, story, enemies, etc. based on that Magic The Gathering plane.
**STATUS:** Very broken. Not recommended for play, unless you want to dip your head in and see a very different overworld map. Pretty much nothing else works properly. _You have been warned._
## Crystal Kingdoms
A plane with a focus on the Final Fantasy cards. Intended to have a unique story, quests, enemies, etc. based on the Final Fantasy games.
**STATUS:** Very pre-alpha. The plane 'works' but is almost identical to the default Shandalar plane. The big differences is new starting decks, and the card rewards in shops and from enemies, have a stronger Final Fantasy bent than usual.
## Innistrad
A plane set in the lore and world of Magic the Gathering's Innistrad plane. The card pool and enemies are limited to Innistrad, with some minor exceptions. There is also a distinctly different overworld, story, enemies, etc. Every map is new and unique compared to Shandalar.
**STATUS:** Technically alpha. Everything is 'functional' in that it is all closed off and separate from the Shandalar plane. The enemies are new, the quests include some base and some new (though the tutorial has some strong similarities). The shops are new-ish (they currently use the base shops, but with a limited card pool to just the plane. This is actively being changed to entirely new shops however, and should become notable in the next few updates.) Every town, dungeon, and cave is unique to Innistrad and been custom built for the plane. Every enemy is also new to Innistrad.
_HOWEVER_: Everything is still very early in design. Most of the biomes are empty. The new quests are limited. The shops under work. The story ends partway through the tutorial. The first bosses are being worked on. The enemies are few in number, but growing rapidly in development. Etc. This plane is best for short plays to see what is going on, but don't expect to spend much time here, Yet. That said, it is the community plane receiving the most work and most consistent updates. With changes coming at the minimum, monthly, preferably weekly.
In it's current state, expect to find bugs. if you do, please report them to Shenshinoman in the Forge Discord.
## Shandalar
The base plane, where all the magic happens, and where the dev team members working on Adventure generally focus their efforts. The story is mostly complete, but there _is_ further work planned. Everything works, and you can easily spend dozens of hours, or more, in this plane.
**STATUS:** Fully functional and live. Enjoy!
## Shandalar Old Border
So, you like the Shandalar plane, but miss "ye good olde days" of yore? Then Shandalar Old Border is for you. This is the Shandalar base plane, but with modifications so that everything is from the Scourge set, or older.
**STATUS:** 99% functional and live. Enjoy! (There may be the occasional bug found. if so, please report it so we can get it resolved.)
Currently, the story is very much a work in progress and will change a lot in the future. The end goal will consist of multiple "tiers" of more difficult dungeons, where you will be guided by quests. Currently, only some of the story dungeons are available, and there are no quests to guide you to them. You can enter these dungeons and fight the bosses.
### Tier 1: Castles
The first tier of story bosses will consist of five dungeons, each found in the center of the five colored biomes. Each boss gives a plethora of loot (cards, life, shards, gold) and if you are able to defeat all five bosses, you can get a Mox card of your choice.
Currently only the Temple of Chandra (red biome) and Temple of Liliana (black biome) are available. These tend to be harder than the castles mentioned above.
1.**Effect** : Extra card starting in your command zone.
1.**Location** : Capital Cities
1.**Price** : 1000 gold
#### Life Amulet
1.**Effect** : +2 starting life
1.**Location** : Capital Cities
1.**Price** : 4000 gold
#### Manasight Amulet
1.**Effect** : Grants Manasight, letting you know the colors used by your adversaries.
1.**Location** : Capital Cities
1.**Price** : 1000 gold
#### Phoenix Charm
1.**Effect** : Extra card starting in your command zone. [Spellbook Content](https://scryfall.com/@Simidhimi/decks/9c917d2c-c30d-4638-92ff-2df40120599c)
1.**Effect** : Extra card starting in your command zone. [Spellbook Content](https://scryfall.com/@Simidhimi/decks/e88b95ce-dd83-48ed-bf77-df997b71bbb6)
1.**Location** : Chandra's Temple

#### Dagger
1.**Effect** : Extra card starting in your battlefield zone.
1.**Effect** : Extra card starting in your command zone. [Spellbook Content](https://scryfall.com/@Simidhimi/decks/47041427-b5d3-4faf-8f7b-1e1e004fa8ab?as=visual&with=usd)
1.**Effect** : Extra card starting in your command zone. [Spellbook Content](https://scryfall.com/@Simidhimi/decks/7fdd8d06-c0c5-487e-a7e7-3050f6d19bed)
Starting life Modifier": -1,
Extra card starting in your battlefield zone: Kraken Hatchling
1.**Effect** : Extra card starting in your command zone. [Spellbook Content](https://scryfall.com/@Simidhimi/decks/bdaa39ed-b4b3-4157-aa85-98e313750dd0)
1.**Effect** : Extra card starting in your battlefield zone. (Entrancing Lyre)
1.**Location** : White Capital City
1.**Cost** : 1000
#### Gold Shield
1.**Effect** : + 3 starting life total
1.**Location** : Blue Capital Arena
#### Grolnok's Skin
1.**Effect** : Extra card starting in your battlefield zone. Lifemodifier: +3 [Spellbook](https://scryfall.com/@Simidhimi/decks/bf16e555-4efb-488b-a05c-25296807777c)
1.**Effect** : Extra card starting in your battlefield zone. (Kite Shield)
Opponent starting life -2
1.**Location** : White Capital City
1.**Cost** : 1500
#### Magic Shard
1.**Effect** : Extra card starting in your battlefield zone. (Magic Shard)
1.**Location** : Blue Capital City
1.**Cost** : 3500
#### Mirror Shield
1.**Effect** : Extra card starting in your battlefield zone. (Mirror Shield)
1.**Location** : Capital Cities
1.**Price** : 2500 gold
#### Mithril Shield
1.**Effect** : Extra card starting in your battlefield zone. (c_0_4_a_wall_defender)
1.**Location** : Blue Capital City
1.**Cost** : 6500
#### Nahiri's Armory
1.**Effect** : Extra card starting in your battlefield zone. Lifemodifier: +3 [Spellbook](https://scryfall.com/@Simidhimi/decks/b7a18cab-c10c-4ad4-80ba-aba88649ef27)
Tested using DS4 COntroller on Windows and Android.
Tested using DS4 Controller on Windows and Android.
If using on Windows OS and you have DS4Windows installed, you might experience dual input because of Emulated/Virtual Controller. To fix this you must use HidHide (better than exclusive mode). Refer to the guide here:
The difficulty you choose will alter your starting health (15 for easy to 7 for extreme), and the amount of cards you start with when you choose the "pile" mode. Also, cards will sell for less in shops and enemies will have more health.
Another difference is that on hard/extreme difficulty, enemies that use computer-generated decks (using .json files) will instead use completely random decks that will have nothing to do with their original deck theme.
## Starting cards
You will have multiple options as to how your starting pool of cards will look
1.__Pile__. This will give you a random pile of cards based on the color you choose. The harder the difficulty you choose, the fewer cards you will get.
When you use this option, it is recommended that you create a 40-card deck (minimum deck size) out of the 60 cards you get.
2.__Chaos__. This is a unique gameplay mode that completely randomizes the enemy decks and the deck you start with.
__This setting is not recommended for new players__
3.__Standard__. With this start, you will get three 20-card preconstructed jumpstart decks from the set you choose (or all of them).
4.__Constructed__. With this setting, you start with a custom preconstructed deck based on a popular theme that's easy to expand during your run. (**Recommended for new players**). Your starting color will determine which starter deck you will get
## General hints and tips
1. Don't immediately sell cards that are not useful in your current deck. Some cards can be very useful in certain boss fights, for instance, cards with " protection from red" are very strong against most red bosses.
2. Make sure to travel to the biome capitals early in your run. Every capital contains a shop that sells 1 life and an item that allows you to teleport back to that capital. Also, they sell items that can't be found elsewhere.
3. Every boss gives you +1 starting life. So if you're having trouble with a certain boss, it might be better to try your hand with other bosses.
4. Arena challenges in the capitals can be a good source of money, items, and cards. Though you need a rather strong deck to consistently win there.
### Insane Guide
If you are playing on Insane and need some pointers, user KingNishi has written up a nice doc about it
Mana Shards are a custom resource created for Adventure Mode in Forge. Shards serve as a secondary form of currency in some parts of the game economy, can be bought and sold via dedicated Shard Trader merchants, and are also used to power the player's special abilities both on the Adventure world map as well as in almost all Adventure Mode duels. During a duel your Shards will appear as a usable resource visible alongside your life total akin to how Energy tokens are used. Your Shards are not tokens or counters, and are thus immune to any abilities that would affect tokens or counters outside of using [equipment](adventure-items) that specifically requires their expenditure. Spending your shards is a permanent choice, however, as your final total at the end of the match will follow you back into your Adventure.
As Mana Shards are not self-replenishing, using them for activated abilities that affect the game world and in-match gameplay is a way of letting the player have a constant challenge of their own comfort level. If the player is comfortable taking on a fight without using extra abilities afforded to them by use of Shards, they will have more of them available to spend on in town, or can simply save their full might for boss-level entities.
The most consistent source of Mana Shards is from match wins, players receive one Shard per win without any rewards having to be explicitly added to the encounter. However, when used to power equipment and abilities, one Shard per match will definitely not break even, as most custom Adventure cards and items will cost 2-4 per use. However, Mana Shards can also be purchased in towns, picked up in dungeons, and can be received in large quantities from completing quests.
Mana Shards can be used for:
* Activated in-match abilities
* Activated game map abilities
* Refreshing card shop inventories
* Spellsmith card purchases as an alternative to gold
With the addition of new planes in Adventure Mode, comes a framework to allow greater customization and even expansion of the available planes. The details behind the framework and each category can be found below. As well as a new section being built with some basic information on each piece of plane construction and modification. The sections will also define some Best Practices to be maintained.
### Getting Started
Modding Adventure mode comes in many fashions. From making small changes to a core plane, such as changing the music that plays on the overworld. To something as complex as an entirely new plane. Regardless of your intended goals, the first thing to do is set-up a back-up method. As any changes you haven't had incorporated into the main game, will potentially be lost on each update of Forge. Since this is a Git project, the method that will be recommended by this wiki, and referenced for the future, is simply to create your own git branch, and use a local repository to control all your files. It is also recommended to follow the directions to [set-up IntelliJ](https://github.com/Card-Forge/forge/wiki/IntelliJ-setup), to manage your local files. (Again, this is the method that will be referenced elsewhere in this wiki.)
### Tools
The following additional tools can also be very useful, or even mandatory, to have for your mod, depending on what all you want to do in your mod/addition.
**[Tiled](https://www.mapeditor.org/)**: If you want to modify or create any maps; (caves, dungeons, towns, etc.) Forge utilizes Tiled. If you want to learn more on this topic, you can find it in the [Create new Maps](https://github.com/Card-Forge/forge/wiki/Create-new-Maps) section of the Wiki.
**[GIMP](https://www.gimp.org/)**: Many of the art files such as the tilesets used in Tiled, are made in GIMP. (A free graphical manipulation program, similar to Photoshop.) While not required to work in Forge's files, if you want to create your own art assets, this is the program that will be used for examples in this Wiki.
### Tutorials
The following pages on this Wiki contain some basic tutorials on various facets of modding Adventure mode, to help get you started.
[Tutorial 1, Create your First Plane](https://github.com/Card-Forge/forge/wiki/Tutorial-1-Create-your-First-Plane)
[Tutorial 2, A New Look (creating your first map.)](https://github.com/Card-Forge/forge/wiki/Tutorial-2-A-New-Look)
[Tutorial 3, Configuration (Configuring your Plane)](https://github.com/Card-Forge/forge/wiki/Tutorial-3-Configuration)
While exploring the world of Shandalar, you will find multiple cities across all biomes. These settlements are a integral part of the game, and can help the player by giving quests, hosting draft/jumpstart events, as well as selling cards and booster packs.
At the inn, players can buy temporary healthpoints, sell cards and join events. Different inns host different events. For example, a town can be hosting a draft for "Urza's Saga Block", while the next one can be hosting a Jumpstart 2025 event.
Going to the job board the player can accept quests, which will reward them with gold and sometimes local reputation. Local reputation reduces the prices of cards sold by shops.
Card shops sells cards according to the biome and the shop itself. For example, shops in a green city will tend to sell green cards, while shops in a black city will focus on black cards.
Different shops sells different types of cards, and most of them can be deduced from their name. For example, the shop called "Control Magic" will sell blue cards used to control opponents (counter, tap, untap, etc), while a shop called "Swords, Plowshares, and Beyond" will sell cards that exile permanents. The shop information can be a found in [this user-friendly JSON](https://github.com/Card-Forge/forge/blob/master/forge-gui/res/adventure/Shandalar/world/shops.json).
Shops with an Hour Glass sign change their stock daily. All other shops restock only using shards to reroll.
Item shops sell items and equipments that the players can use on their journey - such as a rune that allows them to teleport to that capital when on the overworld.
The Smith is one of the most important buildings while trying to build a specific deck. Through a filtering system, players can try to create specific cards, by entering the set, mana value, colors and rarity of one or more cards. The more filters you use, the more expensive it gets.
There are other buildings for you to explore on your journey through Shandalar. Have fun!
Creating a new plane allows you to fully customize your Adventure mode experience, and can even be shared with the community. The most basic step in doing so, is to add a new directory in `...\YourIntelliJProjectFolder\forge-gui\res\adventure\`, copy the 'config.json' file from `...\YourIntelliJProjectFolder\forge-gui\res\adventure\common`, paste it into your new directory. Then copy the contents of `...\YourIntelliJProjectFolder\forge-gui\res\adventure\Shandalar\world` into your new directory. Congratulations, that is the bare minimum to create a new plane. If you tell IntelliJ to run the Forge project using the Adventure mode settings, you can load up Adventure mode, find your plane in the drop-down list, and load into it.
Of course, now many of you will come back here and say "nothing looks different". Don't worry, nothing is broken. As you haven't actually changed any files from their default settings, everything will load up in the exact same way as the default game. This is intentional, so that you can make as many, or as few, changes from the default Shandalar plane as you like, without breaking anything. Look to the other tutorials for guides on how to change each piece of a plane to match your desire.
## Tutorial 2: A New Look (Creating your first map)
Okay, you've got a new plane, now let's make that plane our own. This tutorial is going to teach you how to make a very basic map and get it into the game in your plane. This is a much lengthier tutorial, so we are going to split it up into parts.
1. [Part 1: Setting up Tiled](https://github.com/Card-Forge/forge/wiki/Tutorial-2-A-New-Look#part-1-setting-up-tiled)
2. [Part 2: Building a Basic Map](https://github.com/Card-Forge/forge/wiki/Tutorial-2-A-New-Look#part-2-building-a-basic-map)
3. [Part 3: Adding the Map to your Plane](https://github.com/Card-Forge/forge/wiki/Tutorial-2-A-New-Look#part-3-adding-the-map-to-your-plane)
4. [Part 4: Put it to the Test!](https://github.com/Card-Forge/forge/wiki/Tutorial-2-A-New-Look#part-4-put-it-to-the-test)
### Part 1: Setting up Tiled
To make everything work smoothly, we need to do some set-up of your Tiled project. First, in IntelliJ, navigate to your plane directory (in our case, we named it Test) like so `...\YourIntelliJProjectFolder\forge-gui\res\adventure\Test\`, and create a new directory named 'maps'. Inside that directory make another directory called 'map'. Now open up Tiled, click on New Project, and navigate to the maps directory you just created, (the one with the "map" directory inside it.) Create a new project by picking a name in the file name line, (it is recommended you name it the same as your plane, for ease of bug solving,) and click save.
Next we need to tell Tiled where to find the files Forge uses. Click on Project > "Add Folder to Project...", navigate to `...\YourIntelliJProjectFolder\forge-gui\res\adventure\common\maps`. Select the 'obj' directory, and click "Select Folder". If you are planning to use any of Forge's tilesets, then do the same thing, but this time selecting the 'tileset' directory from `...\YourIntelliJProjectFolder\forge-gui\res\adventure\common\maps` aswell. This adds access to the various objects Forge uses to actually interact with the player. (Enemies, rewards, map transfers, etc.) The tilesets provide the visual appearance of your map, _as well as the collision mapping_. **_Any of these you intend to modify, you must create a new entry in an appropriate subdirectory inside your own plane's directory!_** Otherwise you will be changing it for every single map in every plane that touches those objects or tilesets, which quickly causes unintended consequences. (Further details on how to modify these items, what the subdirectory should look like, and what needs to remain unchanged to be properly read by Forge, will be described in their specific Wiki pages. For now, just keep this information in mind.)
Next click on Project > "Project Properties..." In the 'Paths & Files' section you will a text bow that says 'Extensions Directory' and three dots to the right. Click on those three dots, and navigate to `...\YourIntelliJProjectFolder\forge-gui\res\adventure\common\maps`, select the extensions directory, and click the "Select Folder" button. Then click on "OK". This adds the extensions written for Forge to your project. Without this, your maps will throw errors when Forge tries to do certain object actions, and crash the game. (Learn from my fail on this one.)
Voila! That is the minimum work necessary to prepare your project for adding, or editing, maps unique to your plane.
### Part 2: Building a Basic Map
Alright, so you've got your fancy project, and while this gray screen with a view of multiple folders on the left is cool and all. We know you really came here because you wanted to actually _make a new map_. (Well, technically, you may want to just slightly modify an existing map. But to have it unique to your plane, it will still be effectively a new map in the eyes of IntelliJ.) So let's get to it, let's make a very basic map for Forge. Step one, click the fancy, tempting, "New Map..." button I know you've been staring at. (And possibly already clicked, then came back here to see what you need to do on that next window.) This will bring up a new dialog box, with a bunch of options.
1. Orientation should be Orthogonal
2. tile layout format should be CSV
3. Tile render order should Right Down
Those are the Tiled defaults for those options at the time of this writing, but I want it here in case that changes.
Next up we have the Map size section, the bigger the map, the more you can add, and the longer it will take you to finish. I recommend for our first map, a simple 15 by 15 tiles. You'll make bigger maps, but for your first, don't get overwhelmed. **_DON'T CLICK OK YET!_** I know you want to get started, but we have to change a couple more things. The Tile size needs to be set to 16 wide by 16 high. This is the Forge standard, and the tilesets use it too. Different settings can lead to very unintended side effects. **NOW** you can click "OK".
Welcome to the gray box that is Tiled's default map menu. There's a lot here, but we are going to build a map together, so relax and just follow along. First off, you will often find yourself in need of multiple layers, otherwise your map is simply not going to look good. So, on the right side, you will see a small box with a text line that says "Tile Layer 1". Double click that line, and rename it to "Ground" without the quotes. Next, right-click and select New > Tile Layer. Name this one "Clutter". Do it again, but name this one "Walls". Finally right-click one more time and select New > Object Layer, name this one "Objects". This gives a basic series of layers to allow us to make a map. You can add more if really needed, but try to limit yourself to six or fewer Tile layers to preserve loading speeds. **Only have ONE object layer**, having a second one will confuse the heck out of Forge.
Now that we have our layers, let's make sure they are in the right order. Click and drag each layer so that, from top to bottom, they rea: Objects, Walls, Clutter, Ground. In tiled, like many programs, tiles on layers farther up will appear on top of ones below them. Objects on top ensure nothing gets lost, or accidentally put under a wall, and can easily be seen. Next up, while we have an Object layer, we need to actually tell Forge what layer to render sprites on. (This allows you make roofs or arches, etc, that a character can walk "under"... it also tells Forge where to actually _put_ the sprite, since it doesn't actually default to the Object layer.) For our map, we want the player to be walking on top of any dirt or grime we add to the map, so left click on the Walls layer. On the left side of the screen, you'll see the properties toolbox. Scroll down to the bottom and you will see the "Custom Properties" line. Right click > Add Property. Click the drop-down that says "string" and choose "bool". In the text box, type "spriteLayer" without the quotes. **This is a value read by Forge, so it is case sensitive. Lowercase 's', uppercase 'L', no spaces.** Click "OK". You will see it has added the property to the custom properties, with a check-box. The box is unchecked, check it. (If you had not added this property, made it a bool, set it's name correctly, and checked the box... Any attempts to load this map would cause Forge to bug-out pretty hard and require a restart to just work right again.)
Now that our map-space is set-up, let's go ahead and save all this work we have done. File > Save As. Open your "map" directory inside the "maps" directory, this is a directory of all your maps you are going to use. To save yourself future head-aches, organize maps into sub-directories from the get-go. In our tutorial case, I will be making a beach, so first I will right-click > new > folder while inside of 'map' and name it "beach". Then I will double-click on that "beach" folder. In this sub-directory I name my tutorial map as "test", but you should name your map as something you'll remember it by, then click save. Now that, that's done, let's get around to the art part of this. Click on the Ground layer. You'll notice you can move your mouse over the map and it'll turn red or blue depending on it you are inside the tile space or not. But no matter where you click, nothing happens. That's because we need to actually choose a tileset to use. On the far left side of the screen, you'll see the various folders we imported into our project. Open the tileset folder, and double click on "main.tsx". This will open a new tab, that shows the entire contents of the "main" tileset from Forge. _Since we don't plan to actually make ANY changes to this tileset_ (gentle reminder.) Simply click on the tab for your map (test.tmx in my case,) and you'll see the "main" tileset has appeared in the bottom-right corner of your screen.
Since I chose a beach, I'm going to make a beach for this tutorial, so I'm going to click on the sand-colored tile near the top-left of the "main" tileset, right next to the default empty one. If you move your mouse back over your map, you will see it shows that sand-colored tile for every square you move it over. If you click, it will even place that color on that tile. While you could individually click on each tile one at a time, for filling in large areas that's much more exasperating. Clicking and dragging will simply apply each tile the mouse moves over. So, instead, along the top, you will see a paint-bucket. Click on that. now if you move your mouse back to the map, it'll show a ghostly tan color across the entire map, minus any squares you already clicked on. (The highlight will only apply to every tile of the same type that you hover over, which is not completely separated from the mouse by a different tileset tile. If you are confused, this will make more sense as you make or modify maps.) Click on the empty space, and it will fill in the entire map with our sandy ground.
Now, a blank sandy beach may be more than you had before, but it's not that interesting, so let's add some detail. Click on the Clutter layer. Then, on our "main" tile-set on the bottom right, click on the water tile just below the blank one in the top left. If you move your mouse back to the map, you will see it wants to change every tile to water. That's because we are still in Bucket Fill mode. On the top of the window, you'll see a rubber stamp, two tools to the left of the pain bucket. Click on that. Now we are once again designing our map one tile at a time. I'm going to draw out some water tiles along the edges of the map, leaving just a couple sandy ones at the bottom center of the map.
Now, The map is clearly a sand-bar jutting out into water, but it looks pretty rudimentary, even for Forge. As such, let's click on the Walls layer. Then, on the tile-set on the bottom right, there are a bunch of sandy edges looking like they are meant for the edge of water. I'm going to carefully select various tiles from among them and make my coast-line look much nicer. Exactly which tiles where will depend on how you built the previous layer. If you ever place a tile and it doesn't look right. You can click on the Eraser tool at the top of the screen, and then click that tile on your map. As long as you are on the right layer, it will erase that tile.
Huzzah! You've made you little sandbar... But how can you be certain where players and enemies can walk? Well, that's called Collision. Go to View > and make sure there is a check-mark in "Show Tile Collision Shapes". The water tiles will have much strongly defined border now, and depending on your sand borders and how you built it, you may have new lines in them, or not. Collision is defined by the tile in the Tileset. Each tile in the tile set either has collision shapes, or doesn't. **If you are not happy with the collisions as-is, you will need to either change your map some, or make your own tileset.** As a reminder DO NOT MODIFY THE TILESETS FROM MAIN FORGE. If you need to make changes, look for my future tutorial that explains building your own tile-set and modifying it.
Alright, you have a map, it has water, it has collision... Still looks pretty empty. You could add more details on various layers to make it look nicer, but that won't change the most important things. Namely, there's nothing to _do_ on our sandy beach. Let's fix that. On the left side of the screen, open up the "obj" folder, this has all the objects in Forge. Since they are all Objects, let's click on the Objects layer. Now, click and drag a "gold.tx" object from the obj folder, onto our sand. (make sure its on a portion of sand that isn't blocked by collision.) Unlike the tile layers, objects don't have to be centered to any tile. They can even sit on a grid intersection without issue.
Alright, we got some gold on our map, but that's a bit boring. let's spice it up, by adding an enemy. So click and drag an "enemy.tx" onto the map. Now, all our objects have many properties that make sure they work. But most of them come pre-populated to some degree, so you can just drag-and-drop them. Enemies, however, are diverse and different enough we need to actually define some things about them in order to even function. So click on your placed enemy object, and scroll down the left side to the Custom Properties section. In here, the only option you _have_ define, is the "enemy" property. (Remember, the one in Custom Properties, not the space earlier in the normal properties... Again, learn from my mistakes.) In our case, click on the text box to the right of "enemy", and type "Ghost" without the quotation marks. (While a later tutorial will give more details on all the properties, just know that for this one. It is looking for an entry in your plane's "enemies.json" file. If your plane does not have that file, like ours, it will instead look for the entry in the enemies.json file found in `...\YourIntelliJProjectFolder\forge-gui\res\adventure\common\world`... A later tutorial will guide you through making your own enemies.) You have now defined the enemy, but as-is, it will just stand in place, even if a player gets near. Let's liven things up a little, and make our ghost a bit more annoyed and less sleeping on the job. Go down to the "pursueRange" custom property, and set that text box to 50. Then scroll down further to the "threatRange" box, and set that, also, to 50. Now, if the player gets within 50 pixels of it, the ghost will chase them. The ghost will only move to a distance of 50 pixels from their starting point, doing so, however. (For a more lively enemy that patrols, or jump out, etc. You can find that information in the Configuring Enemies portion of the Wiki.)
Alright, we got a map, we got a reward, and we got an enemy to protect that reward. All good, except, Forge has no idea where to spawn a player who enters the map, and no idea where the exit to the map is. So, go and drag an "entry_up.tx" object to the little entrance of our sandbar, and resize it so that the arrow takes up the majority of the entry space. Congratulations, your map is made, don't forget to **_SAVE_**.
### Part 3: Adding the Map to your Plane
Alright, so you have a map you want to play, it's saved in your plane, why can't you find it? Well, that's because Forge doesn't **actually** know it's ready to _be_ found yet. We need to get some "biomes" and "points of interest" added to your plane, aswell as. So, first, let's give them a place to go in your plane. Go to your plane's directory in IntelliJ, and open the 'world' sub-directory. Inside of 'world' create a new subdirectory called 'biomes'. Navigate to `...\YourIntelliJProjectFolder\forge-gui\res\adventure\common\world\biomes` and copy the following files to your plane's biomes directory, 'base.json' and 'colorless.json'. We also need to copy the 'points_of_interest.json' from `...\YourIntelliJProjectFolder\forge-gui\res\adventure\common\world` to our custom plane's 'world' directory. Now, open up the 'points_of_interest.json' from our plane's 'world' sub-directory. Inside you will find all the maps used by Forge. If you want to add a map to the list, but keep all the others, you would add it's info the existing json. But for the sake of simplicity in this tutorial, we are going to delete everything inside the json except for the following.
For this tutorial, we need to know the following details.
"name" is the map name in Tiled, so we will change `"name": "Aerie",` to `"name": "YOUR MAP NAME",` ("Test" in our case.)
"displayName" is the name that appears when the player enters a map. So we will change `"displayName": "Aerie",` to `"displayName": "YOUR MAP DESCRIPTION",` (in our case, I'm going with "Fanciest Beach").
"count" is how many of this map can spawn in the overworld. Since we want to actually find it, let's change `"count": 1,` to `"count": 30,`
and "map" is where we actually STORED the map file. So we are going to change `"map": "../common/maps/map/aerie/aerie_0.tmx",` to `"map": "../YOUR_PLANE_NAME/maps/map/YOUR_FOLDER_NAME/YOUR_MAP.tmx"` . For my tutorial, that becomes `"map": "../Test/maps/map/beach/test.tmx",`.
You can leave the rest of the Aerie code-block alone for this tutorial. If you followed me exactly, "points_of_interest.json" file should now look like this.
Now, since we want to play our new map, we still need to make sure it spawns in Forge. Go to "colorless.json" file we copied into our Plane's 'biomes' directory. Scroll down until you see the `"pointsOfInterest":` array. We are going to delete everything in this array except for `"Spawn",` and then we are going to add `"YOUR_MAP_NAME"` ("Test" in my case) immediately afterwards. If you copied this tutorial exactly so far, it would like this.
```
"pointsOfInterest": [
"Spawn",
"Test"
],
```
### Part 4: Put it to the test!
Alright, time for the fun part. If you've come this far, everything is ready to go. Tell IntelliJ to fire up Forge, go into Adventure mode. If you hadn't previously, set your plane to your custom plane, (and restart Forge in that case.) You'll still have the tutorial quest prompts and spawn to work through. But when done, when you leave the portal, things will immediately be visibly different. If you don't see a dungeon right away, bring up the Map, track down your dungeon icon in the Wastes biome, and walk on over. (You may need to avoid a few spawns along the way.) Enter, and, assuming you following perfectly, you'll show up in your new map. If you have questions or run into any problems, reach out to me (shenshinoman) on the Forge discord and I will be happy to help.
After you've done that, you may notice the entire world is devoid of any map except the overworld, the spawn to the old man, and the map you just built. While this is nice for testing a new map, we should probably add a city back in, that way we actually have a place to heal up and test other changes to the plane. To make life really easy, we are just going to add a basic town back to the "Wastes". Navigate to ``...\YourIntelliJProjectFolder\forge-gui\res\adventure\common\world' and open up the "points_of_interest.json" file. Towards the bottom we will find the code block for "Waste Town Generic", select it, and copy it into the "points_of_interest.json" inside your plane's 'world' directory. So, if you've followed along precisely, your "points_of_interest.json" should look like this.
Now we need to go and tell the game to actually spawn this town. Inside your plane's "world\biomes" directory, open back up the "colorless.json" file, and add "Waste Town Generic" to the `"pointsOfInterest"` array. If you followed along, that array should now look like this.
```
pointsOfInterest": [
"Spawn",
"Test",
"Waste Town Generic"
],
```
Now if you open up your plane, you have a bunch of dungeons and a bunch of towns. Congratulations, you have completed this tutorial. Pat yourself on the back, cus it was a long one. (Also, don't forget to save if your system doesn't auto-save.)
## Tutorial 3 Configuration, (Configuring your Plane)
Alright, our third tutorial will be a simpler one again. Customizing your plane. A common desire when making a new plane, is to change things such as which sets are available to play, or which cards an be bought or gained as rewards. Maybe you want to be able to more easily build super powerful decks from the get go, or just have entirely new decks. All these settings, and more, can be adjusted from the 'config.json' file in your plane's directory. For this tutorial, to get a basic idea of what things can be changed, we are going to make some minor adjustments to the plane. Further details on other changes and what they do will be covered in other sections and tutorials.
For now, in IntelliJ, open up the 'config.json' file found in `...\YourIntelliJProjectFolder\forge-gui\res\adventure\YourPlane`, as a reminder for our tutorials, I am using a directory called Test. Inside the config file, there are a large number of entries, but we are going to start with the `"restrictedCards"` array. After-all, we are here to have fun, so let's just bring in almost all the craziness. We are going to delete every entry in the array, _except_ for "Black Lotus"... Why not Black Lotus you ask? Because it's too rare, clearly. (Or, maybe just because it is the first entry and I'm being lazy, and keeping one entry is a reminder how this array is formatted.) So, when you are done, the array should look like this.
```
"restrictedCards": [
"Black Lotus"
],
```
To better explain what that array does, is any card in the array will not appear in any generic rewards or shops. This is the closest thing to banning individual cards that one can. However, it should be noted that if those cards are ever specifically added as a reward or a shop's content. Doing so overrides this setting. Essentially allowing you to award cards for very special cases, or hide a fancy shop somewhere in your game.
Now, restricting one card at a time is a very laborious process, especially if there are a large number of cards you want removed. In many cases, you will want entire sets removed from your game, which brings us to the `"restrictedEditions"` array. Any card whose set code appears in this array will be restricted from the game in the exact same way as `"restrictedCards"`. There's already several entries in here, but since I'm making a really wacky unbalanced plane here, we're going to remove every entry from this array, except for "UNF". Why not UNF you ask? Because it's a traitor and made some of it's cards legal and others not, clearly. (Or, maybe again it's because I'm lazy, and at the time of this writing, it's the last entry in the list. As well as once again keeping one entry as a reminder how this array is formatted.) So if you follow my flawless example, it will look like this.
```
"restrictedEditions": [
"UNF"
],
```
Now that we've opened the floodgates for our awards, we could go have fun in the wacky world. However, our Inn's drafts and jumpstarts won't be as open as the rest of the game still. Why? Because the array `"restrictedEvents"` tells our Inn what "events" (aka sealed and draft editions) it can't spawn. Just like `"restrictedEditions"` any set in this array won't appear for draft events (but if it is not in the `"restrictedEditions"` it can still appear as rewards.) As such, let's go ahead and open those flood gates further. In my infinite wisdom, I have decided, for no specific reason at all, that we will remove every set from this list except for "CMM". (Yes, you got the picture, I'm lazy but trying to be helpful still.) If you followed me so far, the entries between `"colorIDNames"` and `"difficulties"` should appear as follows.
```
"restrictedCards": [
"Black Lotus"
],
"restrictedEditions": [
"UNF"
],
"restrictedEvents": [
"CMM"
],
```
Now if you load up the game at this timeframe, and go hopping through towns, you'll notice the occasional card you hadn't seen before. This is good, but man there are a lot of cards in Magic, and it can take a while to find ones to show you've made a change. For a tutorial, that's bad. Also, if you want to make a plane like "Shandalar Old Border" or "Innistrad", you'd have to go through and add every unwanted set to the `"restrictedEditions"` array. including updating this array every time Wizards of the Coast releases another set, and that's **worse** than bad... Luckily, we have a solution for this. We are going to add two new arrays to our 'config.json' file. Where the previous arrays act as a black-list for the entries, the following arrays are a overwrite. If they are present, the plane will only spawn cards and events found in them. The first one is `"allowedEditions"`. For this tutorial, I am going to go completely unhinged and risk making some of our games unstable, but what mod never comes unglued? If you didn't pick up the hints, my array is going to add `"UNG"`, `"UNH"`, and `"UST"`. So my array looks as follows.
```
"allowedEditions": [
"UNG",
"UNH",
"UST"
],
```
The second array I'm going to add is the `"allowedJumpstart"` array. This changes which Jumpstart sets are available, and will allow us to still have them even when the previous arrays have blacklisted them in any way. For this tutorial, I know we all want to get a jump on things, so I'm just going to add `"Jumpstart"`.
```
"allowedJumpstart": [
"Jumpstart"
],
```
You might have noticed that unlike the previous arrays, this one uses the set name, not code. This is a current limitation in Forge, and one we will have to live with. Luckily, there are far fewer jumpstart sets than than other sets. (**NOTE: Adding a set to this list that does not have any official Jumpstart packs will NOT enable them for jumpstart events.** Some of you who have tooled around will have noticed there are Jumpstarts in "Innistrad" that don't exist in normal jumpstarts. Well, that is for a much later tutorial. My apologies.)
As a final note, while this changes the rewards from enemies and shops, as well as what can appear in the Inns. It has no bearing on your starting decks, or enemy decks, just the rewards and shop options. So yes, if you followed along so far, you will playing with 'normal' cards, against normal cards, to earn Silver-Bordered cards.. yeah, it's a bit crazy, but that works... For those who want to customize their plane even further, the next tutorial will focus on modifying both starting decks, and enemy decks. See you there. For now, enjoy, and again. if you have any problems, please reach out me "Shenshinoman" on the discord and I will be happy to help.
Card images are assets used to represent the real cards in game. You DO NOT need images to play forge, however representing real cards is a nice ability forge provides. These images can be any image set you like, or custom images too.
Primarily there are two types of images you'll care about; cards, and tokens.
**Cards** - are the primary card image files, and can be generic (as all cards of the same name have the same rules) or per set since there may be different art work in different editions. Typically these are scans of the original cards and are provided by forge's FTP, or scryfall. You can customize a full generic set, or any edition if you desire... (you could for example, blur every card image and play a literal "blind" or "farsighted" game.)
**Tokens** - are the images for the cards replacing a generic "1/1 zombie" for example. These are less frequently updated, and are typically the bulk of what is missing when doing an audit. However, these are probably where the more true "custom" replacements are available, with either custom artwork, or modified of other existing.
# Downloading
Due to charges in Forges hosting and scryfall terms you can no longer predownload card images. Turn on auto download in forge to download card images when first viewed.
## In Forge Auto Download:
**Download Missing Images - Setting**
- This will download the images from the sources as the game requests the image in situ.
- This can be useful if you don't want to have copies of every card... You can do small pre-caching by loading your decks in the deck editor prior to playing to download just those images.
> I provide my site for free for bulk downloading the entire image catalog. So you don't need to give those spam sites more advertising spots. If the server is loaded bandwidth is shared, right now it's not heavily used so please feel free to download the 4+gb zips, or the individual zips if you need sets. They are the images Kev has uploaded to my site, and the Zips are updated nightly automatically.
**(I'm not gatekeeping, please if you have a private location for bulk downloads or for alternate or custom arts, you can update this wiki too or let us know in the discord. I'll be happy to update the wiki page with additional sources.)**
If you have an older Android device for increased performance or to save bandwidth it might be a good idea to use lower resolution images instead: https://www.slightlymagic.net/forum/viewtopic.php?f=15&t=29104
# Storage
Card images are stored in `pics/cards`, and tokens in `pics/tokens`, in the Cache folder for forge:
If you don't care about the edition's version of cards, images can be stored in the root directory of the cards folder.
`/cache/pics/cards/`
If you want the edition's versions of the cards, they need to go under the edition's code subfolder.
`/cache/pics/cards/AFR` for example for Adventures in the Forgotten Realms.
# File Naming
**File Names:**
- Cards file names follow a simple principle: `Card Name#.border.ext`
-`Card Name` - Card Name with spaces.
-`#` - Alternate Art number; if more than one art exists for the card.
-`border` - Border Type; fullborder, crop. (I don't know all of them.)
-`ext` - Extension, jpg or png are supported.
**Alternate images:**
Alternate images are defined as cards with the same name in the set's edition file, if the edition file does not have the alternate listed forge will not see the alternate there!
**Standard Alternate Arts:**
So for example the AFR set (as most sets) shows these 4 versions of swamp;
```
270 L Swamp @Piotr Dura
271 L Swamp @Sarah Finnigan
272 L Swamp @Titus Lunter
273 L Swamp @Adam Paquette
```
The file naming would be represented by a number after the name:
```
Swamp1.fullborder.jpg
Swamp2.fullborder.jpg
Swamp3.fullborder.jpg
Swamp4.fullborder.jpg
```
**Additional Alternate Arts:**
They may also be listed separately as "extended arts", "showcase", or "borderless" in the same editions file:
```
[cards]
90 U Black Dragon @Mark Zug
```
and
```
[borderless]
291 U Black Dragon @Jason A. Engle
```
Where the files are:
```
black dragon1.fullborder.jpg
black dragon2.fullborder.jpg
```
**Forcing an Alternate:**
Renaming and creating a second of an existing card **will not work**, for example creating two "Burning hands" which does not have alternate art;
```
burning hands1.fullborder.jpg
burning hands2.fullborder.jpg
```
Forge will not see either of those, and will probably download the missing `burning hands.fullborder.jpg` for you. Similarly adding a 3rd black dragon `black dragon3.fullborder.jpg` will **not** work either.
In most cases, each AF subclass implements both the Spell and Ability.
Much of the code is shared, so creating the data object will look very similar.
- **AB** is for Activated Abilities
- **SP** is for Spell
- **DB** is for Drawback and many abilities that are subsidiary to other things, like replacements. They are only used to chain AFs together, and will never be the root AF
- **ST** is for Static, this gets used in case the API should resolve without using the stack<br/> (e.g. the unique *Circling Vultures* special action is directly implemented in the script this way)
>*NOTE:*
> - these factories are refactored from time to time (often to adapt to new sets), so while some entries could be slightly outdated, the base information should still be correct
> - a few factories also have _*All_ variants, these are slowly being phased out
> - some parameters are only added for very exotic cards, these won't be included here to keep the focus on understanding the general concepts of scripting
> - when in doubt you can always cross check with the [available APIs](https://github.com/Card-Forge/forge/tree/master/forge-game/src/main/java/forge/game/ability/effects) code
# Common Parameters
## Cost / UnlessCost
`Cost$ <AbilityCost>` is the appropriate way to set the cost of the ability. Currently for spells, any additional costs including the original Mana cost need to appear in the Cost parameter in the AbilityFactory. For each card that uses it, the order in which the cost is paid will always be the same.
Secondary abilities such as the DB executed by triggers or replacements (usually) don't need costs. (This is one reason to use DB over AB in these cases.)
Read more about it in [Costs](Costs)
## ValidTgts / Defined
Most effects need to know (at least implicitly) which players or objects they're trying to affect. There are two different ways for that:
-`ValidTgts` will need to be used for abilities that target
- if your ability instead describes on what it's applied use `Defined`
Read more about it in [Affected / Targets](Targeting)
## Restrictions / Conditions
Restrictions limit when abilities can be put on the stack and Conditions apply during resolving. Common examples are Putrid Leech's only activate this once per turn or different cards that can activate from Zones like the Hand or the Graveyard.
Read more about it in [Restriction](Restrictions)
## SpellDescription
SpellDescription is how the text of the ability will display on the card and in the option dialog for cards with multiple abilities.
The SpellDescription for secondary abilities (both AB and DB) is now displayed when (and if) the ability prompts for user input in the prompt pane so it is useful to put some nice text there.
## StackDescription
*(Optional)* StackDescription is the description the ability will have on the stack. This is automatically generated by the effect, but may be overridden using this parameter. This is sometimes needed with complex effects, when the generated text can't handle some details. Properties of the spell can be accessed like this: {c:Targeted}. You can reuse the spell text by just putting `SpellDescription` or `None` to leave it empty.
## Remember*
Remembering is often needed when a card becomes a new object, which is then further affected by the ability. Typical example: [Flicker](https://github.com/Card-Forge/forge/blob/master/forge-gui/res/cardsfolder/f/flicker.txt)<br/>
Because cards keep their remembered parts when changing zones manual [cleanup](#Cleanup) is usually required.
## AI params
`IsCurse$ True` - For effects that are normally treated positive e.g. Pump
`AITgts$ BetterThanEvalRating.130`
# Factories (in Alphabetical Order)
## AlterLife
AlterLife is for Abilities that Alter a player's life total.
### GainLife
Have a player gain the specified amount of life.
`A:AB$ GainLife | Cost$ T | LifeAmount$ 1 | SpellDescription$ You gain 1 life.`
LifeAmount$ is required. This is how much life you will gain.
Defined is optional, if it appears the defined player(s) gain life.
Target is optional, if it appears and Defined doesn't then targeted player(s) gain life.
### LoseLife
Have a player lose the specified amount of life.
`A:AB$ LoseLife | Cost$ Sac<1/CARDNAME> | ValidTgts$ Player | TgtPrompt$ Target a player to lose a life | LifeAmount$ 1 | SpellDescription$ Target player loses 1 life.`
`A:SP$ LoseLife | Cost$ 2 B | Defined$ Opponent | LifeAmount$ 2 | SpellDescription$ Each opponent loses 2 life.`
LifeAmount$ is required. This is how much life will be lost.
Target is optional. If Target doesn't appear then Defined will be used.
Remember, if Defined is missing, the default for Players is "You"
Part of resolving sets the **SVar AFLifeLost** to the amount of life lost by all players.
### SetLife
SetLife sets one or both player's life total to a specified value (i.e.
"your life total becomes 20" or "Target player's life total is equal to
the number of cards in your graveyard").
`A:SP$ SetLife | Cost$ 7 W W | ValidTgts$ Player | TgtPrompt$ Select target player | LifeAmount$ 20 | SpellDescription$ Target player's life total becomes 20.`
Parameters:
LifeAmount (required) - the value to set the life total(s) to
Defined is optional. If it exists, it will be used. Target is optional.
If it exists and defined doesn't it will be used. Default player is "You".
### ExchangeLife
ExchangeLife switches the Life total of two players.
`A:AB$ ExchangeLife | Cost$ 6 T | ValidTgts$ Player | TargetMin$ 2 | TargetMax$ 2 | TgtPrompt$ Select target player | SpellDescription$ Two target players exchange life totals.`
One of Defined or Target is required, since there needs to be two
Players exchanging life If Defined it will be used. If Target exists and
defined doesn't it will be used.
If there aren't two determined players by the SA, the activating player is added as the second player.
## Animate
Animate handles animation effects like "This card becomes a 5/5
green creature with flying until end of turn." It is designed to handle
color changing, type changing, P/T setting, and granting/removing abilities.
`A:SP$Animate | Cost$ G | ValidTgts$ Land | TgtPrompt$ Select target land | Power$ 3 | Toughness$ 3 | Types$ Creature | SpellDescription$ Until end of turn, target land becomes a 3/3 creature that's still a land.`
`A:AB$Animate | Cost$ 1 B | Defined$ Self | Power$ 1 | Toughness$ 1 | Types$ Creature,Skeleton | Colors$ Black | Abilities$ ABRegen | SpellDescription$ CARDNAME becomes a 1/1 black Skeleton creature with "B: Regenerate this creature" until end of turn. It's still a land. (If it regenerates, the next time it would be destroyed this turn, it isn't. Instead tap it, remove all damage from it, and remove it from combat.)`
`SVar:ABRegen:AB$Regenerate | Cost$ B | SpellDescription$ Regenerate CARDNAME.`
`A:AB$Animate | Cost$ 2 R G | Defined$ Self | Power$ 3 | Toughness$ 3 | Types$ Creature,Elemental | Colors$ Red,Green | Triggers$ TrigAttack | SpellDescription$ Until end of turn, CARDNAME becomes a 3/3 red and green Elemental creature with "Whenever this creature attacks, put a +1/+1 counter on it." It's still a land.`
`SVar:TrigAttack:Mode$ Attacks | ValidCard$ Creature.Self | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME attacks, put a +1/+1 counter on it.`
- Power (required) - the power to assign to the animated card
- Toughness (required) - the toughness to assign to the animated card
- Types (optional) - the additional types to give the animated card;
comma delimited
- OverwriteTypes (optional) - set to True if the animated being should
have these types **instead** as opposed to **in addition to**
- RemoveTypes (optional) - a list of types to Remove from the animated
card
- ChosenType (optional) - overrides types before it and just will add
the ChosenType
- Keywords (optional) - a " & " delimited list of keywords to give the
animated being (just like AB$Pump)
- HiddenKeywords (optional) - a " & " delimited list of hidden
keywords to give the animated being (just like AB$Pump)
- RemoveKeywords (optional) - a " & " delimited list of keywords to
remove from the animated being (just like AB$Debuff)
- Colors (optional) - a comma-delimited list of Colors to give to the
animated being (capitalized and spelled out) (ChosenColor accepted)
- Abilities (optional) - a comma-delimited list of SVar names which
contain abilities that should be granted to the animated being
- OverwriteAbilities - Remove Abilities from animated being
- Triggers (optional) - a comma-delimited list of SVar names which
contain triggers that should be granted to the animated being
- OverwriteTriggers - Remove/suppress triggers from animated being
- staticAbilities (optional) - a comma-delimited list of SVar names
which contain static abilities that should be granted to the
animated being
- OverwriteStatics- Remove static abilities from animated being
- OverwriteReplacements - Remove replacement effects from animated
being
- RemoveAllAbilities - Remove all Abilities, Triggers, Statics, and
Replacement effects
- sVars(optional) - a comma-delimited list of SVars that should be
granted to the animated being
- Duration (Default is end of turn)
- Permanent
- UntilEndOfCombat - if the effect should last only until End of Combat instead of End of Turn
- UntilHostLeavesPlay - if the effect should last as long as the host is still in play
- UntilYourNextUpkeep
- UntilControllerNextUntap
- UntilYourNextTurn
Target is optional, will be used if possible. Defined is optional, will be used if no Targets (Self by default)
## Attach
Attach is being used directly only for Auras, primarily for Aura Spells, but also for Auras entering the battlefield by some effect.
`AB$ Attach | Cost$ R R | ValidTgts$ Creature | AILogic$ Pump`
Parameters:
- Object (optional) - This is the Object that will be Attached
(generally the Source Card for Auras)
- AILogic - AI Logic tells the AI which base AI code it should use for
Attaching
- GainControl - Gains Control of the Attached Permanent (Control
Magic)
- Curse - A Generic Curse the AI has a handful of checks to see
what the most appropriate Target is.
- Pump - A Generic Pump. The AI has a handful of checks to see
what the most appropriate Target is.
- ChangeType - For Attachments that change types. Evil Presence is
a good example. This logic should be expanded.
- KeepTapped - For Attachments that keep a Permanent tapped. The
AI will also check for a few things like Vigilance, and another
KeepTapped Aura. Paralyzing Grasp is a good example.
Attach separates the actually granting of abilities from the attaching to permanents to streamline how things work.
## BecomeMonarch
## Bond
Soulbonding two creatures. Only used internally by the engine.
## Branch
Sometimes, an ability might do certain things when a specific condition is true, and other things if not. This can be implemented by using `Branch`.
The branch evaluates the SVar specified by the property `BranchConditionSVar`, using the comparison defined with `BranchConditionSVarCompare` (such as `GTY`, `LT1`, etc). Depending on whether the condition evaluated to true or false, the subability defined by `TrueSubAbility` or `FalseSubAbility` is executed.
The example below is for "Composer of Spring", which allows either a "land" or a "land or creature" to be put on the battlefield, depending on the number of enchantments in play under your control.
This allows cards that have a mode to be chosen to occur after a trigger.
Parameters
- CharmNum - Number of Modes to Choose
- Choices - A Comma delimited list of SVars containing the Modes
## Choose*
These can be used to chain effects together. However for common cases many effects already support this directly, e.g. `PutCounter | Choices$``.<br />
Besides making the script shorter using such shortcuts usually also helps the AI making better use of the effect.
### ChooseType
This can be used when you are asked to choose a card type or creature type.
- Type - Required - Can be Card or Creature
- InvalidTypes - Optional - Use to specify any type that cannot be chosen (ex: "Choose any creature type except Wall")
The Defined is for target players.
## Clash
This AF handles clashing. It takes two special parameters: WinSubAbility and
OtherwiseSubAbility. They are both optional and work the same way,
namely that it contains the name of an SVar that in turn contains a
drawback to be executed. The example below is for Release the Ants.
`A:SP$ DealDamage | Cost$ 1 R | Tgt$ TgtCP | NumDmg$ 1 | SubAbility$ DBClash | SpellDescription$ Release the Ants deals 1 damage to target creature or player. Clash with an opponent. If you win, return CARDNAME to its owner's hand.`
A non-functional, maintenance AF used for Cleaning up certain Variables before a Spell finishes Resolving.
Parameters
- ClearRemembered$ (optional) Set to True to clear this card's
remembered list. Generally useful for Cards that Remember a card, do
something to it, then need to forget it once it's done.
- ClearImprinted$ (optional) Set to True to clear the list of
imprinted cards.
- ClearChosenX$ (optional) Set to True to clear the chosen X value.
- ClearTriggered$ (optional) Set to True to clear any delayed triggers
produced by this card.
- ClearCoinFlips$ (optional) Set to True to clear the remembered coin
flip result.
- ClearChosenCard$ (optional) Set to True to clear the chosen cards.
- ForgetDefined$ (optional) If present, remove the specified cards
from this card's remembered list.
## Control
### GainControl
Example: Act of Aggression
`A:SP$ GainControl | Cost$ 3 PR PR | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls. | LoseControl$ EOT | Untap$ True | AddKWs$ Haste | SpellDescription$ Gain control of target creature an opponent controls until end of turn. Untap that creature. It gains haste until end of turn.`
Parameters:
- NewController(Targeted player, if there is no target player, the
default is the ActivatingPlayer)
- AllValid(all valid types, no targets)
- LoseControl(LeavesPlay, Untap, LoseControl, EOT(end of turn))
### ControlExchange
### ControlSpell
## Copy*
### CopyPermanent
Copies a permanent on the battlefield.
Parameters:
- NumCopies - optional - the number of copies to put onto the
battlefield. Supports SVar:X:????.
- Keywords - optional - a list of keywords to add to the copies
- AtEOT - optional
- Sacrifice - set to this is copy should be sacrificed at End of
Turn
- Exile - set to this is copy should be exiled at End of Turn
### CopySpellAbility
Copies a spell on the stack (Twincast, etc.).
## Counter
Countering Spells or Abilities.
`A:SP$ Counter | Cost$ 1 U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | UnlessCost$ 3 | SpellDescription$ Counter target spell unless its controller pays 3.`
`A:SP$ Counter | Cost$ U | TgtPrompt$ Select target Activated or Triggered Ability | ValidTgts$ Card | TargetType$ Activated,Triggered | SpellDescription$ Counter target activated or triggered ability.`
`A:SP$ Counter | Cost$ G | TargetType$ Spell | ValidTgts$ Instant,Aura | TargetValidTargeting$ Permanent.YouCtrl | SpellDescription$ Counter target instant or Aura spell that targets a permanent you control. `
Parameters
- TargetType - Can be Spell,Activated,Triggered. If more than one,
just put a comma in between.
- ValidTgts - a "valid" expression for types of source card (if you
don't know what it is it's just "Card")
- TargetValidTargeting- a "valid" expression for targets of this
spell's target
- Destination - send countered spell to: (only applies to Spells; ignored for Abilities)
- Graveyard (Default)
- Exile
- TopDeck
- Hand
- BottomDeck
- Shuffle
## Counters*
Factories to handle counters on cards.
### Poison
Poison gives a player the specified number of poison counters.
`A:AB$ Poison | Cost$ B T | ValidTgts$ Player | TgtPrompt$ Select target player | Num$ 2 | SpellDescription$ Target player gets 2 poison counters.`
Parameters:
- Num (required) - the number of poison counters to give
Target is optional and used if available. Defined is optional and used if no Target (defaults to "You").
### PutCounter
Put any type of counter on a game object.
`A:AB$ PutCounter | Cost$ T | CounterType$ CHARGE | CounterNum$1 | SpellDescription$ Put a charge counter on CARDNAME.`
`A:SP$ PutCounter | Cost$ G | Tgt$ TgtC | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a charge counter on CARDNAME.`
Target is optional. If no Target is provided, the permanent will put counters on itself.
- CounterType (required) specifies the type of counter and should
appear in all caps. It should be one of the values in the Counters
enum.
- CounterNum (required) specifies how many counters will be put on
the chosen card.
### PutCounterAll
Put any type of counter on all valid cards.
- CounterType (required) specifies the type of counter and should
appear in all caps. It should be one of the values in the Counters
enum.
- CounterNum (required) specifies how many counters will be put on
the chosen cards.
- ValidCards (required) specifies the cards to add counters to.
### RemoveCounter
Remove any type of counter from a card.
Target is optional. If no Target is provided, the permanent will remove
counters from itself.
- CounterType (required) specifies the type of counter and should
appear in all caps. It should be one of the values in the Counters
enum.
- CounterNum (required) specifies how many counters will be removed
from the chosen card.
- UpTo is optional. If an effect states you may remove "up to X
counters", set this to True.
### RemoveCounterAll
Remove any type of counter from all valid cards.
- CounterType$ (required) specifies the type of counter and should
appear in all caps. It should be one of the values in the Counters
enum.
- CounterNum$ (required) specifies how many counters will be removed
from the chosen cards.
- ValidCards$ (required) specifies the card to remove counters from.
### Proliferate
No own parameters.
### MoveCounters
Used for cards that Move Counters on Resolution, requiring the Host card
to have Counters for the Move to occur.
Parameters
- Source - The Source of the Moving Counters
- Defined - The Destination of the Moving Counters
- CounterType - The type of counter to move.
- CounterNum - The number of counters to move.
## Damage
### DealDamage
Deal damage to a specified player or permanent.
`A:AB$ DealDamage | Cost$ T | Tgt$ TgtCP | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to target creature or player.`
NumDmg is required. This is the amount of damage dealt.
### EachDamage
## Debuff
Parameters
- Keywords
- Duration
An AbilityFactory for Removing Keywords, either temporarily or for longer durations.
## Destroy
These APIs handles destruction of cards on the battlefield.
`A:SP$Destroy | Cost$ 1 W W | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SpellDescription$ Destroy target artifact or enchantment.`
## Effect
Effect is an oddball of the AF family. Where usually AFs have similarities to each other to help with AI use, Effect doesn't fall under that jurisdiction. In general, an effect is some type of SA that
lasts longer than its resolution.
A good example is High Tide. For the rest of the turn, High Tide makes
all Islands produce an extra mana. It doesn't matter if the Island was
in play, if it turned into an Island after High Tide was cast, any of that.
`A:SP$ Effect | Cost$ U | Name$ High Tide Effect | Triggers$ IslandTrigger | SVars$ TrigMana | SpellDescription$ Until end of turn, whenever a player taps an Island for mana, that player adds U to his or her mana pool (in addition to the mana the land produces).`
`SVar:IslandTrigger:Mode$ TapsForMana | ValidCard$ Island | Execute$ TrigMana | TriggerDescription$ Whenever a player taps an Island for mana, that player adds U to his or her mana pool (in addition to the mana the land produces).`
`SVar:TrigMana:AB$Mana | Cost$ 0 | Produced$ U | Amount$ 1`
Effect is most similar to Token as it creates a pseudo-permanent, except
Effect creates in the command zone rather than the battlefield. It stays
active there for a set Duration.
Parameters
- Abilities,Triggers,SVars are comma separated lists which contain
SVars that point to the appropriate type that the Effect will gain.
- Duration is how long the Effect lasts. Right now, most effects will
last Until End of Turn. In the future, they may have other
conditions.
Duration$ Permanent for effects that have no specific Duration.
- Stackable$ False - Most Effects are assumed to be Stackable. By
setting the Stackable Flag to False, the AI will know having a
second one in play is useless, so will save it's Resource for
something else.
- Image - a file\_name\_without\_extension (image needs to reside in
the tokens directory)
## Explore
## Fight
## Fog
Fog is an ability based on the original Fog spell. "Prevent all combat
damage that would be dealt this turn." While this could be done with an
effect, the specialized nature of the AI gives it its own AF.
## Game outcome
### GameDraw
### GameLoss
### GameWin
### RestartGame
Used in the script of *Karn Liberated*
## Goad
## Investigate
## Mana
For lands or other permanent to produce mana.
`A:AB$ Mana | Cost$ T | Produced$ <ManaType> | SpellDescription$ Add W to your mana pool.`
In this example ManaType would be W.
## Manifest
## PermanentState
API for things that alter a permanent's state.
### Phases
### SetState
Changing a cards State. This is mostly for Flip Cards or the Transform mechanic.
### Tap
`A:AB$ Tap | Cost$ R | ValidTgts$ Wall | TgtPrompt$ Select target wall | SpellDescription$ Tap target wall.`
### TapOrUntap
### Untap
`A:AB$ Untap | Cost$ G | ActivationLimit$ 1| SpellDescription$ Untap CARDNAME. Activate this ability only once each turn.`
- RepeatSubAbility - required - to set up repeat subability
- RepeatCards - to repeat for each valid card (zone: present zone of
the valid repeat cards, default: battlefield)
- DefinedCards
- RepeatPlayers - to repeat for each valid player
- RepeatCounters - to repeat for each valid counters
## Reveal
### RevealHand
Look at a player's hand.
Target or Defined is required.
`A:AB$ RevealHand | Cost$ T | ValidTgts$ Player | TgtPrompt$ Select target player | SpellDescription$ Look at target player's hand.`
### Reveal
`A:AB$ Reveal | Cost$ 2 U T | Defined$ You | RevealValid$ Card.Blue | AnyNumber$ True | RememberRevealed$ True`
Parameters:
- RevealValid: to limit the valid cards.
- AnyNumber
- Random
- RememberRevealed: to remember the cards revealed
### PeekAndReveal
This AF is very similar to things that Dig can do, but handle a much
simpler form, with less complex coding underneath. Similar to how
RearrangeTopOfLibrary could be handled with Dig.
Primarily used with cards that allow you to Peek at the top card of your
library, and allow you to reveal it if it's of a certain type. The
Kinship cards fit this bill perfectly, so they are used to simplify the
complex popups that would be required if using multiple Dig
SubAbilities.
RevealOptional - Whether or not the Reveal is optional.
RememberRevealed - Whether to remember the revealed cards (after
filtering by Valid)
RememberPeeked - Whether to remember the peeked cards (only if they are
not revealed\!)
RevealValid - defaults to Card, but allows you to set a specific
ValidType if you can only have certain things
PeekAmount - defaults to 1, but allows you to peek at multiple cards if
possible
## RollDice
## Sacrifice
Usually you choose a player and that player has to sacrifice something
`A:SP$ Sacrifice | Cost$ 1 B | ValidTgts$ Player | SacValid$ Creature | SacMessage$ Creature | Amount$ 2 | SpellDescription$ Target player sacrifices a creature.`
Destroy$ True - An optional parameter for destroying permanents target
player chooses (eg: Burning of Xinye, or Imperial Edict).
`A:SP$ Sacrifice | Cost$ 1 B | ValidTgts$ Opponent | SacValid$ Creature | SacMessage$ Creature | Destroy$ True | SpellDescription$ Target opponent chooses a creature he or she controls. Destroy it.`
## Scry
`A:AB$ Scry | Cost$ 1 T | ScryNum$ 2`
## StoreSVar
## Token
Token simply lets you create tokens of any type.
`A:SP$ Token | Cost$ 3 W U | TokenImage$ W 1 1 Bird Flying | TokenAmount$ X | TokenName$ Bird | TokenTypes$ Creature,Bird | TokenOwner$ You | TokenColors$ Blue | TokenPower$ 1 | TokenToughness$ 1 | TokenKeywords$ Flying`
This ability factory does not take a target. All the parameters are
mandatory except for TokenKeywords. If you provide a non-integer for
TokenAmount, TokenPower or TokenToughness the AF will attempt to look for
an SVar of that name and interpret it's contents as a Count$ line. Worth
noting is that TokenTypes and TokenColors are simple commaseparated
lists while TokenKeywords is a list where the items are separated by
"\<\>". If TokenImage is not provided, the factory will attempt to
construct a filename on it's own. TokenOwner can use Defined-like
parameters, such as "You" "Opponent" or the new Triggered-Variables.
You can also use the parameters TokenAbilities$, TokenTriggers$ and
TokenSVars$ to give the created tokens any number of either. For
example, here's how Growth Spasm creates an Eldrazi Spawn token complete
with ability.
SVar:DBToken:DB$Token | TokenAmount$ 1 | TokenName$ Eldrazi Spawn | TokenTypes$ Creature,Eldrazi,Spawn | TokenOwner$ You | TokenColors$ Colorless | TokenPower$ 0 | TokenToughness$ 1 | TokenAbilities$ ABMana SVar:ABMana:AB$Mana | Cost$ Sac<1/CARDNAME> | Produced$ 1 | Amount$ 1 | SpellDescription$ Add 1 to your mana pool.
As another example, here's Mitotic Slimes' use of TokenTriggers$:
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigTokenSenior | TriggerDescription$ When CARDNAME is put into a graveyard from the battlefield, put two 2/2 green Ooze creature tokens onto the battlefield. They have "When this creature is put into a graveyard, put two 1/1 green Ooze creature tokens onto the battlefield."
SVar:TriggerJunior:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigTokenJunior | TriggerDescription$ When this creature is put into a graveyard, put two 1/1 green Ooze creature tokens onto the battlefield. SVar:TrigTokenJunior:AB$Token | Cost$ 0 | TokenImage$ g 1 1 ooze | TokenName$ Ooze | TokenTypes$ Creature,Ooze | TokenColors$ Green | TokenOwner$ You | TokenPower$ 1 | TokenToughness$ 1 | TokenAmount$ 2
## Trigger
If possible split the SpellDescription of the effect so the part for the trigger can become the StackDescription directly.
### DelayedTrigger
### ImmediateTrigger
## Turn structure
### AddPhase
### AddTurn
`A:SP$ AddTurn | Cost$ 1 U | NumTurns$ 1 | SpellDescription$ Take an extra turn after this one.`
### EndTurn
### ReverseTurnOrder
### SkipPhase
### SkipTurn
## ZoneAffecting
For specific effects that handle zones in a specific manner
### ChangeZone
ChangeZone is a united front of any card that changes zone. This does
not include: drawing, discarding, destroying, or milling, as these
represent specific words on which triggers and replacements can react.
There are two primary forms, but the distinction is handled mostly in
the codebase. The only thing that is required is to set appropriate parameters.
Origin and Destination are both required.
Origin is where the card is coming from.
Destination is where the card is going to. If Destination is Library, a
LibraryPosition is recommended, but not required. **Default value of the
LibraryPosition is 0.** 0 represents the top of the library, -1 represents the bottom.
There are two primary versions of ChangeZone.
#### Hidden Origin
The first is hidden, generally used for Origin zones that are not known
information, like the Library or the Hand. The choice of "What card is
changing zones?" happens during resolution.
`A:SP$ ChangeZone | Cost$ W | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Artifact,Enchantment | ChangeNum$ 1 | SpellDescription$ Search your library for an artifact or enchantment card and reveal that card. Shuffle your library, then put the card on top of it.`
`A:AB$ ChangeZone | Cost$ T | Origin$ Hand | Destination$ Battlefield | ChangeType$ Land | ChangeNum$ 1 | Optional$ True | SpellDescription$ You may put a land card from your hand onto the battlefield.`
For Hidden, things like ChangeType and ChangeNum are used to restrict
what can ChangeZone, and how many do. There are two parameters special
to Hidden Origin:
"Chooser" defines which player has to decide which card changes zone
(example You, Opponent).
"Mandatory" most of these abilities are not mandatory, but some are.
#### Known Origin
The second is known, generally used for Origin zones that are known
information, like the Battlefield or the Graveyard. The choice of "What
card is changing zones?" happens on activation, generally by targeting.
`A:AB$ ChangeZone | Cost$ 1 U T | TgtPrompt$ Choose target artifact card in your graveyard | ValidTgts$ Artifact.YouCtrl | Origin$ Graveyard | Destination$ Library | SpellDescription$ Put target artifact card from your graveyard on top of your library.`
`A:SP$ ChangeZone | Cost$ U U | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return target permanent to its owner's hand.`
For Known, since it just uses Target, normal target parameters are used in this Scenario.
### ChangeZoneResolve
This is a helper AF, for chained effects that create multiple permanents which should enter the battlefield at the same time.
To use it, you need to set the param "ChangeZoneTable" on the first effect and then call this at the end.
This is supported by the following effects:
- Amass
- CopyPermanent
- RepeatEach (_NOTE: if you wrap the creation, you don't need to call this AF on its own_)
- Token
### Dig
Dig is for an ability that does basically this: "You look at the X cards
of your Library, put Y of them somewhere, then put the rest somewhere."
Think of Impulse.
- DigNum - Required - look at the top number of cards of your library.
- Reveal - Optional - for abilities that say "Reveal the top X cards
of your library". Default is false.
- SourceZone - Optional - the zone to dig in. Default is Library.
- DestinationZone - Optional - the zone to put the Y cards in. Default
is Hand.
- LibraryPosition - Optional - if DestinationZone is Library, use this
to specify position. Default is -1 (bottom of library).
- ChangeNum - Optional - the number of cards to move to the
DestinationZone (or "All" when it's for things like "put all lands
revealed this way into your hand"). Default is 1.
- ChangeValid - Optional - use this to specify if "you may move an
artifact to DestinationZone". Default is any Card.
- AnyNumber - Optional - use if you can move any number of Cards to
DestinationZone. Default is false. (think of Lead the Stampede)
- Optional - Optional - set this if you "may" move a card to
DestinationZone. Default is false.
- DestinationZone2 - Optional - the zone to put the rest of the cards
in. If it is library, you are prompted for the order. Default is
Library.
- LibraryPosition2 - Optional - if DestinationZone2 is Library, use
this to specify position. Default is -1 (bottom of library).
`A:AB$ Mill | Cost$ 2 T | NumCards$ 2 | ValidTgts$ Player | TgtPrompt$ Choose a player to mill | SpellDescription$ Target player puts the top two cards of his or her library into his or her graveyard.`
### RearrangeTopOfLibrary
### Shuffle
Used for shuffling a player's library
- Optional - Set this parameter if the user should be
prompted Yes/No to shuffle the given library. Default is false.
A reference guide for scripting cards using the API parsed by the Forge engine.
# Base Structure
By opening any file in the /res/cardsfolder folder you can see the basic structure of how the data is created.<br/>
Here's an example of a vanilla creature:
```
Name:Vanilla Creature
ManaCost:2 G
Types:Creature Beast
PT:2/2
Oracle:
```
The name of this card is Vanilla Creature.<br/>
It's casting cost is {2}{G}.<br/>
It has the types Creature and Beast.<br/>
It has a Power-Toughness of 2/2.<br/>
It will not display any additional text in the card's template.<br/>
If a card has two faces, use AlternateMode:{CardStateName} in the front face and separate both by a new line with the text "ALTERNATE".
There are a few other properties that will appear in many cards. These are
| Property | Description
| - | -
|`A`|[Ability effect](AbilityFactory)
|`AI`|RemoveDeck:<br/>* `All`<br/>This will prevent the card from appearing in random AI decks. It is applicable for cards the AI can't use at all like Dark Ritual and also for cards that the AI could use, but only ineffectively like Tortoise Formation. The AI won't draft these cards.<br/>* `Random`<br/> This will prevent the card from appearing in random decks. It is only applicable for cards that are too narrow for random decks like Root Cage or Into the North. The AI won't draft these cards.<br/>* `NonCommander`<br/>
|`Colors`|Color(s) of the card<br/><br/>When a card's color is determined by a color indicator rather than shards in a mana cost, this property must be defined. If no identifier is needed, this property should be omitted.<br/><br/>* `Colors:red` - This is used on Kobolds of Kher Keep, which has a casting cost of {0} and requires a red indicator to make it red.<br/><br/>* `Colors:red,green` - Since Arlinn, Embraced by the Moon has no casting cost (it's the back of a double-faced card), the red and green indicator must be included.
|`DeckHints`|AI-related hints for a deck including this card<br/><br/>To improve synergy this will increase the rank of of all other cards that share some of its DeckHints types. This helps with smoothing the selection so cards without these Entries won't be at an unfair disadvantage.<br/><br/>The relevant code can be found in the [CardRanker](https://github.com/Card-Forge/forge/blob/master/forge-gui/src/main/java/forge/gamemodes/limited/CardRanker.java) class.
|`DeckNeeds`|This can be considered a stronger variant when the AI should not put this card into its deck unless it has whatever other type is specified. The way this works is "inverted": it will directly decrease the rank of the card unless other cards are able to satisfy its types.<br/>If a card demands more than one kind of type you can reuse it:<br/>`DeckNeeds:Type$Human & Type$Warrior` will only find Human Warrior compared to `DeckNeeds:Type$Human\|Warrior` which is either
|`DeckHas`|specifies that the deck now has a certain ability (like, token generation or counters) so that the drafting/deckbuilding AI knows that it now meets requirements for DeckHints/DeckNeeds. This is actually very useful since many of these (such as `Ability$Graveyard, Ability$Token, Ability$Counters`) are not deduced by parsing the abilities, so an explicit hint is necessary. Using the other types is also supported in case the implicit parsing wouldn't find it (TokenScript$ is also included).<br/>It doesn't require exact matching to have an effect but cards that care about multiple entries for a given type will be judged higher if a card seems to provide even "more" synergy for it.<br/>Example:<br/>Chishiro has two abilities so `DeckHas:Ability$Token & Ability$Counters` is used, therefore score for `DeckNeeds:Ability$Token\|Counters` is increased
|`K`|Keyword (see below)
|`Loyalty`|Number of starting loyalty counters
|`ManaCost`|Cost to cast the card shown in mana shards<br/><br/>This property is required. It has a single parameter that is a mana cost.<br/><br/>* `ManaCost:no cost` for cards that cannot be cast<br/>* `ManaCost:1 W W` sets the casting cost to {1}{W}{W}
|`Name`|Name of the card<br/><br/>A string of text that serves as the name of the card. Note that the registered trademark symbol cannot be included, and this property must have at least one character.<br/><br/>Example:<br/>* `Name:A Display of My Dark Power` sets the card's name to "A Display of My Dark Power"
|`Oracle`|The current Oracle text used by the card.<br/><br/>We actually have a Python Script that runs to be able to fill in this information, so don't worry about manually editing a lot of cards when Wizards decides to change the rules. <br/><br/>This field is used by the Deck Editor to allow non-Legendary Creatures to be marked as potential commanders. Make sure "CARDNAME can be your commander." appears in the oracle text.
|`PT`|Power and toughness
|`R`|[Replacement effect](Replacements)
|`S`|[Static ability](static-abilities)
|`SVar`|String variable. Used throughout scripting in a handful of different ways.
|`T`|[Triggered ability](Triggers)
|`Text`|Additional text that needs to be displayed on the CardDetailPanel that doesn't have any spell/ability that generates a description for it, for example "CARDNAME can be your commander." or "X can't be 0.".
|`Types`|Card types and subtypes<br/><br/>Include all card types and subtypes, separated by spaces.<br/><br/>Example:<br/>* `Types:Enchantment Artifact Creature Golem` for a card that reads Enchantment Artifact Creature -- Golem
Rarity and Set info are now defined in edition definition files. These can be found at /res/reditions path.
## Conventions
- filename: all lowercase, skip special characters, underscore for spaces
- Unix(LF) line endings
- use empty lines only when separating multiple faces on a card
- AI SVars right before the Oracle
- try to avoid writing default params to keep scripts concise
- e.g. just `SP$ Draw` instead of `SP$ Draw | Defined$ You | NumCards$ 1`
# Keywords
All keywords need to be prepended with "K:" to be parsed correctly. Each keyword must appear on a separate line.
## Keywords without Parameters
This section is for Keywords that require no additional parameters and are one or two words long. Most of these you would see exactly on cards in the game.<br/>
Cost is a class that attempts to streamline costs throughout all cards. It requires that each cost is separated by a space. I will use examples that could be found in Ability, although certain Keyworded abilities do use Cost too.
# Common
## Description
Description is an optional last parameter in the cost. This is to allow
for complex Type definitions to have a nice Description that is readable.
## CostDesc / PrecostDesc
## UnlessCost
UnlessCost allows the player specified with UnlessPayer (same as
Defined, defaults to TargetedController) to pay mana to prevent the
resolving of the ability. If the script has the param "UnlessSwitched",
then the player pays mana to resolve the ability (usually used to handle
"any player may pay ..." ).
## XChoice
XChoice is the variable that basically means "You can choose whatever
you want for this variable. But you need to decide what X is before you
start paying." This would commonly appear as an SVar definition of X.
## xPaid
xPaid is the amount of Mana Paid for an X Cost. There are a few cards
that will use the X Payment to determine other costs (like Abandon Hope)
This would commonly appear as an SVar definition of X.
## CARDNAME
For Costs that do something to themselves (ex. Discard Self, Sacrifice
Self)
# Types of Cost
## Discard
Discard has two required parameters and one optional in the form
Discard<Num/Type/Description>
- The first is how many cards are being discarded.
- The second is what card types can be discarded. (Hand for the whole
hand, Random for chosen randomly)
## Draw
## Exert
## Exile
Exile has two required parameters and one option in the form of
Exile<Num/Type/Description>
There are also a few sister abilities that all fit under the Exile
umbrella.
- Exile (for cards on the Battlefield)
- ExileFromGraveyard
- ExileFromHand
- ExileFromTop (for cards on top of your library, this doesn't default
Type to Card, so make sure you add it)
Some Examples
- Exile<1/Creature>
- Exile<1/CARDNAME>
- ExileFromHand<1/CARDNAME>
- ExileFromHand<2/Creature>
- ExileFromGrave<1/CARDNAME>
- ExileFromGrave<1/Treefolk>
- ExileFromTop<10/Card>
## FlipCoin
Only used by "Karplusan Minotaur".
## Mana
- Cost$ 2
- 2 colorless mana
- Cost$ B R
- 1 black and 1 red mana
- Cost$ WG
- Hybrid White/Green mana
- Cost$ S
- Snow Mana
- Cost$ Mana<2\\Creature>
- 2 colorless produced by a source with type 'creature'. Note the
backslash - it was chosen because hybrid costs already use slash
Here's some examples:
- Discard<1/Card>
- "Discard 1 Card"
- Discard<0/Hand> (The number is ignored when Hand is used as a
type.)
- Discard your hand
- Discard<2/Random>
- Discard 2 Cards at Random
- Discard<1/CARDNAME>
- Discard Self (CARDNAME)
- Discard<1/Creature.Black/Black Creature>
- Discard 1 Black Creature
## Mill
## Subtract(Remove) Counter
SubCounter has two required parameters in the form of
SubCounter<Num/CounterName>
- SubCounter<2/P1P1>
- SubCounter<1/CHARGE>
Remember the token name should appear all in caps.
As third parameter you can use a ValidCard.
## Sacrifice
Sacrifice has two required parameters and one optional parameter in the
form of Sac<Num/Type/Description>
- Sac<1/Artifact>
- Sac<1/CARDNAME>
## Tap
- Cost$ T
## Untap
- Cost$ Untap
\- or -
- Cost$ Q
## Unattach
## PayEnergy
## PayLife
PayLife has one required parameter in the form of PayLife<Num>
- PayLife<2>
## GainLife
## TapXType
TapXType has two required parameters and one option in the form of
tapXType<Num/Type/Description>
- tapXType<3/Creature.White>
## Return
Return has two required parameters and one optional in the form of
Return<Num/Type/Description>
- Return<1/Land>
- Return<1/CARDNAME>
## Reveal
# Putting it Together
Putting it together is pretty simple. If a card needs to pay mana and tap, it would look like this:
- Cost$1 W T
For a spell that has an additional cost of sacrificing a land, put the
mana cost and the additional cost in the cost:
- Cost$2 G Sac<1/Land>
One of the features of Cost is you can have more than one of the same Cost type:
- Cost$ Sac<1/Swamp> Sac<1/Creature>
There are many examples, but they mostly fall into those categories.
Replacement create replacement effects, as you'd expect. Their script
follows the format introduced by AbilityFactory; simply begin a line
with "R:", to indicate that it's for a replacement effect, followed by a
collection of name-value pairs (name and value are separated by $)
separated by pipes (|). All Replacement effects expect an "Event$"
parameter, which declares what event should be replaced. Most replacement effects will also have a "ReplaceWith$" parameter which points to an SVar which contains what should replace the event. They may have a "Prevent$True" parameter, instead though, which means that nothing happens instead of the event.
Similarly to triggers, the replacing code can access special variables
pertaining to the event it replaced (like triggered-variables). These
are specific to each event, and is listed below. Most replacement effects
will also have a "Description$" parameter which is simply the card text
for the ability.
## DamageDone
DamageDone events are checked when damage is about to be assigned to a
card or player. There are 5 special parameters:
- ValidSource - The damage source must match this for the event to be
replaced.
- ValidTarget - The damage target must match this for the event to be
replaced.
- DamageAmount - The amount of damage must match this.
- IsCombat - If true, the damage must be combat damage, if false, it
can't be.
- IsEquipping - If true, the host card must be equipping something.
There are 3 Replaced-variables:
- DamageAmount - The amount of damage to be assigned.
- Target - The target of the damage.
- Source - The source of the damage.
## Discard
Discard events are checked when a player is about to discard a card.
There are 4 special parameters:
- ValidPlayer - The player who would discard must match this.
- ValidCard - The the card that would be discarded must match this.
- ValidSource - The card causing the discard must match this.
- DiscardFromEffect - If true, only discards caused by spells/effects
will be replaced. Cleanup/statebased discards will not.
There are 2 Replaced-variables:
- Card - The card that would be discarded.
- Player - The player that would have discarded
## Draw
Draw events are checked when a player is about to draw a card. There is
1 special parameter:
- ValidPlayer - The player who would draw must match this.
There are no Replaced-variables.
## GainLife
GainLife events are checked when a player would gain life. There is 1
special parameter:
- ValidPlayer - The player who would gain life must match this.
There is 1 Replaced-variable:
- LifeGained - The amount of damage to be gained.
## GameLoss
GameLoss events are checked when a player would lose. There is 1 special
parameter:
- ValidPlayer - The player who would lose must match this.
There are no Replaced-variables.
## Moved
Moved events are checked when a card would be moved between zones. There
are 3 special parameters:
- ValidCard - The moving card must match this.
- Origin - The card must be moving from this zone.
- Destination - The card must be moving to this zone.
For example: No Rest for the Wicked and the like would use:
`AB$ ChangeZone | Cost$ Sac<1/CARDNAME> | Defined$ ThisTurnEntered Graveyard from Battlefield Creature.YouCtrl | SpellDescription$ Return to your hand all creature cards in your graveyard that were put there from the battlefield this turn.`
### Targeted
Targeted will only appear on a [SubAbility][]. It means "Do this action
to whatever a parent Ability targeted"
That may sound confusing so here's an example.
If you had a spell that says "Untap target Creature. It gains +1/+1
until end of turn" it would look like similar to this.
Depending on which Mode is specified, other parameters may be expected.
Below are the currently available modes.
The script has access to many things that were previously internal to
triggers. These things are accessed via Triggered-variables. Triggered
variables are always of the form "Triggered<VariableName>\[Controller/Owner\]" and are specific to each trigger mode. You can use Triggered-variables that return a card or a player directly in Defined$ parameters or to grab extra info from (like you use "Targeted" for, for instance "SVar:X:TriggeredCard$CardPower").
You can get the controller or owner of a card returned by a Triggered-variable by appending "Controller" or "Owner" to the variable. Triggered-variables that return an integer can only be accessed from Count$, i.e. "SVar:X:Count$TriggeredLifeAmount".
Other parameters that triggers can use are:
- Secondary - If a trigger has Secondary$ True set, it means that it's
trigger description won't show up in a card's text box. This can be
used if you need to use several triggers to emulate a single effect
on a real card.
- Static - This parameter is mainly used for "As CARDNAME enters the
battlefield..." type things. It causes the triggered ability to
resolve right away, instead of going on the stack.
- ResolvingCheck
- NoResolvingCheck - makes a trigger not recheck its condition to resolve
## Always
Always-triggers represent State triggers, a special kind of triggers. These triggers will not do any checks for intervening if-clauses and will not go on the stack if an instance of it has already been put there. They are checked every time state effects are checked.
Examples: Emperor Crocodile.
There are no special parameters and no triggered-variables.
## Attached
Attached-triggers go off when a card is attached, via enchanting or
equipping, to another card.
Examples: Bramble Elemental, Kithkin Armor.
There are 2 special parameters:
- ValidSource - The card that is being attached to another must match
this for the trigger to go off.
- ValidTarget - The card that is having another card attached to it
must match this.
There are 2 Triggered-variables:
- Source - The card that is being attached.
- Target - The card that is being attached to.
## AttackerBlocked
AttackerBlocked-triggers go off when a creature becomes blocked. It goes
off only once (no matter how many blockers there are) right after the
declare blockers step.
Examples: AEther Membrane, Alley Grifters.
There are 2 special parameters:
- ValidCard - The attacking creature must match this for the trigger
to go off.
- ValidBlocker - The blocking creature must match this for the trigger
to go off.
There are 3 Triggered-variables:
- Attacker - The card of the attacking creature.
- Blocker - The card of the blocking creaure.
- NumBlockers - The number of things blocking the attacker
## AttackerUnblocked
AttackerUnblocked-triggers go off when a creature attacks and is not
blocked, right after the declare blockers step.
Examples: Abyssal Nightstalker, Dauthi Mindripper
There is 1 special parameter:
- ValidCard - The attacking creature must match this for the trigger
to go off.
There is 1 Triggered-variable:
- Attacker - The card of the attacking creature.
## AttackersDeclared
Goes off after attackers are declared, if any attackers were declared,
once a combat only.
Examples: Lightmine Field, Curse of Inertia. There are 2 special
parameters:
- AttackingPlayer - The attacking player must match this.
- AttackedTarget - One of the game entities in TriggeredAttackedTarget
must match this.
There are 3 Triggered-variables:
- Attackers - Collection of attacking creatures.
- AttackingPlayer - The targeted object.
- AttackedTarget - Collection of game entities that each attacker is attacking.
## Attacks
Attacks-triggers go off when a creature attacks. That is, it goes off
once for each creature that attacks during your each combat phase (Right
after the declare attackers step).
Examples: Accorder Paladin, Trepanation Blade.
There are 2 special parameters:
- ValidCard - The attacking creature must match this for the trigger
to go off.
- Alone - If this is True, the trigger will only go off if the
creature attacks alone.
There is 1 Triggered-variable:
- Attacker - The card of the attacking creature.
## BecomeMonstrous
BecomeMonstrous-triggers go off when a creature becomes Monstrous,
naturally.
Examples: Arbor Colossus, Ember Swallower.
There is 1 special parameter:
- ValidCard - The card that becomes monstrous must match this.
There is 1 Triggered-variable:
- Card - The card that became monstrous.
## BecomesTarget
BecomesTarget-triggers go off when a spell or ability (or either) is put
on the stack targeting something that matches a Valid-expression.
Using the Forge API you can create your own custom cards and sets. This tutorial will walk you through the process of creating custom cards via the Force Custom Card Script.
If you are trying to script cards for a new set make sure you take advantage of the [Developer Mode](Development/DevMode.md) for testing to try and contribute without any obvious bugs.
Create a new .txt file using all lowercase letters. Spaces are represented with "_" and you can leave out special characters like commas or apostrophes.
> goblin_card_guide.txt
Now we need to define our card. Add the following to your Goblin_Card_Guide text file:
```
Name:Goblin Card Guide
ManaCost:1 R
Types:Creature Goblin
PT:2/2
K:Haste
Oracle:Haste
```
Let's break our card down:
- Name - The name as it appears on the card.
- ManaCost - The card's cost with colorless mana first and spaces between different mana symbols.
- Types - The card's type and then any subtypes seperated by spaces.
- PT - Power and Toughness, which is only used for Creatures (or some cards that turn into creatures like Vehicles)
- K - A Keyword that gives our creature an ability.
- Oracle - The actual text that appears on that card.
For a reference of possible fields and their descriptions please see the [Card Scripting API](Card-scripting-API) document. Note that maintaining an up to date list of every ability of every Magic Card ever printed is not feasable. The API is meant to serve as a guide and includes most common used information.
3. Add Card to a Set
Now that we have a new card we need to add it to a set so that it will be included in the game. For the purposes of this tutorial we'll add our card to an existing set. If you wish to create your own set you can follow this [guide](Creating-a-custom-Set.md).
Navigate to your Custom Editions folder and open the text file for the set you would like to add your card to. Let's add our card to the "DCI Promos" set.
In the "CDI Promos.txt" file we can see a list of the cards in the set. Add a new entry at the end of the list for our new card:
```
78 R Circle of Flame @James Paick
79 U Stormblood Berserker @Greg Staples
80 R Dungrove Elder @Scott Chou
81 R Goblin Card Guide @Forge Team
```
The first number is the collector number for the card in the set. This must be unique.
The next field is the card's rarity in the set - we've made our card a rare.
Next is the card's name and finally the name of the artist for the cards artwork.
Speaking of which - our card doesn't have any artwork. Let's fix that.
4. Add Card Image
Open your Card Images folder. Find the code for the set you added your new card to and open the corrosponding subfolder. For us this will be:
So we've created a creature card and added it to the game. However our creature is a little... boring, so let's update it to have a triggered ability.
"Whenever Goblin Card Guide deals damage to an opponent, draw a card."
Open the txt file again:
> goblin_card_guide.txt
And update the contents to the following:
```
Name:Goblin Card Guide
ManaCost:1 R
Types:Creature Goblin
PT:2/2
K:Haste
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Opponent | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever CARDNAME deals damage to an opponent, draw a card.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1
Oracle:Haste
\nWhenever Goblin Card Guide deals damage to an opponent, draw a card.
```
Let's take a look at our changes:
- T:Mode$ DamageDone - Trigger when Damage is Done.
- ValidSource$ Card.Self - The source of the trigger. "Self" means our our card must be the source of the damage for the trigger to occur.
- ValidTarget$ Opponent - The target of the trigger. "Oppenent" means our oppenent must receive damage for the trigger to occur.
- TriggerZones$ Battlefield - Means the card must be on the battlefield for the trigger to occur.
- Execute$ TrigDraw - What happens then the trigger occurs - in this case trigger a Draw effect.
- SVar:TrigDraw:DB$ Draw - A String Variable used by the script. This one takes the Triggered Draw (TrigDraw) and tells the script to draw a card. "DB$ Draw" means "Drawback" "Draw".*
- Defined$ You - Who is drawing the card(s), in this case the controller of the card.
- NumCards$ 1 - Draw 1 card. This could be changed to any number to trigger drawing that many card.
- TriggerDescription$ - The description of the trigger.
*"Drawback" is a connotation that has since been replaced with "SubAbility".
The original connotation differentiated between AB (Ability), SP (Spell) and DB (Drawback).
AP and SP require costs, whereas Drawback do not. The card scripts still use the "DB$" connotation.
6. Updating our Image
Finally since we createda new effect on our card, we need to update the image. Card images are simply .jpg files and don't update or read from any scripts or gamefiles.
I used the same online card creator to make the change to our card:
You can check the [Abilities](AbilityFactory) and [Triggers](Triggers) documentation for more information on this topic. These documents are meant as a guide and are unlikely to contain information about every ability in the game.
The sinmlest method for creating an effect on your card is to find another card that does the same thing and copying the ability. These can be found in your Forge folder:
>./Forge/res/cardsfolder/cardsfolder.zip
Unzipping this file will sllow you to search for any card in the containing subfolders.
This is a tutorial to start creating your own custom set, or implementing an existing one. We'll take you step by step into implementing a few cards from [MSEM Champions](https://msem-instigator.herokuapp.com/set/MPS_MSE). This is a basic guide to help you get started.
**Note:** This tutorial is currently for **Windows only**.
## Where do the files go?
### Non-image files
Everything but your cards images go into `%appdata%/Forge/custom`. You will need to put the files in the correct directories in order for the game to load them correctly.
The important folders are the following (you can create them if they don't exist):
* **cards**: Your card rules (logic) go inside this folder (.txt). I suggest you create subfolders inside it to keep everything clean.
* **editions**: Your editions (sets) definition files (.txt) go inside this folder.
* **tokens**: Your tokens definition files (.txt) go inside this folder.
### Image files
Your card and token images go into `%localappdata%/Forge/Cache/pics`.
The important folders are the following:
* **cards**: This is where you will put your card images. You'll want to create a folder with the Code of your edition. The images are named using this convention `Card Name.full.jpg`. If you have multiple art for the same card (something like basic lands, or maybe alternate art of the same card), then you can name them `Card Name1.full.jpg`, `Card Name2.full.jpg`, and so forth.
* **tokens**: Same as the cards folder, your tokens will go inside this folder. The naming convention is `token_script_name.jpg`. So if your token script name is `b_5_5_golem_trample`, then you can put your token image inside your edition folder named `b_5_5_golem_trample.jpg`. If there is a collector number, append it at the beginning, such as `1_b_5_5_golem_trample.jpg`
## Creating your edition definition file
As mentioned in the opening section, we'll be partially implementing the **MSEM Champions** set. Let's create a new text file (.txt) inside `%appdata%/Forge/custom/editions`. Let's name it `MSEM Champions.txt`.
> Note: The file's name don't matter, but it'll be easier to find it if you ever need to edit anything.
Let's paste the following inside it:
```
[metadata]
Code=MSEM_CHAMPIONS
Name=MSEM Champions
Date=2017-10-01
Type=Custom
[cards]
7 M Master Chef
33 M Golden Touch
34 M Avatar of Basat
35 M Exeunt
62 M Unearth
78 M Fox of the Orange Orchard
78★ S Fox of the Orange Orchard
107 M Inked Summoner
107★ S Inked Summoner
130 M Plains
131 M Island
132 M Swamp
133 M Mountain
134 M Forest
[tokens]
b_1_1_bird_flying
b_3_3_cat_deathtouch
b_5_5_golem_trample
[CreatureTypes]
Artist:Artists
```
Let's break it down.
```
[metadata]
Code=MSEM_CHAMPIONS
Name=MSEM Champions
Date=2017-10-01
Type=Custom
```
The **[metadata]** section contains the information about your edition.
* **Code** is an **unique** identifier for your edition.
* **Name** is the name of your edition.
* **Date** is the date the set was first released/created.
* **Type** should be `Custom` as Forge do things differently for them, including but not limited to skipping the automatic download of the images.
```
[cards]
7 M Master Chef
34 M Avatar of Basat
35 M Exeunt
62 M Unearth
78 M Fox of the Orange Orchard
78★ S Fox of the Orange Orchard
107 M Inked Summoner
107★ S Inked Summoner
130 L Plains
131 L Island
132 L Swamp
133 L Mountain
134 L Forest
```
The **[cards]** section contains the cards of your edition. Any card appearing under this section has a chance to appear as a reward and in shops.
Each line is as follow: `CollectorNumber Rarity CardName @ArtistName`.
* The collector number should be unique in a given set. You can see some numbers have a ★ next to their name. In this case it denotes an alternate art of a card, but it could also have been a different number altogether.
> Note: While it generally doesn't matter what collector number you use, avoid using the collector number F followed by only digits (ie. `F001`) as it represents a "Funny" card inside a normal edition. (Think of Funny cards as Un- sets cards)
* The rarity is a one letter representation. It can be L (Basic Land), C (Common), U (Uncommon), R (Rare), M (Mythic Rare), S (Special).
* The card name is self-explanatory. It should match the name of the corresponding card rule. (More of that later)
* The artist is optional, but it should be `@Artist Name` if present. We'll omit it in this tutorial.
> Note: You can put the cards in the list even if they aren't scripted yet. Forge will skip over them.
```text
[tokens]
1 b_1_1_bird_flying
2 b_3_3_cat_deathtouch
3 b_5_5_golem_trample
```
The **[tokens]** section is optional, and only needed if you want to use specific token images in this set. They should be named using the name of their token script. `b_1_1_bird_flying` means it is a black 1/1 bird with flying. More on that later.
If you load the game with just file, you'll be able to see that Master Chef, Unearth and the basic lands can already be found in game. That's because they share a name with existing Magic the Gathering cards. **However**, [Master Chef](https://msem-instigator.herokuapp.com/card?q=Master+Chef) from MSEM and [Master Chef](https://scryfall.com/card/clb/241/master-chef) from MTG are two different cards! You must ensure that your custom cards do not have the same name as an existing one, unless you just want it to be another print, just like [Unearth](https://msem-instigator.herokuapp.com/card/CHAMPIONS/62/Unearth) and the basic lands in this example.
Let's comment out Master Chef to avoid a name conflict with an existing MTG card:
```text
[cards]
#7 M Master Chef
33 M Golden Touch
...
```
Save your file, and let's move onto another step.
> If there is a conflict, you can add something in its name for differenciate it, such as a set tag (ie. `Master Chef (MSEM)`).
## Scripting your first cards
As mentioned earlier, your custom card rules need to be located inside `%appdata%/Forge/custom/cards`. I recommend creating subfolders for each starting letter (`Forge/custom/cards/a`, `Forge/custom/cards/b`, etc.) to quickly find if a card has a duplicate name.
Now, you might remember than Unearth was an existing MTG card so we do not need to create a custom card rule for it. Let's create a few others.
> This tutorial will not teach you to script your cards, and they might not be perfect. Please check out [Creating a Custom Card](https://github.com/Card-Forge/forge/wiki/Creating-a-Custom-Card) if you want more info, or look at the existing cards to learn more complex scripting.
A:SP$ Sacrifice | SacValid$ Creature | Defined$ Player | SpellDescription$ Each player sacrifices a creature.
AI:RemoveDeck:All
Oracle:Each player sacrifices a creature.
```
fox_of_the_orange_orchard.txt
```text
Name:Fox of the Orange Orchard
ManaCost:1 W
Types:Creature Fox Spirit
PT:3/1
Oracle:
```
inked_summoner.txt
```text
Name:Inked Summoner
ManaCost:1 B
Types:Creature Human Warlock Artist
PT:1/2
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigBranch | CheckSVar$ X | SVarCompare$ GE2 | TriggerDescription$ At the beginning of your end step, if you lost 2 or more life this turn, create a 1/1 black Bird creature token with flying. If you lost 4 or more life this turn, instead create a 3/3 black Cat creature token with deathtouch. If you lost 6 or more life this turn, instead create a 5/5 black Golem creature token with trample.
Oracle:At the beginning of your end step, if you lost 2 or more life this turn, create a 1/1 black Bird creature token with flying.\nIf you lost 4 or more life this turn, instead create a 3/3 black Cat creature token with deathtouch.\nIf you lost 6 or more life this turn, instead create a 5/5 black Golem creature token with trample.
```
If you load your game now, you should be able to find these cards you just scripted! You'll also notice that Inked Summoner is only listed as a Human Warlock, missing the Artist subtype. That's because Artist is not a real MTG subtype. You can add custom types directing inside the set definition file by following the sections found inside the `res/lists/TypeLists.txt` file. Duplicates will be ignored.
```text
[CreatureTypes]
Artist:Artists
```
Oh no! If you play with Inked Summoner now, you will crash when summoning a token. That's because they don't exist in MTG and we need to define them! Let's go onto the next step!
## Scripting custom tokens
The token scripts are located at `%appdata%/Forge/custom/tokens`.
Let's add the new tokens we need to make Inked Summoner work!
> Just like for card scripting, this tutorial will not teach you about scripting them.
b_1_1_bird.flying.txt
```text
Name:Bird Token
ManaCost:no cost
Colors:black
Types:Creature Bird
PT:1/1
K:Flying
Oracle:Flying
```
b_3_3_cat_deathtouch.txt
```text
Name:Cat Token
ManaCost:no cost
Colors:black
Types:Creature Cat
PT:3/3
K:Deathtouch
Oracle:Deathtouch
```
b_5_5_golem_trample.txt
```text
Name:Golem Token
ManaCost:no cost
Colors:black
Types:Creature Golem
PT:5/5
K:Trample
Oracle:Trample
```
Great! Now Inked Summoner no longer make the game crash! Now let's add some images to spice it all up.
## Adding card and token images
You can find the card images for the MSEM Champions edition [here](https://msem-instigator.herokuapp.com/set/CHAMPIONS). Find the ones you need and save them inside `%appdata%/../Local/Forge/Cache/pics/cards/MSEM_CHAMPIONS` Remember the filename format should be `{cardname}.fullborder.jpg` if you only have one variant in your edition. If you have multiples, then it should be `{cardname}{number}.fullborder.jpg` (ie. `Fox of the Orange Orchard1.fullborder.jpg`, `Fox of the Orange Orchard2.fullborder.jpg`, etc). You can find the alternate images from [here](https://msem-instigator.herokuapp.com/set/MPS_MSE) if you want.
For the tokens, we can deposit them inside `%localappdata%/Forge/Cache/pics/tokens/MSEM_CHAMPIONS`. They should be named the same as their number + token script so `1_b_1_1_bird_flying.jpg`, `2_b_3_3_cat_deathtouch.jpg`, and so forth.
You can now start your game again, and see that the art loads correctly now.
## 🎉 Congratulations
You’ve just added your first custom set in Forge! There's still much more to explore — scripting advanced abilities, custom mechanics, and set structures — but you now have a solid foundation to build from.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.