diff --git a/docs/Card-scripting-API/AbilityFactory.md b/docs/Card-scripting-API/AbilityFactory.md index c549d425af7..da6c5439b34 100644 --- a/docs/Card-scripting-API/AbilityFactory.md +++ b/docs/Card-scripting-API/AbilityFactory.md @@ -1,14 +1,18 @@ AbilityFactory parses differently from the Keyword parser. Your Ability line will look more like this: -`A:{AB/SP/DB/ST}$ | {Necessary$ Parameters} | {Separated$ By} | {Pipes$ Here} | [Optional$ Values]` +`A:$ | | ( | ) | [Optional$ {Values} [Nested$ Dependency]]` -In most cases, each AF subclass implements both the Spell and Ability. -Much of the code is shared, so creating the data object will look very similar. +The ability types are: +- **AB** for Activated Abilities +- **SP** for Spell +- **DB** for Drawback and many abilities that are subsidiary to other things, like replacements. They are only used to chain AFs together, and will never be the root AF +- **ST** for Static, this gets used in case the API should resolve without using the stack
(e.g. the unique *Circling Vultures* special action is directly implemented in the script this way) - - **AB** is for Activated Abilities - - **SP** is for Spell - - **DB** is for Drawback and many abilities that are subsidiary to other things, like replacements. They are only used to chain AFs together, and will never be the root AF - - **ST** is for Static, this gets used in case the API should resolve without using the stack
(e.g. the unique *Circling Vultures* special action is directly implemented in the script this way) +Syntax definitions like the above will use different symbols to separate the variable parts from the plaintext: +- angle brackets for mandatory parts +- square brackets for optional parts +- round brackets for grouping parts that are exclusive to each other +- curly brackets to denote the type of a param >*NOTE:* > - these factories are refactored from time to time (often to adapt to new sets), so while some entries could be slightly outdated, the base information should still be correct @@ -20,7 +24,7 @@ Much of the code is shared, so creating the data object will look very similar. ## Cost / UnlessCost -`Cost$ ` is the appropriate way to set the cost of the ability. Currently for spells, any additional costs including the original Mana cost need to appear in the Cost parameter in the AbilityFactory. For each card that uses it, the order in which the cost is paid will always be the same. +`Cost$ {AbilityCost}` is the appropriate way to set the cost of the ability. Currently for spells, any additional costs including the original Mana cost need to appear in the Cost parameter in the AbilityFactory. For each card that uses it, the order in which the cost is paid will always be the same. Secondary abilities such as the DB executed by triggers or replacements (usually) don't need costs. (This is one reason to use DB over AB in these cases.) @@ -515,8 +519,6 @@ Used in the script of *Karn Liberated* ## Goad -## Investigate - ## Mana For lands or other permanent to produce mana. @@ -722,7 +724,13 @@ player chooses (eg: Burning of Xinye, or Imperial Edict). ## StoreSVar -## Token +## Tokens + +### Amass + +### Investigate + +### Token Token simply lets you create tokens of any type. @@ -759,6 +767,8 @@ If possible split the SpellDescription of the effect so the part for the trigger ### ImmediateTrigger +TriggerAmount + ## Turn structure ### AddPhase diff --git a/docs/Card-scripting-API/Card-scripting-API.md b/docs/Card-scripting-API/Card-scripting-API.md index 887245dd471..c18d61fcc15 100644 --- a/docs/Card-scripting-API/Card-scripting-API.md +++ b/docs/Card-scripting-API/Card-scripting-API.md @@ -25,7 +25,7 @@ There are a few other properties that will appear in many cards. These are | Property | Description | - | - -|`A`|[Ability effect](AbilityFactory) +|`A`|[Ability effect](Card-scripting-API/AbilityFactory.md) |`AI`|RemoveDeck:
* `All`
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.
* `Random`
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.
* `NonCommander`
|`Colors`|Color(s) of the card

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.

* `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.

* `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

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.

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. @@ -37,10 +37,10 @@ There are a few other properties that will appear in many cards. These are |`Name`|Name of the card

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.

Example:
* `Name:A Display of My Dark Power` sets the card's name to "A Display of My Dark Power" |`Oracle`|The current Oracle text used by the card.

We actually have a Python Script that runs to be able to fill in this information, so don't worry about manually editing a lot of cards when Wizards decides to change the rules.

This field is used by the Deck Editor to allow non-Legendary Creatures to be marked as potential commanders. Make sure "CARDNAME can be your commander." appears in the oracle text. |`PT`|Power and toughness -|`R`|[Replacement effect](Replacements) -|`S`|[Static ability](static-abilities) +|`R`|[Replacement effect](Card-scripting-API/Replacements.md) +|`S`|[Static ability](Card-scripting-API/Statics.md) |`SVar`|String variable. Used throughout scripting in a handful of different ways. -|`T`|[Triggered ability](Triggers) +|`T`|[Triggered ability](Card-scripting-API/Triggers.md) |`Text`|Additional text that needs to be displayed on the CardDetailPanel that doesn't have any spell/ability that generates a description for it, for example "CARDNAME can be your commander." or "X can't be 0.". |`Types`|Card types and subtypes

Include all card types and subtypes, separated by spaces.

Example:
* `Types:Enchantment Artifact Creature Golem` for a card that reads Enchantment Artifact Creature -- Golem diff --git a/docs/Card-scripting-API/Statics.md b/docs/Card-scripting-API/Statics.md new file mode 100644 index 00000000000..45cf5459cc1 --- /dev/null +++ b/docs/Card-scripting-API/Statics.md @@ -0,0 +1,23 @@ +There are two major groups of static abilities: + +# Statics for the main 7 layers + +Syntax: +`S:Mode$ | | | [Description$ {String}]` + +Here's an example for layer 7c: +`Affected$ Creature.YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Creatures you control get +1/+1.` + +See [StaticAbility.generateLayer()](https://github.com/Card-Forge/forge/blob/master/forge-game/src/main/java/forge/game/staticability/StaticAbility.java) for the full list of params on each Layer. + +*Note:* Layer 1 is currently only implemented as a resolving effect instead. + +# Statics for the concluding "game rules layer" ([CR 613.11](https://yawgatog.com/resources/magic-rules/#R61311)) + +The available effects are defined here: [StaticAbilityMode](https://github.com/Card-Forge/forge/blob/master/forge-game/src/main/java/forge/game/staticability/StaticAbilityMode.java). + +*Note:* some rules-modifying parts are still coded via `Continuous` mode for now, e.g. `SetMaxHandSize$ {Integer}`. + +## Combat + +## Costs \ No newline at end of file diff --git a/docs/_sidebar.md b/docs/_sidebar.md index d703e663d71..27f06fd79e8 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -38,7 +38,7 @@ - [Ability effects](Card-scripting-API/AbilityFactory.md) - [Triggers](Card-scripting-API/Triggers.md) - [Replacements](Card-scripting-API/Replacements.md) - - Statics + - [Statics](Card-scripting-API/Statics.md) - [Costs](Card-scripting-API/Costs.md) - [Affected / Targets](Card-scripting-API/Targeting.md) - [Restrictions / Conditions](Card-scripting-API/Restrictions.md) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 5c559ca8947..7d111bb96d5 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -702,7 +702,7 @@ public class GameAction { eff.addRemembered(copied); // refresh needed for canEnchant checks - game.getAction().checkStaticAbilities(false, Sets.newHashSet(copied), new CardCollection(copied)); + checkStaticAbilities(false, Sets.newHashSet(copied), new CardCollection(copied)); return eff; } private void cleanStaticEffect(Card eff, Card copied) { @@ -809,8 +809,7 @@ public class GameAction { } } - // Move card in maingame if take card from subgame - // 720.4a + // CR 720.4a Move card in maingame if take card from subgame if (zoneFrom != null && zoneFrom.is(ZoneType.Sideboard) && game.getMaingame() != null) { Card maingameCard = c.getOwner().getMappingMaingameCard(c); if (maingameCard != null) { @@ -913,7 +912,7 @@ public class GameAction { final PlayerZone removed = c.getOwner().getZone(ZoneType.Exile); final Card copied = moveTo(removed, c, cause, params); - if (c.isImmutable()) { + if (c.isImmutable()) { return copied; } @@ -1240,7 +1239,7 @@ public class GameAction { } private StaticAbility findStaticAbilityToApply(StaticAbilityLayer layer, List staticsForLayer, CardCollectionView preList, Map affectedPerAbility, - Table> dependencies) { + Table> dependencies) { if (staticsForLayer.size() == 1) { return staticsForLayer.get(0); } @@ -2080,7 +2079,7 @@ public class GameAction { } if (showRevealDialog) { final String message = Localizer.getInstance().getMessage("lblSacrifice"); - game.getAction().reveal(result, ZoneType.Graveyard, c.getOwner(), false, message, false); + reveal(result, ZoneType.Graveyard, c.getOwner(), false, message, false); } } for (Map.Entry> e : lki.asMap().entrySet()) { @@ -2539,19 +2538,9 @@ public class GameAction { game.getTriggerHandler().runTrigger(TriggerType.TakesInitiative, runParams, false); } - // Make scry an action function so that it can be used for mulligans (with a null cause) - // Assumes that the list of players is in APNAP order, which should be the case - // Optional here as well to handle the way that mulligans do the choice - // 701.17. Scry - // 701.17a To "scry N" means to look at the top N cards of your library, then put any number of them - // on the bottom of your library in any order and the rest on top of your library in any order. - // 701.17b If a player is instructed to scry 0, no scry event occurs. Abilities that trigger whenever a - // player scries won't trigger. - // 701.17c If multiple players scry at once, each of those players looks at the top cards of their library - // at the same time. Those players decide in APNAP order (see rule 101.4) where to put those - // cards, then those cards move at the same time. public void scry(final List players, int numScry, SpellAbility cause) { if (numScry <= 0) { + // CR 701.22b If a player is instructed to scry 0, no scry event occurs. return; } @@ -2577,12 +2566,9 @@ public class GameAction { if (playerScry > 0) { actualPlayers.put(p, playerScry); - // reveal the top N library cards to the player (only) - // no real need to separate out the look if - // there is only one player scrying + // no real need to separate out the look if there is only one player scrying if (players.size() > 1) { - final CardCollection topN = new CardCollection(p.getCardsIn(ZoneType.Library, playerScry)); - revealTo(topN, p); + revealTo(p.getCardsIn(ZoneType.Library, playerScry), p); } } } @@ -2623,7 +2609,6 @@ public class GameAction { } if (cause != null) { - // set up triggers (but not actually do them until later) final Map runParams = AbilityKey.mapFromPlayer(p); runParams.put(AbilityKey.ScryNum, numLookedAt); runParams.put(AbilityKey.ScryBottom, toBottom == null ? 0 : toBottom.size()); @@ -2654,7 +2639,7 @@ public class GameAction { if (showRevealDialog) { final String message = Localizer.getInstance().getMessage("lblMilledCards"); final boolean addSuffix = !toZoneStr.isEmpty(); - game.getAction().reveal(milledPlayer, destination, p, false, message, addSuffix); + reveal(milledPlayer, destination, p, false, message, addSuffix); } game.getGameLog().add(GameLogEntryType.ZONE_CHANGE, p + " milled " + Lang.joinHomogenous(milledPlayer) + toZoneStr + "."); @@ -2671,7 +2656,7 @@ public class GameAction { } public void dealDamage(final boolean isCombat, final CardDamageMap damageMap, final CardDamageMap preventMap, - final GameEntityCounterTable counterTable, final SpellAbility cause) { + final GameEntityCounterTable counterTable, final SpellAbility cause) { // Clear assigned damage if is combat if (isCombat) { for (Map.Entry> et : damageMap.columnMap().entrySet()) {