Compare commits

...

21 Commits

Author SHA1 Message Date
aryst0krat
e70aa429b6 #9136 formatting fix 2025-11-11 16:02:07 +01:00
Paul Hammerton
355722516f Add variant names for OM1 2025-11-11 15:47:15 +01:00
Paul Hammerton
f08d3f6447 Fix audit finding unimplemented funny cards (#9138) 2025-11-11 14:07:31 +01:00
Fulgur14
d7a69b72e4 Watery Grasp (TLA) (#9140) 2025-11-11 12:36:04 +00:00
Hans Mackowiak
bce5466c62 Trigger: reuse Execute for multiple Trigger (#9094)
* Trigger: reuse Execute for multiple Trigger

* Update CardState.java

copy abilityForTrigger

* Update CardState.java

* Update Trigger.java

* Update CardState.java

* Fix Multi Trigger with Idris

* Fix Oasis of Renewal ActivationLimit

* fix storedAbilityForTrigger

* remove hasAbilityForTrigger

* Update script

---------

Co-authored-by: tool4EvEr <tool4EvEr@>
2025-11-11 08:46:53 +01:00
Paul Hammerton
c90ddd1bac Merge pull request #9134 from paulsnoops/edition-updates
Edition updates: TLA, TLE
2025-11-10 19:14:02 +00:00
Paul Hammerton
f12fbb1774 Edition updates: TLA, TLE 2025-11-10 19:11:29 +00:00
Paul Hammerton
c3c74efbe0 Merge pull request #9133 from paulsnoops/bar-10-nov-2025
Banned and Restricted Announcement for November 10, 2025
2025-11-10 18:43:40 +00:00
Paul Hammerton
5fd44c4788 Banned and Restricted Announcement for November 10, 2025 2025-11-10 18:26:12 +00:00
Jetz72
539bea6b11 Merge pull request #9096 from Jetz72/fixes20251106
Minor Adventure Updates
2025-11-10 12:17:36 -06:00
Fulgur14
40e9480c2c Apostrophes correction (#9130)
* Update ruinous_waterbending.txt

* Update waterbenders_restoration.txt
2025-11-10 14:33:39 +01:00
tool4ever
3db276f323 Secret of Bloodbending and support (#9129) 2025-11-10 12:18:53 +00:00
Chris H
c9a5fe9135 Initial checkin for Waterbending (#9120) 2025-11-10 10:27:44 +00:00
Fulgur14
219e3d6182 Update magmasaur.txt (#9125) 2025-11-10 07:30:19 +01:00
Jacob
ee7670abf6 Fix mana cost for Longshot, Rebel Bowman (#9124) 2025-11-09 16:04:34 +00:00
tool4ever
3736a0392d User guide update (#9115) 2025-11-09 09:05:29 +01:00
Jetz
d9e356e20c Add some extra checks for safety. 2025-11-06 09:34:56 -05:00
Jetz
843f2de272 Remove some redundant imports. 2025-11-06 09:30:44 -05:00
Jetz
f27eb1042f Expand anti-cheese. 2025-11-06 09:30:27 -05:00
Jetz
48be3406c7 Reduce bias towards standard in event selection.
Make allowedEvents override other filters.
Ensure set pool filter can't filter out every event.
2025-11-06 08:50:00 -05:00
Jetz
50a98238d1 Add allowedEvents support for adventure configs 2025-11-06 08:29:55 -05:00
239 changed files with 878 additions and 196 deletions

View File

@@ -57,6 +57,8 @@ Card scripting resources are found in the forge-gui/res/ path.
## General Notes
Art files need to be copyright-free and they should be in the public domain.
### Project Hierarchy
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:

View File

@@ -7,6 +7,9 @@ The AI is:
- Poor to Ok in control decks
- Pretty bad for most combo decks
The logic is mostly based on heuristics and split between effect APIs and all other ingame decisions. Sometimes there is hardcoded logic for single cards but that's usually not a healthy approach though it can be more justifiable for highly iconic cards.
Defining general concepts of smart play can help improve the win rate much easier, e.g. the AI will always attack with creatures that it has temporarily gained control of until end of turn in order not to miss the opportunity and thus waste the control effect.
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

View File

@@ -1,9 +1,19 @@
# Advanced Search
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.
Forge implements many ways to help you find the cards you want in your ever growing collection.
Pressing Ctrl+Enter in current search adds another editable search bar.
Here's how searching for all Goblins without Haste-related abilities might look:
![search](search.png)
Click the "X" in the upper right corner of each search widget to remove that filter from the filter stack.
Find-as-you-type is implemented for Deck Editor tables. Just start typing while the table has focus and the next card with a matching string in its name will be highlighted. If more than one card matches, hit Enter to select the next matching card. A popup panel will appear with the search string so you know what you are searching for. If no cards match the string, the string will be highlighted in red. Find-as-you-type mode is automatically exited after 5 seconds of inactivity, or hit Escape to exit find-as-you-type mode immediately.
## Additional information
Another way to filter is using [Scryfall-like syntax](https://scryfall.com/docs/syntax) in the collection search bar.
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".

View File

@@ -8,6 +8,10 @@ Primarily there are two types of images you'll care about; cards, and tokens.
**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.
A deck may explicitly define the edition and art variant of each card it includes. If a deck specifies those for no card, Forge uses a special algorithm to determine which card printings were the latest by the moment all of deck's had been printed. These very editions of cards are used when loading deck into memory to reflect the flavour of the season when the deck was built.
Card images are cleared from memory cache when switching screens and between games.
# 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.

View File

@@ -295,6 +295,8 @@ Parameters:
### ControlExchange
### ControlPlayer
### ControlSpell
## Copy*

View File

@@ -173,3 +173,57 @@ The sinmlest method for creating an effect on your card is to find another card
>./Forge/res/cardsfolder/cardsfolder.zip
Unzipping this file will sllow you to search for any card in the containing subfolders.
# Custom mechanics
We don't accept new mechanics from outside of official Cards into the main repository. This restriction is needed to keep the engine healthy.
However there is some support to simulate them using named abilities:
This will support things like being able to target specific SA using a custom name. (Flash on Meditate abilities in this example)
```text
Name:Plo Koon
ManaCost:3 W W
Types:Legendary Creature KelDor Jedi
PT:4/4
S:Mode$ CastWithFlash | ValidSA$ Activated.NamedAbilityMeditate | Caster$ You | Description$ You may activate meditate abilities any time you could cast an instant.
A:AB$ ChangeZone | Named$ Meditate | Cost$ 1 W | ActivationZone$ Battlefield | SorcerySpeed$ True | Origin$ Battlefield | Destination$ Hand | Defined$ Self | SpellDescription$ Meditate (Return this creature to its owner's hand. Meditate only as a sorcery.)
Oracle:You may activate meditate abilities any time you could cast an instant.\nMeditate 1W (Return this creature to its owner's hand. Meditate only as a sorcery.)
```
Restrict trigger to only if was triggered by a specific type of SA (Only Scry when Meditating and not being bounced), and reduce cost for a specific type of ability.
```text
Name:Jedi Training
ManaCost:U
Types:Enchantment
S:Mode$ ReduceCost | ValidCard$ Card | ValidSpell$ Activated.NamedAbilityMeditate | Activator$ You | Amount$ 1 | Description$ Meditate abilities you activate cost {1} less to activate.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Hand | TriggerZones$ Battlefield | ValidCard$ Creature.Jedi+YouCtrl | Condition$ FromNamedAbilityMeditate | SubAbility$ DBScry | TriggerDescription$ Whenever a Jedi creature you control meditates, scry 1.
SVar:DBScry:DB$ Scry | ScryNum$ 1
Oracle:Meditate abilities you activate cost {1} less to activate.\nWhenever a Jedi creature you control meditates, scry 1.
```
It will also allow for cards to check if a card was cast using an certain ability using `Count$FromNamedAbility<name>.<true>.<false>`:
```text
Name:Chronic Traitor
ManaCost:2 B
Types:Creature Human Rogue
PT:2/1
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigSacrifice | TriggerDescription$ When this creature enters, each player sacrifices a creature. If this creature's paranoia cost was paid, each player sacrifices two creatures instead.
SVar:TrigSacrifice:DB$ Sacrifice | Defined$ Player | SacValid$ Creature | Amount$ X
SVar:X:Count$FromNamedAbilityParanoia.2.1
T:Mode$ ChangesZone | TriggerZones$ Hand | ValidCard$ Permanent.YouCtrl | Origin$ Battlefield | Destination$ Any | Execute$ PayParanoia | TriggerDescription$ Paranoia {2}{B}{B} (You may cast this spell for its paranoia cost when a permanent you control leaves the battlefield.)
SVar:PayParanoia:DB$ Play | Named$ Paranoia | PlayCost$ 2 B B | ValidSA$ Spell.Self | Controller$ You | ValidZone$ Hand | Optional$ True
Oracle:When this creature enters, each player sacrifices a creature. If this creature's paranoia cost was paid, each player sacrifices two creatures instead.\nParanoia {2}{B}{B} (You may cast this spell for its paranoia cost when a permanent you control leaves the battlefield.)
```

View File

@@ -260,6 +260,15 @@ For the tokens, we can deposit them inside `%localappdata%/Forge/Cache/pics/toke
You can now start your game again, and see that the art loads correctly now.
## Excursion: Card variants
There are currently multiple ways to specify a flavor name:
* Manually, by writing `Variant:Foo:FlavorName:Loret Ipsum` in the card script, and adding `${"variant": "Foo"}` to the end of the edition entry. You'll also want to add `Variant:Foo:Oracle:When Loret Ipsum enters...` if the flavor name would appear in the Oracle text, or if it would otherwise be changed.
* By lookup, again by using `Variant:Foo:FlavorName:Loret Ipsum` in the card script, but simply using "Loret Ipsum" as the name in the edition file.
* Automatically, by putting `${"flavorName": "Loret Ipsum"}` at the end of the edition entry.
The third method is the easiest, but besides a simple find/replace for the card name, it won't be able to make any changes to flavor the Oracle text, such as for ability words. They all function the same under the hood once the CardDB is loaded; the latter two are just shortcuts for the first.
## 🎉 Congratulations
Youve 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.

BIN
docs/Development/github.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -6,13 +6,18 @@ https://discord.com/channels/267367946135928833/1095026912927154176
* Search the help posts in Discord
https://discord.com/channels/267367946135928833/1047001034788196452
Note: For now, please also check [this](https://www.slightlymagic.net/forum/viewtopic.php?f=26&t=11825) forum topic for some additional information.
# General
## How do I download content?
Forge has content downloaders within the app itself, you can use those tools to update the graphics assets. More information about card and token image assets can be found here. [Card Images, Downloading](Card-Images#downloading)
Forge has content downloaders within the app itself, you can use those tools to update the graphics assets. More information about card and token image assets can be found here. [Card Images, Downloading](Card-Images.md#downloading)
## My desktop match/deck view is all messed up?
The match and deck editor windows contain panels that can be moved and/or resized. The changes that you make are saved to files that are named "editor.xml" and "match.xml". These files can be found in your userDir/preferences/ directory.
Sometimes people will decide that they do not like the changes that they made and wish to go back to the original layout. To reset layouts to default, go to the Game Settings -> Preferences -> Troubleshooting section. You will find at this location two buttons that will reset the match layout and the deck editor layouts.
Also use the mentioned measure if your match or deckeditor won't start - it would help in 90% of the cases.
## I think I found a bug in Forge. What do I do?
@@ -24,7 +29,7 @@ For starters, please take note of (1) what you had in play, (2) what your oppone
If you did not get a Crash Report, but you have experienced a problem in how Forge handled one or more cards or game rules, *please read the cards (and the Oracle rulings) carefully* to make sure you understand how they work. You may be surprised to find that Forge is actually enforcing the rules correctly.
Because duplicate bug reports use up our limited resources, please research your bug with the **Search** box on Forge's [issue tracker](https://git.cardforge.org/core-developers/forge/-/issues) to see if your bug has already been reported there. For Crash Reports, use key words from the second paragraph of the Crash Report.
Because duplicate bug reports use up our limited resources, please research your bug with the **Search** box on Forge's [issue tracker](https://github.com/Card-Forge/forge/issues) to see if your bug has already been reported there. For Crash Reports, use key words from the second paragraph of the Crash Report.
* If you find a matching issue, examine it to see if you have anything new to contribute. For example, a different way of reproducing a problem can sometimes be helpful. If the issue was posted to the forum, you may post your additional information there.
@@ -46,7 +51,17 @@ A development environment such as [IntelliJ](https://www.jetbrains.com/idea) is
Thanks to the nature of how cards are implemented, you can also contribute these as small plain text files. This is especially helpful during a preview season, when there are a lot of new cards in the backlog. This is mostly coordinated in #card-scripting on the Discord (and the pins there).
To obtain the source code of Forge, read our [Development Guide]((SM-autoconverted)--how-to-get-started-developing-forge).
For smaller first-time contributions using the GitHub web interface is also an alternative:
![github](Development/github.png)
1. Register GitHub Account (if you don't already have one)
2. Make your own fork of Forge, press this button on the main project page (must only be done first time)
3. In your fork you can navigate to `forge-gui/res/cardsfolder/upcoming` and either Upload new files or open an existing one
4. When you're done at the bottom make sure to create a new branch, that makes it easier to keep your changes apart
5. On the next page make sure you choose the original project as merge target (see screens)
6. Please test your scripts and watch for review comments
To obtain the source code of Forge, read our [Development Guide](Development/IntelliJ-setup/IntelliJ-setup.md).
## My system is all setup to help. What now?
@@ -56,7 +71,7 @@ Take a look through the /res/cardsfolder folder. This is where all the card data
## Where do I use Flashback or a similar ability that is in an External area?
Click on the Lightning Bolt icon in the player panel. Since cards with External Activations aren't as clear to activate, we created this shortcut for this specific purpose.
Click on the Lightning Bolt icon in the player panel. Since cards with external Activations aren't as clear to activate, we created this shortcut for this specific purpose. After the last card is removed from a zone window, that window will automatically be hidden.
## How do I target a player?
@@ -71,7 +86,3 @@ If you have an effect that generated you some mana, and you don't know where it
## What is the difference between Fantasy Quest and Normal Quest?
In Normal Quest, you start with 20 life and only have access to the Card Shop. In Fantasy Quest, you start at 15 life and gain additional access to the Bazaar which allows you to buy things like extra life points, Pets, Plants and more.
## Sealed Deck Mode
[HOW-TO: Customize your Sealed Deck games with fantasy blocks](https://www.slightlymagic.net/forum/viewtopic.php?f=26&t=8164)

4
docs/Skins.md Normal file
View File

@@ -0,0 +1,4 @@
Download more skins here:
https://github.com/Card-Forge/forge-extras/releases/tag/themes
[Skin Template + Atlas file](https://github.com/user-attachments/files/23420566/forge_sprite_icons_template.zip)

View File

@@ -1,3 +0,0 @@
This page is a placeholder for Theme creation information.
I'm working on creating an atlas data file for the skin sprite sheets. I'll separate this into 3 sections; Skins, Music and Sounds, and Card Images.

View File

@@ -1,8 +1,6 @@
# User Guide
# Downloads
## Downloads
### Snapshots
## Snapshots
* Snapshots are automated daily builds of the source code.
* They contain the latest bug fixes, features and cards.
@@ -16,7 +14,7 @@
<https://github.com/user-attachments/assets/7a0c7bb8-7cf9-4800-8091-bcc30ff2f4d8>
### Releases
## Releases
* "Releases" are really intended where "99% cards implemented are working and stable".
* **They are NOT bug-free.** They are not updated after they're built, meaning you need to wait for the next release if you encounter a bug, or use the SNAPSHOT version instead.
@@ -27,7 +25,7 @@
* Grab the installer file that ends in .jar
## System Requirements
# System Requirements
**Forge Requires Java** to run, please make sure you have Java installed on your machine prior to attempting to run.
@@ -47,11 +45,13 @@ We have created several scripts that will launch Forge with a greater
allotment of system resources. (We do this by passing `-Xmx1024m` as
an argument to the Java VM.)
## Install and Run
If you plan to eventually download all card images make sure you have several gigabytes of free drive space.
# Install and Run
> Warning: Do **NOT** owerwrite an existing installation. Always unpack/install the package in a new folder to avoid problems!
### Install Wizard (jar)
## Install Wizard (jar)
* Run/Double click "**forge-installer**-VERSION.jar" where VERSION is the current release version and click next until the Target Path window appears. If double clicking the .jar file doesn't load the main interface you can run it via terminal/command line ```java -jar FILENAME.jar``` where FILENAME is the name of the installer.
@@ -67,16 +67,16 @@ Sometimes double-clicking will open the jar file in a different program.
In Windows, you may need to right-click and open the properties to change the launching program to Java.
This might be different in OSX or Linux systems (file permission related).
### Manual Extraction (tar.bz2)
## Manual Extraction (tar.bz2)
#### Desktop Windows
### Desktop Windows
* Unpack "forge...*tar.bz2*" with any unpacking/unzipping app (e.g. 7-zip, winrar, etc)
* You'll end up with "forge...*tar*".
* Unpack that ".tar" file once more into its own folder.
* Run Forge app/exe
#### Desktop Linux/Mac
### Desktop Linux/Mac
* Unpack "forge...*tar.bz2*" with any unpacking app. (Check your package repository, or app store.)
* You'll probably end up with just a folder, and fully extracted.
@@ -87,12 +87,39 @@ This might be different in OSX or Linux systems (file permission related).
* If the command file doesn't appear to do anything, you'll need to [modify the permissions to be executable.](https://support.apple.com/guide/terminal/make-a-file-executable-apdd100908f-06b3-4e63-8a87-32e71241bab4/mac) (This is a temporary bug in the build process.)
* Additionally OSX needs to have a JRE AND a JDK installed because reasons.
#### Android
### Android
* Sideload/Install "forge...apk"
* Run Forge
### Play Adventure Mode on Desktop
# User data migration
There are three defined user data directories: userDir, cacheDir, and cardPicsDir, and their locations depend on the standard paths for your operating system:
Windows:
userDir=%APPDATA%/Forge/
cacheDir=%LOCALAPPDATA%/Forge/Cache/ (or %APPDATA%/Forge/Cache/ for Windows versions before the local/roaming directory split)
OSX:
userDir=$HOME/Library/Application Support/Forge/
cacheDir=$HOME/Library/Caches/Forge/
Linux:
userDir=$HOME/.forge/
cacheDir=$HOME/.cache/forge/
The appdata directory is hidden by default in Windows 7 and above versions. Open a Windows Explorer window (or double-click on My Computer) and in the address field type "%appdata%/forge/" (without the quotes).
cardPicsDir is defined as <cacheDir>/pics/cards/ by default. If you wish to use a non-default directory, please see the forge.profile.properties.example file located in the Forge installation directory root. You can use this file to, for example, share the card pics directory with another program, such as Magic Workstation.
If you are using the Mac OS X version of Forge then you will find the forge.profile.properties.example file by right clicking or control clicking on the Forge.app icon. Select "Show Package Contents" from the contextual menu. A Finder window will open and will display a folder named Contents. Navigate to the folder:
/Contents/Resources/Java/
and you will find the file.
## Import Data
If you have a directory full of deck files, you can use the Import Data dialog to copy or move them to the appropriate directory. The dialog gives you a full listing of all file copy/move operations, so you can see what will happen before you click 'Start Import'.
# Accessibility
We know some people are colorblind and may not be able to differentiate between colors of the default theme. Forge does have access to other [Skins](Skins.md), which use other color palettes that might be more suitable for you.
# Play Adventure Mode on Desktop
* Run the Adventure Mode EXE or Script in the Folder you extracted.
* The game will start with an option for Adventure or Classic Mobile UI.
@@ -101,9 +128,69 @@ This might be different in OSX or Linux systems (file permission related).
* check you're up to date with your version.
* check in the settings that the "Selector Mode" is set to `Default`
## Gameplay
# Gameplay
### Full Control
## Targeting Arrows
When hovering over items on the stack, arrows will be displayed between that item and all of its targets (both cards and players).
The arrow will be red if the spell/ability's activator is an opponent of the target or its controller, and blue if targeting an ally of the target or its controller.
## Card Zoomer
You can gaze at your HQ images in all their glory with just a flick of the mousewheel, holding the middle mouse button down, or holding the left and right mouse buttons down at the same time. This feature will also increase the size of low quality pics up to the size used for high quality pics, but the image will not be very clear.
Instructions:
- Works on any card image in the Deck Editor or Duel screen.
- Move your mouse over the card you want to zoom and mouse-wheel forward.
- Mouse-wheel back, mouse click or pressing ESC closes the zoomed image.
Split cards (name contains "//") are rotated 90 degrees for easier viewing.
If a card is a flip or double-sided card then you can easily view the alternate image using flick wheel forward or tap CTRL key.
The standard flip graphic (the two rotated arrows) is displayed if the card can be flipped or transformed.
Forge supports showing XLHQ (extra large high quality) card pictures when zooming in on a card if these pictures are available. Forge will look for XLHQ card art in the "XLHQ" subfolder of the "pics/cards" folder in Forge cache. XLHQ pictures should have the ".xlhq.jpg" extension instead of the ".full.jpg" one (CCGHQ XLHQ releases comply with this naming scheme).
Please note that XLHQ versions of cards are *only* showed in the zoom view, regular card pictures are still used (LQ/HQ, depending on what you're using) on the battlefield and elsewhere in the game because XLHQ art is significantly more taxing in memory consumption (and in addition to that, XLHQ card borders are not cropped the way Forge expects them in order to show them properly on the battlefield anyway).
XLHQ tokens are also supported, but the naming scheme for them is a little different - they are looked up in "pics/tokens/XLHQ" and have their ordinary names.
## Easier creature type selection
When prompted to select a creature type for a card like *Obelisk of Urd*, creature types present in your deck will appear on top, sorted from most to least frequent, followed by all other creature types.
This should make it so, more often than not, you can just accept the dialog without searching.
## Auto-Target
When playing spells and abilities with the text "target opponent", if you only have one opponent, you will no longer be asked to choose the opponent to target.
When triggered abilities have only one valid target, that target will now be auto-selected.
## Auto-Pay
When paying mana costs, you can press Enter/Spacebar or click the Auto button in the Prompt to automatically pay the mana cost using available mana sources if possible.
- The button will be disabled if you cannot pay the mana cost at that time, in which case Enter/Spacebar will cancel the spell/ability instead.
- Uses the same logic the AI uses to pay mana costs. This means it will try to use mana in your pool before mana sources in play, using colorless mana to pay the colorless part of the cost if any is available.
- You can still manually pay the cost by clicking mana sources in play (e.g. lands) or clicking symbols in your mana pool, which might be a good idea if you want to save specific mana sources for a later play that turn.
- you'll still be prompted when paying Sunburst or cards that care what colors are spent to cast it (ex. Firespout).
## Auto-Yield
- When a spell or an ability appears on the stack and it says "(OPTIONAL)" you can right-click it to decide if you want to always accept or to decline it.
It is possible to specify the granularity level for auto-yields: the difference is that, for example, when yielding per ability if you auto-yield to Hellrider's triggered ability once, all triggers from other Hellrider cards will be automatically auto-yielded to as well. When yielding per card, you will need to auto-yield to each Hellrider separately.
Note that in when auto-yielding per ability, auto-yields will NOT be automatically cleared between games in a match, which should speed the game up. When auto-yielding per card, auto-yields WILL be automatically cleared between games because they are dependent on card IDs which change from game to game, thus you will need to auto-yield to each card again in each game of the match.
- Pressing "End Turn" skips your attack phase and doesn't get cancelled automatically if a spell or ability is put on the stack. You will still be given a chance to declare blockers if your opponent attacks, but after that the rest of your opponent's turn will now be skipped properly.
To alleviate pressing this accidentally, as long as you're passing this way, you'll be able to press Escape or the Cancel button to be given the chance to act again. Phases with stops and spells/abilities resolving will be given a slight delay to allow you to see what's going on.
## Shift Key helper
* When you mouse over a flip, transform or Morph (controlled by you) card in battlefield, hold SHIFT to see other state of that card at the side panel that displays card picture and details.
* Hold SHIFT when clicking on mana pool icons to use as much of that mana type as possible towards the cost.
* Tap all lands in a stack using Shift+click on any in the stack.
* Attack with all creatures in a stack using Shift+click on any in the stack.
## Full Control
Right click/long tap on your player avatar:
This feature lets you skip different helpers that streamline gameplay by avoiding somewhat annoying GUI interactions and instead use AI logic to make reasonable decisions for beginners.\nUseful for certain corner cases or if you want to challenge yourself with the Comprehensive Rules:\ne.g. the opposite cost order is needed if activating an animated "Halo Fountain" that's also tapped.
This feature lets you skip different helpers that streamline gameplay by avoiding somewhat annoying GUI interactions and instead use AI logic to make reasonable decisions for beginners. Useful for certain corner cases or if you want to challenge yourself with the Comprehensive Rules:
e.g. the opposite cost order is needed if activating an animated "Halo Fountain" that's also tapped.
## Repeatable Sequences (Macros)
A feature for advanced users: during a match, you can use the default shortcut shift-R to specify a sequence of actions (mouse clicks, essentially, in the desktop paradigm). Type the IDs of cards/players you'd like to interact with, in order. Then the default shortcut @ (shift-2) will execute your sequence, one "click" at a time, repeating when it reaches the end. This is useful for executing repeated combos, such as sacrificing a recurring creature to Goblin Bombardment. You can see the IDs of cards by turning them on under "Card Overlays" in the "Game" menu.
The macro will dutifully execute your click sequence without regard to changes in game state (so if an opponent kills your specified creature mid-macro, and you continue to execute it, you will be essentially clicking on the creature in the graveyard, which may or may not be what you want).

View File

@@ -10,20 +10,19 @@
- [Advanced search](Advanced-Search.md)
- [Adventure Mode](Adventure/Adventure-Mode.md)
- Gameplay Guide
- [Getting Started](Adventure/Gameplay-Guide.md)
- [Different Planes](Adventure/Different-Planes.md)
- [Currency](Adventure/Currency.md)
- [Deck Building Tips](Adventure/Deck-Building-Tips.md)
- [Towns & Capitals](Adventure/Towns-&-Capitals.md)
- [Dungeons](Adventure/Dungeons.md)
- [Equipments and Items](Adventure/Equipments-and-Items.md)
- [Keyboard Shortcuts](Keyboard-Shortcuts.md)
- [Controller Support](Adventure/GAMEPAD.md)
- [Console and Cheats](Adventure/Console-and-Cheats.md)
- [Modding and content creation](Adventure/Modding.md)
- Gameplay Guide
- [Getting Started](Adventure/Gameplay-Guide.md)
- [Different Planes](Adventure/Different-Planes.md)
- [Currency](Adventure/Currency.md)
- [Deck Building Tips](Adventure/Deck-Building-Tips.md)
- [Towns & Capitals](Adventure/Towns-&-Capitals.md)
- [Dungeons](Adventure/Dungeons.md)
- [Equipment & Items](Adventure/Equipments-and-Items.md)
- [Controller Support](Adventure/GAMEPAD.md)
- [Console & Cheats](Adventure/Console-and-Cheats.md)
- [Modding & content creation](Adventure/Modding.md)
- [Create Enemies](Adventure/Create-Enemies.md)
- [Create Rewards](Adventure/Create-Rewards.md)
- [Create Maps](Adventure/Create-new-Maps.md)
@@ -45,22 +44,23 @@
- [Restrictions / Conditions](Card-scripting-API/Restrictions.md)
- [Tutorial: creating a custom card](Card-scripting-API/Creating-a-Custom-Card.md)
- [Development]((SM-autoconverted)-how-to-get-started-developing-forge.md)
- Development
- [IntelliJ Setup](Development/IntelliJ-setup/IntelliJ-setup.md)
- [Snapshots and Releases](Development/Snapshots-and-Releases.md)
- [Snapshots & Releases](Development/Snapshots-and-Releases.md)
- [Android Builds](Development/Android-Builds.md)
- [Dev Mode](Development/DevMode.md)
- [Ownership](Development/ownership.md)
- [Docker Container](docker-setup.md)
- [Customization and Themes](Themes.md)
- Skins
- Sounds
- [Music](Custom-Music.md)
- Customization & Themes
- [Skins](Skins.md)
- [Sounds & Music](Custom-Audio.md)
- [Card Images](Card-Images.md)
- [File Formats](File-Formats.md)
- [Tutorial: creating your first custom set](Creating-a-custom-Set.md)
- [Fantasy Blocks](fantasy-blocks.md)
- [Missing Cards in Forge](Missing-Cards-in-Forge.md)
- [Uncards, Playtest Cards, and Other Funny Cards](Uncards,-Playtest-Cards,-and-Other-Funny-Cards.md)
- [Credit and Thanks](Credit-and-Thanks.md)
- [Credit & Thanks](Credit-and-Thanks.md)

0
docs/fantasy-blocks.md Normal file
View File

BIN
docs/search.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1345,9 +1345,7 @@ public class ComputerUtilMana {
}
}
if (!effect) {
CostAdjustment.adjust(manaCost, sa, null, test);
}
CostAdjustment.adjust(manaCost, sa, null, test, effect);
if ("NumTimes".equals(sa.getParam("Announce"))) { // e.g. the Adversary cycle
ManaCost mkCost = sa.getPayCosts().getTotalMana();
@@ -1773,15 +1771,18 @@ public class ComputerUtilMana {
/**
* Matches list of creatures to shards in mana cost for convoking.
* @param cost cost of convoked ability
* @param list creatures to be evaluated
* @param improvise
*
* @param cost cost of convoked ability
* @param list creatures to be evaluated
* @param artifacts
* @param creatures
* @return map between creatures and shards to convoke
*/
public static Map<Card, ManaCostShard> getConvokeOrImproviseFromList(final ManaCost cost, List<Card> list, boolean improvise) {
public static Map<Card, ManaCostShard> getConvokeOrImproviseFromList(final ManaCost cost, List<Card> list, boolean artifacts, boolean creatures) {
final Map<Card, ManaCostShard> convoke = new HashMap<>();
Card convoked = null;
if (!improvise) {
if (creatures && !artifacts) {
// Run for convoke but not improvise or waterbending
for (ManaCostShard toPay : cost) {
if (toPay.isSnow() || toPay.isColorless()) {
continue;

View File

@@ -1382,7 +1382,7 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public Map<Card, ManaCostShard> chooseCardsForConvokeOrImprovise(SpellAbility sa, ManaCost manaCost, CardCollectionView untappedCards, boolean improvise) {
public Map<Card, ManaCostShard> chooseCardsForConvokeOrImprovise(SpellAbility sa, ManaCost manaCost, CardCollectionView untappedCards, boolean artifacts, boolean creatures, Integer maxReduction) {
final Player ai = sa.getActivatingPlayer();
final PhaseHandler ph = ai.getGame().getPhaseHandler();
//Filter out mana sources that will interfere with payManaCost()
@@ -1390,9 +1390,10 @@ public class PlayerControllerAi extends PlayerController {
// Filter out creatures if AI hasn't attacked yet
if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
if (improvise) {
if (!creatures) {
untapped = CardLists.filter(untapped, c -> !c.isCreature());
} else {
// TODO AI needs to learn how to use Convoke or Waterbend
return new HashMap<>();
}
}
@@ -1406,13 +1407,16 @@ public class PlayerControllerAi extends PlayerController {
if (!ai.getGame().getStack().isEmpty()) {
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), null);
for (Card c : blockers) {
if (objects.contains(c) && (!improvise || c.isArtifact())) {
if (objects.contains(c) && (creatures || c.isArtifact())) {
untapped.add(c);
}
if (maxReduction != null && untapped.size() >= maxReduction) {
break;
}
}
}
}
return ComputerUtilMana.getConvokeOrImproviseFromList(manaCost, untapped, improvise);
return ComputerUtilMana.getConvokeOrImproviseFromList(manaCost, untapped, artifacts, creatures);
}
@Override

View File

@@ -1,7 +1,5 @@
package forge.ai.ability;
import forge.ai.AiAbilityDecision;
import forge.ai.AiPlayDecision;
import forge.ai.*;
import forge.game.Game;
import forge.game.ability.AbilityUtils;

View File

@@ -776,7 +776,7 @@ public class StaticData {
Queue<String> TOKEN_Q = new ConcurrentLinkedQueue<>();
boolean nifHeader = false;
boolean cniHeader = false;
final Pattern funnyCardCollectorNumberPattern = Pattern.compile("^F\\d+");
final Pattern funnyCardCollectorNumberPattern = Pattern.compile("^F★?\\d+★?");
for (CardEdition e : editions) {
if (CardEdition.Type.FUNNY.equals(e.getType()))
continue;

View File

@@ -75,11 +75,13 @@ public class Game {
private List<Card> activePlanes = null;
public final Phase cleanup;
public final Phase endOfCombat;
public final Phase endOfTurn;
public final Untap untap;
public final Phase upkeep;
public final Phase beginOfCombat;
public final Phase endOfCombat;
public final Phase endOfTurn;
public final Phase cleanup;
// to execute commands for "current" phase each time state based action is checked
public final List<GameCommand> sbaCheckedCommandList;
public final MagicStack stack;
@@ -363,9 +365,10 @@ public class Game {
untap = new Untap(this);
upkeep = new Phase(PhaseType.UPKEEP);
cleanup = new Phase(PhaseType.CLEANUP);
beginOfCombat = new Phase(PhaseType.COMBAT_BEGIN);
endOfCombat = new Phase(PhaseType.COMBAT_END);
endOfTurn = new Phase(PhaseType.END_OF_TURN);
cleanup = new Phase(PhaseType.CLEANUP);
sbaCheckedCommandList = new ArrayList<>();
@@ -428,6 +431,9 @@ public class Game {
public final Phase getUpkeep() {
return upkeep;
}
public final Phase getBeginOfCombat() {
return beginOfCombat;
}
public final Phase getEndOfCombat() {
return endOfCombat;
}

View File

@@ -15,6 +15,7 @@ import forge.game.*;
import forge.game.ability.AbilityFactory.AbilityRecordType;
import forge.game.card.*;
import forge.game.cost.Cost;
import forge.game.cost.CostAdjustment;
import forge.game.cost.IndividualCostPaymentInstance;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
@@ -1527,6 +1528,7 @@ public class AbilityUtils {
else {
cost = new Cost(unlessCost, true);
}
cost = CostAdjustment.adjust(cost, sa, true);
return cost;
}

View File

@@ -23,7 +23,7 @@ public class ChooseSourceEffect extends SpellAbilityEffect {
sb.append(Lang.joinHomogenous(getTargetPlayers(sa)));
sb.append("chooses a source.");
sb.append(" chooses a source.");
return sb.toString();
}

View File

@@ -2,7 +2,6 @@ package forge.game.ability.effects;
import java.util.List;
import forge.GameCommand;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
@@ -28,10 +27,11 @@ public class ControlPlayerEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) {
final Player controller = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Controller"), sa).get(0);
final Game game = controller.getGame();
final boolean combat = sa.hasParam("Combat");
for (final Player pTarget: getTargetPlayers(sa)) {
// before next untap gain control
game.getCleanup().addUntil(pTarget, (GameCommand) () -> {
(combat ? game.getBeginOfCombat() : game.getCleanup()).addUntil(pTarget, () -> {
// CR 800.4b
if (!controller.isInGame()) {
return;
@@ -41,7 +41,7 @@ public class ControlPlayerEffect extends SpellAbilityEffect {
pTarget.addController(ts, controller);
// after following cleanup release control
game.getCleanup().addUntil((GameCommand) () -> pTarget.removeController(ts));
(combat ? game.getEndOfCombat() : game.getCleanup()).addUntil(() -> pTarget.removeController(ts));
});
}
}

View File

@@ -31,7 +31,7 @@ public class EarthbendEffect extends SpellAbilityEffect {
sb.append(amount).append(". (Target land you control becomes a 0/0 creature with haste that's still a land. Put ");
sb.append(Lang.nounWithNumeral(amount, "+1/+1 counter"));
sb.append(" on it. When it dies or is exiled, return it to the battlefield tapped.)");
sb.append(" on it. When it dies or is exiled, return it to the battlefield tapped under your control.)");
return sb.toString();
}
@@ -74,7 +74,7 @@ public class EarthbendEffect extends SpellAbilityEffect {
protected void buildTrigger(SpellAbility sa, Card c, String sbTrig, String zone) {
final Card source = sa.getHostCard();
final Game game = source.getGame();
String trigSA = "DB$ ChangeZone | Defined$ DelayTriggerRemembered | Origin$ " + zone + " | Destination$ Battlefield | Tapped$ True";
String trigSA = "DB$ ChangeZone | Defined$ DelayTriggerRemembered | Origin$ " + zone + " | Destination$ Battlefield | Tapped$ True | GainControl$ You";
final Trigger trig = TriggerHandler.parseTrigger(sbTrig, CardCopyService.getLKICopy(source), sa.isIntrinsic());
final SpellAbility newSa = AbilityFactory.getAbility(trigSA, sa.getHostCard());

View File

@@ -140,6 +140,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
// stores the card traits created by static abilities
private final Table<StaticAbility, String, SpellAbility> storedSpellAbility = TreeBasedTable.create();
private final Table<StaticAbility, String, Trigger> storedTrigger = TreeBasedTable.create();
private final Table<StaticAbility, SpellAbility, SpellAbility> storedAbilityForTrigger = HashBasedTable.create();
private final Table<StaticAbility, String, ReplacementEffect> storedReplacementEffect = TreeBasedTable.create();
private final Table<StaticAbility, String, StaticAbility> storedStaticAbility = TreeBasedTable.create();
@@ -4974,7 +4975,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
String str = trig.toString() + trig.getId();
Trigger result = storedTrigger.get(stAb, str);
if (result == null) {
result = trig.copy(this, false);
SpellAbility ab = null;
if (trig.hasParam("Execute") && trig.getOverridingAbility() != null) {
ab = storedAbilityForTrigger.get(stAb, trig.getOverridingAbility());
if (ab == null) {
ab = trig.getOverridingAbility().copy(this, false);
storedAbilityForTrigger.put(stAb, trig.getOverridingAbility(), ab);
}
}
result = trig.copy(this, false, false, ab);
storedTrigger.put(stAb, str, result);
}
return result;

View File

@@ -79,6 +79,7 @@ public class CardState implements GameObject, IHasSVars, ITranslatable {
private FCollection<StaticAbility> staticAbilities = new FCollection<>();
private String imageKey = "";
private Map<String, String> sVars = Maps.newTreeMap();
private Map<String, SpellAbility> abilityForTrigger = Maps.newHashMap();
private KeywordCollection cachedKeywords = new KeywordCollection();
@@ -732,6 +733,11 @@ public class CardState implements GameObject, IHasSVars, ITranslatable {
setFlavorName(source.getFlavorName());
setSVars(source.getSVars());
abilityForTrigger.clear();
for (Map.Entry<String, SpellAbility> e : source.abilityForTrigger.entrySet()) {
abilityForTrigger.put(e.getKey(), e.getValue().copy(card, lki));
}
abilities.clear();
for (SpellAbility sa : source.abilities) {
if (sa.isIntrinsic()) {
@@ -758,7 +764,7 @@ public class CardState implements GameObject, IHasSVars, ITranslatable {
continue;
}
if (tr.isIntrinsic()) {
triggers.add(tr.copy(card, lki));
triggers.add(tr.copy(card, lki, false, tr.hasParam("Execute") ? abilityForTrigger.get(tr.getParam("Execute")) : null));
}
}
ReplacementEffect runRE = null;
@@ -951,6 +957,10 @@ public class CardState implements GameObject, IHasSVars, ITranslatable {
return cloakUp;
}
public SpellAbility getAbilityForTrigger(String svar) {
return abilityForTrigger.computeIfAbsent(svar, s -> AbilityFactory.getAbility(getCard(), s, this));
}
@Override
public String getTranslationKey() {
String displayName = flavorName == null ? name : flavorName;

View File

@@ -188,6 +188,15 @@ public class Cost implements Serializable {
return this.isAbility;
}
public final String getMaxWaterbend() {
for (CostPart cp : this.costParts) {
if (cp instanceof CostPartMana) {
return ((CostPartMana) cp).getMaxWaterbend();
}
}
return null;
}
private Cost() {
}
@@ -564,6 +573,11 @@ public class Cost implements Serializable {
return new CostRevealChosen(splitStr[0], splitStr.length > 1 ? splitStr[1] : null);
}
if (parse.startsWith("Waterbend<")) {
final String[] splitStr = abCostParse(parse, 1);
return new CostWaterbend(splitStr[0]);
}
if (parse.equals("Forage")) {
return new CostForage();
}
@@ -973,6 +987,7 @@ public class Cost implements Serializable {
} else {
costParts.add(0, new CostPartMana(manaCost.toManaCost(), null));
}
getCostMana().setMaxWaterbend(mPart.getMaxWaterbend());
} else if (part instanceof CostPutCounter || (mergeAdditional && // below usually not desired because they're from different causes
(part instanceof CostDiscard || part instanceof CostDraw ||
part instanceof CostAddMana || part instanceof CostPayLife ||

View File

@@ -31,11 +31,13 @@ import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Predicate;
public class CostAdjustment {
public static Cost adjust(final Cost cost, final SpellAbility sa, boolean effect) {
if (sa.isTrigger() || cost == null || effect) {
sa.setMaxWaterbend(cost);
return cost;
}
@@ -99,6 +101,9 @@ public class CostAdjustment {
host.setState(CardStateName.Original, false);
host.setFaceDown(false);
}
sa.setMaxWaterbend(result);
return result;
}
@@ -171,21 +176,22 @@ public class CostAdjustment {
// If cardsToDelveOut is null, will immediately exile the delved cards and remember them on the host card.
// Otherwise, will return them in cardsToDelveOut and the caller is responsible for doing the above.
public static boolean adjust(ManaCostBeingPaid cost, final SpellAbility sa, CardCollection cardsToDelveOut, boolean test) {
if (sa.isTrigger() || sa.isReplacementAbility()) {
public static boolean adjust(ManaCostBeingPaid cost, final SpellAbility sa, CardCollection cardsToDelveOut, boolean test, boolean effect) {
if (effect) {
adjustCostByWaterbend(cost, sa, test);
}
if (effect || sa.isTrigger() || sa.isReplacementAbility()) {
return true;
}
final Game game = sa.getActivatingPlayer().getGame();
final Card originalCard = sa.getHostCard();
boolean isStateChangeToFaceDown = false;
if (sa.isSpell()) {
if (sa.isCastFaceDown() && !originalCard.isFaceDown()) {
// Turn face down to apply cost modifiers correctly
originalCard.turnFaceDownNoUpdate();
isStateChangeToFaceDown = true;
}
boolean isStateChangeToFaceDown = false;
if (sa.isSpell() && sa.isCastFaceDown() && !originalCard.isFaceDown()) {
// Turn face down to apply cost modifiers correctly
originalCard.turnFaceDownNoUpdate();
isStateChangeToFaceDown = true;
}
CardCollection cardsOnBattlefield = new CardCollection(game.getCardsIn(ZoneType.Battlefield));
@@ -278,17 +284,19 @@ public class CostAdjustment {
table.triggerChangesZoneAll(game, sa);
}
if (sa.getHostCard().hasKeyword(Keyword.CONVOKE)) {
adjustCostByConvokeOrImprovise(cost, sa, false, test);
adjustCostByConvokeOrImprovise(cost, sa, false, true, test);
}
if (sa.getHostCard().hasKeyword(Keyword.IMPROVISE)) {
adjustCostByConvokeOrImprovise(cost, sa, true, test);
adjustCostByConvokeOrImprovise(cost, sa, true, false, test);
}
} // isSpell
if (sa.hasParam("TapCreaturesForMana")) {
adjustCostByConvokeOrImprovise(cost, sa, false, test);
adjustCostByConvokeOrImprovise(cost, sa, false, true, test);
}
adjustCostByWaterbend(cost, sa, test);
// Reset card state (if changed)
if (isStateChangeToFaceDown) {
originalCard.setFaceDown(false);
@@ -299,6 +307,13 @@ public class CostAdjustment {
}
// GetSpellCostChange
private static void adjustCostByWaterbend(ManaCostBeingPaid cost, SpellAbility sa, boolean test) {
Integer maxWaterbend = sa.getMaxWaterbend();
if (maxWaterbend != null && maxWaterbend > 0) {
adjustCostByConvokeOrImprovise(cost, sa, true, true, test);
}
}
private static boolean adjustCostByAssist(ManaCostBeingPaid cost, final SpellAbility sa, boolean test) {
// 702.132a Assist is a static ability that modifies the rules of paying for the spell with assist (see rules 601.2g-h).
// If the total cost to cast a spell with assist includes a generic mana component, before you activate mana abilities while casting it, you may choose another player.
@@ -321,27 +336,33 @@ public class CostAdjustment {
return assistant.getController().helpPayForAssistSpell(cost, sa, genericLeft, requestedAmount);
}
private static void adjustCostByConvokeOrImprovise(ManaCostBeingPaid cost, final SpellAbility sa, boolean improvise, boolean test) {
if (!improvise) {
private static void adjustCostByConvokeOrImprovise(ManaCostBeingPaid cost, final SpellAbility sa, boolean artifacts, boolean creatures, boolean test) {
if (creatures && !artifacts) {
sa.clearTappedForConvoke();
}
final Player activator = sa.getActivatingPlayer();
CardCollectionView untappedCards = CardLists.filter(activator.getCardsIn(ZoneType.Battlefield),
CardPredicates.CAN_TAP);
if (improvise) {
Integer maxReduction = null;
if (artifacts && creatures) {
maxReduction = sa.getMaxWaterbend();
Predicate <Card> isArtifactOrCreature = card -> card.isArtifact() || card.isCreature();
untappedCards = CardLists.filter(untappedCards, isArtifactOrCreature);
} else if (artifacts) {
untappedCards = CardLists.filter(untappedCards, CardPredicates.ARTIFACTS);
} else {
untappedCards = CardLists.filter(untappedCards, CardPredicates.CREATURES);
}
Map<Card, ManaCostShard> convokedCards = activator.getController().chooseCardsForConvokeOrImprovise(sa,
cost.toManaCost(), untappedCards, improvise);
cost.toManaCost(), untappedCards, artifacts, creatures, maxReduction);
CardCollection tapped = new CardCollection();
for (final Entry<Card, ManaCostShard> conv : convokedCards.entrySet()) {
Card c = conv.getKey();
if (!improvise) {
if (creatures && !artifacts) {
sa.addTappedForConvoke(c);
}
cost.decreaseShard(conv.getValue(), 1);

View File

@@ -39,6 +39,8 @@ public class CostPartMana extends CostPart {
private boolean isEnchantedCreatureCost = false;
private boolean isCostPayAnyNumberOfTimes = false;
protected String maxWaterbend;
public int paymentOrder() { return shouldPayLast() ? 200 : 0; }
public boolean shouldPayLast() {
@@ -63,6 +65,13 @@ public class CostPartMana extends CostPart {
this.isEnchantedCreatureCost = enchantedCreatureCost;
}
public String getMaxWaterbend() {
return maxWaterbend;
}
public void setMaxWaterbend(String max) {
maxWaterbend = max;
}
/**
* Gets the mana.
*
@@ -101,7 +110,7 @@ public class CostPartMana extends CostPart {
public boolean isUndoable() { return true; }
@Override
public final String toString() {
public String toString() {
return cost.toString();
}

View File

@@ -0,0 +1,18 @@
package forge.game.cost;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
public class CostWaterbend extends CostPartMana {
public CostWaterbend(final String mana) {
super(new ManaCost(new ManaCostParser(mana)), null);
maxWaterbend = mana;
}
@Override
public final String toString() {
return "Waterbend " + getMana().toString();
}
}

View File

@@ -297,6 +297,7 @@ public class PhaseHandler implements java.io.Serializable {
case COMBAT_BEGIN:
nCombatsThisTurn++;
combat = new Combat(playerTurn);
game.getBeginOfCombat().executeUntil(playerTurn);
//PhaseUtil.verifyCombat();
break;
@@ -756,7 +757,6 @@ public class PhaseHandler implements java.io.Serializable {
}
}
}
// fire blockers declared trigger
final Map<AbilityKey, Object> bdRunParams = AbilityKey.newMap();
bdRunParams.put(AbilityKey.Blockers, declaredBlockers);
bdRunParams.put(AbilityKey.Attackers, blockedAttackers);
@@ -768,7 +768,6 @@ public class PhaseHandler implements java.io.Serializable {
continue;
}
// Run triggers
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Blocker, c1);
runParams.put(AbilityKey.Attackers, combat.getAttackersBlockedBy(c1));

View File

@@ -202,7 +202,7 @@ public abstract class PlayerController {
public abstract CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard);
public abstract CardCollectionView chooseCardsToDelve(int genericAmount, CardCollection grave);
public abstract Map<Card, ManaCostShard> chooseCardsForConvokeOrImprovise(SpellAbility sa, ManaCost manaCost, CardCollectionView untappedCards, boolean improvise);
public abstract Map<Card, ManaCostShard> chooseCardsForConvokeOrImprovise(SpellAbility sa, ManaCost manaCost, CardCollectionView untappedCards, boolean artifacts, boolean creatures, Integer maxReduction);
public abstract List<Card> chooseCardsForSplice(SpellAbility sa, List<Card> cards);
public abstract CardCollectionView chooseCardsToRevealFromHand(int min, int max, CardCollectionView valid);

View File

@@ -144,6 +144,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private final Supplier<CardCollection> tappedForConvoke = Suppliers.memoize(CardCollection::new);
private Card sacrificedAsOffering;
private Card sacrificedAsEmerge;
private Integer maxWaterbend;
private AbilityManaPart manaPart;
@@ -2692,4 +2693,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public void setName(String name) {
this.name = name;
}
public Integer getMaxWaterbend() {
return maxWaterbend;
}
public void setMaxWaterbend(Cost cost) {
if (cost == null || cost.getMaxWaterbend() == null) {
return;
}
maxWaterbend = AbilityUtils.calculateAmount(getHostCard(), cost.getMaxWaterbend(), this);
}
}

View File

@@ -544,14 +544,19 @@ public abstract class Trigger extends TriggerReplacementBase {
}
public final Trigger copy(Card newHost, boolean lki) {
return copy(newHost, lki, false);
return copy(newHost, lki, false, null);
}
public final Trigger copy(Card newHost, boolean lki, boolean keepTextChanges) {
return copy(newHost, lki, keepTextChanges, null);
}
public final Trigger copy(Card newHost, boolean lki, boolean keepTextChanges, SpellAbility spellAbility) {
final Trigger copy = (Trigger) clone();
copyHelper(copy, newHost, lki || keepTextChanges);
if (getOverridingAbility() != null) {
if (spellAbility != null) {
copy.setOverridingAbility(spellAbility);
} else if (getOverridingAbility() != null) {
copy.setOverridingAbility(getOverridingAbility().copy(newHost, lki));
}
@@ -611,7 +616,11 @@ public abstract class Trigger extends TriggerReplacementBase {
public SpellAbility ensureAbility(final IHasSVars sVarHolder) {
SpellAbility sa = getOverridingAbility();
if (sa == null && hasParam("Execute")) {
sa = AbilityFactory.getAbility(getHostCard(), getParam("Execute"), sVarHolder);
if (this.isIntrinsic() && sVarHolder instanceof CardState state) {
sa = state.getAbilityForTrigger(getParam("Execute"));
} else {
sa = AbilityFactory.getAbility(getHostCard(), getParam("Execute"), sVarHolder);
}
setOverridingAbility(sa);
}
return sa;

View File

@@ -428,6 +428,10 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
game.getTriggerHandler().runTrigger(TriggerType.AbilityCast, runParams, true);
}
if (sp.getMaxWaterbend() != null) {
activator.triggerElementalBend(TriggerType.Waterbend);
}
// Run Cycled triggers
if (sp.isCycling()) {
activator.addCycled(sp);

View File

@@ -438,6 +438,9 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
pnlPrefs.add(cbpSwitchStates, comboBoxConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlSwitchStates")), descriptionConstraints);
pnlPrefs.add(cbSROptimize, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlSrOptimize")), descriptionConstraints);
// Sound options
pnlPrefs.add(new SectionLabel(localizer.getMessage("SoundOptions")), sectionConstraints + ", gaptop 2%");
@@ -455,8 +458,7 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
pnlPrefs.add(cbAltSoundSystem, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlAltSoundSystem")), descriptionConstraints);
pnlPrefs.add(cbSROptimize, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlSrOptimize")), descriptionConstraints);
// Keyboard shortcuts
pnlPrefs.add(new SectionLabel(localizer.getMessage("KeyboardShortcuts")), sectionConstraints);

View File

@@ -690,7 +690,7 @@ public class PlayerControllerForTests extends PlayerController {
@Override
public Map<Card, ManaCostShard> chooseCardsForConvokeOrImprovise(SpellAbility sa, ManaCost manaCost,
CardCollectionView untappedCards, boolean improvise) {
CardCollectionView untappedCards, boolean artifacts, boolean creatures, Integer maxReduction) {
// TODO: AI to choose a creature to tap would go here
// Probably along with deciding how many creatures to tap
return new HashMap<>();

View File

@@ -450,7 +450,9 @@ public class EnemySprite extends CharacterSprite implements Steerable<Vector2> {
.filter(paperCard -> !paperCard.isVeryBasicLand())
.collect(Collectors.toList());
if (paperCardList.size() < 6) {
int uniqueRules = paperCardList.stream().map(PaperCard::getRules).collect(Collectors.toSet()).size();
if (uniqueRules < 4 || paperCardList.size() < 10) {
// Player trying to cheese doppleganger and farm cards. Sorry, the fun police have arrived
// Static rewards of 199 GP, 9 Shards, and 1 Cheese Stands Alone
rewards.add(new Reward(199));
@@ -466,7 +468,7 @@ public class EnemySprite extends CharacterSprite implements Steerable<Vector2> {
if (AdventurePlayer.current().isFantasyMode()) {
//random uncommons from deck
List<PaperCard> uncommonCards = paperCardList.stream()
.filter(paperCard -> CardRarity.Uncommon.equals(paperCard.getRarity()) || CardRarity.Special.equals(paperCard.getRarity()))
.filter(paperCard -> paperCard.getRarity() == CardRarity.Uncommon || paperCard.getRarity() == CardRarity.Special)
.collect(Collectors.toList());
if (!uncommonCards.isEmpty()) {
rewards.add(new Reward(Aggregates.random(uncommonCards)));
@@ -474,7 +476,7 @@ public class EnemySprite extends CharacterSprite implements Steerable<Vector2> {
}
//random commons from deck
List<PaperCard> commmonCards = paperCardList.stream()
.filter(paperCard -> CardRarity.Common.equals(paperCard.getRarity()))
.filter(paperCard -> paperCard.getRarity() == CardRarity.Common)
.collect(Collectors.toList());
if (!commmonCards.isEmpty()) {
rewards.add(new Reward(Aggregates.random(commmonCards)));
@@ -483,7 +485,7 @@ public class EnemySprite extends CharacterSprite implements Steerable<Vector2> {
}
//random rare from deck
List<PaperCard> rareCards = paperCardList.stream()
.filter(paperCard -> CardRarity.Rare.equals(paperCard.getRarity()) || CardRarity.MythicRare.equals(paperCard.getRarity()))
.filter(paperCard -> paperCard.getRarity() == CardRarity.Rare || paperCard.getRarity() == CardRarity.MythicRare)
.collect(Collectors.toList());
if (!rareCards.isEmpty()) {
rewards.add(new Reward(Aggregates.random(rareCards)));

View File

@@ -175,17 +175,17 @@ public class AdventureEventData implements Serializable {
private static final Predicate<CardEdition> filterStandard = FModel.getFormats().getStandard().editionLegalPredicate;
public static Predicate<CardEdition> selectSetPool() {
// Should we negate any of these to avoid overlap?
final int rollD100 = MyRandom.getRandom().nextInt(100);
Predicate<CardEdition> rolledFilter;
if (rollD100 < 30) {
rolledFilter = filterStandard;
} else if (rollD100 < 60) {
rolledFilter = filterPioneer;
// Remove standard from older pools because its representation is already inflated.
rolledFilter = filterPioneer.and(filterStandard.negate());
} else if (rollD100 < 80) {
rolledFilter = filterModern;
rolledFilter = filterModern.and(filterStandard.negate());
} else {
rolledFilter = filterVintage;
rolledFilter = filterVintage.and(filterStandard.negate());
}
return rolledFilter;
}
@@ -195,21 +195,33 @@ public class AdventureEventData implements Serializable {
private static CardBlock pickWeightedCardBlock() {
CardEdition.Collection editions = FModel.getMagicDb().getEditions();
ConfigData configData = Config.instance().getConfigData();
Predicate<CardEdition> filter = CardEdition.Predicates.CAN_MAKE_BOOSTER.and(selectSetPool());
Predicate<CardEdition> filter = CardEdition.Predicates.CAN_MAKE_BOOSTER;
if(configData.restrictedEvents != null) {
//Temporary restriction until rewards are more diverse - don't want to award restricted cards so these editions need different rewards added.
//Also includes sets that use conspiracy or commander drafts.
Set<String> restrictedEvents = Set.of(configData.restrictedEvents);
filter = filter.and((q) -> !restrictedEvents.contains(q.getCode()));
if(configData.allowedEvents != null && configData.allowedEvents.length > 0) {
Set<String> allowedEvents = Set.of(configData.allowedEvents);
filter = filter.and(q -> allowedEvents.contains(q.getCode()));
}
if (configData.allowedEditions != null) {
Set<String> allowed = Set.of(configData.allowedEditions);
filter = filter.and(q -> allowed.contains(q.getCode()));
} else {
List<String> restrictedList = Arrays.asList(configData.restrictedEditions);
Set<String> restricted = new HashSet<>(restrictedList); //Would use Set.of but that throws an error if there's any duplicates, and users edit these lists all the time.
filter = filter.and(q -> !restricted.contains(q.getCode()));
else
{
//The whitelist beats all other filters.
if(configData.restrictedEvents != null) {
//Temporary restriction until rewards are more diverse - don't want to award restricted cards so these editions need different rewards added.
//Also includes sets that use conspiracy or commander drafts.
Set<String> restrictedEvents = Set.of(configData.restrictedEvents);
filter = filter.and((q) -> !restrictedEvents.contains(q.getCode()));
}
if (configData.allowedEditions != null && configData.allowedEditions.length > 0) {
Set<String> allowed = Set.of(configData.allowedEditions);
filter = filter.and(q -> allowed.contains(q.getCode()));
} else if(configData.restrictedEditions != null) {
List<String> restrictedList = Arrays.asList(configData.restrictedEditions);
Set<String> restricted = new HashSet<>(restrictedList); //Would use Set.of but that throws an error if there's any duplicates, and users edit these lists all the time.
filter = filter.and(q -> !restricted.contains(q.getCode()));
}
Predicate<CardEdition> setPoolFilter = selectSetPool();
if(editions.stream().anyMatch(setPoolFilter))
filter = filter.and(setPoolFilter);
}
List<CardEdition> allEditions = new ArrayList<>();

View File

@@ -24,5 +24,6 @@ public class ConfigData {
public String[] restrictedEditions;
public String[] allowedEditions;
public String[] restrictedEvents;
public String[] allowedEvents;
public String[] allowedJumpstart;
}

View File

@@ -77,7 +77,7 @@ cardPicsDir=
cardPicsSubDirs=
# This is the top level directory that forge will use for storing and
# locating decklists. The default value (for all plaforms) is:
# locating decklists. The default value (for all platforms) is:
# <the userDir defined above>/decks/
decksDir=

View File

@@ -1,4 +1,4 @@
The Forge archive includes a docs folder and we ask that you spend a few minutes reading this as it contains some information that may prove useful. You can open the .md (markdown) files with any plain Editor.
The Forge archive includes a docs folder and we ask that you spend a few minutes reading this as it contains some information that may prove useful. You can open the .md (markdown) files with any plain Editor - or just browse it online from our GitHub wiki.
The archive format used for the Forge distribution is ".tar.bz2". There are utilities for Windows, Mac OS and the various *nix's that can be used to extract/decompress these ".tar.bz2" archives. We recommend that you extract/decompress the Forge archive into a new and unused folder.

View File

@@ -1,4 +1,5 @@
Name:Agent Venom
Variant:UniversesWithin:FlavorName:Rhilex the Accursed
ManaCost:2 B
Types:Legendary Creature Symbiote Soldier Hero
PT:2/3

View File

@@ -1,4 +1,5 @@
Name:Alien Symbiosis
Variant:UniversesWithin:FlavorName:Aggressive Symbiosis
ManaCost:1 B
Types:Enchantment Aura
K:Enchant:Creature

View File

@@ -1,4 +1,5 @@
Name:Amazing Acrobatics
Variant:UniversesWithin:FlavorName:Drix Interception
ManaCost:1 U U
Types:Instant
A:SP$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBCounter,DBTap

View File

@@ -1,4 +1,5 @@
Name:Angry Rabble
Variant:UniversesWithin:FlavorName:Galvanized Workforce
ManaCost:1 R
Types:Creature Human Citizen
PT:2/2

View File

@@ -1,4 +1,5 @@
Name:Anti-Venom, Horrifying Healer
Variant:UniversesWithin:FlavorName:Verilax the Havenskin
ManaCost:W W W W W
Types:Legendary Creature Symbiote Hero
PT:5/5

View File

@@ -3,7 +3,7 @@ ManaCost:U
Types:Enchantment Aura
K:Enchant:Creature
SVar:AttachAILogic:Curse
R:Event$ Untap | ActiveZones$ Battlefield | ValidCard$ Creature.EnchantedBy | ValidStepTurnToController$ You | Layer$ CantHappen | Description$ Enchanted creature doesn't untap during its controller's untap step. At the beginning of the upkeep of enchanted creature's controller, that player may discard a card at random. If the player does, untap that creature.
R:Event$ Untap | ActiveZones$ Battlefield | ValidCard$ Creature.EnchantedBy | ValidStepTurnToController$ You | Layer$ CantHappen | Description$ Enchanted creature doesn't untap during its controller's untap step.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player.EnchantedController | TriggerZones$ Battlefield | OptionalDecider$ EnchantedController | Execute$ ApathyDiscard | TriggerDescription$ At the beginning of the upkeep of enchanted creature's controller, that player may discard a card at random. If the player does, untap that creature.
SVar:ApathyDiscard:DB$ Discard | Defined$ TriggeredPlayer | Mode$ Random | RememberDiscarded$ True | SubAbility$ ApathyUntap
SVar:ApathyUntap:DB$ Untap | Defined$ Enchanted | SpellDescription$ Untap enchanted creature | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ1 | SubAbility$ DBCleanup

View File

@@ -1,4 +1,5 @@
Name:Arachne, Psionic Weaver
Variant:UniversesWithin:FlavorName:Yera and Oski, Weaver and Guide
ManaCost:2 W
Types:Legendary Creature Spider Human Hero
PT:3/3

View File

@@ -1,4 +1,5 @@
Name:Araña, Heart of the Spider
Variant:UniversesWithin:FlavorName:Gloria, the Great Armorer
ManaCost:1 R W
Types:Legendary Creature Spider Human Hero
PT:3/3

View File

@@ -1,4 +1,5 @@
Name:Aunt May
Variant:UniversesWithin:FlavorName:Zora, Spider Fancier
ManaCost:W
Types:Legendary Creature Human Citizen
PT:0/2

View File

@@ -1,4 +1,5 @@
Name:Bagel and Schmear
Variant:UniversesWithin:FlavorName:Perfected Pastry
ManaCost:1
Types:Artifact Food
A:AB$ PutCounter | PrecostDesc$ Share — | Cost$ W T Sac<1/CARDNAME> | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDraw | SorcerySpeed$ True | SpellDescription$ Put a +1/+1 counter on up to one target creature. Draw a card. Activate only as a sorcery.

View File

@@ -1,4 +1,5 @@
Name:Beetle, Legacy Criminal
Variant:UniversesWithin:FlavorName:Arala, Hedron Scaler
ManaCost:3 U
Types:Legendary Creature Human Rogue Villain
PT:3/3

View File

@@ -1,4 +1,5 @@
Name:Behold the Sinister Six!
Variant:UniversesWithin:FlavorName:Hex of Undeath
ManaCost:6 B
Types:Sorcery
A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose up to six target creature cards with different names in your graveyard | ValidTgts$ Creature.YouOwn | TargetsWithDifferentNames$ True | TargetMin$ 0 | TargetMax$ 6 | SpellDescription$ Return up to six target creature cards with different names from your graveyard to the battlefield.

View File

@@ -1,4 +1,5 @@
Name:Biorganic Carapace
Variant:UniversesWithin:FlavorName:Angler's Shield
ManaCost:2 W U
Types:Artifact Equipment
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigAttach | TriggerDescription$ When this Equipment enters, attach it to target creature you control.

View File

@@ -1,4 +1,5 @@
Name:Black Cat, Cunning Thief
Variant:UniversesWithin:FlavorName:Wrench, Speedway Saboteur
ManaCost:3 B B
Types:Legendary Creature Human Rogue Villain
PT:2/3

View File

@@ -1,4 +1,5 @@
Name:Carnage, Crimson Chaos
Variant:UniversesWithin:FlavorName:Desecrex, Gift of Servitude
ManaCost:2 B R
Types:Legendary Creature Symbiote Villain
PT:4/3

View File

@@ -1,4 +1,5 @@
Name:Chameleon, Master of Disguise
Variant:UniversesWithin:FlavorName:Vazin, Two-Faced Trickster
ManaCost:3 U
Types:Legendary Creature Human Shapeshifter Villain
PT:2/3

View File

@@ -3,6 +3,6 @@ ManaCost:1 G
Types:Sorcery
A:SP$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ DBDamage | AILogic$ PowerDmg | StackDescription$ {c:ThisTargetedCard} | SpellDescription$ Target creature you control
SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature | TgtPrompt$ Select another target creature | TargetUnique$ True | AILogic$ PowerDmg | NumDmg$ X | DamageSource$ ParentTarget | ExcessSVar$ Excess | SubAbility$ DBDiscover | StackDescription$ REP another target creature_{c:ThisTargetedCard} | SpellDescription$ deals damage equal to its power to another target creature.
SVar:DBDiscover:DB$ Discover | Num$ Excess | StackDescription$ SpellDescription | SpellDescription$ If excess damage was dealt this way, discover X, where X is that excess damage. (Exile cards from the top of your library until you exile a nonland card with that mana value or less. Cast it without paying its mana cost or put it into your hand. Put the rest on the bottom in a random order.)
SVar:DBDiscover:DB$ Discover | Num$ Excess | ConditionCheckSVar$ Excess | StackDescription$ SpellDescription | SpellDescription$ If excess damage was dealt this way, discover X, where X is that excess damage. (Exile cards from the top of your library until you exile a nonland card with that mana value or less. Cast it without paying its mana cost or put it into your hand. Put the rest on the bottom in a random order.)
SVar:X:ParentTargeted$CardPower
Oracle:Target creature you control deals damage equal to its power to another target creature. If excess damage was dealt this way, discover X, where X is that excess damage. (Exile cards from the top of your library until you exile a nonland card with that mana value or less. Cast it without paying its mana cost or put it into your hand. Put the rest on the bottom in a random order.)

View File

@@ -3,12 +3,8 @@ ManaCost:4 U
Types:Enchantment
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigIncubate | TriggerDescription$ When CARDNAME enters, incubate 4. (Create an Incubator token with four +1/+1 counters on it and "{2}: Transform this artifact." It transforms into a 0/0 Phyrexian artifact creature.)
SVar:TrigIncubate:DB$ Incubate | Amount$ 4
T:Mode$ Transformed | ValidCard$ Permanent.YouCtrl+inZoneBattlefield | Execute$ TrigDraw | TriggerZones$ Battlefield | OptionalDecider$ You | CheckSVar$ X | SVarCompare$ LT1 | TriggerDescription$ Whenever a permanent you control transforms or a permanent you control enters transformed, you may draw a card. Do this only once each turn.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Permanent.Transformed+YouCtrl | OptionalDecider$ You | TriggerZones$ Battlefield | Execute$ TrigDraw | Secondary$ True | CheckSVar$ X | SVarCompare$ LT1 | TriggerDescription$ Whenever a permanent you control transforms or a permanent you control enters transformed, you may draw a card. Do this only once each turn.
SVar:TrigDraw:DB$ Draw | SubAbility$ DBLog
SVar:DBLog:DB$ StoreSVar | SVar$ X | Type$ Number | Expression$ 1
SVar:X:Number$0
T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBCleanup | Static$ True
SVar:DBCleanup:DB$ StoreSVar | SVar$ X | Type$ Number | Expression$ 0
T:Mode$ Transformed | ValidCard$ Permanent.YouCtrl+inZoneBattlefield | Execute$ TrigDraw | TriggerZones$ Battlefield | OptionalDecider$ You | ResolvedLimit$ 1 | TriggerDescription$ Whenever a permanent you control transforms or a permanent you control enters transformed, you may draw a card. Do this only once each turn.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Permanent.Transformed+YouCtrl | OptionalDecider$ You | TriggerZones$ Battlefield | Execute$ TrigDraw | Secondary$ True | ResolvedLimit$ 1 | TriggerDescription$ Whenever a permanent you control transforms or a permanent you control enters transformed, you may draw a card. Do this only once each turn.
SVar:TrigDraw:DB$ Draw
DeckHas:Ability$Counters|Token & Type$Incubator|Artifact|Phyrexian
Oracle:When Corruption of Towashi enters, incubate 4. (Create an Incubator token with four +1/+1 counters on it and "{2}: Transform this artifact." It transforms into a 0/0 Phyrexian artifact creature.)\nWhenever a permanent you control transforms or a permanent you control enters transformed, you may draw a card. Do this only once each turn.

View File

@@ -1,4 +1,5 @@
Name:Cosmic Spider-Man
Variant:UniversesWithin:FlavorName:Scions of the Ur-Spider
ManaCost:W U B R G
Types:Legendary Creature Spider Human Hero
PT:5/5

View File

@@ -1,4 +1,5 @@
Name:Daily Bugle Building
Variant:UniversesWithin:FlavorName:Fearsome Ridgeline
ManaCost:no cost
Types:Land
A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}.

View File

@@ -1,4 +1,5 @@
Name:Daily Bugle Reporters
Variant:UniversesWithin:FlavorName:Principled Referee
ManaCost:3 W
Types:Creature Human Citizen
PT:2/3

View File

@@ -1,4 +1,5 @@
Name:Damage Control Crew
Variant:UniversesWithin:FlavorName:Selesnya Archivist
ManaCost:3 G
Types:Creature Human Citizen
PT:3/3

View File

@@ -1,4 +1,5 @@
Name:Doc Ock, Sinister Scientist
Variant:UniversesWithin:FlavorName:Ozor, Chronicler of Collapse
ManaCost:4 U
Types:Legendary Creature Human Scientist Villain
PT:4/5

View File

@@ -1,4 +1,5 @@
Name:Doc Ock's Henchmen
Variant:UniversesWithin:FlavorName:Obscura Alleylurkers
ManaCost:2 U
Types:Creature Human Villain
PT:2/1

View File

@@ -1,4 +1,5 @@
Name:Doc Ock's Tentacles
Variant:UniversesWithin:FlavorName:Giantcraft Helm
ManaCost:1
Types:Artifact Equipment
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl+cmcGE5 | TriggerZones$ Battlefield | Execute$ TrigAttach | OptionalDecider$ You | TriggerDescription$ Whenever a creature you control with mana value 5 or greater enters, you may attach this Equipment to it.

View File

@@ -1,4 +1,5 @@
Name:Doctor Octopus, Master Planner
Variant:UniversesWithin:FlavorName:Neach, Pinnacle Pariah
ManaCost:5 U B
Types:Legendary Creature Human Scientist Villain
PT:4/8

View File

@@ -1,4 +1,5 @@
Name:Eddie Brock
Variant:UniversesWithin:FlavorName:Viggo, Enforcer of Ig's Crossing
ManaCost:2 B
Types:Legendary Creature Human Hero Villain
PT:3/3
@@ -12,6 +13,7 @@ Oracle:When Eddie Brock enters, return target creature card with mana value 1 or
ALTERNATE
Name:Venom, Lethal Protector
Variant:UniversesWithin:FlavorName:Viggo, End of Ig's Crossing
ManaCost:3 B R G
Types:Legendary Creature Symbiote Hero Villain
PT:5/5

View File

@@ -1,4 +1,5 @@
Name:Electro, Assaulting Battery
Variant:UniversesWithin:FlavorName:Bayo, Irritable Instructor
ManaCost:1 R R
Types:Legendary Creature Human Villain
PT:2/3

View File

@@ -1,4 +1,5 @@
Name:Electro's Bolt
Variant:UniversesWithin:FlavorName:Deathflame Burst
ManaCost:2 R
Types:Sorcery
A:SP$ DealDamage | ValidTgts$ Creature | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to target creature.

View File

@@ -1,4 +1,5 @@
Name:Ezekiel Sims, Spider-Totem
Variant:UniversesWithin:FlavorName:Orris, Last of the Web Lords
ManaCost:4 G
Types:Legendary Creature Spider Human Advisor
PT:3/5

View File

@@ -1,4 +1,5 @@
Name:Flash Thompson, Spider-Fan
Variant:UniversesWithin:FlavorName:Basil, Cabaretti Loudmouth
ManaCost:1 W
Types:Legendary Creature Human Citizen
PT:2/2

View File

@@ -1,4 +1,5 @@
Name:Flying Octobot
Variant:UniversesWithin:FlavorName:Vexed Bots
ManaCost:1 U
Types:Artifact Creature Robot Villain
PT:1/1

View File

@@ -1,4 +1,5 @@
Name:Friendly Neighborhood
Variant:UniversesWithin:FlavorName:Remarkable Readings
ManaCost:3 W
Types:Enchantment Aura
K:Enchant:Land

View File

@@ -1,4 +1,5 @@
Name:Green Goblin, Revenant
Variant:UniversesWithin:FlavorName:Nu and Sumi, Career Criminals
ManaCost:3 B R
Types:Legendary Creature Goblin Human Villain
PT:3/3

View File

@@ -1,4 +1,5 @@
Name:Guy in the Chair
Variant:UniversesWithin:FlavorName:Eccentric Arachnologist
ManaCost:2 G
Types:Creature Human Advisor
PT:2/3

View File

@@ -1,4 +1,5 @@
Name:Gwen Stacy
Variant:UniversesWithin:FlavorName:Nia, Skysail Storyteller
ManaCost:1 R
Types:Legendary Creature Human Performer Hero
PT:2/1
@@ -14,6 +15,7 @@ Oracle:When Gwen Stacy enters, exile the top card of your library. You may play
ALTERNATE
Name:Ghost-Spider
Variant:UniversesWithin:FlavorName:Nia, Fabled Skyclimber
ManaCost:2 U R W
Types:Legendary Creature Spider Human Hero
PT:4/4

View File

@@ -1,4 +1,5 @@
Name:Gwenom, Remorseless
Variant:UniversesWithin:FlavorName:Egrix the Bile Bulwark
ManaCost:3 B B
Types:Legendary Creature Symbiote Spider Hero
PT:4/4

View File

@@ -1,4 +1,5 @@
Name:Heroes' Hangout
Variant:UniversesWithin:FlavorName:Fire-Brained Scheme
ManaCost:R
Types:Sorcery
A:SP$ Charm | Choices$ DBDateNight,DBPatrolNight

View File

@@ -1,4 +1,5 @@
Name:Hide on the Ceiling
Variant:UniversesWithin:FlavorName:Spectral Restitching
ManaCost:X U
Types:Instant
A:SP$ ChangeZone | ValidTgts$ Artifact,Creature | TgtPrompt$ Select X target artifacts and/or creatures | TargetMin$ X | TargetMax$ X | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DBDelTrig | SpellDescription$ Exile X target artifacts and/or creatures. Return the exiled cards to the battlefield under their owners' control at the beginning of the next end step.

View File

@@ -1,4 +1,5 @@
Name:Hobgoblin, Mantled Marauder
Variant:UniversesWithin:FlavorName:Cam and Farrik, Havoc Duo
ManaCost:1 R
Types:Legendary Creature Goblin Human Villain
PT:1/2

View File

@@ -1,4 +1,5 @@
Name:Hot Dog Cart
Variant:UniversesWithin:FlavorName:Treat Trolley
ManaCost:3
Types:Artifact
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigFood | TriggerDescription$ When this artifact enters, create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this token: You gain 3 life.")

View File

@@ -1,4 +1,5 @@
Name:Hydro-Man, Fluid Felon
Variant:UniversesWithin:FlavorName:Belion, the Parched
ManaCost:U U
Types:Legendary Creature Elemental Villain
PT:2/2

View File

@@ -1,4 +1,5 @@
Name:Inner Demons Gangsters
Variant:UniversesWithin:FlavorName:Caldaia Brawlers
ManaCost:3 B
Types:Creature Human Rogue Villain
PT:3/4

View File

@@ -1,4 +1,5 @@
Name:Interdimensional Web Watch
Variant:UniversesWithin:FlavorName:Reality Fulcrum
ManaCost:4
Types:Artifact
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When this artifact enters, exile the top two cards of your library. Until the end of your next turn, you may play those cards.

View File

@@ -1,4 +1,5 @@
Name:Iron Spider, Stark Upgrade
Variant:UniversesWithin:FlavorName:Fizik, Etherium Mechanic
ManaCost:3
Types:Legendary Artifact Creature Spider Hero
PT:2/3

View File

@@ -1,4 +1,5 @@
Name:J. Jonah Jameson
Variant:UniversesWithin:FlavorName:Lazlo, Enthusiastic Accuser
ManaCost:2 R
Types:Legendary Creature Human Citizen
PT:2/2

View File

@@ -1,4 +1,5 @@
Name:Jackal, Genius Geneticist
Variant:UniversesWithin:FlavorName:Druneth, Reviver of the Hive
ManaCost:G U
Types:Legendary Creature Human Scientist Villain
PT:1/1

View File

@@ -1,4 +1,5 @@
Name:Kapow!
Variant:UniversesWithin:FlavorName:Perilous Lunge
ManaCost:2 G
Types:Sorcery
A:SP$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBFight | SpellDescription$ Put a +1/+1 counter on target creature you control. It fights target creature an opponent controls. (Each deals damage equal to its power to the other.)

View File

@@ -1,4 +1,5 @@
Name:Kraven, Proud Predator
Variant:UniversesWithin:FlavorName:Dreadfang, Loathed by Fans
ManaCost:1 R G
Types:Legendary Creature Human Warrior Villain
PT:*/4

View File

@@ -1,4 +1,5 @@
Name:Kraven the Hunter
Variant:UniversesWithin:FlavorName:Brako, Heartless Hunter
ManaCost:1 B G
Types:Legendary Creature Human Warrior Villain
PT:4/3

View File

@@ -1,4 +1,5 @@
Name:Kraven's Cats
Variant:UniversesWithin:FlavorName:Cruel Caracals
ManaCost:1 G
Types:Creature Cat Villain
PT:2/2

View File

@@ -1,4 +1,5 @@
Name:Kraven's Last Hunt
Variant:UniversesWithin:FlavorName:A Trail of Teacups
ManaCost:3 G
Types:Enchantment Saga
K:Chapter:3:DBMill,DBPump,DBReturn

View File

@@ -1,4 +1,5 @@
Name:Lady Octopus, Inspired Inventor
Variant:UniversesWithin:FlavorName:Merata, Neuron Hacker
ManaCost:U
Types:Legendary Creature Human Scientist Villain
PT:0/2

View File

@@ -2,5 +2,5 @@ Name:Leech Fanatic
ManaCost:1 B
Types:Creature Human Warlock
PT:2/2
S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Lifelink | Condition$ PlayerTurn | Description$ During your turn, CARDNAME has first strike.
S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Lifelink | Condition$ PlayerTurn | Description$ During your turn, CARDNAME has lifelink.
Oracle:During your turn, Leech Fanatic has lifelink.

View File

@@ -1,4 +1,5 @@
Name:Living Brain, Mechanical Marvel
Variant:UniversesWithin:FlavorName:Error-9, Viral Node
ManaCost:4
Types:Legendary Artifact Creature Robot Villain
PT:3/3

View File

@@ -1,4 +1,5 @@
Name:Lizard, Connors's Curse
Variant:UniversesWithin:FlavorName:King of the Coldblood Curse
ManaCost:2 G G
Types:Legendary Creature Lizard Villain
PT:5/5

View File

@@ -1,4 +1,5 @@
Name:Madame Web, Clairvoyant
Variant:UniversesWithin:FlavorName:Olx, Mouth to Many Eyes
ManaCost:4 U U
Types:Legendary Creature Mutant Advisor
PT:4/4

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