Compare commits

...

39 Commits

Author SHA1 Message Date
Chris H
1bb40ed838 Add TLA Booster info and Rankins 2025-11-11 10:14:33 -05:00
Fulgur14
1be45d73dd Avatar Destiny (TLA) (#9078) 2025-11-08 13:04:52 +00:00
Fulgur14
33e7bfae13 TLA cards, 7th November (#9102) 2025-11-08 12:19:13 +00:00
Fulgur14
61e04be781 Koh, the Face Stealer (TLA) (#9100) 2025-11-08 12:00:29 +00:00
Fulgur14
417ec3043e TLA cards, 6th November (#9097) 2025-11-08 11:12:45 +00:00
Paul Hammerton
30e9cc5254 Merge pull request #9117 from paulsnoops/edition-updates
Edition updates: SLD, TLA, TLE
2025-11-08 11:06:02 +00:00
Paul Hammerton
4b89f60513 Edition updates: SLD, TLA, TLE 2025-11-08 10:59:33 +00:00
Eradev
c5ba0f2c21 Search fixes (#9114)
* Remove old parser and added support for |  (or) and negated text.
2025-11-08 09:06:30 +00:00
Fulgur14
113c422478 11 TLA leaked cards (#9066) 2025-11-08 09:00:53 +00:00
Fulgur14
85d856e332 TLA/TLE cards, 5th November, batch 3 (#9092) 2025-11-07 18:11:00 +00:00
Fulgur14
9fe1328452 TLE cards, 5th November, batch 2 (a few TLA as well) (#9090) 2025-11-07 13:07:35 +00:00
tool4ever
a29ead42b0 Remove outdated stuff (#9105) 2025-11-07 11:28:33 +00:00
tool4ever
c765769466 Combine Adventure docs (#9098) 2025-11-06 18:19:58 +00:00
Fulgur14
ec814fc706 TLA cards, 4th November, batch 2 (#9081) 2025-11-06 13:25:34 +00:00
Paul Hammerton
9248deebdb Merge pull request #9093 from paulsnoops/edition-updates
Edition updates: SLD, TLA, TLE
2025-11-05 18:25:15 +00:00
Paul Hammerton
5d91f6a8fb oops spaces 2025-11-05 18:24:10 +00:00
Paul Hammerton
02bc97b8d6 Edition updates: SLD, TLA, TLE 2025-11-05 18:08:59 +00:00
Fulgur14
03c1a0e6f0 Create the_legend_of_yangchen_avatar_yangchen.txt (#9055) 2025-11-05 17:47:40 +00:00
Fulgur14
3399a455ba TLE cards, 4th November (#9083) 2025-11-05 16:47:52 +00:00
Jetz72
75d717cce9 Merge pull request #9073 from h4ilst0rm/master
Brighter Holofoil cards
2025-11-05 09:42:07 -06:00
Jetz72
39d35fd016 Merge pull request #8742 from Jetz72/fixes20250918
Refactor "Unique Cards Only" Setting for Mobile Editor
2025-11-05 09:27:38 -06:00
tool4ever
a8ae802474 Fix counters on both 2025-11-05 16:23:16 +01:00
Janek Gröhl
561ad37f4f Fix commander adventure mode crash in limited event. When putting card from sideboard, isCommanderEditor returned true, even though game is in limited mode. Accessing the command zone then led to nullptr. (#9064) 2025-11-05 16:17:00 +01:00
Fulgur14
42cb1f55e1 TLE cards, 5th November (#9086)
* Support Tale of Katara and Toph
2025-11-05 15:43:27 +01:00
Jetz
01382fbcc4 Merge branch 'master' into fixes20250918
# Conflicts:
#	forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java
2025-11-05 09:06:04 -05:00
tool4ever
227a1df06c Update ancient_animus.txt
Closes #9082
2025-11-04 21:41:40 +00:00
Jetz72
01da2df6f0 Merge pull request #9080 from Jetz72/fixes20251104
Apply display/flavor names in more places
2025-11-04 09:53:20 -06:00
Fulgur14
ec84a3f137 TLA cards, 4th November (#9079) 2025-11-04 15:42:21 +01:00
Jetz
02e2e713ed Cleanup - use Card.getTranslatedName method 2025-11-04 09:27:12 -05:00
Jetz
a21666040c Apply Card.getDisplayName to user-facing text 2025-11-04 09:17:52 -05:00
Jetz
fe2bd016b6 Add card face display name for card name lists. 2025-11-04 08:41:42 -05:00
Fulgur14
0bb67ec8d0 TLA cards, 3rd November (#9072)
Firebender Ascension and support
2025-11-04 14:20:26 +01:00
Paul Hammerton
6d188c09ca Merge pull request #9077 from paulsnoops/edition-updates
Edition updates: PSPL, PURL, PW25, SLC, SLD, TLA, TLE
2025-11-04 09:29:50 +00:00
Paul Hammerton
f25d8c58d9 Edition updates: PSPL, PURL, PW25, SLC, SLD, TLA, TLE 2025-11-04 09:25:28 +00:00
Fulgur14
f9c5fc7317 Iroh's Demonstration (TLA) (#9076) 2025-11-04 10:20:02 +01:00
Fulgur14
02031e159d TLA/TLE cards 3rd November - Aftermath (#9075) 2025-11-04 10:03:56 +01:00
tool4ever
dd35f97035 Cleanup scripting API first pass (#9070) 2025-11-04 09:23:21 +01:00
h4ilst0rm
58cb309940 Brighter Holofoil cards
Increased saturation to keep the colours while making it less opaque so it doesn't dim the underlying cards as much
2025-11-03 21:24:57 +02:00
Jetz
bd04a6f1b9 Move unique card filtering from editor to card manager. 2025-09-18 09:46:03 -04:00
298 changed files with 3974 additions and 1633 deletions

View File

@@ -2,8 +2,6 @@
[Official repo](https://github.com/Card-Forge/forge.git).
Dev instructions here: [Getting Started](https://github.com/Card-Forge/forge/wiki) (Somewhat outdated)
## Requirements / Tools
- your favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
@@ -18,11 +16,10 @@ Dev instructions here: [Getting Started](https://github.com/Card-Forge/forge/wik
## Project Quick Setup
- Login into GitHub with your user account and fork the project.
- 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`
- 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
@@ -30,107 +27,28 @@ IntelliJ is the recommended IDE for Forge development. Quick start guide for [se
## Eclipse
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
At this time, Eclipse is not the recommended IDE for Forge development.
Eclipse includes Maven integration so a separate install is not necessary.
Google no longer supports Android SDK releases for Eclipse.
### Project Setup
- Follow the instructions for cloning from GitHub. You'll need to setup an account and your SSH key.
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.
- Fork the Forge git repo to your GitHub account.
- Clone your forked repo to your local machine.
- Make sure the Java SDK is installed -- not just the JRE. Java 17 or newer required. If you execute `java -version` at the shell or command prompt, it should report version 17 or later.
- Install Eclipse 2021-12 or later for Java. Launch it.
- 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
ensure everything is checked > Finish.
- 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.
- Once everything builds, all errors should disappear. You can now advance to Project launch.
### Project Launch
#### Desktop
This is the standard configuration used for releasing to Windows / Linux / MacOS.
- Right-click on forge-gui-desktop > Run As... > Java Application > "Main - forge.view" > Ok
- The familiar Forge splash screen, etc. should appear. Enjoy!
#### Mobile (Desktop dev)
This is the configuration used for doing mobile development using the Windows / Linux / MacOS front-end. Knowledge of libgdx is helpful here.
- Right-click on forge-gui-mobile-dev > Run As... > Java Application > "Main - forge.app" > Ok.
- A view similar to a mobile phone should appear. Enjoy!
### Eclipse / Android SDK Integration
Google no longer supports Android SDK releases for Eclipse. use IntelliJ.
#### Android SDK
## Windows
TBD
##### Windows
## Linux / Mac OSX
TBD
##### Linux / Mac OSX
### Android Platform
TBD
#### Android Plugin for Eclipse
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:
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
### 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).
#### Android Build
TBD
#### Android Deploy
TBD
#### Android Debugging
TBD
### 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
## Card Scripting
Visit [this page](https://github.com/Card-Forge/forge/wiki/Card-scripting-API) for information on scripting.
@@ -193,3 +111,5 @@ Mobile GUI game logic utilizing [libgdx](https://libgdx.badlogicgames.com/) libr
#### forge-gui-mobile-dev
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
#### forge-installer

View File

@@ -32,7 +32,7 @@ Join the **Forge community** on [Discord](https://discord.gg/HcPJNyD66a)!
4. **Java Requirement:** Ensure you have **Java 17 or later** installed.
### 📱 Android Installation
- _(Note: **Android 11** is the minimum requirements with at least **6GB RAM** to run smoothly. You need to enable **"Install unknown apps"** for Forge to initialize and update itself)_
- _(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.
---
@@ -61,7 +61,7 @@ Test your skills against AI in multiple formats:
- **Commander**
- **Cube**
For comprehensive gameplay instructions, visit our [Gameplay Guide](https://github.com/Card-Forge/forge/wiki/Gameplay-Guide).
For comprehensive gameplay instructions, visit our [User Guide](https://github.com/Card-Forge/forge/wiki/User-Guide).
<img width="1282" height="752" alt="Sealed" src="https://github.com/user-attachments/assets/ae603dbd-4421-4753-a333-87cb0a28d772" />

View File

@@ -1,11 +1,11 @@
# About Forge's Artificial Intelligence
The AI is *not* "trained". It uses basic rules and can be easy to overcome knowing it's weaknesses.
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
- 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.

182
docs/Advanced-Search.md Normal file
View File

@@ -0,0 +1,182 @@
# 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.
## 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:`
Examples:
`in:rare` - Cards that have been printed at rare.
### Multi-faced cards
#### Keyword(s): `is:split`
Find split-faced cards.
#### Keyword(s): `is:flip`
Find flip cards.
#### Keyword(s): `is:transform`
Find cards that transform.
#### Keyword(s): `is:meld`
Find cards that meld.
#### Keyword(s) `is:leveler`
Find cards with Level Up.
### Others
#### Keyword(s): `is:modal`
Find modal cards.
#### Keyword(s): `is:custom`
Find cards from custom sets.
#### Keyword(s) `is:foil`
Find foil cards.
#### Keyword(s) `is:nonfoil`
Find nonfoil cards.

View File

@@ -0,0 +1,43 @@
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 <item name or code?> | 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 |

View File

@@ -1,6 +1,6 @@
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).
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
@@ -64,7 +64,7 @@ Supported directions are "Right","Left","Up","Down","RightDown","LeftDown","Left
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) to get a random generated deck.
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).
@@ -85,17 +85,17 @@ Decimal - Relative frequency with which this enemy will be picked to spawn in ap
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). For comparison, the player's base speed is set at a value of 32 (before any equipment / ability modifiers).
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).
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) for the syntax.
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).

View File

@@ -107,7 +107,7 @@ Valid options are:
* `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), this functions as a `card` reward that is limited to cards found in that enemy's deck.
* `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", ...}`

View File

@@ -0,0 +1,46 @@
There are many currencies in the game, and most of them can be interchanged.
# Cards
Acquired by:
- World drops
- Match reward
- Draft wins
- Shop purchases
- Quests
Spent on:
- Selling to shops
# Gold
Acquired by:
- World drop
- Match reward
- Draft reward
- Quests
Spent on:
- Cards and items from shops
- Drafts games
- Crafting cards
- Shards
# Shards
Acquired by
- World drop
- Quest reward
Spent on
- Crafting cards
- Shop Re-rolls
- Gold exchange
# Challenge Coins
Acquired by
- At the start of the game
Spent on
- Drafts

View File

@@ -0,0 +1,28 @@
# Adding basic lands and special arts
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.

View File

@@ -1,6 +1,6 @@
Basic Gamepad Support for Adventure Mode
Tested using DS4 Cpntroller 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:
https://vigem.org/projects/HidHide/Simple-Setup-Guide/

View File

@@ -26,6 +26,8 @@ Due to charges in Forges hosting and scryfall terms you can no longer predownloa
**(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:

File diff suppressed because it is too large Load Diff

View File

@@ -1,40 +1,37 @@
A reference guide for Scripting Cards using the API parsed by the Forge
Engine.
A reference guide for scripting cards using the API parsed by the Forge engine.
### Base Structure
# Base Structure
By opening any file in the /res/cardsfolder folder you can see the basic
structure of how the data is created. Here's an example of a vanilla
creature:
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
Text:no text
PT:2/2
Oracle:
```
The name of this card is Vanilla Creature. It's casting cost is 2G. It
has the types Creature and Beast. It will not display any additional
text in the card's template. It has a Power-Toughness of 2/2. The text
line appears only if any rules printed on card are not backed by
abilities defined for the given card (in most cases presence of Text
line means such abilities are hardcoded).
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 Parameters that will appear in most, if not all, cards. These are
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://git.cardforge.org/core-developers/forge/-/blob/master/forge-gui/src/main/java/forge/gamemodes/limited/CardRanker.java) class.
|`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.<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
|`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"
@@ -49,95 +46,22 @@ There are a few other Parameters that will appear in most, if not all, cards. Th
Rarity and Set info are now defined in edition definition files. These can be found at /res/reditions path.
## General SVars
## 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`
`SVar:SoundEffect:goblinpolkaband.mp3`
# Keywords
The sound system supports a special SVar that defines the sound that should be played when the spell is cast.
All keywords need to be prepended with "K:" to be parsed correctly. Each keyword must appear on a separate line.
`SVar:AltCost:[cost]`
## Keywords without Parameters
This SVar is for cards that have an Alternate cost, such as Force of
Will. You are allowed to pay the Alternate Cost instead of the normal
Mana cost when casting this spell.
`SVar:AIPreference:SacCost$Creature.token,Creature.cmcLE2`
`SVar:AntiBuffedBy:[ValidCards]`
If a permanent with this SVar is on the battlefield under human control
the AI will play the specified cards in Main1. Applicable for cards like
Heart Sliver or Timid Drake.
`SVar:BuffedBy:[ValidCards]`
If a permanent with this SVar is on the battlefield under its control
the AI will play the specified cards in Main1. Applicable for creatures
with a P/T setting static ability (Kithkin Rabble) or additional buffes
(Radiant, Archangel).
`SVar:EnchantMe:[Multiple]/[Once]`
Creatures with "Multiple" in this SVar will always be prefered when the
AI enchants (Rabid Wombat), creatures with "Once" only if they are not
enchanted already (Gate Hound).
`SVar:EquipMe:[Multiple]/[Once]`
Creatures with "Multiple" in this SVar will always be prefered when the
AI equippes (Myr Adapter), creatures with "Once" only if they are not
equipped already (Kor Duelist).
`SVar:AIEvaluationModifier:[ValidAmount]`
`SVar:EndOfTurnLeavePlay:True`
`SVar:maxLevel:`
`SVar:HasCombatEffect:True`
`SVar:HasAttackEffect:True`
`SVar:HasBlockEffect:True`
`SVar:MustAttack:True`
`SVar:MustBeBlocked:True`
`SVar:NeedsToPlayVar:[ValidCards]`
`SVar:ManaNeededToAvoidNegativeEffect:`
`SVar:NonStackingEffect:True`
`SVar:PlayMain1:TRUE/ALWAYS/OPPONENTCREATURES`
The AI will play cards with this SVar in its first main phase. Without other AILogic, it will usually not play any permanents without this in Main1.
`SVar:SacMe:[number]`
The AI will sacrifice these cards to pay costs. The higher the number the higher the priority. Example: Hatching Plans has SVar:SacMe:5.
`SVar:Targeting:Dies`
`SVar:UntapMe:True`
The AI will prioritize untapping of this card.
`SVar:AIUntapPreference:`
`SVar:X:Count$`
Count is our general value computation function. It's quite varied with a lot of different things it can calculate and is often being updated.
## 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.
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 />
Examples:
- Cascade
- Changeling
@@ -201,7 +125,7 @@ This section is for Keywords that require no additional parameters and are one o
- Vigilance
- Wither
### Keywords with parameters
## Keywords with parameters
- Adapt:{cost}
- AdjustLandPlays:{params}
@@ -272,10 +196,11 @@ This section is for Keywords that require no additional parameters and are one o
- UpkeepCost:{cost}
- Vanishing:{TimeCounters}
### Longer Card Properties
## Plaintext keywords
Plaintext properties/triggers. This section is for Keywords that are two
words or longer. CARDNAME is replaced by the card's name.
These are hardcoded but not truly keywords rules-wise and will eventually be turned into static abilities.
Only listing the most common ones here so you can recognize them.
CARDNAME is replaced by the card's name ingame.
- All creatures able to block CARDNAME do so.
- CARDNAME assigns no combat damage
@@ -352,10 +277,81 @@ words or longer. CARDNAME is replaced by the card's name.
- You may have CARDNAME assign its combat damage as though it weren't blocked.
- Your life total can't change.
## Developer Mode
# General SVars
[Forge\_DevMode](Forge_DevMode "wikilink")
`SVar:SoundEffect:goblinpolkaband.mp3`
## Remaining Cards
The sound system supports a special SVar that defines the sound that should be played when the spell is cast.
<https://docs.google.com/spreadsheet/ccc?key=0Aipjpk0ZcU8fdFlMczZRR2tmazZGSGZYeDh1Z24teVE&usp=sharing>
`SVar:X:Count$`
Count is our general value computation function. It's quite varied with a lot of different things it can calculate and is often being updated.
# AI specific SVars
`SVar:AIPreference:SacCost$Creature.token,Creature.cmcLE2`
`SVar:AntiBuffedBy:[ValidCards]`
If a permanent with this SVar is on the battlefield under human control
the AI will play the specified cards in Main1. Applicable for cards like
Heart Sliver or Timid Drake.
`SVar:BuffedBy:[ValidCards]`
If a permanent with this SVar is on the battlefield under its control
the AI will play the specified cards in Main1. Applicable for creatures
with a P/T setting static ability (Kithkin Rabble) or additional buffes
(Radiant, Archangel).
`SVar:EnchantMe:[Multiple]/[Once]`
Creatures with "Multiple" in this SVar will always be prefered when the
AI enchants (Rabid Wombat), creatures with "Once" only if they are not
enchanted already (Gate Hound).
`SVar:EquipMe:[Multiple]/[Once]`
Creatures with "Multiple" in this SVar will always be prefered when the
AI equippes (Myr Adapter), creatures with "Once" only if they are not
equipped already (Kor Duelist).
`SVar:AIEvaluationModifier:[ValidAmount]`
`SVar:EndOfTurnLeavePlay:True`
`SVar:maxLevel:`
`SVar:HasCombatEffect:True`
`SVar:HasAttackEffect:True`
`SVar:HasBlockEffect:True`
`SVar:MustAttack:True`
`SVar:MustBeBlocked:True`
`SVar:NeedsToPlayVar:[ValidCards]`
`SVar:ManaNeededToAvoidNegativeEffect:`
`SVar:NonStackingEffect:True`
`SVar:PlayMain1:TRUE/ALWAYS/OPPONENTCREATURES`
The AI will play cards with this SVar in its first main phase. Without other AILogic, it will usually not play any permanents without this in Main1.
`SVar:SacMe:[number]`
The AI will sacrifice these cards to pay costs. The higher the number the higher the priority. Example: Hatching Plans has SVar:SacMe:5.
`SVar:Targeting:Dies`
`SVar:UntapMe:True`
The AI will prioritize untapping of this card.
`SVar:AIUntapPreference:`
`SVar:NoZeroToughnessAI:True`

View File

@@ -1,9 +1,6 @@
# Guide - Creating a Custom Card
Using the Forge API you can create your own custom cards and sets. This guide will walk you through the process of creating custom cards via the Force Custom Card Script.
The next tutorial will walk you through the process of adding your new cards to a custom set.
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.
## File Locations
@@ -84,11 +81,11 @@ Let's break our card down:
- K - A Keyword that gives our creature an ability.
- Oracle - The actual text that appears on that card.
For a refrence 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.
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 turorial we'll add out card to an existing set. If you wish to create your own set you can follow this guide: `Guide Coming Soon`
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.
@@ -171,7 +168,7 @@ Simply save and rename the image to "Goblin Card Guide.fullborder.jpg" then over
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 sinplest 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:
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

View File

@@ -1,5 +1,3 @@
# Creating a custom set
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**.
@@ -111,11 +109,11 @@ Each line is as follow: `CollectorNumber Rarity CardName @ArtistName`.
> Note: You can put the cards in the list even if they aren't scripted yet. Forge will skip over them.
```
```text
[tokens]
b_1_1_bird_flying
b_3_3_cat_deathtouch
b_5_5_golem_trample
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.
@@ -124,7 +122,7 @@ If you load the game with just file, you'll be able to see that Master Chef, Une
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
@@ -133,6 +131,8 @@ Let's comment out Master Chef to avoid a name conflict with an existing MTG card
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.
@@ -143,7 +143,8 @@ Now, you might remember than Unearth was an existing MTG card so we do not need
Let's create the following files:
avatar_of_basat.txt
```
```text
Name:Avatar of Basat
ManaCost:R
Types:Creature Avatar
@@ -154,7 +155,8 @@ Oracle:Menace\nAvatar of Basat can't block.
```
exhunt.txt
```
```text
Name:Exeunt
ManaCost:B
Types:Instant
@@ -164,7 +166,8 @@ 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
@@ -173,7 +176,8 @@ Oracle:
```
inked_summoner.txt
```
```text
Name:Inked Summoner
ManaCost:1 B
Types:Creature Human Warlock Artist
@@ -192,7 +196,7 @@ Oracle:At the beginning of your end step, if you lost 2 or more life this turn,
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
```
@@ -207,7 +211,8 @@ 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
@@ -218,7 +223,8 @@ Oracle:Flying
```
b_3_3_cat_deathtouch.txt
```
```text
Name:Cat Token
ManaCost:no cost
Colors:black
@@ -229,7 +235,8 @@ Oracle:Deathtouch
```
b_5_5_golem_trample.txt
```
```text
Name:Golem Token
ManaCost:no cost
Colors:black
@@ -243,9 +250,9 @@ Great! Now Inked Summoner no longer make the game crash! Now let's add some imag
## 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 something like `Swamp.full.jpg` if you only have one variant in your edition. If you have multiples, then it should be something like `Fox of the Orange Orchard1.full.jpg`, `Fox of the Orange Orchard2.full.jpg`, etc. You can find the alternate images from [here](https://msem-instigator.herokuapp.com/set/MPS_MSE) if you want.
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 token script so `b_1_1_bird_flying.jpg` and so forth.
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.
![b_1_1_bird_flying](https://github.com/user-attachments/assets/531583c1-3985-4744-858a-3a49fd12740a)
![b_3_3_cat_deathtouch](https://github.com/user-attachments/assets/15a24e62-be43-4c0c-aeac-0ddb38fca97a)
@@ -253,6 +260,6 @@ 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.
## 🎉 Congratulations!
## 🎉 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.
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.

View File

@@ -42,5 +42,3 @@ mvn -U -B clean \\
-Dcardforge.user=${FORGE_FTP_USER} \\
-Dcardforge.pass=${FORGE_FTP_PASS}
```
After this, the appropriate forum posts will need to be made announcing the new release.

View File

@@ -2,19 +2,10 @@ Developer Mode is a Mode that allows Developers to try out different
things and gain different shortcuts during play. In a Normal program,
this Mode would be available in a Debug build, but removed from the code
during the Retail build. Since Forge is in constant Beta, this Mode is
available during our Beta releases. On the NewGame Screen, just make
sure it's checked and you'll be able to edit the options at any point
during the game.
available during our Beta releases. You can turn on or off this mode at the
Home View -> Game Settings -> Preferences -> Gameplay Options section.
## Lose By Decking
This one is a simple Checkbox. If your Library is Empty and you try to
try a card, you normally lose the game. This checkbox should be on by
Default (meaning you lose) but if you are running a deck without many
cards, uncheck this box so you don't lose when failing to draw a card
when you need to.
\[THIS OPTION NOT READILY VISIBLE IN VERSIONS 1.6.23\]
It's important to note you won't get achievements in a game once you started cheating.
## View Zone
@@ -26,8 +17,7 @@ that Zone are revealed to you.
## Generate Mana
Useful for not needing Land in your Deck while you are just trying to
test something out, by selecting Generate Mana, 7 of each color and 7
colorless is generated and put into your Mana Pool.
test something out, by selecting Generate Mana, 7 of each type is generated and put into your Mana Pool.
## Setup Game State
@@ -179,5 +169,3 @@ AI hands and leaves them intact, and adds Force of Nature, Raging
Goblin, and Amulet of Kroog to the computer's graveyard. Does not
replace either library. Sets the current player to Human and the current
phase to Main 1.
It's important to note you won't get achievements in a game once you started cheating.

View File

@@ -1,44 +0,0 @@
# Why Rewrite Network Play?
The current implementation of **Network Play** relies on [Java serialization/deserialization](https://www.geeksforgeeks.org/serialization-in-java/) via [Netty](https://netty.io/). While this does work, it is inefficient, transferring large amounts of unnecessary (duplicate) data. The transferring of duplicate data has two negatives:
1. increased latency
2. increased bandwidth
The increased latency is very noticeable throughout a network game.
The increased bandwidth is a potential concern for mobile players, not everyone has an unlimited data plan.
Testing of the existing **Network Play** implementation has shown an individual **Game** transferring over 300MB of data.
# The Rewrite
The rewrite will utilize [protobuf](https://developers.google.com/protocol-buffers) and be approached in phases:
1. Lobby
2. Match
3. Game
## Lobby
The **Lobby** portion will handle:
* Handshake
* Player
* Name
* Avatar
* Game Rules Selection
* Deck Submission
The **Handshake** portion of the **Lobby** will be responsible for ensuring that it is a **Forge** client that is connecting **and** that the client is running a compatible **Network Play** implementation.
## Match
Number of **Games** that comprise a **Match**, normally first player to win 2 **Games**. This is important, because it is not *technically* best of 3. For example if either of the first two games of a **Match** are a *draw*, it is entirely possible to play a fourth **Game** in a **Match**. (Need a judge ruling reference on this)
## Game
This will be broken down in more detail, but the important bits to hand first are:
* Phases
* Passing of **priority**
* Notification upon receipt of **priority**

View File

@@ -40,7 +40,5 @@ It would be useful for other people to be comfortable with the release process.
* Create a PR from your branch and get it merged as quickly as you can (ideally before other PRs are merged).
* Create a new release from https://github.com/Card-Forge/forge/releases
* Upload the package and its sha to the create new release page
4. Marketing
5. Marketing
* Advertise in the #announcements channel in the Discord

View File

@@ -26,6 +26,8 @@ Mostly old-style interface with new age loading screen. Initial card scripting i
Supports over 10,000 cards. We start using a reconstructed UI that allows for better theming. And makes things feel less like an "Application" and more like a "Game"
## 2013-10 - ver 1.5.1 is the first with Commander
## 2014-05 - Android app is published via Maven
## 2015-04 - Network play is rudimentary but available
@@ -36,8 +38,6 @@ Supports over 10,000 cards. We start using a reconstructed UI that allows for be
## 2024-07 - _All non-Un set cards have been added to Forge_
## 2024-08 - Bloomburrow release
## 2024-09 - Duskmouth Release 1.6.65 (**Last release that supports Java 1.8**)
Supports over 28,500 cards.
@@ -68,8 +68,6 @@ For any of you curious about what the first attempt at making a Shandalar-like A
Interestingly, the above-mentioned thread refers to the proposed game mode as "Adventure Mode" a few times (closer to the end of the thread), something that we actually currently have and actively develop 👍
The interesting aspect of that development was the day/night cycle, I don't know why but I remember I thought that it looked quite atmospheric back in the day
## Abe Sergeant writes about Forge
2009-09-11 - Here is the article on star city.
@@ -80,15 +78,12 @@ And here is it in archive.org in case that gets taken down
https://web.archive.org/web/20210707215155/https://articles.starcitygames.com/articles/the-kitchen-table-302-an-mtg-forge-quest/
MTG Forge comes with a Quest Mode. In Quest Mode, you begin with a random selection of cards, and have to build a 60-card deck. Then you play against decks by various computer opponents, and as you win, you get more cards, and the difficulty of your opponents increases.
Quest is the most fun Ive had playing Magic in a year.
What I am going to do is show you a quick 10-game win in quest. You can play to 10, 20, 30, or 40 wins, but Im just going to show you a 10 win in quest. I will show you where the game has bugs too, so you can see what I am talking about when I say WIP. Im not holding anything back.
> MTG Forge comes with a Quest Mode. In Quest Mode, you begin with a random selection of cards, and have to build a 60-card deck. Then you play against decks by various computer opponents, and as you win, you get more cards, and the difficulty of your opponents increases.<br />
> Quest is the most fun Ive had playing Magic in a year.
# Major disruptions
One interesting thing about Forge is the way it grew. Much of the first few years was solely on the back of an Amateur software engineer called "MTG Rares" soon enough "Dennis Bergkamp" came along and was doing a bunch more development. New Software Engineers joined the ranks off and on. From there on people would jump in, help for a handful of years and get too busy with life, or stop really playing magic or whatever. A handful of us have been around the longhaul, but not too many.
One interesting thing about Forge is the way it grew. Much of the first few years was solely on the back of an Amateur software engineer called "MTG Rares". Soon enough "Dennis Bergkamp" came along and was doing a bunch more development. New Software Engineers joined the ranks off and on. From there on people would jump in, help for a handful of years and get too busy with life, or stop really playing magic or whatever. A handful of us have been around the longhaul, but not too many.
## Sourceforge SVN (2007-2008)
Original location. Moved when the name changed.
@@ -103,13 +98,11 @@ One interesting thing about Forge is the way it grew. Much of the first few year
## Github (2021-Present)
Conversion from Git to Git was a lot easier.
## Jendave's modularization (2011)
The initial modularization attempt pulled everything from living under forge-gui/ module and built out some of the Maven structure.
## Maxmtg's modularization (2013?)
Took this the next step further massively reorganizing the codebase. It caused major issues and took a few months to get resolved.
## Hanmac's modularization (2017?
This was a smaller modularization mostly within certains areas that ultimately was positive, but led to some headaches during the process.
## Hanmac's modularization (2017?)
This was a smaller modularization mostly within certains areas that ultimately was positive, but led to some headaches during the process.

View File

@@ -1,35 +1,20 @@
This is a list of basic troubleshooting questions that come up for most new players, before running to the discord about your issue, please review this FAQ for some of the more common issues.
### Check the FAQ in Discord
This is a list of basic troubleshooting questions that come up for most new players, before running to the discord about your issue, please review this document for some of the more common issues.
* Check the FAQ in Discord (probably more up-to-date if things break unexpectedly on us)
https://discord.com/channels/267367946135928833/1095026912927154176
### Search the help posts in Discord
https://discord.com/channels/267367946135928833/1047001034788196452
### Write a post in help section of Discord
* 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?
## 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)
* 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
### How do I extract Forge?
* Forge uses a .tar.bz2 format for archiving. Depending on your operating system, different utilities can be used to untar the archive.
* If you use Windows, you may want to try 7-Zip (http://www.7-zip.org/download.html).
### I think I found a bug in Forge. What do I do?
## I think I found a bug in Forge. What do I do?
*Most users, who are running beta versions of Forge, should continue to use these instructions. As for alpha testers, these instructions have yet to be made congruent with the latest automatic bug reporting from within Forge.*
@@ -37,8 +22,7 @@ Bug reports from users are the lifeblood of Forge. Please keep in mind that "bet
For starters, please take note of (1) what you had in play, (2) what your opponent had in play and (3) what you were doing when the error occurred. If you get a Crash Report from inside Forge, please save the data to a file. This information is very important when reporting a problem. Don't worry if you didn't think of that right away, until your next start, the "Forge.log" in the game directory will also provide that information.
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.
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.
@@ -48,13 +32,13 @@ Because duplicate bug reports use up our limited resources, please research your
* If you're unsure, you can also post on one of the support channels of the discord. In case you do not get a timely response, please submit a new issue anyway to make sure it doesn't get lost.
### I have an idea to make Forge better. What do I do?
## I have an idea to make Forge better. What do I do?
Follow the directions in [Bug Reports](Frequently-Asked-Questions#i-think-i-found-a-bug-in-forge-what-do-i-do), keeping in mind that you are not reporting a bug, but rather a **Feature Request**.
# Development
### I want to help develop Forge. How do I get started?
## I want to help develop Forge. How do I get started?
Forge is written in Java, so knowledge in that language (or similar Object Oriented languages like C++ or C\#) is very helpful. However, it is possible to learn the grammar for writing the data objects of cards without programming experience.
@@ -64,30 +48,30 @@ Thanks to the nature of how cards are implemented, you can also contribute these
To obtain the source code of Forge, read our [Development Guide]((SM-autoconverted)--how-to-get-started-developing-forge).
### My system is all setup to help. What now?
## My system is all setup to help. What now?
Take a look through the /res/cardsfolder folder. This is where all the card data lives. If you know of cards that are missing from Forge, see if there are similar cards that already exist.
# Gameplay
### Where do I use Flashback or a similar ability that is in an External area?
## 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.
### How do I target a player?
## How do I target a player?
Just click on the player's Avatar in the Player Panel when prompted to select a Player as a target.
### Where did my mana go?
## Where did my mana go?
If you have an effect that generated you some mana, and you don't know where it is. Check out the Player Panel. There are 6 different mana subpools one for each color/colorless that should have it. If you accidentally tapped your mana before your Main Phase, your mana is gone. Sorry, we don't have a way at this time to revert these actions. In general, I'd say it's easier/better to start casting a spell first, then activate your mana so this doesn't happen.
# Quest Mode
### What is the difference between Fantasy Quest and Normal Quest?
## 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
## Sealed Deck Mode
[HOW-TO: Customize your Sealed Deck games with fantasy blocks](https://www.slightlymagic.net/forum/viewtopic.php?f=26&t=8164)

View File

@@ -1,5 +1,5 @@
> [!CAUTION]
> - if you want to contribute to this Wiki please only make pull requests against the main repos docs folder or your changes might get lost
> - if you want to contribute to this Wiki please only make pull requests against the main repositories *docs* folder or your changes might get lost
> - due to GitHub limitations all filenames should be unique
# What is Forge?
@@ -8,37 +8,35 @@ Forge is a "Rules Engine" for the game Magic: the Gathering.
Forge is not related in any way with Wizards of the Coast.
Forge is open source software released under the GNU Public License.
Up to 8 players are supported, with control of each assigned to human or AI control. Player decks can be imported, user-created with the Deck Editor, or automatically generated. Over 99% (and counting) of all cards in Magic's existence are available, with the missing ones mostly being pointless to implement in the context (e. g. the notorious Chaos Orb) or impossible. That's more than the official Magic Online!
Up to 8 players are supported, with each assigned to human or AI control. Player decks can be imported, user-created with the Deck Editor, or automatically generated. Over [99%](Missing-Cards-in-Forge.md) (and counting) of all cards in Magic's existence are available, with the missing ones mostly being pointless to implement in the context (e.g. the notorious Chaos Orb) or impossible. That's more than the official Magic Online!
For a complete list of unimplemented cards, either check the most recent release topic on the forums or use the "Audit Card and Image Data" check from "Content Downloaders" menu.
Forge creates a unique experience by combining this enormous card library with some RPG elements in [**Quest mode**](https://www.slightlymagic.net/forum/viewtopic.php?f=26&t=9258) on the desktop version, and **Planar Conquest** on the mobile version.
Forge creates a unique experience by combining this enormous card library with some RPG elements in [**Quest mode**](https://www.slightlymagic.net/forum/viewtopic.php?f=26&t=9258) (comparable to the 'Shandalar', the late 90's PC Game) on the desktop version, and **Planar Conquest** on the mobile version. Forge now includes a Graphical Map Based game mode called "Adventure Mode" which is more akin to 'Shandalar.'
Forge also features a wide variety of puzzles. For more details and features see the other pages in this wiki.
Forge also features a wide variety of puzzles. For more details and features see the MANUAL.txt in your game folder.
Currently, Forge functions best in Human vs. AI matches. Playing against another human player over the Internet with Online Multiplayer mode is functional, but may still result in game play errors. See the Network Play section of this wiki.
Currently, Forge functions best in Human vs. AI matches. Playing against another human player over the Internet with [Online Multiplayer mode](Network-FAQ.md) is functional, but may still result in game play errors.
# Adventure Mode
Forge now has an Adventure Mode, along with the Classic deck building and match game modes with AI.
Forge now includes a Graphical Map Based game mode, along with the Classic deck building and match game modes with AI.
Adventure mode is a work-in-progress game mode where you explore the ever-changing landscape of Shandalar, and duel creatures to gain gold and new cards ~~to become the best and collect them all!~~ to battle the bosses in the castles for each color. You can visit towns to buy equipment and cards, crawl through dungeons to find artifacts and loot to help you on your journey. Adventure mode is an awesome reimagining of the original 'Shandalar' 90's PC Game in Forge.
Adventure mode is a work-in-progress game mode where you explore the ever-changing landscape of Shandalar, and duel creatures to gain gold and new cards ~~to become the best and collect them all!~~ to battle the bosses in the castles for each color. You can visit towns to buy equipment and cards, crawl through dungeons to find artifacts and loot to help you on your journey. Adventure mode is an awesome reimagining of the original 'Shandalar' late 90's PC Game in Forge.
Adventure is baked into the Android/Mobile release of Forge, and as a separate executable already provided in the Desktop release package.
# Download and Install
* Most users please see the [User Guide.](User-Guide)
* Most users please see the [User Guide](User-Guide.md).
* For SteamDeck or Bazzite Installation see [Steam Deck Install.](Steam-Deck-and-Bazzite-Install)
* For SteamDeck or Bazzite devices see this [extra manual](Steam-Deck-and-Bazzite-Install.md).
# Support
[Basic Troubleshooting](Troubleshooting-FAQ) - Check here first.
[Basic Troubleshooting](#Troubleshooting.md) - Check here first.
[Join the Discord](https://discord.com/invite/3v9JCVr)! - We're happy to help you get going.
[Other Frequently Asked Questions](Frequently-Asked-Questions) - For more advanced questions about forge mechanics and gameplay.
[Other Frequently Asked Questions](Frequently-Asked-Questions.md) - For more advanced explanations about Forge mechanics and gameplay.
# Forge Developers
# Developers
The original programmer can be found at http://mtgrares.blogspot.com. A while
back he open sourced the project and let the other developers improve it
@@ -52,6 +50,8 @@ lot of behind the scenes action going on. For each release, it is common
for the release developer to give a shout out for those that helped
specifically for that version. Feel free to give kudos there.
If you are curious about the written/oral history of Forge, we're working on recreating some of that [Forge Historical reference](Forge-historical-reference)
If you are curious about the written/oral history of Forge, we're working on recreating some of that [Forge Historical reference](Forge-historical-reference.md)
***
> <span style="color: red;">Note:</span> if you're reading this locally here's the [Table of Contents](_sidebar.md)

View File

@@ -1,5 +0,0 @@
Forge can work with card images of your choice.
The naming convention is as follows:
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

View File

@@ -1,10 +1,8 @@
# SteamDeck and Bazzite Support
_This instruction was written using Bazzite, however should be similar enough for SteamOS._
In order to support the SteamDeck "natively" for full Forge Desktop mode, we would likely need to have a flatpack installer for the best user install experience, currently Forge has no intention to have a flatpack. The current **best** and recommended way to have Forge on your SteamDeck is to install and run the Android APK version in Waydroid Android Container.
* You will need to have installed Waydroid first, this reddit post may work for you; https://www.reddit.com/r/SteamDeck/comments/1ay7ev8/how_to_install_waydroid_android_on_your_steam_deck/
* You will need to have installed Waydroid first, this reddit post may work for you: https://www.reddit.com/r/SteamDeck/comments/1ay7ev8/how_to_install_waydroid_android_on_your_steam_deck/
## Installing Forge Android in Waydroid (Recommended Method)
Once you've installed Waydroid, you can follow the same steps you would in any Android device.

View File

@@ -1,116 +1,109 @@
# Downloads
* **Snapshots**;
* READ THESE NOTES BEFORE DOWNLOADING SNAPSHOTS:
* **Please use snapshots for Adventure Mode!**
* May contain more bugs, bug fixes, **definitely gets newest cards faster** and newer features.
* These are **NOW** automatically released daily.
* If the snapshot isn't in the location below, it's because its in the middle of uploading a new snapshot. Come back later to grab it.
* [_**CLICK HERE FOR DOWNLOAD LINKS - Forge SNAPSHOT Version (DESKTOP/ANDROID)**_](https://github.com/Card-Forge/forge/releases/tag/daily-snapshots)
* For desktop, grab the installer file that ends in .jar
* For android, grab the android file that ends in .apk
* **Android Installation Guide**
* Quick Guide for installing Android Snapshots: <br />
# User Guide
## Downloads
https://github.com/user-attachments/assets/7a0c7bb8-7cf9-4800-8091-bcc30ff2f4d8
### Snapshots
* Snapshots are automated daily builds of the source code.
* They contain the latest bug fixes, features and cards.
* If the snapshot isn't in the location below, it's because its in the middle of uploading a new snapshot - come back later to grab it.
[***CLICK HERE FOR DOWNLOAD LINKS - Forge SNAPSHOT Version (DESKTOP/ANDROID)***](https://github.com/Card-Forge/forge/releases/tag/daily-snapshots)
* **Releases**;
* READ THESE NOTES BEFORE DOWNLOADING RELEASES:
- "Releases" are really intended where "99% cards implemented are working and stable."
- If you are looking for newly spoiled cards as soon as possible, grab the snapshot instead.
- The current release mechanism is failing unexpectedly for Android. So just stick with snapshots for Android users.
* [_**CLICK HERE FOR DOWNLOAD LINKS - RELEASE DESKTOP**_](https://github.com/Card-Forge/forge/releases/latest)
- Grab the installer file that ends in .jar
* For desktop, grab the installer file that ends in .jar
* For android, grab the android file that ends in .apk
&dash; Watch the screen recording if one of following steps isn't clear for you
# Java Requirement
<https://github.com/user-attachments/assets/7a0c7bb8-7cf9-4800-8091-bcc30ff2f4d8>
### 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.
* If you are looking for newly spoiled cards as soon as possible, grab the SNAPSHOT version instead.
* The current release mechanism is failing unexpectedly for Android. So just stick with snapshots for Android users.
[***CLICK HERE FOR DOWNLOAD LINKS - RELEASE DESKTOP***](https://github.com/Card-Forge/forge/releases/latest)
* Grab the installer file that ends in .jar
## System Requirements
**Forge Requires Java** to run, please make sure you have Java installed on your machine prior to attempting to run.
* **Java 17** is required as minimum version and can be acquired through the Standard Edition Development Kit (JDK) or the OpenJDK. Continued development provides new features in those editions, therefore you need the Java Development Kit to have those newer editions;
- Download - [https://jdk.java.net/](https://jdk.java.net/)
- Source Code - [https://github.com/openjdk/jdk/](https://github.com/openjdk/jdk/)
* **Java 17** is required as minimum version and can be acquired through the Standard Edition Development Kit (JDK) or the OpenJDK. Continued development provides new features in those editions, therefore you need the Java Development Kit to have those newer editions:
* Download - [https://jdk.java.net/](https://jdk.java.net/)
* Source Code - [https://github.com/openjdk/jdk/](https://github.com/openjdk/jdk/)
Most people who have problems setting up Forge, do not have Java setup properly. If you are having trouble, open your terminal/command line and run `java --version`. That number should be 17 or higher.
Most people who have problems setting up Forge, do not have Java setup properly. If you are having trouble, open your terminal/command line and run `java --version`. That number should be 17 or higher.
# Install and Run
The memory requirements for Forge have fluctuated over time. The default
setting on your computer for the Java heap space may not be enough to
prevent the above problems. If you launch Forge by double-clicking the
jar files directly you could eventually receive a **java heap space
error**.
Forge requires Java to run.
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.)
_**Download and unpack /install the package to their own new folder!**_
## 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)
* 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.
* Browse to your preferred install directory (create a new directory for clean installation) and click next until installation starts.
* Browse to your preferred install directory and click next until installation starts.
![image](https://github.com/Card-Forge/forge/assets/9781539/b7575f49-f6b3-4933-a15f-726314547c4f)
* After the installation finishes, close the installer. Run the executable forge|forge-adventure (.exe/.sh/.cmd)
### What if double-clicking doesnt work?
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)
* **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**:
* 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.
* If you do end up with a ".tar" file, unpack that file also into it's own folder.
* Run Forge script;
* Linux: Run the ".sh" file in a terminal (double clicking might work.)
* MacOS/OSX: Run the ".command" file by double clicking in Finder, or run from the terminal.
* 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**:
* Sideload/Install "forge...apk"
* Run Forge
#### Desktop Windows
## Play Adventure Mode on Desktop
* 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
* 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.
* If you do end up with a ".tar" file, unpack that file also into its own folder.
* Run Forge script:
* Linux: Run the ".sh" file in a terminal (double clicking might work.)
* MacOS/OSX: Run the ".command" file by double clicking in Finder, or run from the terminal.
* 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
* Sideload/Install "forge...apk"
* Run Forge
### 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.
* Android/Mobile builds are built as the Adventure Mode or Mobile UI and nothing special is needed.
- If adventure mode option does not show up;
- check you're up to date with your version.
- check in the settings that the "Selector Mode" is set to `Default`
* If adventure mode option does not show up;
* check you're up to date with your version.
* check in the settings that the "Selector Mode" is set to `Default`
# System Requirements and Historic Details
## Gameplay
Since Forge is written in Java, it is compatible on any Operating System
that can run the Java Runtime Environment. Forge requires
Java 17 (Forge is not backwards compatible with older versions of Java).
If you have difficulties with your System not working with Forge,
please come to the Discord so we can attempt to help.
This program works best with a screen resolution of **1280 by 720** or
better. Forge can now have it's window minimized to **800 by 600**
pixels but this may make the display area cramped and possibly limit
your ability to play. (This means Forge may not compatible with some
netbook computers.)
### Full Control
The memory requirements for Forge have fluctuated over time. The default
setting on your computer for the java heap space may not be enough to
prevent the above problems. If you launch Forge by double-clicking the
file **run-forge.jar** you will eventually receive a **java heap space
error**. The name of the forge jar file has changed as part of our new
Maven based build and release system. The name format now used is:
**forge-**{version number}**-jar-with-dependencies.jar**
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.) People using Windows OS should double click
the **forge.exe** file. People using Apple's Mac OS X should use the Mac
OS version and double click the **forge.command** file. People using one of the
other \*nix OS should double click the **forge.sh** file.
# What if double-clicking doesnt work?
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).
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.

View File

@@ -1,4 +1,5 @@
### [Forge Wiki Home](Home.md)
- [User Guide](User-Guide.md)
- [FAQ](Frequently-Asked-Questions.md)
- [SteamDeck/Bazzite](Steam-Deck-and-Bazzite-Install.md)
@@ -6,55 +7,59 @@
- [Network Play](network-play.md)
- [Network FAQ](Network-FAQ.md)
- [Network Extra](Networking-Extras.md)
- [Advanced search](Advanced-Search.md)
- [Adventure Mode](Adventure-Mode.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)
- Gameplay Guide
- [Modding and content creation](Adventure/Modding.md)
- [Getting Started](Gameplay-Guide.md)
- [Different Planes](Different-Planes.md)
- [Towns & Capitals](Towns-&-Capitals.md)
- [Dungeons](Dungeons.md)
- [Equipments and Items](Equipments-and-Items.md)
- [Keyboard Shortcuts](Keyboard-Shortcuts.md)
- [Modding and Development](Modding-and-Development.md)
- [Create Enemies](Create-Enemies.md)
- [Create Rewards](Create-Rewards.md)
- [Create Maps](Create-new-Maps.md)
- [Configure Planes](Configure-Planes.md)
- [Configure Starting Sets](Configure-Sets.md)
- [Create Enemies](Adventure/Create-Enemies.md)
- [Create Rewards](Adventure/Create-Rewards.md)
- [Create Maps](Adventure/Create-new-Maps.md)
- [Configure Planes](Adventure/Configure-Planes.md)
- [Configure Starting Sets](Adventure/Configure-Sets.md)
- Tutorials
- [Tutorial 1, Create your first Plane](Tutorial-1-Create-your-First-Plane.md)
- [Tutorial 2, A New Look (creating your first map.)](Tutorial-2-A-New-Look.md)
- [Tutorial 3, Configuration (Configuring your Plane)](Tutorial-3-Configuration.md)
- [Tutorial 1, Create your first Plane](Adventure/Tutorial-1-Create-your-First-Plane.md)
- [Tutorial 2, A New Look (creating your first map.)](Adventure/Tutorial-2-A-New-Look.md)
- [Tutorial 3, Configuration (Configuring your Plane)](Adventure/Tutorial-3-Configuration.md)
- [Card Scripting API](Card-scripting-API/Card-scripting-API.md)
- [Ability effects](Card-scripting-API/AbilityFactory.md)
- [Triggers](Card-scripting-API/Triggers.md)
- [Replacements](Card-scripting-API/Replacements.md)
- Statics
- [Costs](Card-scripting-API/Costs.md)
- [Affected / Targets](Card-scripting-API/Targeting.md)
- [Restrictions](Card-scripting-API/Restrictions.md)
- [Guide: Creating a Custom Card](Card-scripting-API/Creating-a-Custom-Card.md)
- [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)
- [IntelliJ Setup](Development/IntelliJ-setup/IntelliJ-setup.md)
- [Snapshots and Releases](Snapshots-and-Releases.md)
- [Android Builds](Development/android-builds.md)
- [Snapshots and 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.md](Themes.md)
- [Customization and Themes](Themes.md)
- Skins
- Sounds
- [Music](Custom-Music.md)
- [Card Images](Card-Images.md)
- [File Formats](File-Formats.md)
- [Creating your first custom set](Creating-a-custom-set.md)
- [Tutorial: creating your first custom set](Creating-a-custom-Set.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)

View File

@@ -124,13 +124,11 @@ public abstract class ManifestBaseAi extends SpellAbilityAi {
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
}
if ( MyRandom.getRandom().nextFloat() < .8) {
if (MyRandom.getRandom().nextFloat() < .8) {
// 80% chance to play a Manifest spell
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
} else {
// 20% chance to not play a Manifest spell
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
@Override

View File

@@ -475,11 +475,11 @@ public final class CardRules implements ICardCharacteristics {
return getName();
ICardFace mainFace = Objects.requireNonNullElse(mainPart.getFunctionalVariant(variantName), mainPart);
String mainPartName = Objects.requireNonNullElse(mainFace.getFlavorName(), mainFace.getName());
String mainPartName = mainFace.getDisplayName();
if(splitType.getAggregationMethod() == CardSplitType.FaceSelectionMethod.COMBINE) {
ICardFace otherFace = Objects.requireNonNullElse(otherPart.getFunctionalVariant(variantName), otherPart);
String otherPartName = Objects.requireNonNullElse(otherFace.getFlavorName(), otherFace.getName());
String otherPartName = otherFace.getDisplayName();
return mainPartName + " // " + otherPartName;
}
else

View File

@@ -9,6 +9,15 @@ import java.util.Map;
public interface ICardFace extends ICardCharacteristics, ICardRawAbilites, Comparable<ICardFace> {
String getFlavorName();
/**
* @return this card's flavor name if it has one. Otherwise, the card's Oracle name.
*/
default String getDisplayName() {
if (this.getFlavorName() != null)
return this.getFlavorName();
return this.getName();
}
boolean hasFunctionalVariants();
ICardFace getFunctionalVariant(String variant);
Map<String, ? extends ICardFace> getFunctionalVariants();

View File

@@ -1776,7 +1776,7 @@ public class GameAction {
if (sb.length() == 0) {
sb.append(p).append(" ").append(Localizer.getInstance().getMessage("lblAssigns")).append("\n");
}
String creature = CardTranslation.getTranslatedName(assignee.getName()) + " (" + assignee.getId() + ")";
String creature = assignee.getTranslatedName() + " (" + assignee.getId() + ")";
sb.append(creature).append(" ").append(sector).append("\n");
}
if (sb.length() > 0) {
@@ -1793,7 +1793,7 @@ public class GameAction {
c.getGame().getTracker().flush();
c.setMoveToCommandZone(false);
if (c.getOwner().getController().confirmAction(c.getFirstSpellAbility(), PlayerActionConfirmMode.ChangeZoneToAltDestination, c.getName() + ": If a commander is in a graveyard or in exile and that card was put into that zone since the last time state-based actions were checked, its owner may put it into the command zone.", null)) {
if (c.getOwner().getController().confirmAction(c.getFirstSpellAbility(), PlayerActionConfirmMode.ChangeZoneToAltDestination, c.getDisplayName() + ": If a commander is in a graveyard or in exile and that card was put into that zone since the last time state-based actions were checked, its owner may put it into the command zone.", null)) {
moveTo(c.getOwner().getZone(ZoneType.Command), c, null, mapParams);
return true;
}
@@ -2217,7 +2217,7 @@ public class GameAction {
/** Delivers a message to all players. (use reveal to show Cards) */
public void notifyOfValue(SpellAbility saSource, GameObject relatedTarget, String value, Player playerExcept) {
if (saSource != null) {
String name = CardTranslation.getTranslatedName(saSource.getHostCard().getName());
String name = saSource.getHostCard().getTranslatedName();
value = TextUtil.fastReplace(value, "CARDNAME", name);
value = TextUtil.fastReplace(value, "NICKNAME", Lang.getInstance().getNickName(name));
}
@@ -2420,7 +2420,7 @@ public class GameAction {
// it to either player or the papercard object so it feels like rule based for the player side..
if (!c.hasMarkedColor()) {
if (takesAction.isAI()) {
String prompt = CardTranslation.getTranslatedName(c.getName()) + ": " +
String prompt = c.getTranslatedName() + ": " +
Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(2));
SpellAbility sa = new SpellAbility.EmptySa(ApiType.ChooseColor, c, takesAction);
sa.putParam("AILogic", "MostProminentInComputerDeck");
@@ -2806,7 +2806,7 @@ public class GameAction {
final FCollection<Player> players = game.getPlayers().filter(PlayerPredicates.canBeAttached(source, null));
final Player pa = p.getController().chooseSingleEntityForEffect(players, aura,
Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", source.getTranslatedName()), null);
if (pa != null) {
source.attachToEntity(pa, null, true);
return true;
@@ -2831,7 +2831,7 @@ public class GameAction {
}
final Card o = p.getController().chooseSingleEntityForEffect(list, aura,
Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", source.getTranslatedName()), null);
if (o != null) {
source.attachToEntity(game.getCardState(o), null, true);
return true;

View File

@@ -633,7 +633,7 @@ public final class GameActionUtil {
}
} else if (o.equals("Conspire")) {
final String conspireCost = "tapXType<2/Creature.SharesColorWith/" +
"creature that shares a color with " + host.getName() + ">";
"creature that shares a color with " + host.getDisplayName() + ">";
final Cost cost = new Cost(conspireCost, false);
String str = "Pay for Conspire? " + cost.toSimpleString();
@@ -744,7 +744,7 @@ public final class GameActionUtil {
for (KeywordInterface ki : c.getKeywords()) {
if (kw.equals(ki.getOriginal())) {
final Cost cost = new Cost(ManaCost.ONE, false);
String str = "Choose Amount for " + c.getName() + ": " + cost.toSimpleString();
String str = "Choose Amount for " + c.getDisplayName() + ": " + cost.toSimpleString();
int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE);

View File

@@ -229,18 +229,18 @@ public abstract class GameEntity implements GameObject, IIdentifiable {
}
public String cantBeAttachedMsg(final Card attach, SpellAbility sa, boolean checkSBA) {
if (!attach.isAttachment()) {
return attach.getName() + " is not an attachment";
return attach.getDisplayName() + " is not an attachment";
}
if (equals(attach)) {
return attach.getName() + " can't attach to itself";
return attach.getDisplayName() + " can't attach to itself";
}
if (attach.isCreature() && !attach.hasKeyword(Keyword.RECONFIGURE)) {
return attach.getName() + " is a creature without reconfigure";
return attach.getDisplayName() + " is a creature without reconfigure";
}
if (attach.isPhasedOut()) {
return attach.getName() + " is phased out";
return attach.getDisplayName() + " is phased out";
}
if (attach.isAura()) {

View File

@@ -147,7 +147,7 @@ public abstract class SpellAbilityEffect {
sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace("X","=",String.valueOf(amount))));
}
String currentName = CardTranslation.getTranslatedName(sa.getHostCard().getName());
String currentName = sa.getHostCard().getTranslatedName();
String substitutedDesc = TextUtil.fastReplace(sb.toString(), "CARDNAME", currentName);
substitutedDesc = TextUtil.fastReplace(substitutedDesc, "NICKNAME", Lang.getInstance().getNickName(currentName));
return substitutedDesc;
@@ -670,7 +670,7 @@ public abstract class SpellAbilityEffect {
}
// build an Effect with that information
String name = host.getName() + "'s Effect";
String name = host.getDisplayName() + "'s Effect";
final Card eff = createEffect(sa, controller, name, host.getImageKey());
if (cards != null) {
@@ -735,7 +735,7 @@ public abstract class SpellAbilityEffect {
Map<String, Object> params = Maps.newHashMap();
params.put("Attacker", c);
defender = sa.getActivatingPlayer().getController().chooseSingleEntityForEffect(defs, sa,
Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(c.getName())), false, params);
Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", c.getTranslatedName()), false, params);
if (defender != null && !combat.getAttackersOf(defender).contains(c)) {
// we might be reselecting

View File

@@ -9,7 +9,6 @@ import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Localizer;
public class AbandonEffect extends SpellAbilityEffect {
@@ -24,7 +23,7 @@ public class AbandonEffect extends SpellAbilityEffect {
Player controller = source.getController();
boolean isOptional = sa.hasParam("Optional");
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeAbandonSource", CardTranslation.getTranslatedName(source.getName())), null)) {
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeAbandonSource", source.getTranslatedName()), null)) {
return;
}

View File

@@ -69,7 +69,7 @@ public class AddTurnEffect extends SpellAbilityEffect {
public static void createCantSetSchemesInMotionEffect(SpellAbility sa) {
final Card hostCard = sa.getHostCard();
final Game game = hostCard.getGame();
final String name = hostCard.getName() + "'s Effect";
final String name = hostCard.getDisplayName() + "'s Effect";
final String image = hostCard.getImageKey();
final Card eff = createEffect(sa, sa.getActivatingPlayer(), name, image);

View File

@@ -20,7 +20,6 @@ import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
import forge.util.collect.FCollection;
@@ -124,7 +123,7 @@ public class AttachEffect extends SpellAbilityEffect {
}
String attachToName;
if (attachTo instanceof Card) {
attachToName = CardTranslation.getTranslatedName(((Card)attachTo).getName());
attachToName = ((Card) attachTo).getTranslatedName();
} else {
attachToName = attachTo.toString();
}
@@ -141,7 +140,7 @@ public class AttachEffect extends SpellAbilityEffect {
continue;
}
String message = Localizer.getInstance().getMessage("lblDoYouWantAttachSourceToTarget", CardTranslation.getTranslatedName(attachment.getName()), attachToName);
String message = Localizer.getInstance().getMessage("lblDoYouWantAttachSourceToTarget", attachment.getTranslatedName(), attachToName);
if (sa.hasParam("Optional") && !activator.getController().confirmAction(sa, null, message, null))
// TODO add params for message
continue;

View File

@@ -9,7 +9,6 @@ import forge.game.event.GameEventCombatChanged;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
@@ -35,7 +34,7 @@ public class ChangeCombatantsEffect extends SpellAbilityEffect {
// TODO: may expand this effect for defined blocker (False Orders, General Jarkeld, Sorrow's Path, Ydwen Efreet)
for (final Card c : getTargetCards(sa)) {
String cardString = CardTranslation.getTranslatedName(c.getName()) + " (" + c.getId() + ")";
String cardString = c.getTranslatedName() + " (" + c.getId() + ")";
if (isOptional && !activator.getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblChangeCombatantOption", cardString), null)) {
continue;

View File

@@ -553,7 +553,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
hostCard.addRemembered(CardCopyService.getLKICopy(gameCard));
}
final String prompt = TextUtil.concatWithSpace(Localizer.getInstance().getMessage("lblDoYouWantMoveTargetFromOriToDest", CardTranslation.getTranslatedName(gameCard.getName()), Lang.joinHomogenous(origin, ZoneType::getTranslatedName), destination.getTranslatedName()));
final String prompt = TextUtil.concatWithSpace(Localizer.getInstance().getMessage("lblDoYouWantMoveTargetFromOriToDest", gameCard.getTranslatedName(), Lang.joinHomogenous(origin, ZoneType::getTranslatedName), destination.getTranslatedName()));
if (optional && !chooser.getController().confirmAction(sa, null, prompt, null)) {
continue;
}
@@ -710,7 +710,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("AttachAfter"), hostCard.getController(), hostCard, sa);
}
if (!list.isEmpty()) {
String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(gameCard.getName()));
String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", gameCard.getTranslatedName());
Map<String, Object> params = Maps.newHashMap();
params.put("Attach", gameCard);
Card attachedTo = chooser.getController().chooseSingleEntityForEffect(list, sa, title, params);
@@ -735,7 +735,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (ZoneType.Hand.equals(destination) && ZoneType.Command.equals(originZone.getZoneType())) {
StringBuilder sb = new StringBuilder();
sb.append(movedCard.getName()).append(" has moved from Command Zone to ").append(activator).append("'s hand.");
sb.append(movedCard.getDisplayName()).append(" has moved from Command Zone to ").append(activator).append("'s hand.");
game.getGameLog().add(GameLogEntryType.ZONE_CHANGE, sb.toString());
commandCards.add(movedCard); //add to list to reveal the commandzone cards
}
@@ -1043,10 +1043,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
final int fetchNum = Math.min(player.getCardsIn(ZoneType.Library).size(), 4);
CardCollectionView shown = !decider.hasKeyword("LimitSearchLibrary") ? player.getCardsIn(ZoneType.Library) : player.getCardsIn(ZoneType.Library, fetchNum);
// Look at whole library before moving onto choosing a card
delayedReveal = new DelayedReveal(shown, ZoneType.Library, PlayerView.get(player), CardTranslation.getTranslatedName(source.getName()) + " - " + Localizer.getInstance().getMessage("lblLookingCardIn") + " ");
delayedReveal = new DelayedReveal(shown, ZoneType.Library, PlayerView.get(player), source.getTranslatedName() + " - " + Localizer.getInstance().getMessage("lblLookingCardIn") + " ");
}
else if (origin.contains(ZoneType.Hand) && player.isOpponentOf(decider)) {
delayedReveal = new DelayedReveal(player.getCardsIn(ZoneType.Hand), ZoneType.Hand, PlayerView.get(player), CardTranslation.getTranslatedName(source.getName()) + " - " + Localizer.getInstance().getMessage("lblLookingCardIn") + " ");
delayedReveal = new DelayedReveal(player.getCardsIn(ZoneType.Hand), ZoneType.Hand, PlayerView.get(player), source.getTranslatedName() + " - " + Localizer.getInstance().getMessage("lblLookingCardIn") + " ");
}
}
@@ -1346,7 +1346,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
list = CardLists.filter(list, CardPredicates.canBeAttached(c, sa));
}
if (!list.isEmpty()) {
String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(c.getName()));
String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", c.getTranslatedName());
Map<String, Object> params = Maps.newHashMap();
params.put("Attach", c);
Card attachedTo = decider.getController().chooseSingleEntityForEffect(list, sa, title, params);
@@ -1363,7 +1363,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (sa.hasParam("AttachedToPlayer")) {
FCollectionView<Player> list = AbilityUtils.getDefinedPlayers(source, sa.getParam("AttachedToPlayer"), sa);
if (!list.isEmpty()) {
String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(c.getName()));
String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", c.getTranslatedName());
Map<String, Object> params = Maps.newHashMap();
params.put("Attach", c);
Player attachedTo = player.getController().chooseSingleEntityForEffect(list, sa, title, params);
@@ -1391,7 +1391,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("AttachAfter"), c.getController(), c, sa);
}
if (!list.isEmpty()) {
String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(c.getName()));
String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", c.getTranslatedName());
Map<String, Object> params = Maps.newHashMap();
params.put("Attach", movedCard);
Card attachedTo = decider.getController().chooseSingleEntityForEffect(list, sa, title, params);
@@ -1530,7 +1530,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
continue;
}
SpellAbility tgtSA = decider.getController().getAbilityToPlay(tgtCard, sas);
if (!decider.getController().confirmAction(tgtSA, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", CardTranslation.getTranslatedName(tgtCard.getName())), null)) {
if (!decider.getController().confirmAction(tgtSA, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", tgtCard.getTranslatedName()), null)) {
continue;
}
// if played, that card cannot be found

View File

@@ -16,7 +16,6 @@ import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.util.Aggregates;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
import forge.util.collect.FCollection;
@@ -235,7 +234,7 @@ public class CharmEffect extends SpellAbilityEffect {
}
boolean isOptional = sa.hasParam("Optional");
if (isOptional && !activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeCharm", CardTranslation.getTranslatedName(source.getName())), null)) {
if (isOptional && !activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeCharm", source.getTranslatedName()), null)) {
return false;
}

View File

@@ -6,7 +6,6 @@ import com.google.common.collect.Lists;
import forge.game.Direction;
import forge.game.player.DelayedReveal;
import forge.game.player.PlayerView;
import forge.util.CardTranslation;
import forge.card.CardType;
import forge.game.Game;
@@ -256,7 +255,7 @@ public class ChooseCardEffect extends SpellAbilityEffect {
CardCollectionView shown = !p.hasKeyword("LimitSearchLibrary")
? searched.getCardsIn(ZoneType.Library) : searched.getCardsIn(ZoneType.Library, fetchNum);
DelayedReveal delayedReveal = new DelayedReveal(shown, ZoneType.Library, PlayerView.get(searched),
CardTranslation.getTranslatedName(host.getName()) + " - " +
host.getTranslatedName() + " - " +
Localizer.getInstance().getMessage("lblLookingCardIn") + " ");
Card choice = p.getController().chooseSingleEntityForEffect(pChoices, delayedReveal, sa, title,
!sa.hasParam("Mandatory"), p, null);

View File

@@ -23,7 +23,7 @@ public class ClashEffect extends SpellAbilityEffect {
*/
@Override
protected String getStackDescription(final SpellAbility sa) {
return sa.getHostCard().getName() + " - Clash with an opponent.";
return sa.getHostCard().getDisplayName() + " - Clash with an opponent.";
}
/* (non-Javadoc)
@@ -101,7 +101,7 @@ public class ClashEffect extends SpellAbilityEffect {
pCMC = pCard.getCMC();
toReveal.add(pCard);
reveal.append(player).append(" " + Localizer.getInstance().getMessage("lblReveals") + ": ").append(pCard.getName()).append(". " + Localizer.getInstance().getMessage("lblCMC") + "= ").append(pCMC);
reveal.append(player).append(" " + Localizer.getInstance().getMessage("lblReveals") + ": ").append(pCard.getDisplayName()).append(". " + Localizer.getInstance().getMessage("lblCMC") + "= ").append(pCMC);
reveal.append("\n");
}
if (!oLib.isEmpty()) {
@@ -109,7 +109,7 @@ public class ClashEffect extends SpellAbilityEffect {
oCMC = oCard.getCMC();
toReveal.add(oCard);
reveal.append(opponent).append(" " + Localizer.getInstance().getMessage("lblReveals") + ": ").append(oCard.getName()).append(". " + Localizer.getInstance().getMessage("lblCMC") + "= ").append(oCMC);
reveal.append(opponent).append(" " + Localizer.getInstance().getMessage("lblReveals") + ": ").append(oCard.getDisplayName()).append(". " + Localizer.getInstance().getMessage("lblCMC") + "= ").append(oCMC);
reveal.append("\n");
}
@@ -138,7 +138,7 @@ public class ClashEffect extends SpellAbilityEffect {
final GameAction action = p.getGame().getAction();
final boolean putOnTop = p.getController().willPutCardOnTop(c);
final String location = putOnTop ? "top" : "bottom";
final String clashOutcome = p.getName() + " clashed and put " + c.getName() + " to the " + location + " of library.";
final String clashOutcome = p.getName() + " clashed and put " + c.getDisplayName() + " to the " + location + " of library.";
if (putOnTop) {
action.moveToLibrary(c, sa);

View File

@@ -11,7 +11,6 @@ import forge.game.card.Card;
import forge.game.event.GameEventRandomLog;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.CardTranslation;
import forge.util.Localizer;
public class CleanUpEffect extends SpellAbilityEffect {
@@ -76,7 +75,7 @@ public class CleanUpEffect extends SpellAbilityEffect {
protected String logOutput(SpellAbility sa, Card source) {
final StringBuilder log = new StringBuilder();
final String name = CardTranslation.getTranslatedName(source.getName());
final String name = source.getTranslatedName();
String linebreak = "\r\n";
if (sa.hasParam("ClearRemembered") && source.getRememberedCount() != 0) {

View File

@@ -11,7 +11,6 @@ import forge.game.event.GameEventCardStatsChanged;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.IterableUtil;
import forge.util.Localizer;
import forge.util.collect.FCollection;
@@ -109,7 +108,7 @@ public class CloneEffect extends SpellAbilityEffect {
}
final boolean optional = sa.hasParam("Optional");
if (optional && !host.getController().getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantCopy", CardTranslation.getTranslatedName(cardToCopy.getName())), null)) {
if (optional && !host.getController().getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantCopy", cardToCopy.getTranslatedName()), null)) {
return;
}

View File

@@ -9,7 +9,6 @@ import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.CardTranslation;
import forge.util.Localizer;
@@ -87,8 +86,8 @@ public class ControlExchangeEffect extends SpellAbilityEffect {
if (sa.hasParam("Optional") && !sa.getActivatingPlayer().getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblExchangeControl",
CardTranslation.getTranslatedName(object1.getName()),
CardTranslation.getTranslatedName(object2.getName())), null)) {
object1.getTranslatedName(),
object2.getTranslatedName()), null)) {
return;
}

View File

@@ -22,7 +22,6 @@ import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Localizer;
public class ControlGainEffect extends SpellAbilityEffect {
@@ -154,7 +153,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
if (sa.hasParam("Optional") && !activator.getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblGainControlConfirm", newController,
CardTranslation.getTranslatedName(tgtC.getName())), null)) {
tgtC.getTranslatedName()), null)) {
continue;
}

View File

@@ -92,7 +92,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
}
for (SpellAbility chosenSA : copySpells) {
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoyouWantCopyTheSpell", CardTranslation.getTranslatedName(chosenSA.getHostCard().getName())), null)) {
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoyouWantCopyTheSpell", chosenSA.getHostCard().getTranslatedName()), null)) {
continue;
}

View File

@@ -19,7 +19,6 @@ import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Localizer;
import forge.util.TextUtil;
@@ -182,7 +181,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
if (counterNum.equals("Any")) {
tgtCards = activator.getController().chooseCardsForEffect(
tgtCards, sa, Localizer.getInstance().getMessage("lblChooseCardToGetCountersFrom",
cType.getName(), CardTranslation.getTranslatedName(source.getName())),
cType.getName(), source.getTranslatedName()),
0, tgtCards.size(), true, params);
}
@@ -212,7 +211,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
params.put("Target", cur);
int cnum = activator.getController().chooseNumber(sa,
Localizer.getInstance().getMessage("lblPutHowManyTargetCounterOnCard", cType.getName(),
CardTranslation.getTranslatedName(cur.getName())),
cur.getTranslatedName()),
0, source.getCounters(cType), params);
if (cnum > 0) {
@@ -353,7 +352,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
int min = sa.hasParam("NonZero") && countersToAdd.isEmpty() ? 1 : 0;
cnum = pc.chooseNumber(
sa, Localizer.getInstance().getMessage("lblTakeHowManyTargetCounterFromCard",
cType.getName(), CardTranslation.getTranslatedName(src.getName())),
cType.getName(), src.getTranslatedName()),
min, cmax, params);
} else {
cnum = Math.min(cmax, AbilityUtils.calculateAmount(host, counterNum, sa));

View File

@@ -486,7 +486,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
} else {
counterAmount = pc.chooseNumber(sa,
Localizer.getInstance().getMessage("lblHowManyCountersThis",
CardTranslation.getTranslatedName(gameCard.getName())),
gameCard.getTranslatedName()),
1, counterRemain, params);
}
}
@@ -529,7 +529,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
String message = Localizer.getInstance().getMessage(
"lblDoYouWantPutTargetP1P1CountersOnCard", String.valueOf(counterAmount),
CardTranslation.getTranslatedName(gameCard.getName()));
gameCard.getTranslatedName());
placer = pc.chooseSingleEntityForEffect(activator.getOpponents(), sa,
Localizer.getInstance().getMessage("lblChooseAnOpponent"), params);
@@ -721,7 +721,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
protected String logOutput(Map<Object, Integer> randomMap, Card card) {
StringBuilder randomLog = new StringBuilder();
randomLog.append(card.getName()).append(" randomly distributed ");
randomLog.append(card.getDisplayName()).append(" randomly distributed ");
if (randomMap.entrySet().size() == 0) {
randomLog.append("no counters.");
} else {

View File

@@ -16,7 +16,6 @@ import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.SpellAbility;
import forge.util.CardTranslation;
import forge.util.Expressions;
import forge.util.Lang;
import forge.util.Localizer;
@@ -80,7 +79,7 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
}
if (sa.hasParam("Optional") && !pl.getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblWouldYouLikePutRemoveCounters", ctype.getName(),
CardTranslation.getTranslatedName(gameCard.getName())), null)) {
gameCard.getTranslatedName()), null)) {
continue;
}
if (gameCard.hasCounters()) {
@@ -135,7 +134,7 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
putCounter = false;
} else {
params.put("CounterType", chosenType);
prompt = Localizer.getInstance().getMessage("lblWhatToDoWithTargetCounter", chosenType.getName(), CardTranslation.getTranslatedName(tgtCard.getName())) + " ";
prompt = Localizer.getInstance().getMessage("lblWhatToDoWithTargetCounter", chosenType.getName(), tgtCard.getTranslatedName()) + " ";
putCounter = pc.chooseBinary(sa, prompt, BinaryChoiceType.AddOrRemove, params);
}
}

View File

@@ -28,7 +28,7 @@ public class DestroyAllEffect extends SpellAbilityEffect {
final StringBuilder sb = new StringBuilder();
final boolean noRegen = sa.hasParam("NoRegen");
sb.append(sa.getHostCard().getName()).append(" - Destroy permanents.");
sb.append(sa.getHostCard().getDisplayName()).append(" - Destroy permanents.");
if (noRegen) {
sb.append(" They can't be regenerated");

View File

@@ -16,7 +16,6 @@ import forge.game.player.Player;
import forge.game.player.PlayerView;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
import forge.util.TextUtil;
@@ -197,7 +196,7 @@ public class DigEffect extends SpellAbilityEffect {
}
else if (!sa.hasParam("NoLooking")) {
// show the user the revealed cards
delayedReveal = new DelayedReveal(top, srcZone, PlayerView.get(p), CardTranslation.getTranslatedName(host.getName()) + " - " + Localizer.getInstance().getMessage("lblLookingCardIn") + " ");
delayedReveal = new DelayedReveal(top, srcZone, PlayerView.get(p), host.getTranslatedName() + " - " + Localizer.getInstance().getMessage("lblLookingCardIn") + " ");
}
if (sa.hasParam("RememberRevealed") && hasRevealed) {
@@ -249,7 +248,7 @@ public class DigEffect extends SpellAbilityEffect {
// Optional abilities that use a dialog box to prompt the user to skip the ability (e.g. Explorer's Scope, Quest for Ula's Temple)
if (optional && mayBeSkipped && !valid.isEmpty()) {
String prompt = optionalAbilityPrompt != null ? optionalAbilityPrompt : Localizer.getInstance().getMessage("lblWouldYouLikeProceedWithOptionalAbility") + " " + host + "?\n\n(" + sa.getDescription() + ")";
if (!p.getController().confirmAction(sa, null, TextUtil.fastReplace(prompt, "CARDNAME", CardTranslation.getTranslatedName(host.getName())), null)) {
if (!p.getController().confirmAction(sa, null, TextUtil.fastReplace(prompt, "CARDNAME", host.getTranslatedName()), null)) {
return;
}
}

View File

@@ -19,7 +19,6 @@ import forge.game.trigger.TriggerType;
import forge.game.zone.PlayerZone;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
@@ -79,7 +78,7 @@ public class DiscoverEffect extends SpellAbilityEffect {
params.put("Card", found);
if (found != null) {
String prompt = Localizer.getInstance().getMessage("lblDiscoverChoice",
CardTranslation.getTranslatedName(found.getName()));
found.getTranslatedName());
final Zone origin = found.getZone();
List<String> options =
Arrays.asList(StringUtils.capitalize(Localizer.getInstance().getMessage("lblCast")),

View File

@@ -24,7 +24,7 @@ import java.util.*;
final StringBuilder sb = new StringBuilder();
sb.append(player).append(" drafts a card from ").append(source.getName()).append("'s spellbook");
sb.append(player).append(" drafts a card from ").append(source.getDisplayName()).append("'s spellbook");
if (zone.equals(ZoneType.Hand)) {
sb.append(".");
} else if (zone.equals(ZoneType.Battlefield)) {

View File

@@ -11,7 +11,6 @@ import forge.game.card.CardCollectionView;
import forge.game.card.CardZoneTable;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.CardTranslation;
import forge.util.Localizer;
public class EncodeEffect extends SpellAbilityEffect {
@@ -47,7 +46,7 @@ public class EncodeEffect extends SpellAbilityEffect {
}
// Handle choice of whether or not to encoded
if (!activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantExileCardAndEncodeOntoYouCreature", CardTranslation.getTranslatedName(host.getName())), null)) {
if (!activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantExileCardAndEncodeOntoYouCreature", host.getTranslatedName()), null)) {
return;
}

View File

@@ -21,7 +21,6 @@ import forge.game.event.GameEventTokenCreated;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
@@ -69,7 +68,7 @@ public class EndureEffect extends TokenEffectBase {
params.put("Amount", amount);
if (gamec != null && gamec.isInPlay() && gamec.equalsWithGameTimestamp(c) && gamec.canReceiveCounters(CounterEnumType.P1P1)
&& pl.getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblEndureAction", CardTranslation.getTranslatedName(c.getName()), amount),
Localizer.getInstance().getMessage("lblEndureAction", c.getTranslatedName(), amount),
gamec, params)) {
gamec.addCounter(CounterEnumType.P1P1, amount, pl, table);
} else {

View File

@@ -18,7 +18,6 @@ import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
@@ -82,7 +81,7 @@ public class ExploreEffect extends SpellAbilityEffect {
params.put("RevealedCard", r);
if (pl.getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblPutThisCardToYourGraveyard",
CardTranslation.getTranslatedName(r.getName())), r, params))
r.getTranslatedName()), r, params))
game.getAction().moveTo(ZoneType.Graveyard, r, sa, moveParams);
}
}

View File

@@ -17,7 +17,6 @@ import forge.game.player.Player;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
@@ -65,7 +64,7 @@ public class FightEffect extends DamageBaseEffect {
Player controller = host.getController();
boolean isOptional = sa.hasParam("Optional");
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeFight", CardTranslation.getTranslatedName(fighters.get(0).getName()), CardTranslation.getTranslatedName(fighters.get(1).getName())), null)) {
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeFight", fighters.get(0).getTranslatedName(), fighters.get(1).getTranslatedName()), null)) {
return;
}

View File

@@ -206,7 +206,7 @@ public class FlipCoinEffect extends SpellAbilityEffect {
} else {
// no reason to ask if result is fixed anyway
if (!noCall) {
choice = flipper.getController().chooseBinary(sa, sa.getHostCard().getName() + " - " + Localizer.getInstance().getMessage("lblCallCoinFlip") + info, PlayerController.BinaryChoiceType.HeadsOrTails);
choice = flipper.getController().chooseBinary(sa, sa.getHostCard().getDisplayName() + " - " + Localizer.getInstance().getMessage("lblCallCoinFlip") + info, PlayerController.BinaryChoiceType.HeadsOrTails);
}
for (int i = 0; i < multiplier; i++) {

View File

@@ -109,7 +109,7 @@ public class MakeCardEffect extends SpellAbilityEffect {
chosen = Aggregates.random(faces).getName();
} else {
final String sbName = sa.hasParam("SpellbookName") ? sa.getParam("SpellbookName") :
CardTranslation.getTranslatedName(source.getName());
source.getTranslatedName();
final String message = sa.hasParam("Choices") ?
Localizer.getInstance().getMessage("lblChooseaCard") :
Localizer.getInstance().getMessage("lblChooseFromSpellbook", sbName);

View File

@@ -13,7 +13,6 @@ import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
@@ -82,7 +81,7 @@ public class PeekAndRevealEffect extends SpellAbilityEffect {
boolean doReveal = !sa.hasParam("NoReveal") && !revealableCards.isEmpty();
if (!noPeek) {
peekingPlayer.getController().reveal(peekCards, srcZone, zoneToPeek,
CardTranslation.getTranslatedName(source.getName()) + " - " +
source.getTranslatedName() + " - " +
Localizer.getInstance().getMessage("lblLookingCardFrom"));
}
@@ -91,7 +90,7 @@ public class PeekAndRevealEffect extends SpellAbilityEffect {
if (doReveal) {
peekingPlayer.getGame().getAction().reveal(revealableCards, srcZone, zoneToPeek, !noPeek,
CardTranslation.getTranslatedName(source.getName()) + " - " +
source.getTranslatedName() + " - " +
Localizer.getInstance().getMessage("lblRevealingCardFrom"));
if (rememberRevealed) {

View File

@@ -247,7 +247,7 @@ public class PlayEffect extends SpellAbilityEffect {
game.getAction().revealTo(tgtCard, controller);
}
String prompt = sa.hasParam("CastTransformed") ? "lblDoYouWantPlayCardTransformed" : "lblDoYouWantPlayCard";
if (singleOption && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage(prompt, CardTranslation.getTranslatedName(tgtCard.getName())), tgtCard, null)) {
if (singleOption && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage(prompt, tgtCard.getTranslatedName()), tgtCard, null)) {
if (wasFaceDown) {
tgtCard.turnFaceDownNoUpdate();
tgtCard.updateStateForView();
@@ -490,7 +490,7 @@ public class PlayEffect extends SpellAbilityEffect {
public static void addReplaceGraveyardEffect(Card c, Card hostCard, SpellAbility sa, SpellAbility tgtSA, String zone) {
final Game game = hostCard.getGame();
final Player controller = sa.getActivatingPlayer();
final String name = hostCard.getName() + "'s Effect";
final String name = hostCard.getDisplayName() + "'s Effect";
final String image = hostCard.getImageKey();
final Card eff = createEffect(sa, controller, name, image);

View File

@@ -94,7 +94,7 @@ public class SacrificeEffect extends SpellAbilityEffect {
if (valid.equals("Self") && game.getZoneOf(host) != null) {
if (host.getController().equals(activator) && game.getZoneOf(host).is(ZoneType.Battlefield) &&
(!optional || activator.getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", host.getName()), null))) {
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", host.getDisplayName()), null))) {
if (game.getAction().sacrifice(new CardCollection(host), sa, true, params) != null && remSacrificed) {
host.addRemembered(host);
}

View File

@@ -11,7 +11,6 @@ import forge.game.spellability.SpellAbility;
import forge.util.Lang;
import forge.util.Localizer;
public class ScryEffect extends SpellAbilityEffect {
@Override
protected String getStackDescription(SpellAbility sa) {
@@ -37,10 +36,8 @@ public class ScryEffect extends SpellAbilityEffect {
}
boolean isOptional = sa.hasParam("Optional");
final List<Player> players = Lists.newArrayList();
final List<Player> players = Lists.newArrayList(); // players really affected
// Optional here for spells that have optional multi-player scrying
for (final Player p : getTargetPlayers(sa)) {
if (!p.isInGame()) {
continue;

View File

@@ -162,7 +162,7 @@ public class SetStateEffect extends SpellAbilityEffect {
}
if (optional) {
String message = TextUtil.concatWithSpace("Transform", gameCard.getName(), "?");
String message = TextUtil.concatWithSpace("Transform", gameCard.getDisplayName(), "?");
if (!p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message, null)) {
return;
}
@@ -183,22 +183,22 @@ public class SetStateEffect extends SpellAbilityEffect {
}
if (hasTransformed) {
if (sa.isMorphUp()) {
String sb = p + " has unmorphed " + gameCard.getName();
String sb = p + " has unmorphed " + gameCard.getDisplayName();
game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
} else if (sa.isManifestUp()) {
String sb = p + " has unmanifested " + gameCard.getName();
String sb = p + " has unmanifested " + gameCard.getDisplayName();
game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
} else if (sa.isDisguiseUp()) {
String sb = p + " has undisguised " + gameCard.getName();
String sb = p + " has undisguised " + gameCard.getDisplayName();
game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
} else if (sa.isCloakUp()) {
String sb = p + " has uncloaked " + gameCard.getName();
String sb = p + " has uncloaked " + gameCard.getDisplayName();
game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
} else if (sa.isKeyword(Keyword.DOUBLE_AGENDA)) {
String sb = p + " has revealed " + gameCard.getName() + " with the chosen names: " + gameCard.getNamedCards();
String sb = p + " has revealed " + gameCard.getDisplayName() + " with the chosen names: " + gameCard.getNamedCards();
game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
} else if (sa.isKeyword(Keyword.HIDDEN_AGENDA)) {
String sb = p + " has revealed " + gameCard.getName() + " with the chosen name " + gameCard.getNamedCard();
String sb = p + " has revealed " + gameCard.getDisplayName() + " with the chosen name " + gameCard.getNamedCard();
game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
}
game.fireEvent(new GameEventCardStatsChanged(gameCard));

View File

@@ -23,7 +23,6 @@ import forge.game.spellability.SpellAbility;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
import forge.util.collect.FCollectionView;
@@ -77,7 +76,7 @@ public class SubgameEffect extends SpellAbilityEffect {
Card cmd = Card.fromPaperCard(card.getPaperCard(), player);
if (cmd.hasKeyword("If CARDNAME is your commander, choose a color before the game begins.")) {
List<String> colorChoices = new ArrayList<>(MagicColor.Constant.ONLY_COLORS);
String prompt = Localizer.getInstance().getMessage("lblChooseAColorFor", cmd.getName());
String prompt = Localizer.getInstance().getMessage("lblChooseAColorFor", cmd.getDisplayName());
List<String> chosenColors;
SpellAbility cmdColorsa = new SpellAbility.EmptySa(ApiType.ChooseColor, cmd, player);
chosenColors = player.getController().chooseColors(prompt,cmdColorsa, 1, 1, colorChoices);
@@ -164,7 +163,7 @@ public class SubgameEffect extends SpellAbilityEffect {
Game subgame = createSubGame(maingame, startingLife);
String startMessage = Localizer.getInstance().getMessage("lblSubgameStart",
CardTranslation.getTranslatedName(hostCard.getName()));
hostCard.getTranslatedName());
maingame.fireEvent(new GameEventSubgameStart(subgame, startMessage));
prepareAllZonesSubgame(maingame, subgame);

View File

@@ -11,7 +11,6 @@ import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
@@ -64,7 +63,7 @@ public class TapOrUntapEffect extends SpellAbilityEffect {
// If the effected card is controlled by the same controller of the SA, default to untap.
boolean tap;
if(!toggle)
tap = pc.chooseBinary(sa, Localizer.getInstance().getMessage("lblTapOrUntapTarget", CardTranslation.getTranslatedName(gameCard.getName())), PlayerController.BinaryChoiceType.TapOrUntap,
tap = pc.chooseBinary(sa, Localizer.getInstance().getMessage("lblTapOrUntapTarget", gameCard.getTranslatedName()), PlayerController.BinaryChoiceType.TapOrUntap,
!gameCard.getController().equals(tapper));
else
tap = !gameCard.isTapped();

View File

@@ -19,7 +19,6 @@ import forge.game.player.PlayerController;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Localizer;
public class TimeTravelEffect extends SpellAbilityEffect {
@@ -53,7 +52,7 @@ public class TimeTravelEffect extends SpellAbilityEffect {
Map<String, Object> params = Maps.newHashMap();
params.put("Target", c);
params.put("CounterType", counterType);
prompt = Localizer.getInstance().getMessage("lblWhatToDoWithTargetCounter", counterType.getName(), CardTranslation.getTranslatedName(c.getName())) + " ";
prompt = Localizer.getInstance().getMessage("lblWhatToDoWithTargetCounter", counterType.getName(), c.getTranslatedName()) + " ";
boolean putCounter = pc.chooseBinary(sa, prompt, BinaryChoiceType.AddOrRemove, params);
if (putCounter) {

View File

@@ -195,6 +195,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
private boolean startedTheTurnUntapped = false;
private boolean cameUnderControlSinceLastUpkeep = true; // for Echo
private boolean tapped = false;
private int tappedThisTurn;
private boolean sickness = true; // summoning sickness
private boolean collectible = false;
private boolean tokenCard = false;
@@ -4897,8 +4898,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
runParams.put(AbilityKey.Attacker, attacker);
runParams.put(AbilityKey.Cause, cause);
runParams.put(AbilityKey.Player, tapper);
runParams.put(AbilityKey.FirstTime, tappedThisTurn == 0);
getGame().getTriggerHandler().runTrigger(TriggerType.Taps, runParams, false);
tappedThisTurn++;
setTapped(true);
view.updateNeedsTapAnimation(tapAnimation);
getGame().fireEvent(new GameEventCardTapped(this, true));
@@ -7171,7 +7175,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
String v = kwt.getValidType();
String desc = kwt.getTypeDescription();
if (!isValid(v.split(","), aura.getController(), aura, null) || (!v.contains("inZone") && !isInPlay())) {
return getName() + " is not " + Lang.nounWithAmount(1, desc);
return getDisplayName() + " is not " + Lang.nounWithAmount(1, desc);
}
}
}
@@ -7182,17 +7186,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
@Override
protected String cantBeEquippedByMsg(final Card equip, SpellAbility sa) {
if (!isInPlay()) {
return getName() + " is not in play";
return getDisplayName() + " is not in play";
}
if (sa != null && sa.isEquip()) {
if (!isValid(sa.getTargetRestrictions().getValidTgts(), sa.getActivatingPlayer(), equip, sa)) {
Equip eq = (Equip) sa.getKeyword();
return getName() + " is not " + Lang.nounWithAmount(1, eq.getValidDescription());
return getDisplayName() + " is not " + Lang.nounWithAmount(1, eq.getValidDescription());
}
return null;
}
if (!isCreature()) {
return getName() + " is not a creature";
return getDisplayName() + " is not a creature";
}
return null;
}
@@ -7200,13 +7204,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
@Override
protected String cantBeFortifiedByMsg(final Card fort) {
if (!isLand()) {
return getName() + " is not a Land";
return getDisplayName() + " is not a Land";
}
if (!isInPlay()) {
return getName() + " is not in play";
return getDisplayName() + " is not in play";
}
if (fort.isLand()) {
return fort.getName() + " is a Land";
return fort.getDisplayName() + " is a Land";
}
return null;
@@ -7215,7 +7219,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
@Override
public String cantBeAttachedMsg(final Card attach, SpellAbility sa, boolean checkSBA) {
if (isPhasedOut() && !attach.isPhasedOut()) {
return getName() + " is phased out";
return getDisplayName() + " is phased out";
}
return super.cantBeAttachedMsg(attach, sa, checkSBA);
}
@@ -7456,7 +7460,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
}
public void onCleanupPhase(final Player turn) {
resetExcessDamage();
tappedThisTurn = 0;
setRegeneratedThisTurn(0);
resetShieldCount();
targetedFromThisTurn.clear();
@@ -7466,13 +7470,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
getDamageHistory().setCreatureAttackedLastTurnOf(turn, getDamageHistory().getCreatureAttacksThisTurn() > 0);
getDamageHistory().newTurn();
damageReceivedThisTurn.clear();
resetExcessDamage();
clearBlockedByThisTurn();
clearBlockedThisTurn();
resetMayPlayTurn();
resetExertedThisTurn();
resetCrewed();
resetSaddled();
visitedThisTurn = false;
resetMayPlayTurn();
resetChosenModeTurn();
resetAbilityResolvedThisTurn();
}

View File

@@ -222,7 +222,7 @@ public class CardFactoryUtil {
//Predicate<Card> pc = Predicates.in(player.getAllCards());
// TODO This would be better to send in the player's deck, not all cards
String name = player.getController().chooseCardName(sa, cpp, "Card",
"Name a card for " + card.getName());
"Name a card for " + card.getDisplayName());
if (name == null || name.isEmpty()) {
return false;
}
@@ -230,7 +230,7 @@ public class CardFactoryUtil {
if (ki.getKeyword().equals(Keyword.DOUBLE_AGENDA)) {
String name2 = player.getController().chooseCardName(sa, cpp, "Card.!NamedCard",
"Name a second card for " + card.getName());
"Name a second card for " + card.getDisplayName());
if (name2 == null || name2.isEmpty()) {
return false;
}
@@ -1289,10 +1289,10 @@ public class CardFactoryUtil {
final StringBuilder sb = new StringBuilder();
if (card.isCreature()) {
sb.append("When ").append(card.getName());
sb.append("When ").append(card.getDisplayName());
sb.append(" enters or the creature it haunts dies, ");
} else {
sb.append("When the creature ").append(card.getName());
sb.append("When the creature ").append(card.getDisplayName());
sb.append(" haunts dies, ");
}
@@ -1484,7 +1484,7 @@ public class CardFactoryUtil {
if (!"ManaCost".equals(manacost)) {
desc.append(ManaCostParser.parse(manacost)).append(" ");
}
desc.append(" - " + card.getName());
desc.append(" - " + card.getDisplayName());
final String trigStr = "Mode$ Exiled | ValidCard$ Card.Self | Secondary$ True | TriggerDescription$ " + desc.toString();
@@ -3762,10 +3762,10 @@ public class CardFactoryUtil {
c.addCounter(CounterEnumType.TIME, counters, getActivatingPlayer(), table);
table.replaceCounterEffect(game, this, false); // this is a special Action, not an Effect
String sb = TextUtil.concatWithSpace(getActivatingPlayer().toString(),"has suspended", c.getName(), "with", String.valueOf(counters),"time counters on it.");
String sb = TextUtil.concatWithSpace(getActivatingPlayer().toString(),"has suspended", c.getDisplayName(), "with", String.valueOf(counters),"time counters on it.");
game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
//reveal suspended card
game.getAction().reveal(new CardCollection(c), c.getOwner(), true, c.getName() + " is suspended with " + counters + " time counters in ");
game.getAction().reveal(new CardCollection(c), c.getOwner(), true, c.getDisplayName() + " is suspended with " + counters + " time counters in ");
}
};
final StringBuilder sbDesc = new StringBuilder();

View File

@@ -91,6 +91,8 @@ public enum CounterEnumType implements CounterType {
COMPONENT("COMPN", 224, 160, 48),
CONQUEROR("CONQR", 225, 210, 25),
CONTESTED("CONTES", 255, 76, 2),
CORPSE("CRPSE", 230, 186, 209),

View File

@@ -38,7 +38,6 @@ import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.IterableUtil;
import forge.util.Localizer;
import forge.util.collect.FCollection;
@@ -733,7 +732,7 @@ public class Combat {
"defending player and/or any number of creatures they control.")
&& blocker.getController().getController().confirmStaticApplication(blocker, PlayerActionConfirmMode.AlternativeDamageAssignment,
Localizer.getInstance().getMessage("lblAssignCombatDamageAsChoose",
CardTranslation.getTranslatedName(blocker.getName())), null);
blocker.getTranslatedName()), null);
// choose defending player
if (divideCombatDamageAsChoose) {
defender = blocker.getController().getController().chooseSingleEntityForEffect(attackingPlayer.getOpponents(), null, Localizer.getInstance().getMessage("lblChoosePlayer"), null);
@@ -811,7 +810,7 @@ public class Combat {
&& StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) {
assignToPlayer = assigningPlayer.getController().confirmStaticApplication(attacker, PlayerActionConfirmMode.AlternativeDamageAssignment,
Localizer.getInstance().getMessage("lblAssignCombatDamageWerentBlocked",
CardTranslation.getTranslatedName(attacker.getName())), null);
attacker.getTranslatedName()), null);
}
boolean divideCombatDamageAsChoose = false;
@@ -823,7 +822,7 @@ public class Combat {
"defending player and/or any number of creatures they control.")
&& assigningPlayer.getController().confirmStaticApplication(attacker, PlayerActionConfirmMode.AlternativeDamageAssignment,
Localizer.getInstance().getMessage("lblAssignCombatDamageAsChoose",
CardTranslation.getTranslatedName(attacker.getName())), null);
attacker.getTranslatedName()), null);
if (defender instanceof Card && divideCombatDamageAsChoose) {
defender = getDefenderPlayerByAttacker(attacker);
}
@@ -831,7 +830,7 @@ public class Combat {
assignCombatDamageToCreature = !attacker.getGame().getCombat().isBlocked(attacker) && getDefendersCreatures().size() > 0 &&
attacker.hasKeyword("If CARDNAME is unblocked, you may have it assign its combat damage to a creature defending player controls.") &&
assigningPlayer.getController().confirmStaticApplication(attacker, PlayerActionConfirmMode.AlternativeDamageAssignment,
Localizer.getInstance().getMessage("lblAssignCombatDamageToCreature", CardTranslation.getTranslatedName(attacker.getName())), null);
Localizer.getInstance().getMessage("lblAssignCombatDamageToCreature", attacker.getTranslatedName()), null);
if (divideCombatDamageAsChoose) {
if (orderedBlockers == null || orderedBlockers.isEmpty()) {
orderedBlockers = getDefendersCreatures();

View File

@@ -3017,7 +3017,7 @@ public class Player extends GameEntity implements Comparable<Player> {
if (color) {
Player p = cmd.getController();
List<String> colorChoices = new ArrayList<>(MagicColor.Constant.ONLY_COLORS);
String prompt = Localizer.getInstance().getMessage("lblChooseAColorFor", cmd.getName());
String prompt = Localizer.getInstance().getMessage("lblChooseAColorFor", cmd.getDisplayName());
List<String> chosenColors;
SpellAbility cmdColorsa = new SpellAbility.EmptySa(ApiType.ChooseColor, cmd, p);
chosenColors = p.getController().chooseColors(prompt,cmdColorsa, 1, 1, colorChoices);
@@ -3219,7 +3219,7 @@ public class Player extends GameEntity implements Comparable<Player> {
}
public static DetachedCardEffect createCompanionEffect(Game game, Card companion) {
final String name = Lang.getInstance().getPossesive(companion.getName()) + " Companion Effect";
final String name = Lang.getInstance().getPossesive(companion.getDisplayName()) + " Companion Effect";
DetachedCardEffect eff = new DetachedCardEffect(companion, name);
String addToHandAbility = "Mode$ Continuous | EffectZone$ Command | Affected$ Card.YouOwn+EffectSource | AffectedZone$ Command | AddAbility$ MoveToHand";

View File

@@ -9,6 +9,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Expressions;
import forge.util.TextUtil;
@@ -282,6 +283,14 @@ public class PlayerProperty {
if (!player.maxSpeed()) {
return false;
}
} else if (property.equals("targetedBy")) {
if (!(spellAbility instanceof SpellAbility)) {
return false;
}
SpellAbility sp = (SpellAbility)spellAbility;
if (!sp.getRootAbility().isTargeting(player)) {
return false;
}
} else if (property.startsWith("controls")) {
// this allows escaping _ with \ in case of complex restrictions (used on Turf War)
List<String> type = new ArrayList<>();

View File

@@ -45,7 +45,6 @@ import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.util.CardTranslation;
import forge.util.Localizer;
import forge.util.TextUtil;
import forge.util.Visitor;
@@ -311,7 +310,7 @@ public class ReplacementHandler {
replacementEffect.getParam("OptionalDecider"), effectSA).get(0);
}
String name = CardTranslation.getTranslatedName(MoreObjects.firstNonNull(host.getRenderForUI() ? host.getCardForUi() : null, host).getName());
String name = MoreObjects.firstNonNull(host.getRenderForUI() ? host.getCardForUi() : null, host).getTranslatedName();
String effectDesc = TextUtil.fastReplace(replacementEffect.getDescription(), "CARDNAME", name);
final String question = runParams.containsKey(AbilityKey.Card)
? Localizer.getInstance().getMessage("lblApplyCardReplacementEffectToCardConfirm", name, runParams.get(AbilityKey.Card).toString(), effectDesc)

View File

@@ -1025,12 +1025,12 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public String getStackDescription() {
String text = getHostCard().getView().getText();
if (stackDescription.equals(text) && !text.isEmpty()) {
return getHostCard().getName() + " - " + text;
return getHostCard().getDisplayName() + " - " + text;
}
if (stackDescription.isEmpty()) {
return "";
}
return TextUtil.fastReplace(stackDescription, "CARDNAME", getHostCard().getName());
return TextUtil.fastReplace(stackDescription, "CARDNAME", getHostCard().getDisplayName());
}
public void setStackDescription(final String s) {
originalStackDescription = s;
@@ -1129,7 +1129,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
desc = TextUtil.fastReplace(desc, "CARDNAME", translatedName);
desc = TextUtil.fastReplace(desc, "NICKNAME", Lang.getInstance().getNickName(translatedName));
if (node.getOriginalHost() != null) {
desc = TextUtil.fastReplace(desc, "ORIGINALHOST", node.getOriginalHost().getName());
desc = TextUtil.fastReplace(desc, "ORIGINALHOST", node.getOriginalHost().getDisplayName());
}
sb.append(desc);
}
@@ -1954,7 +1954,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
resetTargets();
targetChosen.add(card);
setStackDescription(getHostCard().getName() + " - targeting " + card);
setStackDescription(getHostCard().getDisplayName() + " - targeting " + card);
}
/**

View File

@@ -128,14 +128,14 @@ public abstract class Trigger extends TriggerReplacementBase {
desc = TextUtil.fastReplace(desc,"CARDNAME", translatedName);
desc = TextUtil.fastReplace(desc,"NICKNAME", Lang.getInstance().getNickName(translatedName));
if (desc.contains("ORIGINALHOST") && this.getOriginalHost() != null) {
desc = TextUtil.fastReplace(desc, "ORIGINALHOST", this.getOriginalHost().getName());
desc = TextUtil.fastReplace(desc, "ORIGINALHOST", this.getOriginalHost().getDisplayName());
}
}
if (getHostCard().getEffectSource() != null) {
if (active)
desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().toString());
else
desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().getName());
desc = TextUtil.fastReplace(desc, "EFFECTSOURCE", getHostCard().getEffectSource().getDisplayName());
}
sb.append(desc);
if (!this.triggerRemembered.isEmpty()) {
@@ -210,7 +210,7 @@ public abstract class Trigger extends TriggerReplacementBase {
saDesc = saDesc.substring(0, 1).toLowerCase() + saDesc.substring(1);
}
if (saDesc.contains("ORIGINALHOST") && sa.getOriginalHost() != null) {
saDesc = TextUtil.fastReplace(saDesc, "ORIGINALHOST", sa.getOriginalHost().getName());
saDesc = TextUtil.fastReplace(saDesc, "ORIGINALHOST", sa.getOriginalHost().getDisplayName());
}
} else {
saDesc = "<take no action>"; // printed in case nothing is chosen for the ability (e.g. Charm with Up to X)

View File

@@ -21,11 +21,12 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardZoneTable;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Localizer;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
@@ -84,14 +85,13 @@ public class TriggerAbilityTriggered extends Trigger {
return false;
}
if (hasParam("TriggeredOwnAbility") && "True".equals(getParam("TriggeredOwnAbility")) && !Iterables.contains(causes, source)) {
if (hasParam("TriggeredOwnAbility") && !Iterables.contains(causes, source)) {
return false;
}
return true;
}
/** {@inheritDoc} */
@Override
public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) {
@@ -118,14 +118,16 @@ public class TriggerAbilityTriggered extends Trigger {
newRunParams.put(AbilityKey.Cause, ImmutableList.of(runParams.get(AbilityKey.Card)));
} else if (regtrig.getMode() == TriggerType.ChangesZoneAll) {
final CardZoneTable table = (CardZoneTable) runParams.get(AbilityKey.Cards);
Set<String> destinations = new HashSet<>();
for (ZoneType dest : ZoneType.values()) {
if (table.containsColumn(dest) && !table.column(dest).isEmpty()) {
destinations.add(dest.toString());
}
}
newRunParams.put(AbilityKey.Destination, TextUtil.join(destinations, ","));
newRunParams.put(AbilityKey.Destination, StringUtils.join(table.columnKeySet(), ","));
newRunParams.put(AbilityKey.Cause, table.allCards());
} else if (regtrig.getMode() == TriggerType.Attacks) {
newRunParams.put(AbilityKey.Cause, ImmutableList.of(runParams.get(AbilityKey.Attacker)));
} else if (regtrig.getMode() == TriggerType.AttackersDeclared || regtrig.getMode() == TriggerType.AttackersDeclaredOneTarget) {
CardCollection attackers = (CardCollection) runParams.get(AbilityKey.Attackers);
if (regtrig.hasParam("ValidAttackers")) {
attackers = CardLists.getValidCards(attackers, regtrig.getParam("ValidAttackers"), regtrig.getHostCard().getController(), regtrig.getHostCard(), regtrig);
}
newRunParams.put(AbilityKey.Cause, attackers);
}
newRunParams.put(AbilityKey.SpellAbility, sa);

View File

@@ -69,6 +69,12 @@ public class TriggerTaps extends Trigger {
}
}
if (hasParam("FirstTime")) {
if (!(boolean) runParams.get(AbilityKey.FirstTime)) {
return false;
}
}
return true;
}

View File

@@ -84,9 +84,9 @@ public class MessageUtil {
default:
String tgt = mayBeYou(player, target);
if (tgt.equals("(null)")) {
return Localizer.getInstance().getMessage("lblCardEffectValueIs", CardTranslation.getTranslatedName(sa.getHostCard().getName()), value);
return Localizer.getInstance().getMessage("lblCardEffectValueIs", sa.getHostCard().getTranslatedName(), value);
} else {
return Localizer.getInstance().getMessage("lblCardEffectToTargetValueIs", CardTranslation.getTranslatedName(sa.getHostCard().getName()), tgt, value);
return Localizer.getInstance().getMessage("lblCardEffectToTargetValueIs", sa.getHostCard().getTranslatedName(), tgt, value);
}
}
}

View File

@@ -488,7 +488,6 @@
<fileset dir="${basedir}/../forge-gui/release-files/" includes="INSTALLATION.txt" />
<fileset dir="${basedir}/../forge-gui/release-files/" includes="ISSUES.txt" />
<fileset dir="${basedir}/../forge-gui-desktop/target/" includes="CHANGES.txt" />
<fileset dir="${basedir}/../forge-gui/release-files/" includes="GAMEPAD_README.txt" />
<fileset dir="${basedir}/../forge-gui/" includes="MANUAL.txt" />
<fileset dir="${basedir}/../forge-gui-mobile-dev/" includes="sentry.properties" />
<fileset dir="${basedir}/../forge-gui/">

View File

@@ -530,7 +530,6 @@ try {
<fileset dir="${basedir}/../forge-gui/release-files/" includes="CONTRIBUTORS.txt" />
<fileset dir="${basedir}/../forge-gui/release-files/" includes="ISSUES.txt" />
<fileset dir="${basedir}/../forge-gui/release-files/" includes="INSTALLATION.txt" />
<fileset dir="${basedir}/../forge-gui/release-files/" includes="GAMEPAD_README.txt" />
<fileset dir="${basedir}/../forge-gui/" includes="MANUAL.txt" />
<fileset dir="${basedir}/" includes="sentry.properties" />
</copy>

View File

@@ -1514,9 +1514,9 @@ public final class CMatchUI
Set<FullControlFlag> controlFlags = getGameView().getGame().getPlayer(pv).getController().getFullControl();
final String lblFullControl = Localizer.getInstance().getMessage("lblFullControl");
final JPopupMenu menu = new JPopupMenu(lblFullControl);
GuiUtils.addMenuItem(menu, lblFullControl, null, () -> {
FOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblFullControlDetails"), lblFullControl);
});
menu.add(
GuiUtils.createMenuItem("- " + lblFullControl + " -", null, null, false, true)
);
addFullControlEntry(menu, "lblChooseCostOrder", FullControlFlag.ChooseCostOrder, controlFlags);
addFullControlEntry(menu, "lblChooseCostReductionOrder", FullControlFlag.ChooseCostReductionOrderAndVariableAmount, controlFlags);

View File

@@ -27,7 +27,7 @@ import java.util.function.Predicate;
*/
public class RewardData implements Serializable {
private static final long serialVersionUID = 3158932532013393718L;
public String type;
public String type; // TODO convert to enum
public float probability;
public int count;
public int addMaxCount;
@@ -119,6 +119,7 @@ public class RewardData implements Serializable {
return false;
if (Iterables.contains(input.getRules().getMainPart().getKeywords(), "Remove CARDNAME from your deck before playing if you're not playing for ante."))
return false;
// TODO check if commander player
if (input.getRules().getAiHints().getRemNonCommanderDecks())
return false;
if (input.getRules().isCustom() &&
@@ -155,7 +156,6 @@ public class RewardData implements Serializable {
}
public Array<Reward> generate(boolean isForEnemy, Iterable<PaperCard> cards, boolean useSeedlessRandom, boolean isNoSell) {
boolean allCardVariants = Config.instance().getSettingData().useAllCardVariants;
Random rewardRandom = useSeedlessRandom ? new Random() : WorldSave.getCurrentSave().getWorld().getRandom();
//Keep using same generation method for shop rewards, but fully randomize loot drops by not using the instance pre-seeded by the map

View File

@@ -85,7 +85,7 @@ public class AdventureDeckEditor extends FDeckEditor {
}
@Override
public ItemPool<PaperCard> getCardPool(boolean wantUnique) {
public ItemPool<PaperCard> getCardPool() {
ItemPool<PaperCard> pool = new ItemPool<>(PaperCard.class);
pool.addAll(Current.player().getCards());
return pool;
@@ -128,6 +128,8 @@ public class AdventureDeckEditor extends FDeckEditor {
@Override
public boolean isCommanderEditor() {
if (isLimitedEditor())
return false;
if (AdventurePlayer.current().getAdventureMode() == AdventureModes.Commander)
return true;
return super.isCommanderEditor();
@@ -161,7 +163,7 @@ public class AdventureDeckEditor extends FDeckEditor {
}
@Override
public ItemPool<PaperCard> getCardPool(boolean wantUnique) {
public ItemPool<PaperCard> getCardPool() {
return deckToPreview.getAllCardsInASinglePool(true, true);
}

View File

@@ -66,8 +66,8 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
return gameType == null ? null : gameType.getDeckFormat();
}
public ItemPool<PaperCard> getCardPool(boolean wantUnique) {
return wantUnique ? FModel.getUniqueCardsNoAlt() : FModel.getAllCardsNoAlt();
public ItemPool<PaperCard> getCardPool() {
return FModel.getAllCardsNoAlt();
}
protected Predicate<PaperCard> getCardFilter() { return null; }
@@ -172,10 +172,10 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
@Override public boolean hasCommander() { return deckFormat.hasCommander(); }
@Override
public ItemPool<PaperCard> getCardPool(boolean wantUnique) {
public ItemPool<PaperCard> getCardPool() {
if(this.itemPoolSupplier != null)
return itemPoolSupplier.get();
return super.getCardPool(wantUnique);
return super.getCardPool();
}
@Override
@@ -1713,7 +1713,7 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
//Clone the pool to ensure we don't mutate it by adding to or removing from this page.
//Can override this if that behavior is desired.
ItemPool<PaperCard> cardPool = CardPool.createFrom(parentScreen.getEditorConfig().getCardPool(cardManager.getWantUnique()), PaperCard.class);
ItemPool<PaperCard> cardPool = CardPool.createFrom(parentScreen.getEditorConfig().getCardPool(), PaperCard.class);
if(editorConfig.usePlayerInventory() && currentDeck != null) {
//Remove any items from the pool that are in the deck.
@@ -1886,8 +1886,8 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
menu.addItem(new FCheckBoxMenuItem(Forge.getLocalizer().getMessage("lblUniqueCardsOnly"), cardManager.getWantUnique(), e -> {
boolean wantUnique = !cardManager.getWantUnique();
cardManager.setWantUnique(wantUnique);
refresh();
cardManager.getConfig().setUniqueCardsOnly(wantUnique);
cardManager.refresh();
}));
}
}

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