mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 09:48:02 +00:00
Compare commits
3 Commits
Hanmac-pat
...
forge-2.0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72f32e5772 | ||
|
|
43ce790ebe | ||
|
|
b1a3ad1b39 |
40
.github/workflows/maven-publish.yml
vendored
40
.github/workflows/maven-publish.yml
vendored
@@ -81,7 +81,7 @@ jobs:
|
||||
export _JAVA_OPTIONS="-Xmx2g"
|
||||
d=$(date +%m.%d)
|
||||
# build only desktop and only try to move desktop files
|
||||
mvn -U -B clean -P windows-linux install -DskipTests -Dskip.flatten=true -e -T 1C release:clean release:prepare release:perform
|
||||
mvn -U -B clean -P windows-linux install -e -T 1C release:clean release:prepare release:perform -DskipTests
|
||||
mkdir izpack
|
||||
# move bz2 and jar from work dir to izpack dir
|
||||
mv /home/runner/work/forge/forge/forge-installer/*/*.{bz2,jar} izpack/
|
||||
@@ -128,44 +128,6 @@ jobs:
|
||||
removeArtifacts: true
|
||||
makeLatest: true
|
||||
|
||||
- name: 🔧 Install XML tools
|
||||
run: sudo apt-get install -y libxml2-utils
|
||||
|
||||
- name: 🔼 Bump versionCode in root POM
|
||||
id: bump_version
|
||||
run: |
|
||||
cd /home/runner/work/forge/forge/
|
||||
|
||||
current_version=$(xmllint --xpath "//*[local-name()='versionCode']/text()" pom.xml)
|
||||
echo "Current versionCode: $current_version"
|
||||
|
||||
IFS='.' read -r major minor patch <<< "${current_version}"
|
||||
new_patch=$(printf "%02d" $((10#$patch + 1)))
|
||||
new_version="${major}.${minor}.${new_patch}"
|
||||
|
||||
sed -i -E "s|<versionCode>.*</versionCode>|<versionCode>${new_version}</versionCode>|" pom.xml
|
||||
|
||||
echo "version_code=${new_version}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: ♻️ Restore {revision} in child POMs
|
||||
run: |
|
||||
find . -name pom.xml ! -path "./pom.xml" | while read -r pom; do
|
||||
sed -i -E 's|<version>2\.0+\.[0-9]+(-SNAPSHOT)?</version>|<version>${revision}</version>|' "$pom"
|
||||
done
|
||||
|
||||
- name: 💾 Commit restored {revision}
|
||||
run: |
|
||||
# Add only pom.xml files
|
||||
find . -name pom.xml -exec git add {} \;
|
||||
|
||||
# Commit if there are changes
|
||||
if git diff --cached --quiet; then
|
||||
echo "No pom.xml changes to commit."
|
||||
else
|
||||
git commit -m "Restore POM files for preparation of next release" || echo "No changes to commit"
|
||||
git push
|
||||
fi
|
||||
|
||||
- name: Send failure notification to Discord
|
||||
if: failure() # This step runs only if the job fails
|
||||
run: |
|
||||
|
||||
@@ -6,7 +6,7 @@ Dev instructions here: [Getting Started](https://github.com/Card-Forge/forge/wik
|
||||
|
||||
## Requirements / Tools
|
||||
|
||||
- your favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
|
||||
- you favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
|
||||
- Java JDK 17 or later
|
||||
- Git
|
||||
- Git client (optional)
|
||||
@@ -22,41 +22,42 @@ Dev instructions here: [Getting Started](https://github.com/Card-Forge/forge/wik
|
||||
|
||||
- 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
|
||||
|
||||
IntelliJ is the recommended IDE for Forge development. Quick start guide for [setting up the Forge project within IntelliJ](https://github.com/Card-Forge/forge/wiki/IntelliJ-setup).
|
||||
|
||||
|
||||
## Eclipse
|
||||
|
||||
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
|
||||
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.
|
||||
|
||||
### Project Setup
|
||||
|
||||
- Follow the instructions for cloning from GitHub. You'll need to setup an account and your SSH key.
|
||||
- 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.
|
||||
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.
|
||||
- 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.
|
||||
- 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
|
||||
- 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
|
||||
- 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.
|
||||
- Once everything builds, all errors should disappear. You can now advance to Project launch.
|
||||
|
||||
### Project Launch
|
||||
|
||||
@@ -66,15 +67,15 @@ 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!
|
||||
- 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.
|
||||
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!
|
||||
- A view similar to a mobile phone should appear. Enjoy!
|
||||
|
||||
### Eclipse / Android SDK Integration
|
||||
|
||||
@@ -98,7 +99,7 @@ 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
|
||||
@@ -123,11 +124,10 @@ TBD
|
||||
|
||||
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...
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -141,7 +141,7 @@ Card scripting resources are found in the forge-gui/res/ path.
|
||||
|
||||
### Project Hierarchy
|
||||
|
||||
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:
|
||||
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:
|
||||
|
||||
- forge-ai
|
||||
- forge-core
|
||||
@@ -158,38 +158,32 @@ The platform-specific projects are:
|
||||
|
||||
#### forge-ai
|
||||
|
||||
The forge-ai project contains the computer opponent logic for gameplay. It includes decision-making algorithms for specific abilities, cards and turn phases.
|
||||
|
||||
#### forge-core
|
||||
|
||||
The forge-core project contains the core game engine, card mechanics, rules engine, and fundamental game logic. It includes the implementation of Magic: The Gathering rules, card interactions, and the game state management system.
|
||||
|
||||
#### forge-game
|
||||
|
||||
The forge-game project handles the game session management, player interactions, and game flow control. It includes implementations for multiplayer support, game modes, matchmaking, and game state persistence. This module bridges the core game engine with the user interface and networking components.
|
||||
|
||||
#### forge-gui
|
||||
|
||||
The forge-gui project contains the user interface components and rendering logic for the game. It includes the main game window, card displays, player interactions, and the scripting resource definitions in the res/ path.
|
||||
The forge-gui project includes the scripting resource definitions in the res/ path.
|
||||
|
||||
#### forge-gui-android
|
||||
|
||||
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
|
||||
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
|
||||
|
||||
#### forge-gui-desktop
|
||||
|
||||
Java Swing based GUI targeting desktop machines.
|
||||
|
||||
Screen layout and game logic revolving around the GUI is found here. For example, the overlay arrows (when enabled) that indicate attackers and blockers, or the targets of the stack are defined and drawn by this.
|
||||
Screen layout and game logic revolving around the GUI is found here. For example, the overlay arrows (when enabled) that indicate attackers and blockers, or the targets of the stack are defined and drawn by this.
|
||||
|
||||
#### forge-gui-ios
|
||||
|
||||
Libgdx-based backend targeting iOS. Relies on forge-gui-mobile for GUI logic.
|
||||
Libgdx-based backend targeting iOS. Relies on forge-gui-mobile for GUI logic.
|
||||
|
||||
#### forge-gui-mobile
|
||||
|
||||
Mobile GUI game logic utilizing [libgdx](https://libgdx.badlogicgames.com/) library. Screen layout and game logic revolving around the GUI for the mobile platforms is found here.
|
||||
Mobile GUI game logic utilizing [libgdx](https://libgdx.badlogicgames.com/) library. Screen layout and game logic revolving around the GUI for the mobile platforms is found here.
|
||||
|
||||
#### forge-gui-mobile-dev
|
||||
|
||||
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
|
||||
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
|
||||
|
||||
@@ -32,7 +32,6 @@ 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)_
|
||||
- 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.
|
||||
|
||||
---
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.03</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.03</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -37,7 +37,6 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityPredicates;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
|
||||
import forge.game.staticability.StaticAbilityMode;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
@@ -1588,7 +1587,7 @@ public class AiAttackController {
|
||||
// but there are no creatures it can target, no need to exert with it
|
||||
boolean missTarget = false;
|
||||
for (StaticAbility st : c.getStaticAbilities()) {
|
||||
if (!st.checkMode(StaticAbilityMode.OptionalAttackCost)) {
|
||||
if (!"OptionalAttackCost".equals(st.getParam("Mode"))) {
|
||||
continue;
|
||||
}
|
||||
SpellAbility sa = st.getPayingTrigSA();
|
||||
|
||||
@@ -54,7 +54,6 @@ import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityDisableTriggers;
|
||||
import forge.game.staticability.StaticAbilityMode;
|
||||
import forge.game.staticability.StaticAbilityMustTarget;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
@@ -1130,7 +1129,7 @@ public class AiController {
|
||||
// Memory Crystal-like effects need special handling
|
||||
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||
for (StaticAbility s : c.getStaticAbilities()) {
|
||||
if (s.checkMode(StaticAbilityMode.ReduceCost)
|
||||
if ("ReduceCost".equals(s.getParam("Mode"))
|
||||
&& "Spell.Buyback".equals(s.getParam("ValidSpell"))) {
|
||||
neededMana -= AbilityUtils.calculateAmount(c, s.getParam("Amount"), s);
|
||||
}
|
||||
@@ -2369,7 +2368,7 @@ public class AiController {
|
||||
|
||||
// TODO move to more common place
|
||||
public static <T extends TriggerReplacementBase> List<T> filterList(List<T> input, Function<SpellAbility, Object> pred, Object value) {
|
||||
return filterList(input, trb -> trb.ensureAbility() != null && pred.apply(trb.ensureAbility()) == value);
|
||||
return filterList(input, trb -> pred.apply(trb.ensureAbility()) == value);
|
||||
}
|
||||
|
||||
public static List<SpellAbility> filterListByApi(List<SpellAbility> input, ApiType type) {
|
||||
|
||||
@@ -48,7 +48,6 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityMode;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.trigger.WrappedAbility;
|
||||
@@ -1459,14 +1458,15 @@ public class ComputerUtil {
|
||||
// check for Continuous abilities that grant Haste
|
||||
for (final Card c : all) {
|
||||
for (StaticAbility stAb : c.getStaticAbilities()) {
|
||||
if (stAb.checkMode(StaticAbilityMode.Continuous) && stAb.hasParam("AddKeyword")
|
||||
&& stAb.getParam("AddKeyword").contains("Haste")) {
|
||||
Map<String, String> params = stAb.getMapParams();
|
||||
if ("Continuous".equals(params.get("Mode")) && params.containsKey("AddKeyword")
|
||||
&& params.get("AddKeyword").contains("Haste")) {
|
||||
|
||||
if (c.isEquipment() && c.getEquipping() == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final String affected = stAb.getParam("Affected");
|
||||
final String affected = params.get("Affected");
|
||||
if (affected.contains("Creature.YouCtrl")
|
||||
|| affected.contains("Other+YouCtrl")) {
|
||||
return true;
|
||||
@@ -1519,10 +1519,11 @@ public class ComputerUtil {
|
||||
|
||||
for (final Card c : opp) {
|
||||
for (StaticAbility stAb : c.getStaticAbilities()) {
|
||||
if (stAb.checkMode(StaticAbilityMode.Continuous) && stAb.hasParam("AddKeyword")
|
||||
&& stAb.getParam("AddKeyword").contains("Haste")) {
|
||||
Map<String, String> params = stAb.getMapParams();
|
||||
if ("Continuous".equals(params.get("Mode")) && params.containsKey("AddKeyword")
|
||||
&& params.get("AddKeyword").contains("Haste")) {
|
||||
|
||||
final ArrayList<String> affected = Lists.newArrayList(stAb.getParam("Affected").split(","));
|
||||
final ArrayList<String> affected = Lists.newArrayList(params.get("Affected").split(","));
|
||||
if (affected.contains("Creature")) {
|
||||
return true;
|
||||
}
|
||||
@@ -2428,7 +2429,7 @@ public class ComputerUtil {
|
||||
// Are we picking a type to reduce costs for that type?
|
||||
boolean reducingCost = false;
|
||||
for (StaticAbility s : sa.getHostCard().getStaticAbilities()) {
|
||||
if (s.checkMode(StaticAbilityMode.ReduceCost) && "Card.ChosenType".equals(s.getParam("ValidCard"))) {
|
||||
if ("ReduceCost".equals(s.getParam("Mode")) && "Card.ChosenType".equals(s.getParam("ValidCard"))) {
|
||||
reducingCost = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementLayer;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityMode;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.zone.MagicStack;
|
||||
import forge.game.zone.ZoneType;
|
||||
@@ -692,8 +691,6 @@ public class ComputerUtilCard {
|
||||
public static boolean canBeBlockedProfitably(final Player ai, Card attacker, boolean checkingOther) {
|
||||
AiBlockController aiBlk = new AiBlockController(ai, checkingOther);
|
||||
Combat combat = new Combat(ai);
|
||||
// avoid removing original attacker
|
||||
attacker.setCombatLKI(null);
|
||||
combat.addAttacker(attacker, ai);
|
||||
final List<Card> attackers = Lists.newArrayList(attacker);
|
||||
aiBlk.assignBlockersGivenAttackers(combat, attackers);
|
||||
@@ -1214,7 +1211,8 @@ public class ComputerUtilCard {
|
||||
// if this thing is both owned and controlled by an opponent and it has a continuous ability,
|
||||
// assume it either benefits the player or disrupts the opponent
|
||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||
if (stAb.checkMode(StaticAbilityMode.Continuous) && stAb.isIntrinsic()) {
|
||||
final Map<String, String> params = stAb.getMapParams();
|
||||
if (params.get("Mode").equals("Continuous") && stAb.isIntrinsic()) {
|
||||
priority = true;
|
||||
break;
|
||||
}
|
||||
@@ -1245,16 +1243,17 @@ public class ComputerUtilCard {
|
||||
}
|
||||
} else {
|
||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||
final Map<String, String> params = stAb.getMapParams();
|
||||
//continuous buffs
|
||||
if (stAb.checkMode(StaticAbilityMode.Continuous) && "Creature.YouCtrl".equals(stAb.getParam("Affected"))) {
|
||||
if (params.get("Mode").equals("Continuous") && "Creature.YouCtrl".equals(params.get("Affected"))) {
|
||||
int bonusPT = 0;
|
||||
if (stAb.hasParam("AddPower")) {
|
||||
bonusPT += AbilityUtils.calculateAmount(c, stAb.getParam("AddPower"), stAb);
|
||||
if (params.containsKey("AddPower")) {
|
||||
bonusPT += AbilityUtils.calculateAmount(c, params.get("AddPower"), stAb);
|
||||
}
|
||||
if (stAb.hasParam("AddToughness")) {
|
||||
bonusPT += AbilityUtils.calculateAmount(c, stAb.getParam("AddPower"), stAb);
|
||||
if (params.containsKey("AddToughness")) {
|
||||
bonusPT += AbilityUtils.calculateAmount(c, params.get("AddPower"), stAb);
|
||||
}
|
||||
String kws = stAb.getParam("AddKeyword");
|
||||
String kws = params.get("AddKeyword");
|
||||
if (kws != null) {
|
||||
bonusPT += 4 * (1 + StringUtils.countMatches(kws, "&")); //treat each added keyword as a +2/+2 for now
|
||||
}
|
||||
@@ -1785,7 +1784,7 @@ public class ComputerUtilCard {
|
||||
// remove old boost that might be copied
|
||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||
vCard.removePTBoost(c.getLayerTimestamp(), stAb.getId());
|
||||
if (!stAb.checkMode(StaticAbilityMode.Continuous)) {
|
||||
if (!stAb.checkMode("Continuous")) {
|
||||
continue;
|
||||
}
|
||||
if (!stAb.hasParam("Affected")) {
|
||||
|
||||
@@ -39,7 +39,6 @@ import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
|
||||
import forge.game.staticability.StaticAbilityMode;
|
||||
import forge.game.staticability.StaticAbilityMustAttack;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
@@ -901,7 +900,7 @@ public class ComputerUtilCombat {
|
||||
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
||||
for (final Card card : cardList) {
|
||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||
if (!stAb.checkMode(StaticAbilityMode.Continuous)) {
|
||||
if (!stAb.checkMode("Continuous")) {
|
||||
continue;
|
||||
}
|
||||
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("blocking")) {
|
||||
@@ -1197,7 +1196,7 @@ public class ComputerUtilCombat {
|
||||
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
||||
for (final Card card : cardList) {
|
||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||
if (!stAb.checkMode(StaticAbilityMode.Continuous)) {
|
||||
if (!stAb.checkMode("Continuous")) {
|
||||
continue;
|
||||
}
|
||||
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("attacking")) {
|
||||
@@ -1388,7 +1387,7 @@ public class ComputerUtilCombat {
|
||||
final CardCollectionView cardList = game.getCardsIn(ZoneType.Battlefield);
|
||||
for (final Card card : cardList) {
|
||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||
if (!stAb.checkMode(StaticAbilityMode.Continuous)) {
|
||||
if (!"Continuous".equals(stAb.getParam("Mode"))) {
|
||||
continue;
|
||||
}
|
||||
if (!stAb.hasParam("Affected")) {
|
||||
@@ -1735,7 +1734,6 @@ public class ComputerUtilCombat {
|
||||
final int attackerLife = getDamageToKill(attacker, false)
|
||||
+ predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities, withoutAttackerStaticAbilities);
|
||||
|
||||
// AI should be less worried about Deathtouch
|
||||
if (blocker.hasDoubleStrike()) {
|
||||
if (defenderDamage > 0 && (hasKeyword(blocker, "Deathtouch", withoutAbilities, combat) || attacker.hasSVar("DestroyWhenDamaged"))) {
|
||||
return true;
|
||||
@@ -1965,7 +1963,6 @@ public class ComputerUtilCombat {
|
||||
final int attackerLife = getDamageToKill(attacker, false)
|
||||
+ predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities, withoutAttackerStaticAbilities);
|
||||
|
||||
// AI should be less worried about deathtouch
|
||||
if (attacker.hasDoubleStrike()) {
|
||||
if (attackerDamage >= defenderLife) {
|
||||
return true;
|
||||
|
||||
@@ -608,7 +608,7 @@ public class ComputerUtilCost {
|
||||
}
|
||||
|
||||
return ComputerUtilMana.canPayManaCost(cost, sa, player, extraManaNeeded, effect)
|
||||
&& CostPayment.canPayAdditionalCosts(cost, sa, effect, player);
|
||||
&& CostPayment.canPayAdditionalCosts(cost, sa, effect);
|
||||
}
|
||||
|
||||
public static Set<String> getAvailableManaColors(Player ai, Card additionalLand) {
|
||||
|
||||
@@ -158,7 +158,7 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
// Mana abilities on the same card
|
||||
String shardMana = shard.toShortString();
|
||||
String shardMana = shard.toString().replaceAll("\\{", "").replaceAll("\\}", "");
|
||||
|
||||
boolean payWithAb1 = ability1.getManaPart().mana(ability1).contains(shardMana);
|
||||
boolean payWithAb2 = ability2.getManaPart().mana(ability2).contains(shardMana);
|
||||
|
||||
@@ -13,7 +13,6 @@ import forge.card.mana.ManaAtom;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.DetachedCardEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
@@ -1306,10 +1305,10 @@ public abstract class GameState {
|
||||
} else if (info.startsWith("FaceDown")) {
|
||||
c.turnFaceDown(true);
|
||||
if (info.endsWith("Manifested")) {
|
||||
c.setManifested(new SpellAbility.EmptySa(ApiType.Manifest, c));
|
||||
c.setManifested(true);
|
||||
}
|
||||
if (info.endsWith("Cloaked")) {
|
||||
c.setCloaked(new SpellAbility.EmptySa(ApiType.Cloak, c));
|
||||
c.setCloaked(true);
|
||||
}
|
||||
} else if (info.startsWith("Transformed")) {
|
||||
c.setState(CardStateName.Transformed, true);
|
||||
@@ -1409,7 +1408,7 @@ public abstract class GameState {
|
||||
} else if (info.equals("Foretold")) {
|
||||
c.setForetold(true);
|
||||
c.turnFaceDown(true);
|
||||
c.addMayLookFaceDownExile(c.getOwner());
|
||||
c.addMayLookTemp(c.getOwner());
|
||||
} else if (info.equals("ForetoldThisTurn")) {
|
||||
c.setTurnInZone(turn);
|
||||
} else if (info.equals("IsToken")) {
|
||||
|
||||
@@ -15,7 +15,6 @@ import forge.game.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.ability.effects.RollDiceEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
@@ -746,30 +745,6 @@ public class PlayerControllerAi extends PlayerController {
|
||||
return Aggregates.random(rolls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> chooseDiceToReroll(List<Integer> rolls) {
|
||||
//TODO create AI logic for this
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer chooseRollToModify(List<Integer> rolls) {
|
||||
//TODO create AI logic for this
|
||||
return Aggregates.random(rolls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RollDiceEffect.DieRollResult chooseRollToSwap(List<RollDiceEffect.DieRollResult> rolls) {
|
||||
//TODO create AI logic for this
|
||||
return Aggregates.random(rolls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseRollSwapValue(List<String> swapChoices, Integer currentResult, int power, int toughness) {
|
||||
//TODO create AI logic for this
|
||||
return Aggregates.random(swapChoices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mulliganKeepHand(Player firstPlayer, int cardsToReturn) {
|
||||
return !ComputerUtil.wantMulligan(player, cardsToReturn);
|
||||
@@ -1232,11 +1207,6 @@ public class PlayerControllerAi extends PlayerController {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean payCostDuringRoll(final Cost cost, final SpellAbility sa, final FCollectionView<Player> allPayers) {
|
||||
// TODO logic for AI to pay rerolls and modification costs
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orderAndPlaySimultaneousSa(List<SpellAbility> activePlayerSAs) {
|
||||
for (final SpellAbility sa : getAi().orderPlaySa(activePlayerSAs)) {
|
||||
|
||||
@@ -359,28 +359,18 @@ public class SpecialCardAi {
|
||||
private static final int demonSacThreshold = Integer.MAX_VALUE; // if we're in dire conditions, sac everything from worst to best hoping to find an answer
|
||||
|
||||
public static boolean considerSacrificingCreature(final Player ai, final SpellAbility sa) {
|
||||
Card c = sa.getHostCard();
|
||||
|
||||
// Only check for sacrifice if it's the owner's turn, and it can attack.
|
||||
// TODO: Maybe check if sacrificing a creature allows AI to kill the opponent with the rest on their turn?
|
||||
if (!CombatUtil.canAttack(c) ||
|
||||
!ai.getGame().getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CardCollection flyingCreatures = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
|
||||
CardPredicates.UNTAPPED.and(
|
||||
CardPredicates.hasKeyword(Keyword.FLYING).or(CardPredicates.hasKeyword(Keyword.REACH))));
|
||||
CardPredicates.UNTAPPED.and(
|
||||
CardPredicates.hasKeyword(Keyword.FLYING).or(CardPredicates.hasKeyword(Keyword.REACH))));
|
||||
boolean hasUsefulBlocker = false;
|
||||
|
||||
for (Card fc : flyingCreatures) {
|
||||
if (!ComputerUtilCard.isUselessCreature(ai, fc)) {
|
||||
for (Card c : flyingCreatures) {
|
||||
if (!ComputerUtilCard.isUselessCreature(ai, c)) {
|
||||
hasUsefulBlocker = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ai.getLife() <= c.getNetPower() && !hasUsefulBlocker;
|
||||
return ai.getLife() <= sa.getHostCard().getNetPower() && !hasUsefulBlocker;
|
||||
}
|
||||
|
||||
public static int getSacThreshold() {
|
||||
@@ -1479,7 +1469,6 @@ public class SpecialCardAi {
|
||||
if (best != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(best);
|
||||
sa.setXManaCostPaid(best.getCMC());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityContinuous;
|
||||
import forge.game.staticability.StaticAbilityLayer;
|
||||
import forge.game.staticability.StaticAbilityMode;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.FileSection;
|
||||
import forge.util.collect.FCollectionView;
|
||||
@@ -563,7 +562,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
CardTraitChanges traits = card.getChangedCardTraits().get(timestamp, 0);
|
||||
if (traits != null) {
|
||||
for (StaticAbility stAb : traits.getStaticAbilities()) {
|
||||
if (stAb.checkMode(StaticAbilityMode.Continuous)) {
|
||||
if ("Continuous".equals(stAb.getParam("Mode"))) {
|
||||
for (final StaticAbilityLayer layer : stAb.getLayers()) {
|
||||
StaticAbilityContinuous.applyContinuousAbility(stAb, new CardCollection(card), layer);
|
||||
}
|
||||
|
||||
@@ -15,23 +15,20 @@ import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostSacrifice;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.replacement.ReplacementLayer;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
||||
import forge.game.staticability.StaticAbilityMode;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -75,7 +72,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attach spells always have a target
|
||||
@@ -133,7 +130,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
int power = 0, toughness = 0;
|
||||
List<String> keywords = Lists.newArrayList();
|
||||
for (StaticAbility stAb : source.getStaticAbilities()) {
|
||||
if (stAb.checkMode(StaticAbilityMode.Continuous)) {
|
||||
if ("Continuous".equals(stAb.getParam("Mode"))) {
|
||||
if (stAb.hasParam("AddPower")) {
|
||||
power += AbilityUtils.calculateAmount(source, stAb.getParam("AddPower"), stAb);
|
||||
}
|
||||
@@ -310,8 +307,9 @@ public class AttachAi extends SpellAbilityAi {
|
||||
String type = "";
|
||||
|
||||
for (final StaticAbility stAb : attachSource.getStaticAbilities()) {
|
||||
if (stAb.checkMode(StaticAbilityMode.Continuous) && stAb.hasParam("AddType")) {
|
||||
type = stAb.getParam("AddType");
|
||||
final Map<String, String> stab = stAb.getMapParams();
|
||||
if (stab.get("Mode").equals("Continuous") && stab.containsKey("AddType")) {
|
||||
type = stab.get("AddType");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,39 +371,9 @@ public class AttachAi extends SpellAbilityAi {
|
||||
*/
|
||||
private static Card attachAIKeepTappedPreference(final SpellAbility sa, final List<Card> list, final boolean mandatory, final Card attachSource) {
|
||||
// AI For Cards like Paralyzing Grasp and Glimmerdust Nap
|
||||
|
||||
// check for ETB Trigger
|
||||
boolean tapETB = isAuraSpell(sa) && attachSource.getTriggers().anyMatch(t -> {
|
||||
if (t.getMode() != TriggerType.ChangesZone) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ZoneType.Battlefield.toString().equals(t.getParam("Destination"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (t.hasParam("ValidCard") && !t.getParam("ValidCard").contains("Self")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SpellAbility tSa = t.ensureAbility();
|
||||
if (tSa == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ApiType.Tap.equals(tSa.getApi())) {
|
||||
return false;
|
||||
}
|
||||
if (!"Enchanted".equals(tSa.getParam("Defined"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
final List<Card> prefList = CardLists.filter(list, c -> {
|
||||
// Don't do Untapped Vigilance cards
|
||||
if (!tapETB && c.isCreature() && c.hasKeyword(Keyword.VIGILANCE) && c.isUntapped()) {
|
||||
if (c.isCreature() && c.hasKeyword(Keyword.VIGILANCE) && c.isUntapped()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -420,9 +388,20 @@ public class AttachAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// already affected
|
||||
if (!c.canUntap(c.getController(), true)) {
|
||||
return false;
|
||||
|
||||
if (!c.isEnchanted()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final Iterable<Card> auras = c.getEnchantedBy();
|
||||
for (Card aura : auras) {
|
||||
SpellAbility auraSA = aura.getSpells().get(0);
|
||||
if (auraSA.getApi() == ApiType.Attach) {
|
||||
if ("KeepTapped".equals(auraSA.getParam("AILogic"))) {
|
||||
// Don't attach multiple KeepTapped Auras to one card
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -507,29 +486,29 @@ public class AttachAi extends SpellAbilityAi {
|
||||
*/
|
||||
private static Card attachAIAnimatePreference(final SpellAbility sa, final List<Card> list, final boolean mandatory,
|
||||
final Card attachSource) {
|
||||
if (list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Card card = null;
|
||||
if (list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Card card = null;
|
||||
// AI For choosing a Card to Animate.
|
||||
List<Card> betterList = CardLists.getNotType(list, "Creature");
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Animate Artifact")) {
|
||||
betterList = CardLists.filter(betterList, c -> c.getCMC() > 0);
|
||||
card = ComputerUtilCard.getMostExpensivePermanentAI(betterList);
|
||||
} else {
|
||||
List<Card> evenBetterList = CardLists.filter(betterList, c -> c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.hasKeyword(Keyword.HEXPROOF));
|
||||
if (!evenBetterList.isEmpty()) {
|
||||
betterList = evenBetterList;
|
||||
}
|
||||
evenBetterList = CardLists.filter(betterList, CardPredicates.UNTAPPED);
|
||||
if (!evenBetterList.isEmpty()) {
|
||||
betterList = evenBetterList;
|
||||
}
|
||||
evenBetterList = CardLists.filter(betterList, c -> c.getTurnInZone() != c.getGame().getPhaseHandler().getTurn());
|
||||
if (!evenBetterList.isEmpty()) {
|
||||
betterList = evenBetterList;
|
||||
}
|
||||
evenBetterList = CardLists.filter(betterList, c -> {
|
||||
List<Card> evenBetterList = CardLists.filter(betterList, c -> c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.hasKeyword(Keyword.HEXPROOF));
|
||||
if (!evenBetterList.isEmpty()) {
|
||||
betterList = evenBetterList;
|
||||
}
|
||||
evenBetterList = CardLists.filter(betterList, CardPredicates.UNTAPPED);
|
||||
if (!evenBetterList.isEmpty()) {
|
||||
betterList = evenBetterList;
|
||||
}
|
||||
evenBetterList = CardLists.filter(betterList, c -> c.getTurnInZone() != c.getGame().getPhaseHandler().getTurn());
|
||||
if (!evenBetterList.isEmpty()) {
|
||||
betterList = evenBetterList;
|
||||
}
|
||||
evenBetterList = CardLists.filter(betterList, c -> {
|
||||
for (final SpellAbility sa1 : c.getSpellAbilities()) {
|
||||
if (sa1.isAbility() && sa1.getPayCosts().hasTapCost()) {
|
||||
return false;
|
||||
@@ -537,10 +516,10 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!evenBetterList.isEmpty()) {
|
||||
betterList = evenBetterList;
|
||||
}
|
||||
card = ComputerUtilCard.getWorstAI(betterList);
|
||||
if (!evenBetterList.isEmpty()) {
|
||||
betterList = evenBetterList;
|
||||
}
|
||||
card = ComputerUtilCard.getWorstAI(betterList);
|
||||
}
|
||||
|
||||
|
||||
@@ -570,46 +549,28 @@ public class AttachAi extends SpellAbilityAi {
|
||||
final Card attachSource) {
|
||||
// AI For choosing a Card to Animate.
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
Card attachSourceLki = null;
|
||||
for (Trigger t : attachSource.getTriggers()) {
|
||||
if (!t.getMode().equals(TriggerType.ChangesZone)) {
|
||||
continue;
|
||||
}
|
||||
if (!"Battlefield".equals(t.getParam("Destination"))) {
|
||||
continue;
|
||||
}
|
||||
if (!"Card.Self".equals(t.getParam("ValidCard"))) {
|
||||
continue;
|
||||
}
|
||||
SpellAbility trigSa = t.ensureAbility();
|
||||
SpellAbility animateSa = trigSa.findSubAbilityByType(ApiType.Animate);
|
||||
if (animateSa == null) {
|
||||
continue;
|
||||
}
|
||||
animateSa.setActivatingPlayer(sa.getActivatingPlayer());
|
||||
attachSourceLki = AnimateAi.becomeAnimated(attachSource, animateSa);
|
||||
}
|
||||
if (attachSourceLki == null) {
|
||||
return null;
|
||||
}
|
||||
final Card attachSourceLki = CardCopyService.getLKICopy(attachSource);
|
||||
attachSourceLki.setLastKnownZone(ai.getZone(ZoneType.Battlefield));
|
||||
final Card finalAttachSourceLki = attachSourceLki;
|
||||
// Suppress original attach Spell to replace it with another
|
||||
attachSourceLki.getFirstAttachSpell().setSuppressed(true);
|
||||
|
||||
//TODO for Reanimate Auras i need the new Attach Spell, in later versions it might be part of the Enchant Keyword
|
||||
attachSourceLki.addSpellAbility(AbilityFactory.getAbility(attachSourceLki, "NewAttach"));
|
||||
List<Card> betterList = CardLists.filter(list, c -> {
|
||||
final Card lki = CardCopyService.getLKICopy(c);
|
||||
// need to fake it as if lki would be on the battlefield
|
||||
lki.setLastKnownZone(ai.getZone(ZoneType.Battlefield));
|
||||
|
||||
// Reanimate Auras use "Enchant creature put onto the battlefield with CARDNAME" with Remembered
|
||||
finalAttachSourceLki.clearRemembered();
|
||||
finalAttachSourceLki.addRemembered(lki);
|
||||
attachSourceLki.clearRemembered();
|
||||
attachSourceLki.addRemembered(lki);
|
||||
|
||||
// need to check what the cards would be on the battlefield
|
||||
// do not attach yet, that would cause Events
|
||||
CardCollection preList = new CardCollection(lki);
|
||||
preList.add(finalAttachSourceLki);
|
||||
preList.add(attachSourceLki);
|
||||
c.getGame().getAction().checkStaticAbilities(false, Sets.newHashSet(preList), preList);
|
||||
boolean result = lki.canBeAttached(finalAttachSourceLki, null);
|
||||
boolean result = lki.canBeAttached(attachSourceLki, null);
|
||||
|
||||
//reset static abilities
|
||||
c.getGame().getAction().checkStaticAbilities(false);
|
||||
@@ -834,45 +795,27 @@ public class AttachAi extends SpellAbilityAi {
|
||||
int totPower = 0;
|
||||
final List<String> keywords = new ArrayList<>();
|
||||
|
||||
boolean cantAttack = false;
|
||||
boolean cantBlock = false;
|
||||
|
||||
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
||||
if (stAbility.checkMode(StaticAbilityMode.CantAttack)) {
|
||||
String valid = stAbility.getParam("ValidCard");
|
||||
if (valid.contains(stCheck) || valid.contains("AttachedBy")) {
|
||||
cantAttack = true;
|
||||
}
|
||||
} else if (stAbility.checkMode(StaticAbilityMode.CantBlock)) {
|
||||
String valid = stAbility.getParam("ValidCard");
|
||||
if (valid.contains(stCheck) || valid.contains("AttachedBy")) {
|
||||
cantBlock = true;
|
||||
}
|
||||
} else if (stAbility.checkMode(StaticAbilityMode.CantBlockBy)) {
|
||||
String valid = stAbility.getParam("ValidBlocker");
|
||||
if (valid.contains(stCheck) || valid.contains("AttachedBy")) {
|
||||
cantBlock = true;
|
||||
}
|
||||
}
|
||||
final Map<String, String> stabMap = stAbility.getMapParams();
|
||||
|
||||
if (!stAbility.checkMode(StaticAbilityMode.Continuous)) {
|
||||
if (!stabMap.get("Mode").equals("Continuous")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String affected = stAbility.getParam("Affected");
|
||||
final String affected = stabMap.get("Affected");
|
||||
|
||||
if (affected == null) {
|
||||
continue;
|
||||
}
|
||||
if ((affected.contains(stCheck) || affected.contains("AttachedBy"))) {
|
||||
totToughness += AbilityUtils.calculateAmount(attachSource, stAbility.getParam("AddToughness"), sa);
|
||||
totPower += AbilityUtils.calculateAmount(attachSource, stAbility.getParam("AddPower"), sa);
|
||||
totToughness += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddToughness"), sa);
|
||||
totPower += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddPower"), sa);
|
||||
|
||||
String kws = stAbility.getParam("AddKeyword");
|
||||
String kws = stabMap.get("AddKeyword");
|
||||
if (kws != null) {
|
||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
||||
}
|
||||
kws = stAbility.getParam("AddHiddenKeyword");
|
||||
kws = stabMap.get("AddHiddenKeyword");
|
||||
if (kws != null) {
|
||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
||||
}
|
||||
@@ -908,12 +851,6 @@ public class AttachAi extends SpellAbilityAi {
|
||||
prefList = CardLists.filter(prefList, c -> c.getNetPower() > 0 && ComputerUtilCombat.canAttackNextTurn(c));
|
||||
}
|
||||
|
||||
if (cantAttack) {
|
||||
prefList = CardLists.filter(prefList, c -> c.isCreature() && ComputerUtilCombat.canAttackNextTurn(c));
|
||||
} else if (cantBlock) { // TODO better can block filter?
|
||||
prefList = CardLists.filter(prefList, c -> c.isCreature() && !ComputerUtilCard.isUselessCreature(ai, c));
|
||||
}
|
||||
|
||||
//some auras aren't useful in multiples
|
||||
if (attachSource.hasSVar("NonStackingAttachEffect")) {
|
||||
prefList = CardLists.filter(prefList,
|
||||
@@ -988,10 +925,6 @@ public class AttachAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isAuraSpell(final SpellAbility sa) {
|
||||
return sa.isSpell() && sa.getHostCard().isAura();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach preference.
|
||||
*
|
||||
@@ -1007,23 +940,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
*/
|
||||
private static boolean attachPreference(final SpellAbility sa, final TargetRestrictions tgt, final boolean mandatory) {
|
||||
GameObject o;
|
||||
boolean spellCanTargetPlayer = false;
|
||||
if (isAuraSpell(sa)) {
|
||||
Card source = sa.getHostCard();
|
||||
if (!source.hasKeyword(Keyword.ENCHANT)) {
|
||||
return false;
|
||||
}
|
||||
for (KeywordInterface ki : source.getKeywords(Keyword.ENCHANT)) {
|
||||
String ko = ki.getOriginal();
|
||||
String m[] = ko.split(":");
|
||||
String v = m[1];
|
||||
if (v.contains("Player") || v.contains("Opponent")) {
|
||||
spellCanTargetPlayer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tgt.canTgtPlayer() && (!isAuraSpell(sa) || spellCanTargetPlayer)) {
|
||||
if (tgt.canTgtPlayer()) {
|
||||
List<Player> targetable = new ArrayList<>();
|
||||
for (final Player player : sa.getHostCard().getGame().getPlayers()) {
|
||||
if (sa.canTarget(player)) {
|
||||
@@ -1088,8 +1005,9 @@ public class AttachAi extends SpellAbilityAi {
|
||||
CardCollection toRemove = new CardCollection();
|
||||
for (Trigger t : attachSource.getTriggers()) {
|
||||
if (t.getMode() == TriggerType.ChangesZone) {
|
||||
if ("Card.Self".equals(t.getParam("ValidCard"))
|
||||
&& "Battlefield".equals(t.getParam("Destination"))) {
|
||||
final Map<String, String> params = t.getMapParams();
|
||||
if ("Card.Self".equals(params.get("ValidCard"))
|
||||
&& "Battlefield".equals(params.get("Destination"))) {
|
||||
SpellAbility trigSa = t.ensureAbility();
|
||||
if (trigSa != null && trigSa.getApi() == ApiType.DealDamage && "Enchanted".equals(trigSa.getParam("Defined"))) {
|
||||
for (Card target : list) {
|
||||
@@ -1126,17 +1044,17 @@ public class AttachAi extends SpellAbilityAi {
|
||||
// Probably want to "weight" the list by amount of Enchantments and
|
||||
// choose the "lightest"
|
||||
|
||||
List<Card> betterList = CardLists.filter(magnetList, c -> CombatUtil.canAttack(c, ai.getWeakestOpponent()));
|
||||
if (!betterList.isEmpty()) {
|
||||
return ComputerUtilCard.getBestAI(betterList);
|
||||
}
|
||||
List<Card> betterList = CardLists.filter(magnetList, c -> CombatUtil.canAttack(c, ai.getWeakestOpponent()));
|
||||
if (!betterList.isEmpty()) {
|
||||
return ComputerUtilCard.getBestAI(betterList);
|
||||
}
|
||||
|
||||
// Magnet List should not be attached when they are useless
|
||||
betterList = CardLists.filter(magnetList, c -> !ComputerUtilCard.isUselessCreature(ai, c));
|
||||
// Magnet List should not be attached when they are useless
|
||||
betterList = CardLists.filter(magnetList, c -> !ComputerUtilCard.isUselessCreature(ai, c));
|
||||
|
||||
if (!betterList.isEmpty()) {
|
||||
return ComputerUtilCard.getBestAI(betterList);
|
||||
}
|
||||
if (!betterList.isEmpty()) {
|
||||
return ComputerUtilCard.getBestAI(betterList);
|
||||
}
|
||||
|
||||
//return ComputerUtilCard.getBestAI(magnetList);
|
||||
}
|
||||
@@ -1149,27 +1067,29 @@ public class AttachAi extends SpellAbilityAi {
|
||||
boolean grantingExtraBlock = false;
|
||||
|
||||
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
||||
if (!stAbility.checkMode(StaticAbilityMode.Continuous)) {
|
||||
final Map<String, String> stabMap = stAbility.getMapParams();
|
||||
|
||||
if (!"Continuous".equals(stabMap.get("Mode"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String affected = stAbility.getParam("Affected");
|
||||
final String affected = stabMap.get("Affected");
|
||||
|
||||
if (affected == null) {
|
||||
continue;
|
||||
}
|
||||
if (affected.contains(stCheck) || affected.contains("AttachedBy")) {
|
||||
totToughness += AbilityUtils.calculateAmount(attachSource, stAbility.getParam("AddToughness"), stAbility);
|
||||
totPower += AbilityUtils.calculateAmount(attachSource, stAbility.getParam("AddPower"), stAbility);
|
||||
totToughness += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddToughness"), stAbility);
|
||||
totPower += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddPower"), stAbility);
|
||||
|
||||
grantingAbilities |= stAbility.hasParam("AddAbility");
|
||||
grantingExtraBlock |= stAbility.hasParam("CanBlockAmount") || stAbility.hasParam("CanBlockAny");
|
||||
grantingAbilities |= stabMap.containsKey("AddAbility");
|
||||
grantingExtraBlock |= stabMap.containsKey("CanBlockAmount") || stabMap.containsKey("CanBlockAny");
|
||||
|
||||
String kws = stAbility.getParam("AddKeyword");
|
||||
String kws = stabMap.get("AddKeyword");
|
||||
if (kws != null) {
|
||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
||||
}
|
||||
kws = stAbility.getParam("AddHiddenKeyword");
|
||||
kws = stabMap.get("AddHiddenKeyword");
|
||||
if (kws != null) {
|
||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
||||
}
|
||||
@@ -1223,13 +1143,13 @@ public class AttachAi extends SpellAbilityAi {
|
||||
prefList = ComputerUtil.getSafeTargets(ai, sa, prefList);
|
||||
|
||||
if (attachSource.isAura()) {
|
||||
if (!attachSource.getName().equals("Daybreak Coronet")) {
|
||||
// TODO For Auras like Rancor, that aren't as likely to lead to
|
||||
// card disadvantage, this check should be skipped
|
||||
prefList = CardLists.filter(prefList, c -> !c.isEnchanted() || c.hasKeyword(Keyword.HEXPROOF));
|
||||
}
|
||||
if (!attachSource.getName().equals("Daybreak Coronet")) {
|
||||
// TODO For Auras like Rancor, that aren't as likely to lead to
|
||||
// card disadvantage, this check should be skipped
|
||||
prefList = CardLists.filter(prefList, c -> !c.isEnchanted() || c.hasKeyword(Keyword.HEXPROOF));
|
||||
}
|
||||
|
||||
// should not attach Auras to creatures that does leave the play
|
||||
// should not attach Auras to creatures that does leave the play
|
||||
prefList = CardLists.filter(prefList, c -> !c.hasSVar("EndOfTurnLeavePlay"));
|
||||
}
|
||||
|
||||
@@ -1238,15 +1158,10 @@ public class AttachAi extends SpellAbilityAi {
|
||||
// TODO Somehow test for definitive advantage (e.g. opponent low on health, AI is attacking)
|
||||
// to be able to deal the final blow with an enchanted vehicle like that
|
||||
boolean canOnlyTargetCreatures = true;
|
||||
if (attachSource.isAura()) {
|
||||
for (KeywordInterface ki : attachSource.getKeywords(Keyword.ENCHANT)) {
|
||||
String o = ki.getOriginal();
|
||||
String m[] = o.split(":");
|
||||
String v = m[1];
|
||||
if (!v.startsWith("Creature")) {
|
||||
canOnlyTargetCreatures = false;
|
||||
break;
|
||||
}
|
||||
for (String valid : ObjectUtils.firstNonNull(attachSource.getFirstAttachSpell(), sa).getTargetRestrictions().getValidTgts()) {
|
||||
if (!valid.startsWith("Creature")) {
|
||||
canOnlyTargetCreatures = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (canOnlyTargetCreatures && (attachSource.isAura() || attachSource.isEquipment())) {
|
||||
@@ -1257,7 +1172,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
// Probably prefer to Enchant Creatures that Can Attack
|
||||
// Filter out creatures that can't Attack or have Defender
|
||||
if (keywords.isEmpty()) {
|
||||
final int powerBonus = totPower;
|
||||
final int powerBonus = totPower;
|
||||
prefList = CardLists.filter(prefList, c -> {
|
||||
if (!c.isCreature()) {
|
||||
return true;
|
||||
@@ -1472,6 +1387,8 @@ public class AttachAi extends SpellAbilityAi {
|
||||
c = attachAICuriosityPreference(sa, prefList, mandatory, attachSource);
|
||||
} else if ("ChangeType".equals(logic)) {
|
||||
c = attachAIChangeTypePreference(sa, prefList, mandatory, attachSource);
|
||||
} else if ("KeepTapped".equals(logic)) {
|
||||
c = attachAIKeepTappedPreference(sa, prefList, mandatory, attachSource);
|
||||
} else if ("Animate".equals(logic)) {
|
||||
c = attachAIAnimatePreference(sa, prefList, mandatory, attachSource);
|
||||
} else if ("Reanimate".equals(logic)) {
|
||||
@@ -1482,12 +1399,6 @@ public class AttachAi extends SpellAbilityAi {
|
||||
c = attachAIHighestEvaluationPreference(prefList);
|
||||
}
|
||||
|
||||
if (isAuraSpell(sa)) {
|
||||
if (attachSource.getReplacementEffects().anyMatch(re -> re.getMode().equals(ReplacementType.Untap) && re.getLayer().equals(ReplacementLayer.CantHappen))) {
|
||||
c = attachAIKeepTappedPreference(sa, prefList, mandatory, attachSource);
|
||||
}
|
||||
}
|
||||
|
||||
// Consider exceptional cases which break the normal evaluation rules
|
||||
if (!isUsefulAttachAction(ai, c, sa)) {
|
||||
return null;
|
||||
|
||||
@@ -1285,9 +1285,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
list.remove(choice);
|
||||
if (sa.canTarget(choice)) {
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
|
||||
// Honor the Single Zone restriction. For now, simply remove targets that do not belong to the same zone as the first targeted card.
|
||||
|
||||
@@ -14,7 +14,7 @@ public class CloakAi extends ManifestBaseAi {
|
||||
// (e.g. Grafdigger's Cage)
|
||||
Card topCopy = CardCopyService.getLKICopy(card);
|
||||
topCopy.turnFaceDownNoUpdate();
|
||||
topCopy.setCloaked(sa);
|
||||
topCopy.setCloaked(true);
|
||||
|
||||
if (ComputerUtil.isETBprevented(topCopy)) {
|
||||
return false;
|
||||
|
||||
@@ -95,7 +95,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = calculateDamageAmount(sa, source, damage);
|
||||
|
||||
if (damage.equals("X") || (dmg == 0 && source.getSVar("X").equals("Count$xPaid"))) {
|
||||
if (damage.equals("X") || source.getSVar("X").equals("Count$xPaid")) {
|
||||
if (sa.getSVar("X").equals("Count$xPaid") || sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
dmg = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||
|
||||
|
||||
@@ -277,7 +277,6 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
choice = aura;
|
||||
}
|
||||
}
|
||||
// TODO What about stolen permanents we're getting back at the end of the turn?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,9 +286,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
list.remove(choice);
|
||||
if (sa.canTarget(choice)) {
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
} else if (sa.hasParam("Defined")) {
|
||||
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||
@@ -366,10 +363,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
Card c = ComputerUtilCard.getBestAI(preferred);
|
||||
|
||||
if (sa.canTarget(c)) {
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
sa.getTargets().add(c);
|
||||
preferred.remove(c);
|
||||
}
|
||||
}
|
||||
@@ -390,9 +384,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
} else {
|
||||
c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false);
|
||||
}
|
||||
if (sa.canTarget(c)) {
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
sa.getTargets().add(c);
|
||||
list.remove(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ public class ManifestAi extends ManifestBaseAi {
|
||||
// (e.g. Grafdigger's Cage)
|
||||
Card topCopy = CardCopyService.getLKICopy(card);
|
||||
topCopy.turnFaceDownNoUpdate();
|
||||
topCopy.setManifested(sa);
|
||||
topCopy.setManifested(true);
|
||||
|
||||
if (ComputerUtil.isETBprevented(topCopy)) {
|
||||
return false;
|
||||
|
||||
@@ -14,7 +14,6 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityMode;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -155,7 +154,7 @@ public class PermanentCreatureAi extends PermanentAi {
|
||||
boolean canCastAtOppTurn = true;
|
||||
for (Card c : ai.getGame().getCardsIn(ZoneType.Battlefield)) {
|
||||
for (StaticAbility s : c.getStaticAbilities()) {
|
||||
if (s.checkMode(StaticAbilityMode.CantBeCast) && StringUtils.contains(s.getParam("Activator"), "NonActive")
|
||||
if ("CantBeCast".equals(s.getParam("Mode")) && StringUtils.contains(s.getParam("Activator"), "NonActive")
|
||||
&& (!s.getParam("Activator").startsWith("You") || c.getController().equals(ai))) {
|
||||
canCastAtOppTurn = false;
|
||||
break;
|
||||
|
||||
@@ -367,7 +367,8 @@ public class TokenAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
private boolean tgtRoleAura(final Player ai, final SpellAbility sa, final Card tok, final boolean mandatory) {
|
||||
boolean isCurse = "Curse".equals(sa.getParam("AILogic")) || "Curse".equals(tok.getSVar("AttachAILogic"));
|
||||
boolean isCurse = "Curse".equals(sa.getParam("AILogic")) ||
|
||||
tok.getFirstAttachSpell().getParamOrDefault("AILogic", "").equals("Curse");
|
||||
List<Card> tgts = CardUtil.getValidCardsToTarget(sa);
|
||||
|
||||
// look for card without role from ai
|
||||
|
||||
@@ -96,7 +96,7 @@ public class GameCopier {
|
||||
newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn());
|
||||
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
|
||||
newPlayer.setSpeed(origPlayer.getSpeed());
|
||||
newPlayer.setBlessing(origPlayer.hasBlessing(), null);
|
||||
newPlayer.setBlessing(origPlayer.hasBlessing());
|
||||
newPlayer.setRevolt(origPlayer.hasRevolt());
|
||||
newPlayer.setDescended(origPlayer.getDescended());
|
||||
newPlayer.setLibrarySearched(origPlayer.getLibrarySearched());
|
||||
@@ -373,10 +373,10 @@ public class GameCopier {
|
||||
if (c.isFaceDown()) {
|
||||
newCard.turnFaceDown(true);
|
||||
if (c.isManifested()) {
|
||||
newCard.setManifested(c.getManifestedSA());
|
||||
newCard.setManifested(true);
|
||||
}
|
||||
if (c.isCloaked()) {
|
||||
newCard.setCloaked(c.getCloakedSA());
|
||||
newCard.setCloaked(true);
|
||||
}
|
||||
}
|
||||
if (c.isMonstrous()) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.03</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-core</artifactId>
|
||||
|
||||
@@ -23,14 +23,10 @@ public final class ImageKeys {
|
||||
|
||||
public static final String HIDDEN_CARD = "hidden";
|
||||
public static final String MORPH_IMAGE = "morph";
|
||||
public static final String DISGUISED_IMAGE = "disguised";
|
||||
public static final String MANIFEST_IMAGE = "manifest";
|
||||
public static final String CLOAKED_IMAGE = "cloaked";
|
||||
public static final String FORETELL_IMAGE = "foretell";
|
||||
public static final String BLESSING_IMAGE = "blessing";
|
||||
public static final String INITIATIVE_IMAGE = "initiative";
|
||||
public static final String MONARCH_IMAGE = "monarch";
|
||||
public static final String THE_RING_IMAGE = "the_ring";
|
||||
public static final String RADIATION_IMAGE = "radiation";
|
||||
|
||||
public static final String BACKFACE_POSTFIX = "$alt";
|
||||
public static final String SPECFACE_W = "$wspec";
|
||||
@@ -102,18 +98,8 @@ public final class ImageKeys {
|
||||
|
||||
final String dir;
|
||||
final String filename;
|
||||
String[] tempdata = null;
|
||||
if (key.startsWith(ImageKeys.TOKEN_PREFIX)) {
|
||||
tempdata = key.substring(ImageKeys.TOKEN_PREFIX.length()).split("\\|");
|
||||
String tokenname = tempdata[0];
|
||||
if (tempdata.length > 1) {
|
||||
tokenname += "_" + tempdata[1];
|
||||
}
|
||||
if (tempdata.length > 2) {
|
||||
tokenname += "_" + tempdata[2];
|
||||
}
|
||||
filename = tokenname;
|
||||
|
||||
filename = key.substring(ImageKeys.TOKEN_PREFIX.length());
|
||||
dir = CACHE_TOKEN_PICS_DIR;
|
||||
} else if (key.startsWith(ImageKeys.ICON_PREFIX)) {
|
||||
filename = key.substring(ImageKeys.ICON_PREFIX.length());
|
||||
@@ -154,30 +140,6 @@ public final class ImageKeys {
|
||||
cachedCards.put(filename, file);
|
||||
return file;
|
||||
}
|
||||
if (dir.equals(CACHE_TOKEN_PICS_DIR)) {
|
||||
String setlessFilename = tempdata[0];
|
||||
String setCode = tempdata.length > 1 ? tempdata[1] : "";
|
||||
String collectorNumber = tempdata.length > 2 ? tempdata[2] : "";
|
||||
if (!setCode.isEmpty()) {
|
||||
if (!collectorNumber.isEmpty()) {
|
||||
file = findFile(dir, setCode + "/" + collectorNumber + "_" + setlessFilename);
|
||||
if (file != null) {
|
||||
cachedCards.put(filename, file);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
file = findFile(dir, setCode + "/" + setlessFilename);
|
||||
if (file != null) {
|
||||
cachedCards.put(filename, file);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
file = findFile(dir, setlessFilename);
|
||||
if (file != null) {
|
||||
cachedCards.put(filename, file);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
// AE -> Ae and Ae -> AE for older cards with different file names
|
||||
// on case-sensitive file systems
|
||||
@@ -259,7 +221,39 @@ public final class ImageKeys {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
if (filename.contains("/")) {
|
||||
if (dir.equals(CACHE_TOKEN_PICS_DIR)) {
|
||||
int index = filename.lastIndexOf('_');
|
||||
if (index != -1) {
|
||||
String setlessFilename = filename.substring(0, index);
|
||||
String setCode = filename.substring(index + 1);
|
||||
// try with upper case set
|
||||
file = findFile(dir, setlessFilename + "_" + setCode.toUpperCase());
|
||||
if (file != null) {
|
||||
cachedCards.put(filename, file);
|
||||
return file;
|
||||
}
|
||||
// try with lower case set
|
||||
file = findFile(dir, setlessFilename + "_" + setCode.toLowerCase());
|
||||
if (file != null) {
|
||||
cachedCards.put(filename, file);
|
||||
return file;
|
||||
}
|
||||
// try without set name
|
||||
file = findFile(dir, setlessFilename);
|
||||
if (file != null) {
|
||||
cachedCards.put(filename, file);
|
||||
return file;
|
||||
}
|
||||
// if there's an art variant try without it
|
||||
if (setlessFilename.matches(".*[0-9]*$")) {
|
||||
file = findFile(dir, setlessFilename.replaceAll("[0-9]*$", ""));
|
||||
if (file != null) {
|
||||
cachedCards.put(filename, file);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (filename.contains("/")) {
|
||||
String setlessFilename = filename.substring(filename.indexOf('/') + 1);
|
||||
file = findFile(dir, setlessFilename);
|
||||
if (file != null) {
|
||||
|
||||
@@ -95,12 +95,12 @@ public class StaticData {
|
||||
if (!loadNonLegalCards) {
|
||||
for (CardEdition e : editions) {
|
||||
if (e.getType() == CardEdition.Type.FUNNY || e.getBorderColor() == CardEdition.BorderColor.SILVER) {
|
||||
List<CardEdition.EditionEntry> eternalCards = e.getFunnyEternalCards();
|
||||
List<CardEdition.CardInSet> eternalCards = e.getFunnyEternalCards();
|
||||
|
||||
for (CardEdition.EditionEntry cis : e.getAllCardsInSet()) {
|
||||
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
|
||||
if (eternalCards.contains(cis))
|
||||
continue;
|
||||
funnyCards.add(cis.name());
|
||||
funnyCards.add(cis.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,9 +217,6 @@ public class StaticData {
|
||||
}
|
||||
|
||||
public CardEdition getCardEdition(String setCode) {
|
||||
if (CardEdition.UNKNOWN_CODE.equals(setCode)) {
|
||||
return CardEdition.UNKNOWN;
|
||||
}
|
||||
CardEdition edition = this.editions.get(setCode);
|
||||
return edition;
|
||||
}
|
||||
@@ -251,15 +248,6 @@ public class StaticData {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a PaperCard by looking at all available card databases for any matching print.
|
||||
* @param cardName The name of the card
|
||||
* @return PaperCard instance found in one of the available CardDb databases, or <code>null</code> if not found.
|
||||
*/
|
||||
public PaperCard fetchCard(final String cardName) {
|
||||
return fetchCard(cardName, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a PaperCard by looking at all available card databases;
|
||||
* @param cardName The name of the card
|
||||
@@ -790,11 +778,11 @@ public class StaticData {
|
||||
|
||||
Map<String, Pair<Boolean, Integer>> cardCount = new HashMap<>();
|
||||
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||
for (CardEdition.EditionEntry c : e.getAllCardsInSet()) {
|
||||
if (cardCount.containsKey(c.name())) {
|
||||
cardCount.put(c.name(), Pair.of(c.collectorNumber() != null && c.collectorNumber().startsWith("F"), cardCount.get(c.name()).getRight() + 1));
|
||||
for (CardEdition.CardInSet c : e.getAllCardsInSet()) {
|
||||
if (cardCount.containsKey(c.name)) {
|
||||
cardCount.put(c.name, Pair.of(c.collectorNumber != null && c.collectorNumber.startsWith("F"), cardCount.get(c.name).getRight() + 1));
|
||||
} else {
|
||||
cardCount.put(c.name(), Pair.of(c.collectorNumber() != null && c.collectorNumber().startsWith("F"), 1));
|
||||
cardCount.put(c.name, Pair.of(c.collectorNumber != null && c.collectorNumber.startsWith("F"), 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -856,9 +844,9 @@ public class StaticData {
|
||||
futures.clear();
|
||||
|
||||
// TODO: Audit token images here...
|
||||
for(Map.Entry<String, Collection<CardEdition.EditionEntry>> tokenEntry : e.getTokens().asMap().entrySet()) {
|
||||
for(Map.Entry<String, Integer> tokenEntry : e.getTokens().entrySet()) {
|
||||
final String name = tokenEntry.getKey();
|
||||
final int artIndex = tokenEntry.getValue().size();
|
||||
final int artIndex = tokenEntry.getValue();
|
||||
try {
|
||||
PaperToken token = getAllTokens().getToken(name, e.getCode());
|
||||
if (token == null) {
|
||||
@@ -995,23 +983,4 @@ public class StaticData {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public String getOtherImageKey(String name, String set) {
|
||||
if (this.editions.get(set) != null) {
|
||||
String realSetCode = this.editions.get(set).getOtherSet(name);
|
||||
if (realSetCode != null) {
|
||||
CardEdition.EditionEntry ee = this.editions.get(realSetCode).findOther(name);
|
||||
if (ee != null) { // TODO add collector Number and new ImageKey format
|
||||
return ImageKeys.getTokenKey(String.format("%s|%s|%s", name, realSetCode, ee.collectorNumber()));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (CardEdition e : this.editions) {
|
||||
CardEdition.EditionEntry ee = e.findOther(name);
|
||||
if (ee != null) { // TODO add collector Number and new ImageKey format
|
||||
return ImageKeys.getTokenKey(String.format("%s|%s|%s", name, e.getCode(), ee.collectorNumber()));
|
||||
}
|
||||
}
|
||||
// final fallback
|
||||
return ImageKeys.getTokenKey(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import forge.StaticData;
|
||||
import forge.card.CardEdition.EditionEntry;
|
||||
import forge.card.CardEdition.CardInSet;
|
||||
import forge.card.CardEdition.Type;
|
||||
import forge.deck.generation.IDeckGenPool;
|
||||
import forge.item.IPaperCard;
|
||||
@@ -42,8 +42,7 @@ import java.util.stream.Stream;
|
||||
public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
public final static String foilSuffix = "+";
|
||||
public final static char NameSetSeparator = '|';
|
||||
public final static String FlagPrefix = "#";
|
||||
public static final String FlagSeparator = "\t";
|
||||
public final static String colorIDPrefix = "#";
|
||||
private final String exlcudedCardName = "Concentrate";
|
||||
private final String exlcudedCardSet = "DS0";
|
||||
|
||||
@@ -94,19 +93,19 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
public int artIndex;
|
||||
public boolean isFoil;
|
||||
public String collectorNumber;
|
||||
public Map<String, String> flags;
|
||||
public Set<String> colorID;
|
||||
|
||||
private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber) {
|
||||
this(name, edition, artIndex, isFoil, collectorNumber, null);
|
||||
}
|
||||
|
||||
private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber, Map<String, String> flags) {
|
||||
private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber, Set<String> colorID) {
|
||||
cardName = name;
|
||||
this.edition = edition;
|
||||
this.artIndex = artIndex;
|
||||
this.isFoil = isFoil;
|
||||
this.collectorNumber = collectorNumber;
|
||||
this.flags = flags;
|
||||
this.colorID = colorID;
|
||||
}
|
||||
|
||||
public static boolean isFoilCardName(final String cardName){
|
||||
@@ -121,8 +120,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
|
||||
public static String compose(String cardName, String setCode) {
|
||||
if(setCode == null || StringUtils.isBlank(setCode) || setCode.equals(CardEdition.UNKNOWN_CODE))
|
||||
setCode = "";
|
||||
setCode = setCode != null ? setCode : "";
|
||||
cardName = cardName != null ? cardName : "";
|
||||
if (cardName.indexOf(NameSetSeparator) != -1)
|
||||
// If cardName is another RequestString, just get card name and forget about the rest.
|
||||
@@ -136,36 +134,16 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
return requestInfo + NameSetSeparator + artIndex;
|
||||
}
|
||||
|
||||
public static String compose(String cardName, String setCode, String collectorNumber) {
|
||||
String requestInfo = compose(cardName, setCode);
|
||||
// CollectorNumber will be wrapped in square brackets
|
||||
collectorNumber = preprocessCollectorNumber(collectorNumber);
|
||||
return requestInfo + NameSetSeparator + collectorNumber;
|
||||
}
|
||||
|
||||
public static String compose(String cardName, String setCode, int artIndex, Map<String, String> flags) {
|
||||
public static String compose(String cardName, String setCode, int artIndex, Set<String> colorID) {
|
||||
String requestInfo = compose(cardName, setCode);
|
||||
artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX);
|
||||
if(flags == null)
|
||||
return requestInfo + NameSetSeparator + artIndex;
|
||||
return requestInfo + NameSetSeparator + artIndex + getFlagSegment(flags);
|
||||
String cid = colorID == null ? "" : NameSetSeparator +
|
||||
colorID.toString().replace("[", colorIDPrefix).replace(", ", colorIDPrefix).replace("]", "");
|
||||
return requestInfo + NameSetSeparator + artIndex + cid;
|
||||
}
|
||||
|
||||
public static String compose(String cardName, String setCode, String collectorNumber, Map<String, String> flags) {
|
||||
public static String compose(String cardName, String setCode, String collectorNumber) {
|
||||
String requestInfo = compose(cardName, setCode);
|
||||
collectorNumber = preprocessCollectorNumber(collectorNumber);
|
||||
if(flags == null || flags.isEmpty())
|
||||
return requestInfo + NameSetSeparator + collectorNumber;
|
||||
return requestInfo + NameSetSeparator + collectorNumber + getFlagSegment(flags);
|
||||
}
|
||||
|
||||
public static String compose(PaperCard card) {
|
||||
String name = compose(card.getName(), card.isFoil());
|
||||
return compose(name, card.getEdition(), card.getCollectorNumber(), card.getMarkedFlags().toMap());
|
||||
}
|
||||
|
||||
public static String compose(String cardName, String setCode, int artIndex, String collectorNumber) {
|
||||
String requestInfo = compose(cardName, setCode, artIndex);
|
||||
// CollectorNumber will be wrapped in square brackets
|
||||
collectorNumber = preprocessCollectorNumber(collectorNumber);
|
||||
return requestInfo + NameSetSeparator + collectorNumber;
|
||||
@@ -182,21 +160,19 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
return collectorNumber;
|
||||
}
|
||||
|
||||
private static String getFlagSegment(Map<String, String> flags) {
|
||||
if(flags == null)
|
||||
return "";
|
||||
String flagText = flags.entrySet().stream()
|
||||
.map(e -> e.getKey() + "=" + e.getValue())
|
||||
.collect(Collectors.joining(FlagSeparator));
|
||||
return NameSetSeparator + FlagPrefix + "{" + flagText + "}";
|
||||
public static String compose(String cardName, String setCode, int artIndex, String collectorNumber) {
|
||||
String requestInfo = compose(cardName, setCode, artIndex);
|
||||
// CollectorNumber will be wrapped in square brackets
|
||||
collectorNumber = preprocessCollectorNumber(collectorNumber);
|
||||
return requestInfo + NameSetSeparator + collectorNumber;
|
||||
}
|
||||
|
||||
private static boolean isCollectorNumber(String s) {
|
||||
return s.startsWith("[") && s.endsWith("]");
|
||||
}
|
||||
|
||||
private static boolean isFlagSegment(String s) {
|
||||
return s.startsWith(FlagPrefix);
|
||||
private static boolean isColorIDString(String s) {
|
||||
return s.startsWith(colorIDPrefix);
|
||||
}
|
||||
|
||||
private static boolean isArtIndex(String s) {
|
||||
@@ -225,36 +201,44 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
return null;
|
||||
|
||||
String[] info = TextUtil.split(reqInfo, NameSetSeparator);
|
||||
int index = 1;
|
||||
int setPos;
|
||||
int artPos;
|
||||
int cNrPos;
|
||||
int clrPos;
|
||||
if (info.length >= 4) { // name|set|artIndex|[collNr]
|
||||
setPos = isSetCode(info[1]) ? 1 : -1;
|
||||
artPos = isArtIndex(info[2]) ? 2 : -1;
|
||||
cNrPos = isCollectorNumber(info[3]) ? 3 : -1;
|
||||
int pos = cNrPos > 0 ? -1 : 3;
|
||||
clrPos = pos > 0 ? isColorIDString(info[pos]) ? pos : -1 : -1;
|
||||
} else if (info.length == 3) { // name|set|artIndex (or CollNr)
|
||||
setPos = isSetCode(info[1]) ? 1 : -1;
|
||||
artPos = isArtIndex(info[2]) ? 2 : -1;
|
||||
cNrPos = isCollectorNumber(info[2]) ? 2 : -1;
|
||||
int pos = cNrPos > 0 ? -1 : 2;
|
||||
clrPos = pos > 0 ? isColorIDString(info[pos]) ? pos : -1 : -1;
|
||||
} else if (info.length == 2) { // name|set (or artIndex, even if not possible via compose)
|
||||
setPos = isSetCode(info[1]) ? 1 : -1;
|
||||
artPos = isArtIndex(info[1]) ? 1 : -1;
|
||||
cNrPos = -1;
|
||||
clrPos = -1;
|
||||
} else {
|
||||
setPos = -1;
|
||||
artPos = -1;
|
||||
cNrPos = -1;
|
||||
clrPos = -1;
|
||||
}
|
||||
String cardName = info[0];
|
||||
boolean isFoil = false;
|
||||
int artIndex = IPaperCard.NO_ART_INDEX;
|
||||
String setCode = null;
|
||||
String collectorNumber = IPaperCard.NO_COLLECTOR_NUMBER;
|
||||
Map<String, String> flags = null;
|
||||
if (isFoilCardName(cardName)) {
|
||||
cardName = cardName.substring(0, cardName.length() - foilSuffix.length());
|
||||
isFoil = true;
|
||||
}
|
||||
|
||||
if(info.length > index && isSetCode(info[index])) {
|
||||
setCode = info[index];
|
||||
index++;
|
||||
}
|
||||
if(info.length > index && isArtIndex(info[index])) {
|
||||
artIndex = Integer.parseInt(info[index]);
|
||||
index++;
|
||||
}
|
||||
if(info.length > index && isCollectorNumber(info[index])) {
|
||||
collectorNumber = info[index].substring(1, info[index].length() - 1);
|
||||
index++;
|
||||
}
|
||||
if (info.length > index && isFlagSegment(info[index])) {
|
||||
String flagText = info[index].substring(FlagPrefix.length());
|
||||
flags = parseRequestFlags(flagText);
|
||||
}
|
||||
|
||||
if (CardEdition.UNKNOWN_CODE.equals(setCode)) { // ???
|
||||
int artIndex = artPos > 0 ? Integer.parseInt(info[artPos]) : IPaperCard.NO_ART_INDEX; // default: no art index
|
||||
String collectorNumber = cNrPos > 0 ? info[cNrPos].substring(1, info[cNrPos].length() - 1) : IPaperCard.NO_COLLECTOR_NUMBER;
|
||||
String setCode = setPos > 0 ? info[setPos] : null;
|
||||
Set<String> colorID = clrPos > 0 ? Arrays.stream(info[clrPos].substring(1).split(colorIDPrefix)).collect(Collectors.toSet()) : null;
|
||||
if (setCode != null && setCode.equals(CardEdition.UNKNOWN.getCode())) { // ???
|
||||
setCode = null;
|
||||
}
|
||||
if (setCode == null) {
|
||||
@@ -269,29 +253,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
// finally, check whether any between artIndex and CollectorNumber has been set
|
||||
if (collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER) && artIndex == IPaperCard.NO_ART_INDEX)
|
||||
artIndex = IPaperCard.DEFAULT_ART_INDEX;
|
||||
return new CardRequest(cardName, setCode, artIndex, isFoil, collectorNumber, flags);
|
||||
}
|
||||
|
||||
private static Map<String, String> parseRequestFlags(String flagText) {
|
||||
flagText = flagText.trim();
|
||||
if(flagText.isEmpty())
|
||||
return null;
|
||||
if(!flagText.startsWith("{")) {
|
||||
//Legacy form for marked colors. They'll be of the form "W#B#R"
|
||||
Map<String, String> flags = new HashMap<>();
|
||||
String normalizedColorString = ColorSet.fromNames(flagText.split(FlagPrefix)).toString();
|
||||
flags.put("markedColors", String.join("", normalizedColorString));
|
||||
return flags;
|
||||
}
|
||||
flagText = flagText.substring(1, flagText.length() - 1); //Trim the braces.
|
||||
//List of flags, a series of "key=value" text broken up by tabs.
|
||||
return Arrays.stream(flagText.split(FlagSeparator))
|
||||
.map(f -> f.split("=", 2))
|
||||
.filter(f -> f.length > 0)
|
||||
.collect(Collectors.toMap(
|
||||
entry -> entry[0],
|
||||
entry -> entry.length > 1 ? entry[1] : "true" //If there's no '=' in the entry, treat it as a boolean flag.
|
||||
));
|
||||
return new CardRequest(cardName, setCode, artIndex, isFoil, collectorNumber, colorID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,27 +294,27 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
}
|
||||
|
||||
private void addSetCard(CardEdition e, EditionEntry cis, CardRules cr) {
|
||||
private void addSetCard(CardEdition e, CardInSet cis, CardRules cr) {
|
||||
int artIdx = IPaperCard.DEFAULT_ART_INDEX;
|
||||
String key = e.getCode() + "/" + cis.name();
|
||||
String key = e.getCode() + "/" + cis.name;
|
||||
if (artIds.containsKey(key)) {
|
||||
artIdx = artIds.get(key) + 1;
|
||||
}
|
||||
|
||||
artIds.put(key, artIdx);
|
||||
addCard(new PaperCard(cr, e.getCode(), cis.rarity(), artIdx, false, cis.collectorNumber(), cis.artistName(), cis.functionalVariantName()));
|
||||
addCard(new PaperCard(cr, e.getCode(), cis.rarity, artIdx, false, cis.collectorNumber, cis.artistName, cis.functionalVariantName));
|
||||
}
|
||||
|
||||
private boolean addFromSetByName(String cardName, CardEdition ed, CardRules cr) {
|
||||
List<EditionEntry> cardsInSet = ed.getCardInSet(cardName); // empty collection if not present
|
||||
List<CardInSet> cardsInSet = ed.getCardInSet(cardName); // empty collection if not present
|
||||
if (cr.hasFunctionalVariants()) {
|
||||
cardsInSet = cardsInSet.stream().filter(c -> StringUtils.isEmpty(c.functionalVariantName())
|
||||
|| cr.getSupportedFunctionalVariants().contains(c.functionalVariantName())
|
||||
cardsInSet = cardsInSet.stream().filter(c -> StringUtils.isEmpty(c.functionalVariantName)
|
||||
|| cr.getSupportedFunctionalVariants().contains(c.functionalVariantName)
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
if (cardsInSet.isEmpty())
|
||||
return false;
|
||||
for (EditionEntry cis : cardsInSet) {
|
||||
for (CardInSet cis : cardsInSet) {
|
||||
addSetCard(ed, cis, cr);
|
||||
}
|
||||
return true;
|
||||
@@ -397,15 +359,15 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
upcomingSet = e;
|
||||
}
|
||||
|
||||
for (CardEdition.EditionEntry cis : e.getAllCardsInSet()) {
|
||||
CardRules cr = rulesByName.get(cis.name());
|
||||
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
|
||||
CardRules cr = rulesByName.get(cis.name);
|
||||
if (cr == null) {
|
||||
missingCards.add(cis.name());
|
||||
missingCards.add(cis.name);
|
||||
continue;
|
||||
}
|
||||
if (cr.hasFunctionalVariants()) {
|
||||
if (StringUtils.isNotEmpty(cis.functionalVariantName())
|
||||
&& !cr.getSupportedFunctionalVariants().contains(cis.functionalVariantName())) {
|
||||
if (StringUtils.isNotEmpty(cis.functionalVariantName)
|
||||
&& !cr.getSupportedFunctionalVariants().contains(cis.functionalVariantName)) {
|
||||
//Supported card, unsupported variant.
|
||||
//Could note the card as missing but since these are often un-cards,
|
||||
//it's likely absent because it does something out of scope.
|
||||
@@ -444,7 +406,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown));
|
||||
} else if (enableUnknownCards && !this.filtered.contains(cr.getName())) {
|
||||
System.err.println("The card " + cr.getName() + " was not assigned to any set. Adding it to UNKNOWN set... to fix see res/editions/ folder. ");
|
||||
addCard(new PaperCard(cr, CardEdition.UNKNOWN_CODE, CardRarity.Special));
|
||||
addCard(new PaperCard(cr, CardEdition.UNKNOWN.getCode(), CardRarity.Special));
|
||||
}
|
||||
} else {
|
||||
System.err.println("The custom card " + cr.getName() + " was not assigned to any set. Adding it to custom USER set, and will try to load custom art from USER edition.");
|
||||
@@ -463,8 +425,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
lang = new LangEnglish();
|
||||
}
|
||||
// for now just check Universes Within
|
||||
for (EditionEntry cis : editions.get("SLX").getCards()) {
|
||||
String orgName = alternateName.get(cis.name());
|
||||
for (CardInSet cis : editions.get("SLX").getCards()) {
|
||||
String orgName = alternateName.get(cis.name);
|
||||
if (orgName != null) {
|
||||
// found original (beyond) print
|
||||
CardRules org = getRules(orgName);
|
||||
@@ -494,7 +456,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
CardRules within = new CardRules(new ICardFace[] { renamedMain, renamedOther, null, null, null, null, null }, org.getSplitType(), org.getAiHints());
|
||||
// so workshop can edit same script
|
||||
within.setNormalizedName(org.getNormalizedName());
|
||||
rulesByName.put(cis.name(), within);
|
||||
rulesByName.put(cis.name, within);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,15 +592,15 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaperCard getCard(final String cardName, String setCode, int artIndex, Map<String, String> flags) {
|
||||
String reqInfo = CardRequest.compose(cardName, setCode, artIndex, flags);
|
||||
public PaperCard getCard(final String cardName, String setCode, int artIndex, String collectorNumber) {
|
||||
String reqInfo = CardRequest.compose(cardName, setCode, artIndex, collectorNumber);
|
||||
CardRequest request = CardRequest.fromString(reqInfo);
|
||||
return tryGetCard(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaperCard getCard(final String cardName, String setCode, String collectorNumber, Map<String, String> flags) {
|
||||
String reqInfo = CardRequest.compose(cardName, setCode, collectorNumber, flags);
|
||||
public PaperCard getCard(final String cardName, String setCode, int artIndex, Set<String> colorID) {
|
||||
String reqInfo = CardRequest.compose(cardName, setCode, artIndex, colorID);
|
||||
CardRequest request = CardRequest.fromString(reqInfo);
|
||||
return tryGetCard(request);
|
||||
}
|
||||
@@ -649,17 +611,14 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
return null;
|
||||
// 1. First off, try using all possible search parameters, to narrow down the actual cards looked for.
|
||||
String reqEditionCode = request.edition;
|
||||
if (reqEditionCode != null && !reqEditionCode.isEmpty()) {
|
||||
if (reqEditionCode != null && reqEditionCode.length() > 0) {
|
||||
// This get is robust even against expansion aliases (e.g. TE and TMP both valid for Tempest) -
|
||||
// MOST of the extensions have two short codes, 141 out of 221 (so far)
|
||||
// ALSO: Set Code are always UpperCase
|
||||
CardEdition edition = editions.get(reqEditionCode.toUpperCase());
|
||||
|
||||
PaperCard cardFromSet = this.getCardFromSet(request.cardName, edition, request.artIndex, request.collectorNumber, request.isFoil);
|
||||
if(cardFromSet != null && request.flags != null)
|
||||
cardFromSet = cardFromSet.copyWithFlags(request.flags);
|
||||
|
||||
return cardFromSet;
|
||||
return this.getCardFromSet(request.cardName, edition, request.artIndex,
|
||||
request.collectorNumber, request.isFoil, request.colorID);
|
||||
}
|
||||
|
||||
// 2. Card lookup in edition with specified filter didn't work.
|
||||
@@ -702,6 +661,11 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
|
||||
@Override
|
||||
public PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil) {
|
||||
return getCardFromSet(cardName, edition, artIndex, collectorNumber, isFoil, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil, Set<String> colorID) {
|
||||
if (edition == null || cardName == null) // preview cards
|
||||
return null; // No cards will be returned
|
||||
|
||||
@@ -710,18 +674,18 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
cardName = cardNameRequest.cardName;
|
||||
isFoil = isFoil || cardNameRequest.isFoil;
|
||||
|
||||
String code1 = edition.getCode(), code2 = edition.getCode2();
|
||||
|
||||
Predicate<PaperCard> filter = (c) -> {
|
||||
String ed = c.getEdition();
|
||||
return ed.equalsIgnoreCase(code1) || ed.equalsIgnoreCase(code2);
|
||||
};
|
||||
if (artIndex > 0)
|
||||
filter = filter.and((c) -> artIndex == c.getArtIndex());
|
||||
if (collectorNumber != null && !collectorNumber.isEmpty() && !collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER))
|
||||
filter = filter.and((c) -> collectorNumber.equals(c.getCollectorNumber()));
|
||||
|
||||
List<PaperCard> candidates = getAllCards(cardName, filter);
|
||||
List<PaperCard> candidates = getAllCards(cardName, c -> {
|
||||
boolean artIndexFilter = true;
|
||||
boolean collectorNumberFilter = true;
|
||||
boolean setFilter = c.getEdition().equalsIgnoreCase(edition.getCode()) ||
|
||||
c.getEdition().equalsIgnoreCase(edition.getCode2());
|
||||
if (artIndex > 0)
|
||||
artIndexFilter = (c.getArtIndex() == artIndex);
|
||||
if ((collectorNumber != null) && (collectorNumber.length() > 0)
|
||||
&& !(collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER)))
|
||||
collectorNumberFilter = (c.getCollectorNumber().equals(collectorNumber));
|
||||
return setFilter && artIndexFilter && collectorNumberFilter;
|
||||
});
|
||||
if (candidates.isEmpty())
|
||||
return null;
|
||||
|
||||
@@ -735,7 +699,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
while (!candidate.hasImage() && candidatesIterator.hasNext())
|
||||
candidate = candidatesIterator.next();
|
||||
candidate = candidate.hasImage() ? candidate : firstCandidate;
|
||||
return isFoil ? candidate.getFoiled() : candidate;
|
||||
return isFoil ? candidate.getFoiled().getColorIDVersion(colorID) : candidate.getColorIDVersion(colorID);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -778,6 +742,11 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaperCard getCardFromEditions(final String cardInfo, final CardArtPreference artPreference, int artIndex, Set<String> colorID) {
|
||||
return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, null, false, null, colorID);
|
||||
}
|
||||
|
||||
/*
|
||||
* ===============================================
|
||||
* 4. SPECIALISED CARD LOOKUP BASED ON
|
||||
@@ -851,7 +820,12 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
|
||||
private PaperCard tryToGetCardFromEditions(String cardInfo, CardArtPreference artPreference, int artIndex,
|
||||
Date releaseDate, boolean releasedBeforeFlag, Predicate<PaperCard> filter) {
|
||||
Date releaseDate, boolean releasedBeforeFlag, Predicate<PaperCard> filter){
|
||||
return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, releaseDate, releasedBeforeFlag, filter, null);
|
||||
}
|
||||
|
||||
private PaperCard tryToGetCardFromEditions(String cardInfo, CardArtPreference artPreference, int artIndex,
|
||||
Date releaseDate, boolean releasedBeforeFlag, Predicate<PaperCard> filter, Set<String> colorID){
|
||||
if (cardInfo == null)
|
||||
return null;
|
||||
final CardRequest cr = CardRequest.fromString(cardInfo);
|
||||
@@ -891,7 +865,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
for (PaperCard card : cards) {
|
||||
String setCode = card.getEdition();
|
||||
CardEdition ed;
|
||||
if (setCode.equals(CardEdition.UNKNOWN_CODE))
|
||||
if (setCode.equals(CardEdition.UNKNOWN.getCode()))
|
||||
ed = CardEdition.UNKNOWN;
|
||||
else
|
||||
ed = editions.get(card.getEdition());
|
||||
@@ -932,7 +906,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
candidate = candidate.hasImage() ? candidate : firstCandidate;
|
||||
//If any, we're sure that at least one candidate is always returned despite it having any image
|
||||
return cr.isFoil ? candidate.getFoiled() : candidate;
|
||||
return cr.isFoil ? candidate.getFoiled().getColorIDVersion(colorID) : candidate.getColorIDVersion(colorID);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1043,7 +1017,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
public static final Predicate<PaperCard> EDITION_NON_PROMO = paperCard -> {
|
||||
String code = paperCard.getEdition();
|
||||
CardEdition edition = StaticData.instance().getCardEdition(code);
|
||||
if(edition == null && code.equals(CardEdition.UNKNOWN_CODE))
|
||||
if(edition == null && code.equals("???"))
|
||||
return true;
|
||||
return edition != null && edition.getType() != Type.PROMO;
|
||||
};
|
||||
@@ -1051,7 +1025,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
public static final Predicate<PaperCard> EDITION_NON_REPRINT = paperCard -> {
|
||||
String code = paperCard.getEdition();
|
||||
CardEdition edition = StaticData.instance().getCardEdition(code);
|
||||
if(edition == null && code.equals(CardEdition.UNKNOWN_CODE))
|
||||
if(edition == null && code.equals("???"))
|
||||
return true;
|
||||
return edition != null && Type.REPRINT_SET_TYPES.contains(edition.getType());
|
||||
};
|
||||
@@ -1107,8 +1081,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
public Collection<PaperCard> getAllCards(CardEdition edition) {
|
||||
List<PaperCard> cards = Lists.newArrayList();
|
||||
|
||||
for (EditionEntry cis : edition.getAllCardsInSet()) {
|
||||
PaperCard card = this.getCard(cis.name(), edition.getCode());
|
||||
for (CardInSet cis : edition.getAllCardsInSet()) {
|
||||
PaperCard card = this.getCard(cis.name, edition.getCode());
|
||||
if (card == null) {
|
||||
// Just in case the card is listed in the edition file but Forge doesn't support it
|
||||
continue;
|
||||
@@ -1152,6 +1126,29 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
.anyMatch(rarity::equals);
|
||||
}
|
||||
|
||||
public StringBuilder appendCardToStringBuilder(PaperCard card, StringBuilder sb) {
|
||||
final boolean hasBadSetInfo = card.getEdition().equals(CardEdition.UNKNOWN.getCode()) || StringUtils.isBlank(card.getEdition());
|
||||
sb.append(card.getName());
|
||||
if (card.isFoil()) {
|
||||
sb.append(CardDb.foilSuffix);
|
||||
}
|
||||
|
||||
if (!hasBadSetInfo) {
|
||||
int artCount = getArtCount(card.getName(), card.getEdition(), card.getFunctionalVariant());
|
||||
sb.append(CardDb.NameSetSeparator).append(card.getEdition());
|
||||
if (artCount >= IPaperCard.DEFAULT_ART_INDEX) {
|
||||
sb.append(CardDb.NameSetSeparator).append(card.getArtIndex()); // indexes start at 1 to match image file name conventions
|
||||
}
|
||||
if (card.getColorID() != null) {
|
||||
sb.append(CardDb.NameSetSeparator);
|
||||
for (String color : card.getColorID())
|
||||
sb.append(CardDb.colorIDPrefix).append(color);
|
||||
}
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
public PaperCard createUnsupportedCard(String cardRequest) {
|
||||
CardRequest request = CardRequest.fromString(cardRequest);
|
||||
CardEdition cardEdition = CardEdition.UNKNOWN;
|
||||
@@ -1160,10 +1157,10 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
// May iterate over editions and find out if there is any card named 'cardRequest' but not implemented with Forge script.
|
||||
if (StringUtils.isBlank(request.edition)) {
|
||||
for (CardEdition edition : editions) {
|
||||
for (EditionEntry cardInSet : edition.getAllCardsInSet()) {
|
||||
if (cardInSet.name().equals(request.cardName)) {
|
||||
for (CardInSet cardInSet : edition.getAllCardsInSet()) {
|
||||
if (cardInSet.name.equals(request.cardName)) {
|
||||
cardEdition = edition;
|
||||
cardRarity = cardInSet.rarity();
|
||||
cardRarity = cardInSet.rarity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1174,9 +1171,9 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
} else {
|
||||
cardEdition = editions.get(request.edition);
|
||||
if (cardEdition != null) {
|
||||
for (EditionEntry cardInSet : cardEdition.getAllCardsInSet()) {
|
||||
if (cardInSet.name().equals(request.cardName)) {
|
||||
cardRarity = cardInSet.rarity();
|
||||
for (CardInSet cardInSet : cardEdition.getAllCardsInSet()) {
|
||||
if (cardInSet.name.equals(request.cardName)) {
|
||||
cardRarity = cardInSet.rarity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1227,9 +1224,9 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
// @leriomaggio: DONE! re-using here the same strategy implemented for lazy-loading!
|
||||
for (CardEdition e : editions.getOrderedEditions()) {
|
||||
int artIdx = IPaperCard.DEFAULT_ART_INDEX;
|
||||
for (EditionEntry cis : e.getCardInSet(cardName))
|
||||
paperCards.add(new PaperCard(rules, e.getCode(), cis.rarity(), artIdx++, false,
|
||||
cis.collectorNumber(), cis.artistName(), cis.functionalVariantName()));
|
||||
for (CardInSet cis : e.getCardInSet(cardName))
|
||||
paperCards.add(new PaperCard(rules, e.getCode(), cis.rarity, artIdx++, false,
|
||||
cis.collectorNumber, cis.artistName, cis.functionalVariantName));
|
||||
}
|
||||
} else {
|
||||
String lastEdition = null;
|
||||
@@ -1243,17 +1240,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
if (ed == null) {
|
||||
continue;
|
||||
}
|
||||
List<EditionEntry> cardsInSet = ed.getCardInSet(cardName);
|
||||
List<CardInSet> cardsInSet = ed.getCardInSet(cardName);
|
||||
if (cardsInSet.isEmpty())
|
||||
continue;
|
||||
int cardInSetIndex = Math.max(artIdx-1, 0); // make sure doesn't go below zero
|
||||
EditionEntry cds = cardsInSet.get(cardInSetIndex); // use ArtIndex to get the right Coll. Number
|
||||
CardInSet cds = cardsInSet.get(cardInSetIndex); // use ArtIndex to get the right Coll. Number
|
||||
paperCards.add(new PaperCard(rules, lastEdition, tuple.getValue(), artIdx++, false,
|
||||
cds.collectorNumber(), cds.artistName(), cds.functionalVariantName()));
|
||||
cds.collectorNumber, cds.artistName, cds.functionalVariantName));
|
||||
}
|
||||
}
|
||||
if (paperCards.isEmpty()) {
|
||||
paperCards.add(new PaperCard(rules, CardEdition.UNKNOWN_CODE, CardRarity.Special));
|
||||
paperCards.add(new PaperCard(rules, CardEdition.UNKNOWN.getCode(), CardRarity.Special));
|
||||
}
|
||||
// 2. add them to db
|
||||
for (PaperCard paperCard : paperCards) {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package forge.card;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.card.CardDb.CardArtPreference;
|
||||
import forge.deck.CardPool;
|
||||
@@ -166,49 +165,20 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<String, String> sortableCollNumberLookup = new HashMap<>();
|
||||
/**
|
||||
* This method implements the main strategy to allow for natural ordering of collectorNumber
|
||||
* (i.e. "1" < "10"), overloading the default lexicographic order (i.e. "10" < "1").
|
||||
* Any non-numerical parts in the input collectorNumber will be also accounted for, and attached to the
|
||||
* resulting sorting key, accordingly.
|
||||
*
|
||||
* @param collectorNumber: Input collectorNumber tro transform in a Sorting Key
|
||||
* @return A 5-digits zero-padded collector number + any non-numerical parts attached.
|
||||
*/
|
||||
public static String getSortableCollectorNumber(final String collectorNumber){
|
||||
String inputCollNumber = collectorNumber;
|
||||
if (collectorNumber == null || collectorNumber.isEmpty())
|
||||
inputCollNumber = "50000"; // very big number of 5 digits to have them in last positions
|
||||
public static class CardInSet implements Comparable<CardInSet> {
|
||||
public final CardRarity rarity;
|
||||
public final String collectorNumber;
|
||||
public final String name;
|
||||
public final String artistName;
|
||||
public final String functionalVariantName;
|
||||
|
||||
String matchedCollNr = sortableCollNumberLookup.getOrDefault(inputCollNumber, null);
|
||||
if (matchedCollNr != null)
|
||||
return matchedCollNr;
|
||||
|
||||
// Now, for proper sorting, let's zero-pad the collector number (if integer)
|
||||
int collNr;
|
||||
String sortableCollNr;
|
||||
try {
|
||||
collNr = Integer.parseInt(inputCollNumber);
|
||||
sortableCollNr = String.format("%05d", collNr);
|
||||
} catch (NumberFormatException ex) {
|
||||
String nonNumSub = inputCollNumber.replaceAll("[0-9]", "");
|
||||
String onlyNumSub = inputCollNumber.replaceAll("[^0-9]", "");
|
||||
try {
|
||||
collNr = Integer.parseInt(onlyNumSub);
|
||||
} catch (NumberFormatException exon) {
|
||||
collNr = 0; // this is the case of ONLY-letters collector numbers
|
||||
}
|
||||
if ((collNr > 0) && (inputCollNumber.startsWith(onlyNumSub))) // e.g. 12a, 37+, 2018f,
|
||||
sortableCollNr = String.format("%05d", collNr) + nonNumSub;
|
||||
else // e.g. WS6, S1
|
||||
sortableCollNr = nonNumSub + String.format("%05d", collNr);
|
||||
public CardInSet(final String name, final String collectorNumber, final CardRarity rarity, final String artistName, final String functionalVariantName) {
|
||||
this.name = name;
|
||||
this.collectorNumber = collectorNumber;
|
||||
this.rarity = rarity;
|
||||
this.artistName = artistName;
|
||||
this.functionalVariantName = functionalVariantName;
|
||||
}
|
||||
sortableCollNumberLookup.put(inputCollNumber, sortableCollNr);
|
||||
return sortableCollNr;
|
||||
}
|
||||
|
||||
public record EditionEntry(String name, String collectorNumber, CardRarity rarity, String artistName, String functionalVariantName) implements Comparable<EditionEntry> {
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -216,7 +186,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
sb.append(collectorNumber);
|
||||
sb.append(' ');
|
||||
}
|
||||
if (rarity != CardRarity.Unknown && rarity != CardRarity.Token) {
|
||||
if (rarity != CardRarity.Unknown) {
|
||||
sb.append(rarity);
|
||||
sb.append(' ');
|
||||
}
|
||||
@@ -232,8 +202,50 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static final Map<String, String> sortableCollNumberLookup = new HashMap<>();
|
||||
/**
|
||||
* This method implements the main strategy to allow for natural ordering of collectorNumber
|
||||
* (i.e. "1" < "10"), overloading the default lexicographic order (i.e. "10" < "1").
|
||||
* Any non-numerical parts in the input collectorNumber will be also accounted for, and attached to the
|
||||
* resulting sorting key, accordingly.
|
||||
*
|
||||
* @param collectorNumber: Input collectorNumber tro transform in a Sorting Key
|
||||
* @return A 5-digits zero-padded collector number + any non-numerical parts attached.
|
||||
*/
|
||||
public static String getSortableCollectorNumber(final String collectorNumber){
|
||||
String inputCollNumber = collectorNumber;
|
||||
if (collectorNumber == null || collectorNumber.isEmpty())
|
||||
inputCollNumber = "50000"; // very big number of 5 digits to have them in last positions
|
||||
|
||||
String matchedCollNr = sortableCollNumberLookup.getOrDefault(inputCollNumber, null);
|
||||
if (matchedCollNr != null)
|
||||
return matchedCollNr;
|
||||
|
||||
// Now, for proper sorting, let's zero-pad the collector number (if integer)
|
||||
int collNr;
|
||||
String sortableCollNr;
|
||||
try {
|
||||
collNr = Integer.parseInt(inputCollNumber);
|
||||
sortableCollNr = String.format("%05d", collNr);
|
||||
} catch (NumberFormatException ex) {
|
||||
String nonNumSub = inputCollNumber.replaceAll("[0-9]", "");
|
||||
String onlyNumSub = inputCollNumber.replaceAll("[^0-9]", "");
|
||||
try {
|
||||
collNr = Integer.parseInt(onlyNumSub);
|
||||
} catch (NumberFormatException exon) {
|
||||
collNr = 0; // this is the case of ONLY-letters collector numbers
|
||||
}
|
||||
if ((collNr > 0) && (inputCollNumber.startsWith(onlyNumSub))) // e.g. 12a, 37+, 2018f,
|
||||
sortableCollNr = String.format("%05d", collNr) + nonNumSub;
|
||||
else // e.g. WS6, S1
|
||||
sortableCollNr = nonNumSub + String.format("%05d", collNr);
|
||||
}
|
||||
sortableCollNumberLookup.put(inputCollNumber, sortableCollNr);
|
||||
return sortableCollNr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(EditionEntry o) {
|
||||
public int compareTo(CardInSet o) {
|
||||
final int nameCmp = name.compareToIgnoreCase(o.name);
|
||||
if (0 != nameCmp) {
|
||||
return nameCmp;
|
||||
@@ -250,17 +262,11 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
|
||||
private final static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
|
||||
|
||||
/**
|
||||
* Equivalent to the set code of CardEdition.UNKNOWN
|
||||
*/
|
||||
public static final String UNKNOWN_CODE = "???";
|
||||
public static final CardEdition UNKNOWN = new CardEdition("1990-01-01", UNKNOWN_CODE, "??", Type.UNKNOWN, "Undefined", FoilType.NOT_SUPPORTED, new EditionEntry[]{});
|
||||
public static final CardEdition UNKNOWN = new CardEdition("1990-01-01", "???", "??", Type.UNKNOWN, "Undefined", FoilType.NOT_SUPPORTED, new CardInSet[]{});
|
||||
private Date date;
|
||||
private String code;
|
||||
private String code2;
|
||||
private String scryfallCode;
|
||||
private String tokensCode;
|
||||
private String tokenFallbackCode;
|
||||
private String cardsLanguage;
|
||||
private Type type;
|
||||
private String name;
|
||||
@@ -290,32 +296,31 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
private String doublePickDuringDraft = "";
|
||||
private String[] chaosDraftThemes = new String[0];
|
||||
|
||||
private final ListMultimap<String, EditionEntry> cardMap;
|
||||
private final List<EditionEntry> cardsInSet;
|
||||
private final ListMultimap<String, EditionEntry> tokenMap;
|
||||
private final ListMultimap<String, CardInSet> cardMap;
|
||||
private final List<CardInSet> cardsInSet;
|
||||
private final Map<String, Integer> tokenNormalized;
|
||||
// custom print sheets that will be loaded lazily
|
||||
private final Map<String, List<String>> customPrintSheetsToParse;
|
||||
private ListMultimap<String, EditionEntry> otherMap = ArrayListMultimap.create();
|
||||
|
||||
private int boosterArts = 1;
|
||||
private SealedTemplate boosterTpl = null;
|
||||
private final Map<String, SealedTemplate> boosterTemplates = new HashMap<>();
|
||||
|
||||
private CardEdition(ListMultimap<String, EditionEntry> cardMap, ListMultimap<String, EditionEntry> tokens, Map<String, List<String>> customPrintSheetsToParse) {
|
||||
private CardEdition(ListMultimap<String, CardInSet> cardMap, Map<String, Integer> tokens, Map<String, List<String>> customPrintSheetsToParse) {
|
||||
this.cardMap = cardMap;
|
||||
this.cardsInSet = new ArrayList<>(cardMap.values());
|
||||
Collections.sort(cardsInSet);
|
||||
this.tokenMap = tokens;
|
||||
this.tokenNormalized = tokens;
|
||||
this.customPrintSheetsToParse = customPrintSheetsToParse;
|
||||
}
|
||||
|
||||
private CardEdition(EditionEntry[] cards, ListMultimap<String, EditionEntry> tokens) {
|
||||
List<EditionEntry> cardsList = Arrays.asList(cards);
|
||||
private CardEdition(CardInSet[] cards, Map<String, Integer> tokens) {
|
||||
List<CardInSet> cardsList = Arrays.asList(cards);
|
||||
this.cardMap = ArrayListMultimap.create();
|
||||
this.cardMap.replaceValues("cards", cardsList);
|
||||
this.cardsInSet = new ArrayList<>(cardsList);
|
||||
Collections.sort(cardsInSet);
|
||||
this.tokenMap = tokens;
|
||||
this.tokenNormalized = tokens;
|
||||
this.customPrintSheetsToParse = new HashMap<>();
|
||||
}
|
||||
|
||||
@@ -332,8 +337,8 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
* @param name the name of the set
|
||||
* @param cards the cards in the set
|
||||
*/
|
||||
private CardEdition(String date, String code, String code2, Type type, String name, FoilType foil, EditionEntry[] cards) {
|
||||
this(cards, ArrayListMultimap.create());
|
||||
private CardEdition(String date, String code, String code2, Type type, String name, FoilType foil, CardInSet[] cards) {
|
||||
this(cards, new HashMap<>());
|
||||
this.code = code;
|
||||
this.code2 = code2;
|
||||
this.type = type;
|
||||
@@ -356,7 +361,6 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
public String getCode() { return code; }
|
||||
public String getCode2() { return code2; }
|
||||
public String getScryfallCode() { return scryfallCode.toLowerCase(); }
|
||||
public String getTokensCode() { return tokensCode.toLowerCase(); }
|
||||
public String getCardsLangCode() { return cardsLanguage.toLowerCase(); }
|
||||
public Type getType() { return type; }
|
||||
public String getName() { return name; }
|
||||
@@ -381,14 +385,14 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
public String getSheetReplaceCardFromSheet2() { return sheetReplaceCardFromSheet2; }
|
||||
public String[] getChaosDraftThemes() { return chaosDraftThemes; }
|
||||
|
||||
public List<EditionEntry> getCards() { return cardMap.get(EditionSectionWithCollectorNumbers.CARDS.getName()); }
|
||||
public List<EditionEntry> getRebalancedCards() { return cardMap.get(EditionSectionWithCollectorNumbers.REBALANCED.getName()); }
|
||||
public List<EditionEntry> getFunnyEternalCards() { return cardMap.get(EditionSectionWithCollectorNumbers.ETERNAL.getName()); }
|
||||
public List<EditionEntry> getAllCardsInSet() {
|
||||
public List<CardInSet> getCards() { return cardMap.get(EditionSectionWithCollectorNumbers.CARDS.getName()); }
|
||||
public List<CardInSet> getRebalancedCards() { return cardMap.get(EditionSectionWithCollectorNumbers.REBALANCED.getName()); }
|
||||
public List<CardInSet> getFunnyEternalCards() { return cardMap.get(EditionSectionWithCollectorNumbers.ETERNAL.getName()); }
|
||||
public List<CardInSet> getAllCardsInSet() {
|
||||
return cardsInSet;
|
||||
}
|
||||
|
||||
private ListMultimap<String, EditionEntry> cardsInSetLookupMap = null;
|
||||
private ListMultimap<String, CardInSet> cardsInSetLookupMap = null;
|
||||
|
||||
/**
|
||||
* Get all the CardInSet instances with the input card name.
|
||||
@@ -396,12 +400,12 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
* @return A List of all the CardInSet instances for a given name.
|
||||
* If not fount, an Empty sequence (view) will be returned instead!
|
||||
*/
|
||||
public List<EditionEntry> getCardInSet(String cardName){
|
||||
public List<CardInSet> getCardInSet(String cardName){
|
||||
if (cardsInSetLookupMap == null) {
|
||||
// initialise
|
||||
cardsInSetLookupMap = Multimaps.newListMultimap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER), Lists::newArrayList);
|
||||
List<EditionEntry> cardsInSet = this.getAllCardsInSet();
|
||||
for (EditionEntry cis : cardsInSet){
|
||||
List<CardInSet> cardsInSet = this.getAllCardsInSet();
|
||||
for (CardInSet cis : cardsInSet){
|
||||
String key = cis.name;
|
||||
cardsInSetLookupMap.put(key, cis);
|
||||
}
|
||||
@@ -409,19 +413,8 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
return this.cardsInSetLookupMap.get(cardName);
|
||||
}
|
||||
|
||||
public EditionEntry getCardFromCollectorNumber(String collectorNumber) {
|
||||
if(collectorNumber == null || collectorNumber.isEmpty())
|
||||
return null;
|
||||
for(EditionEntry c : this.cardsInSet) {
|
||||
//Could build a map for this one too if it's used for more than one-offs.
|
||||
if (c.collectorNumber.equalsIgnoreCase(collectorNumber))
|
||||
return c;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isRebalanced(String cardName) {
|
||||
for (EditionEntry cis : getRebalancedCards()) {
|
||||
for (CardInSet cis : getRebalancedCards()) {
|
||||
if (cis.name.equals(cardName)) {
|
||||
return true;
|
||||
}
|
||||
@@ -431,33 +424,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
|
||||
public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others
|
||||
|
||||
public Multimap<String, EditionEntry> getTokens() { return tokenMap; }
|
||||
|
||||
public String getTokenSet(String token) {
|
||||
if (tokenMap.containsKey(token)) {
|
||||
return this.getCode();
|
||||
}
|
||||
if (this.tokenFallbackCode != null) {
|
||||
return StaticData.instance().getCardEdition(this.tokenFallbackCode).getTokenSet(token);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public String getOtherSet(String token) {
|
||||
if (otherMap.containsKey(token)) {
|
||||
return this.getCode();
|
||||
}
|
||||
if (this.tokenFallbackCode != null) {
|
||||
return StaticData.instance().getCardEdition(this.tokenFallbackCode).getOtherSet(token);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public EditionEntry findOther(String name) {
|
||||
if (otherMap.containsKey(name)) {
|
||||
return Aggregates.random(otherMap.get(name));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public Map<String, Integer> getTokens() { return tokenNormalized; }
|
||||
|
||||
@Override
|
||||
public int compareTo(final CardEdition o) {
|
||||
@@ -541,8 +508,8 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
for (String sectionName : cardMap.keySet()) {
|
||||
PrintSheet sheet = new PrintSheet(String.format("%s %s", this.getCode(), sectionName));
|
||||
|
||||
List<EditionEntry> cards = cardMap.get(sectionName);
|
||||
for (EditionEntry card : cards) {
|
||||
List<CardInSet> cards = cardMap.get(sectionName);
|
||||
for (CardInSet card : cards) {
|
||||
int index = 1;
|
||||
if (cardToIndex.containsKey(card.name)) {
|
||||
index = cardToIndex.get(card.name) + 1;
|
||||
@@ -595,7 +562,6 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
it should also match the Un-set and older alternate art cards
|
||||
like Merseine from FEM.
|
||||
*/
|
||||
// Collector numbers now should allow hyphens for Planeswalker Championship Promos
|
||||
//"(^(?<cnum>[0-9]+.?) )?((?<rarity>[SCURML]) )?(?<name>.*)$"
|
||||
/* Ideally we'd use the named group above, but Android 6 and
|
||||
earlier don't appear to support named groups.
|
||||
@@ -609,20 +575,12 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
* functional variant name - grouping #9
|
||||
*/
|
||||
// "(^(.?[0-9A-Z]+.?))?(([SCURML]) )?(.*)$"
|
||||
"(^(.?[0-9A-Z-]+\\S?[A-Z]*)\\s)?(([SCURML])\\s)?([^@\\$]*)( @([^\\$]*))?( \\$(.+))?$"
|
||||
"(^(.?[0-9A-Z]+\\S?[A-Z]*)\\s)?(([SCURML])\\s)?([^@\\$]*)( @([^\\$]*))?( \\$(.+))?$"
|
||||
);
|
||||
|
||||
final Pattern tokenPattern = Pattern.compile(
|
||||
/*
|
||||
* cnum - grouping #2
|
||||
* name - grouping #3
|
||||
* artist name - grouping #5
|
||||
*/
|
||||
"(^(.?[0-9A-Z]+\\S?[A-Z]*)\\s)?([^@]*)( @(.*))?$"
|
||||
);
|
||||
|
||||
ListMultimap<String, EditionEntry> cardMap = ArrayListMultimap.create();
|
||||
ListMultimap<String, CardInSet> cardMap = ArrayListMultimap.create();
|
||||
List<BoosterSlot> boosterSlots = null;
|
||||
Map<String, Integer> tokenNormalized = new HashMap<>();
|
||||
Map<String, List<String>> customPrintSheetsToParse = new HashMap<>();
|
||||
List<String> editionSectionsWithCollectorNumbers = EditionSectionWithCollectorNumbers.getNames();
|
||||
|
||||
@@ -653,7 +611,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
String cardName = matcher.group(5);
|
||||
String artistName = matcher.group(7);
|
||||
String functionalVariantName = matcher.group(9);
|
||||
EditionEntry cis = new EditionEntry(cardName, collectorNumber, r, artistName, functionalVariantName);
|
||||
CardInSet cis = new CardInSet(cardName, collectorNumber, r, artistName, functionalVariantName);
|
||||
|
||||
cardMap.put(sectionName, cis);
|
||||
}
|
||||
@@ -667,58 +625,40 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
}
|
||||
}
|
||||
|
||||
ListMultimap<String, EditionEntry> tokenMap = ArrayListMultimap.create();
|
||||
ListMultimap<String, EditionEntry> otherMap = ArrayListMultimap.create();
|
||||
// parse tokens section
|
||||
if (contents.containsKey("tokens")) {
|
||||
for (String line : contents.get("tokens")) {
|
||||
if (StringUtils.isBlank(line))
|
||||
continue;
|
||||
Matcher matcher = tokenPattern.matcher(line);
|
||||
|
||||
if (!matcher.matches()) {
|
||||
continue;
|
||||
if (!tokenNormalized.containsKey(line)) {
|
||||
tokenNormalized.put(line, 1);
|
||||
} else {
|
||||
tokenNormalized.put(line, tokenNormalized.get(line) + 1);
|
||||
}
|
||||
|
||||
String collectorNumber = matcher.group(2);
|
||||
String cardName = matcher.group(3);
|
||||
String artistName = matcher.group(5);
|
||||
// rarity isn't used for this anyway
|
||||
EditionEntry tis = new EditionEntry(cardName, collectorNumber, CardRarity.Token, artistName, null);
|
||||
tokenMap.put(cardName, tis);
|
||||
}
|
||||
}
|
||||
if (contents.containsKey("other")) {
|
||||
for (String line : contents.get("other")) {
|
||||
if (StringUtils.isBlank(line))
|
||||
continue;
|
||||
Matcher matcher = tokenPattern.matcher(line);
|
||||
|
||||
if (!matcher.matches()) {
|
||||
continue;
|
||||
}
|
||||
String collectorNumber = matcher.group(2);
|
||||
String cardName = matcher.group(3);
|
||||
String artistName = matcher.group(5);
|
||||
EditionEntry tis = new EditionEntry(cardName, collectorNumber, CardRarity.Unknown, artistName, null);
|
||||
otherMap.put(cardName, tis);
|
||||
}
|
||||
}
|
||||
|
||||
CardEdition res = new CardEdition(cardMap, tokenMap, customPrintSheetsToParse);
|
||||
CardEdition res = new CardEdition(cardMap, tokenNormalized, customPrintSheetsToParse);
|
||||
res.boosterSlots = boosterSlots;
|
||||
// parse metadata section
|
||||
res.name = metadata.get("name");
|
||||
res.date = parseDate(metadata.get("date"));
|
||||
res.code = metadata.get("code");
|
||||
res.code2 = metadata.get("code2", res.code);
|
||||
res.scryfallCode = metadata.get("ScryfallCode", res.code);
|
||||
res.tokensCode = metadata.get("TokensCode", "T" + res.scryfallCode);
|
||||
res.tokenFallbackCode = metadata.get("TokenFallbackCode");
|
||||
res.cardsLanguage = metadata.get("CardLang", "en");
|
||||
res.boosterArts = metadata.getInt("BoosterCovers", 1);
|
||||
res.code2 = metadata.get("code2");
|
||||
if (res.code2 == null) {
|
||||
res.code2 = res.code;
|
||||
}
|
||||
res.scryfallCode = metadata.get("ScryfallCode");
|
||||
if (res.scryfallCode == null) {
|
||||
res.scryfallCode = res.code;
|
||||
}
|
||||
res.cardsLanguage = metadata.get("CardLang");
|
||||
if (res.cardsLanguage == null) {
|
||||
res.cardsLanguage = "en";
|
||||
}
|
||||
|
||||
res.otherMap = otherMap;
|
||||
res.boosterArts = metadata.getInt("BoosterCovers", 1);
|
||||
|
||||
String boosterDesc = metadata.get("Booster");
|
||||
|
||||
@@ -838,7 +778,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
initAliases(E); //Made a method in case the system changes, so it's consistent.
|
||||
}
|
||||
CardEdition customBucket = new CardEdition("2990-01-01", "USER", "USER",
|
||||
Type.CUSTOM_SET, "USER", FoilType.NOT_SUPPORTED, new EditionEntry[]{});
|
||||
Type.CUSTOM_SET, "USER", FoilType.NOT_SUPPORTED, new CardInSet[]{});
|
||||
this.add(customBucket);
|
||||
initAliases(customBucket);
|
||||
this.lock = true; //Consider it initialized and prevent from writing any more data.
|
||||
@@ -870,7 +810,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
|
||||
public CardEdition getEditionByCodeOrThrow(final String code) {
|
||||
final CardEdition set = this.get(code);
|
||||
if (null == set && code.equals(UNKNOWN_CODE)) //Hardcoded set ??? is not with the others, needs special check.
|
||||
if (null == set && code.equals("???")) //Hardcoded set ??? is not with the others, needs special check.
|
||||
return UNKNOWN;
|
||||
if (null == set) {
|
||||
throw new RuntimeException("Edition with code '" + code + "' not found");
|
||||
@@ -1001,12 +941,4 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean hasBasicLands() {
|
||||
for(String landName : MagicColor.Constant.BASIC_LANDS) {
|
||||
if (null == StaticData.instance().getCommonCards().getCard(landName, this.getCode(), 0))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,10 +285,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
return true;
|
||||
}
|
||||
CardType type = mainPart.getType();
|
||||
if (!type.isLegendary()) {
|
||||
return false;
|
||||
}
|
||||
if (canBeCreature() || type.isVehicle() || type.isSpacecraft()) {
|
||||
if (type.isLegendary() && canBeCreature()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -399,7 +396,6 @@ public final class CardRules implements ICardCharacteristics {
|
||||
}
|
||||
|
||||
public int getSetColorID() {
|
||||
//Could someday generalize this to support other kinds of markings.
|
||||
return setColorID;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -316,12 +315,6 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
return landTypes;
|
||||
}
|
||||
|
||||
public Set<String> getBattleTypes() {
|
||||
if(!isBattle())
|
||||
return Set.of();
|
||||
return subtypes.stream().filter(CardType::isABattleType).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStringType(String t) {
|
||||
if (t.isEmpty()) {
|
||||
@@ -516,7 +509,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
@Override
|
||||
public boolean isAttachment() { return isAura() || isEquipment() || isFortification(); }
|
||||
@Override
|
||||
public boolean isAura() { return hasSubtype("Aura"); }
|
||||
public boolean isAura() { return hasSubtype("Aura"); }
|
||||
@Override
|
||||
public boolean isEquipment() { return hasSubtype("Equipment"); }
|
||||
@Override
|
||||
@@ -529,9 +522,6 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
return hasSubtype("Contraption");
|
||||
}
|
||||
|
||||
public boolean isVehicle() { return hasSubtype("Vehicle"); }
|
||||
public boolean isSpacecraft() { return hasSubtype("Spacecraft"); }
|
||||
|
||||
@Override
|
||||
public boolean isSaga() {
|
||||
return hasSubtype("Saga");
|
||||
|
||||
@@ -16,7 +16,6 @@ public interface CardTypeView extends Iterable<String>, Serializable {
|
||||
|
||||
Set<String> getCreatureTypes();
|
||||
Set<String> getLandTypes();
|
||||
Set<String> getBattleTypes();
|
||||
|
||||
boolean hasStringType(String t);
|
||||
boolean hasType(CoreType type);
|
||||
|
||||
@@ -25,8 +25,6 @@ import forge.util.BinaryUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* <p>CardColor class.</p>
|
||||
@@ -293,8 +291,14 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
final ManaCostShard[] orderedShards = getOrderedShards();
|
||||
return Arrays.stream(orderedShards).map(ManaCostShard::toShortString).collect(Collectors.joining());
|
||||
if (this.orderWeight == -1) {
|
||||
return "n/a";
|
||||
}
|
||||
final String toReturn = MagicColor.toLongString(myColor);
|
||||
if (toReturn.equals(MagicColor.Constant.COLORLESS) && myColor != 0) {
|
||||
return "multi";
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,10 +376,6 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
||||
}
|
||||
}
|
||||
|
||||
public Stream<MagicColor.Color> stream() {
|
||||
return this.toEnumSet().stream();
|
||||
}
|
||||
|
||||
//Get array of mana cost shards for color set in the proper order
|
||||
public ManaCostShard[] getOrderedShards() {
|
||||
return shardOrderLookup[myColor];
|
||||
|
||||
@@ -5,42 +5,43 @@ import forge.item.PaperCard;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Magic Cards Database.
|
||||
* --------------------
|
||||
* This interface defines the general API for Database Access and Cards' Lookup.
|
||||
* <p>
|
||||
* Methods for single Card's lookup currently support three alternative strategies:
|
||||
* 1. [getCard]: Card search based on a single card's attributes
|
||||
* (i.e. name, edition, art, collectorNumber)
|
||||
* <p>
|
||||
* 2. [getCardFromSet]: Card Lookup from a single Expansion set.
|
||||
* Particularly useful in Deck Editors when a specific Set is specified.
|
||||
* <p>
|
||||
* 3. [getCardFromEditions]: Card search considering a predefined `SetPreference` policy and/or a specified Date
|
||||
* when no expansion is specified for a card.
|
||||
* This method is particularly useful for Re-prints whenever no specific
|
||||
* Expansion is specified (e.g. in Deck Import) and a decision should be made
|
||||
* on which card to pick. This methods allows to adopt a SetPreference selection
|
||||
* policy to make this decision.
|
||||
* <p>
|
||||
* The API also includes methods to fetch Collection of Card (i.e. PaperCard instances):
|
||||
* - all cards (no filter)
|
||||
* - all unique cards (by name)
|
||||
* - all prints of a single card
|
||||
* - all cards from a single Expansion Set
|
||||
* - all cards compliant with a filter condition (i.e. Predicate)
|
||||
* <p>
|
||||
* Finally, various utility methods are supported:
|
||||
* - Get the foil version of a Card (if Any)
|
||||
* - Get the Order Number of a Card in an Expansion Set
|
||||
* - Get the number of Print/Arts for a card in a Set (useful for those exp. having multiple arts)
|
||||
* */
|
||||
public interface ICardDatabase extends Iterable<PaperCard> {
|
||||
/**
|
||||
* Magic Cards Database.
|
||||
* --------------------
|
||||
* This interface defines the general API for Database Access and Cards' Lookup.
|
||||
*
|
||||
* Methods for single Card's lookup currently support three alternative strategies:
|
||||
* 1. [getCard]: Card search based on a single card's attributes
|
||||
* (i.e. name, edition, art, collectorNumber)
|
||||
*
|
||||
* 2. [getCardFromSet]: Card Lookup from a single Expansion set.
|
||||
* Particularly useful in Deck Editors when a specific Set is specified.
|
||||
*
|
||||
* 3. [getCardFromEditions]: Card search considering a predefined `SetPreference` policy and/or a specified Date
|
||||
* when no expansion is specified for a card.
|
||||
* This method is particularly useful for Re-prints whenever no specific
|
||||
* Expansion is specified (e.g. in Deck Import) and a decision should be made
|
||||
* on which card to pick. This methods allows to adopt a SetPreference selection
|
||||
* policy to make this decision.
|
||||
*
|
||||
* The API also includes methods to fetch Collection of Card (i.e. PaperCard instances):
|
||||
* - all cards (no filter)
|
||||
* - all unique cards (by name)
|
||||
* - all prints of a single card
|
||||
* - all cards from a single Expansion Set
|
||||
* - all cards compliant with a filter condition (i.e. Predicate)
|
||||
*
|
||||
* Finally, various utility methods are supported:
|
||||
* - Get the foil version of a Card (if Any)
|
||||
* - Get the Order Number of a Card in an Expansion Set
|
||||
* - Get the number of Print/Arts for a card in a Set (useful for those exp. having multiple arts)
|
||||
* */
|
||||
|
||||
/* SINGLE CARD RETRIEVAL METHODS
|
||||
* ============================= */
|
||||
// 1. Card Lookup by attributes
|
||||
@@ -49,20 +50,22 @@ public interface ICardDatabase extends Iterable<PaperCard> {
|
||||
PaperCard getCard(String cardName, String edition, int artIndex);
|
||||
// [NEW Methods] Including the card CollectorNumber as criterion for DB lookup
|
||||
PaperCard getCard(String cardName, String edition, String collectorNumber);
|
||||
PaperCard getCard(String cardName, String edition, int artIndex, Map<String, String> flags);
|
||||
PaperCard getCard(String cardName, String edition, String collectorNumber, Map<String, String> flags);
|
||||
PaperCard getCard(String cardName, String edition, int artIndex, String collectorNumber);
|
||||
PaperCard getCard(String cardName, String edition, int artIndex, Set<String> colorID);
|
||||
|
||||
// 2. Card Lookup from a single Expansion Set
|
||||
PaperCard getCardFromSet(String cardName, CardEdition edition, boolean isFoil); // NOT yet used, included for API symmetry
|
||||
PaperCard getCardFromSet(String cardName, CardEdition edition, String collectorNumber, boolean isFoil);
|
||||
PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, boolean isFoil);
|
||||
PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil);
|
||||
PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil, Set<String> colorID);
|
||||
|
||||
// 3. Card lookup based on CardArtPreference Selection Policy
|
||||
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference);
|
||||
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, Predicate<PaperCard> filter);
|
||||
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex);
|
||||
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex, Predicate<PaperCard> filter);
|
||||
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex, Set<String> colorID);
|
||||
|
||||
// 4. Specialised Card Lookup on CardArtPreference Selection and Release Date
|
||||
PaperCard getCardFromEditionsReleasedBefore(String cardName, CardArtPreference artPreference, Date releaseDate);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package forge.card;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import forge.deck.DeckRecognizer;
|
||||
|
||||
/**
|
||||
* Holds byte values for each color magic has.
|
||||
@@ -188,12 +187,6 @@ public final class MagicColor {
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getLocalizedName() {
|
||||
//Should probably move some of this logic back here, or at least to a more general location.
|
||||
return DeckRecognizer.getLocalisedMagicColorName(getName());
|
||||
}
|
||||
|
||||
public byte getColormask() {
|
||||
return colormask;
|
||||
}
|
||||
|
||||
@@ -94,7 +94,6 @@ public enum ManaCostShard {
|
||||
/** The cmpc. */
|
||||
private final float cmpc;
|
||||
private final String stringValue;
|
||||
private final String shortStringValue;
|
||||
|
||||
/** The image key. */
|
||||
private final String imageKey;
|
||||
@@ -126,7 +125,6 @@ public enum ManaCostShard {
|
||||
this.cmc = this.getCMC();
|
||||
this.cmpc = this.getCmpCost();
|
||||
this.stringValue = "{" + sValue + "}";
|
||||
this.shortStringValue = sValue;
|
||||
this.imageKey = imgKey;
|
||||
}
|
||||
|
||||
@@ -234,21 +232,16 @@ public enum ManaCostShard {
|
||||
return ManaCostShard.valueOf(atoms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the string representation of this shard - e.g. "{W}" "{2/U}" "{G/P}"
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public final String toString() {
|
||||
return this.stringValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The string representation of this shard without brackets - e.g. "W" "2/U" "G/P"
|
||||
*/
|
||||
public final String toShortString() {
|
||||
return this.shortStringValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cmc.
|
||||
*
|
||||
|
||||
@@ -52,10 +52,7 @@ public class CardPool extends ItemPool<PaperCard> {
|
||||
|
||||
public void add(final String cardRequest, final int amount) {
|
||||
CardDb.CardRequest request = CardDb.CardRequest.fromString(cardRequest);
|
||||
if(request.collectorNumber != null && !request.collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER))
|
||||
this.add(CardDb.CardRequest.compose(request.cardName, request.isFoil), request.edition, request.collectorNumber, amount, false, request.flags);
|
||||
else
|
||||
this.add(CardDb.CardRequest.compose(request.cardName, request.isFoil), request.edition, request.artIndex, amount, false, request.flags);
|
||||
this.add(CardDb.CardRequest.compose(request.cardName, request.isFoil), request.edition, request.artIndex, amount, false, request.colorID);
|
||||
}
|
||||
|
||||
public void add(final String cardName, final String setCode) {
|
||||
@@ -74,20 +71,7 @@ public class CardPool extends ItemPool<PaperCard> {
|
||||
public void add(String cardName, String setCode, int artIndex, final int amount) {
|
||||
this.add(cardName, setCode, artIndex, amount, false, null);
|
||||
}
|
||||
private void add(String cardName, String setCode, String collectorNumber, final int amount, boolean addAny, Map<String, String> flags) {
|
||||
Map<String, CardDb> dbs = StaticData.instance().getAvailableDatabases();
|
||||
for (Map.Entry<String, CardDb> entry: dbs.entrySet()){
|
||||
CardDb db = entry.getValue();
|
||||
PaperCard paperCard = db.getCard(cardName, setCode, collectorNumber, flags);
|
||||
if (paperCard != null) {
|
||||
this.add(paperCard, amount);
|
||||
return;
|
||||
}
|
||||
}
|
||||
//Failed to find it. Fall back accordingly?
|
||||
this.add(cardName, setCode, IPaperCard.NO_ART_INDEX, amount, addAny, flags);
|
||||
}
|
||||
private void add(String cardName, String setCode, int artIndex, final int amount, boolean addAny, Map<String, String> flags) {
|
||||
public void add(String cardName, String setCode, int artIndex, final int amount, boolean addAny, Set<String> colorID) {
|
||||
Map<String, CardDb> dbs = StaticData.instance().getAvailableDatabases();
|
||||
PaperCard paperCard = null;
|
||||
String selectedDbName = "";
|
||||
@@ -97,7 +81,7 @@ public class CardPool extends ItemPool<PaperCard> {
|
||||
for (Map.Entry<String, CardDb> entry: dbs.entrySet()){
|
||||
String dbName = entry.getKey();
|
||||
CardDb db = entry.getValue();
|
||||
paperCard = db.getCard(cardName, setCode, artIndex, flags);
|
||||
paperCard = db.getCard(cardName, setCode, artIndex, colorID);
|
||||
if (paperCard != null) {
|
||||
selectedDbName = dbName;
|
||||
break;
|
||||
@@ -139,7 +123,7 @@ public class CardPool extends ItemPool<PaperCard> {
|
||||
int cnt = artGroups[i - 1];
|
||||
if (cnt <= 0)
|
||||
continue;
|
||||
PaperCard randomCard = cardDb.getCard(cardName, setCode, i, flags);
|
||||
PaperCard randomCard = cardDb.getCard(cardName, setCode, i, colorID);
|
||||
this.add(randomCard, cnt);
|
||||
}
|
||||
}
|
||||
@@ -446,6 +430,7 @@ public class CardPool extends ItemPool<PaperCard> {
|
||||
public String toCardList(String separator) {
|
||||
List<Entry<PaperCard, Integer>> main2sort = Lists.newArrayList(this);
|
||||
main2sort.sort(ItemPoolSorter.BY_NAME_THEN_SET);
|
||||
final CardDb commonDb = StaticData.instance().getCommonCards();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
boolean isFirst = true;
|
||||
@@ -456,8 +441,10 @@ public class CardPool extends ItemPool<PaperCard> {
|
||||
else
|
||||
isFirst = false;
|
||||
|
||||
CardDb db = !e.getKey().getRules().isVariant() ? commonDb : StaticData.instance().getVariantCards();
|
||||
sb.append(e.getValue()).append(" ");
|
||||
sb.append(CardDb.CardRequest.compose(e.getKey()));
|
||||
db.appendCardToStringBuilder(e.getKey(), sb);
|
||||
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -476,4 +463,20 @@ public class CardPool extends ItemPool<PaperCard> {
|
||||
}
|
||||
return filteredPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a predicate to this CardPool's cards.
|
||||
* @param predicate the Predicate to apply to this CardPool
|
||||
* @return a new CardPool made from this CardPool with only the cards that agree with the provided Predicate
|
||||
*/
|
||||
public CardPool getFilteredPoolWithCardsCount(Predicate<PaperCard> predicate) {
|
||||
CardPool filteredPool = new CardPool();
|
||||
for (Entry<PaperCard, Integer> entry : this.items.entrySet()) {
|
||||
PaperCard pc = entry.getKey();
|
||||
int count = entry.getValue();
|
||||
if (predicate.test(pc))
|
||||
filteredPool.add(pc, count);
|
||||
}
|
||||
return filteredPool;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
|
||||
Map<String, List<String>> referenceDeckLoadingMap;
|
||||
if (deferredSections != null) {
|
||||
this.normalizeDeferredSections();
|
||||
this.validateDeferredSections();
|
||||
referenceDeckLoadingMap = new HashMap<>(this.deferredSections);
|
||||
} else
|
||||
referenceDeckLoadingMap = new HashMap<>(loadedSections);
|
||||
@@ -267,7 +267,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
continue;
|
||||
final List<String> cardsInSection = s.getValue();
|
||||
ArrayList<String> cardNamesWithNoEdition = getAllCardNamesWithNoSpecifiedEdition(cardsInSection);
|
||||
if (!cardNamesWithNoEdition.isEmpty()) {
|
||||
if (cardNamesWithNoEdition.size() > 0) {
|
||||
includeCardsFromUnspecifiedSet = true;
|
||||
if (smartCardArtSelection)
|
||||
cardsWithNoEdition.put(sec, cardNamesWithNoEdition);
|
||||
@@ -281,10 +281,10 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
optimiseCardArtSelectionInDeckSections(cardsWithNoEdition);
|
||||
}
|
||||
|
||||
private void normalizeDeferredSections() {
|
||||
private void validateDeferredSections() {
|
||||
/*
|
||||
Construct a temporary (DeckSection, CardPool) Maps, to be sanitised and finalised
|
||||
before copying into `this.parts`. This sanitization is applied because of the
|
||||
before copying into `this.parts`. This sanitisation is applied because of the
|
||||
validation schema introduced in DeckSections.
|
||||
*/
|
||||
Map<String, List<String>> validatedSections = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
@@ -296,33 +296,61 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
}
|
||||
|
||||
final List<String> cardsInSection = s.getValue();
|
||||
List<Pair<String, Integer>> originalCardRequests = CardPool.processCardList(cardsInSection);
|
||||
CardPool pool = CardPool.fromCardList(cardsInSection);
|
||||
if (pool.countDistinct() == 0)
|
||||
continue; // pool empty, no card has been found!
|
||||
|
||||
List<String> validatedSection = validatedSections.computeIfAbsent(s.getKey(), (k) -> new ArrayList<>());
|
||||
for (Entry<PaperCard, Integer> entry : pool) {
|
||||
PaperCard card = entry.getKey();
|
||||
String normalizedRequest = getPoolRequest(entry);
|
||||
if(deckSection.validate(card))
|
||||
validatedSection.add(normalizedRequest);
|
||||
else {
|
||||
// Card was in the wrong section. Move it to the right section.
|
||||
DeckSection cardSection = DeckSection.matchingSection(card);
|
||||
assert(cardSection.validate(card)); //Card doesn't fit in the matchingSection?
|
||||
List<String> sectionCardList = validatedSections.computeIfAbsent(cardSection.name(), (k) -> new ArrayList<>());
|
||||
sectionCardList.add(normalizedRequest);
|
||||
}
|
||||
// Filter pool by applying DeckSection Validation schema for Card Types (to avoid inconsistencies)
|
||||
CardPool filteredPool = pool.getFilteredPoolWithCardsCount(deckSection::validate);
|
||||
// Add all the cards from ValidPool anyway!
|
||||
List<String> whiteList = validatedSections.getOrDefault(s.getKey(), null);
|
||||
if (whiteList == null)
|
||||
whiteList = new ArrayList<>();
|
||||
for (Entry<PaperCard, Integer> entry : filteredPool) {
|
||||
String poolRequest = getPoolRequest(entry, originalCardRequests);
|
||||
whiteList.add(poolRequest);
|
||||
}
|
||||
validatedSections.put(s.getKey(), whiteList);
|
||||
|
||||
if (filteredPool.countDistinct() != pool.countDistinct()) {
|
||||
CardPool blackList = pool.getFilteredPoolWithCardsCount(input -> !(deckSection.validate(input)));
|
||||
|
||||
for (Entry<PaperCard, Integer> entry : blackList) {
|
||||
DeckSection cardSection = DeckSection.matchingSection(entry.getKey());
|
||||
String poolRequest = getPoolRequest(entry, originalCardRequests);
|
||||
List<String> sectionCardList = validatedSections.getOrDefault(cardSection.name(), null);
|
||||
if (sectionCardList == null)
|
||||
sectionCardList = new ArrayList<>();
|
||||
sectionCardList.add(poolRequest);
|
||||
validatedSections.put(cardSection.name(), sectionCardList);
|
||||
} // end for blacklist
|
||||
} // end if
|
||||
} // end main for on deferredSections
|
||||
|
||||
// Overwrite deferredSections
|
||||
this.deferredSections = validatedSections;
|
||||
}
|
||||
|
||||
private String getPoolRequest(Entry<PaperCard, Integer> entry) {
|
||||
private String getPoolRequest(Entry<PaperCard, Integer> entry, List<Pair<String, Integer>> originalCardRequests) {
|
||||
PaperCard card = entry.getKey();
|
||||
int amount = entry.getValue();
|
||||
String poolCardRequest = CardDb.CardRequest.compose(entry.getKey());
|
||||
String poolCardRequest = CardDb.CardRequest.compose(
|
||||
card.isFoil() ? CardDb.CardRequest.compose(card.getName(), true) : card.getName(),
|
||||
card.getEdition(), card.getArtIndex(), card.getColorID());
|
||||
String originalRequestCandidate = null;
|
||||
for (Pair<String, Integer> originalRequest : originalCardRequests){
|
||||
String cardRequest = originalRequest.getLeft();
|
||||
if (!StringUtils.startsWithIgnoreCase(poolCardRequest, cardRequest))
|
||||
continue;
|
||||
originalRequestCandidate = cardRequest;
|
||||
int cardAmount = originalRequest.getRight();
|
||||
if (amount == cardAmount)
|
||||
return String.format("%d %s", cardAmount, cardRequest);
|
||||
}
|
||||
// This is just in case, it should never happen as we're
|
||||
if (originalRequestCandidate != null)
|
||||
return String.format("%d %s", amount, originalRequestCandidate);
|
||||
return String.format("%d %s", amount, poolCardRequest);
|
||||
}
|
||||
|
||||
|
||||
@@ -987,7 +987,7 @@ public class DeckRecognizer {
|
||||
private static String getMagicColourLabel(MagicColor.Color magicColor) {
|
||||
if (magicColor == null) // Multicolour
|
||||
return String.format("%s {W}{U}{B}{R}{G}", getLocalisedMagicColorName("Multicolour"));
|
||||
return String.format("%s %s", magicColor.getLocalizedName(), magicColor.getSymbol());
|
||||
return String.format("%s %s", getLocalisedMagicColorName(magicColor.getName()), magicColor.getSymbol());
|
||||
}
|
||||
|
||||
private static final HashMap<Integer, String> manaSymbolsMap = new HashMap<Integer, String>() {{
|
||||
@@ -1006,8 +1006,8 @@ public class DeckRecognizer {
|
||||
if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS
|
||||
|| magicColor1 == MagicColor.Color.COLORLESS)
|
||||
return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2));
|
||||
String localisedName1 = magicColor1.getLocalizedName();
|
||||
String localisedName2 = magicColor2.getLocalizedName();
|
||||
String localisedName1 = getLocalisedMagicColorName(magicColor1.getName());
|
||||
String localisedName2 = getLocalisedMagicColorName(magicColor2.getName());
|
||||
String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColormask() | magicColor2.getColormask());
|
||||
return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol);
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ public enum DeckSection {
|
||||
CardType t = card.getRules().getType();
|
||||
// NOTE: Same rules applies to both Deck and Side, despite "Conspiracy cards" are allowed
|
||||
// in the SideBoard (see Rule 313.2)
|
||||
// Those will be matched later, in case (see `Deck::normalizeDeferredSections`)
|
||||
// Those will be matched later, in case (see `Deck::validateDeferredSections`)
|
||||
return !t.isConspiracy() && !t.isDungeon() && !t.isPhenomenon() && !t.isPlane() && !t.isScheme() && !t.isVanguard();
|
||||
};
|
||||
|
||||
|
||||
@@ -61,8 +61,6 @@ public class DeckSerializer {
|
||||
}
|
||||
|
||||
for(Entry<DeckSection, CardPool> s : d) {
|
||||
if(s.getValue().isEmpty())
|
||||
continue;
|
||||
out.add(TextUtil.enclosedBracket(s.getKey().toString()));
|
||||
out.add(s.getValue().toCardList(System.lineSeparator()));
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ package forge.item;
|
||||
|
||||
import forge.card.CardRarity;
|
||||
import forge.card.CardRules;
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.ICardFace;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
|
||||
public interface IPaperCard extends InventoryItem, Serializable {
|
||||
|
||||
@@ -20,7 +20,7 @@ public interface IPaperCard extends InventoryItem, Serializable {
|
||||
String getEdition();
|
||||
String getCollectorNumber();
|
||||
String getFunctionalVariant();
|
||||
ColorSet getMarkedColors();
|
||||
Set<String> getColorID();
|
||||
int getArtIndex();
|
||||
boolean isFoil();
|
||||
boolean isToken();
|
||||
|
||||
@@ -26,9 +26,10 @@ import forge.util.Localizer;
|
||||
import forge.util.TextUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A lightweight version of a card that matches real-world cards, to use outside of games (eg. inventory, decks, trade).
|
||||
@@ -38,7 +39,6 @@ import java.util.stream.Collectors;
|
||||
* @author Forge
|
||||
*/
|
||||
public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet, IPaperCard {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 2942081982620691205L;
|
||||
|
||||
// Reference to rules
|
||||
@@ -55,15 +55,16 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
private String artist;
|
||||
private final int artIndex;
|
||||
private final boolean foil;
|
||||
private final PaperCardFlags flags;
|
||||
private final String sortableName;
|
||||
private Boolean hasImage;
|
||||
private final boolean noSell;
|
||||
private Set<String> colorID;
|
||||
private String sortableName;
|
||||
private final String functionalVariant;
|
||||
|
||||
// Calculated fields are below:
|
||||
private transient CardRarity rarity; // rarity is given in ctor when set is assigned
|
||||
// Reference to a new instance of Self, but foiled!
|
||||
private transient PaperCard foiledVersion, noSellVersion, flaglessVersion;
|
||||
private transient Boolean hasImage;
|
||||
private transient PaperCard foiledVersion, noSellVersion;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@@ -88,8 +89,8 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColorSet getMarkedColors() {
|
||||
return this.flags.markedColors;
|
||||
public Set<String> getColorID() {
|
||||
return colorID;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -146,32 +147,32 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
return unFoiledVersion;
|
||||
}
|
||||
public PaperCard getNoSellVersion() {
|
||||
if (this.flags.noSellValue)
|
||||
if (this.noSell)
|
||||
return this;
|
||||
|
||||
if (this.noSellVersion == null)
|
||||
this.noSellVersion = new PaperCard(this, this.flags.withNoSellValueFlag(true));
|
||||
if (this.noSellVersion == null) {
|
||||
this.noSellVersion = new PaperCard(this.rules, this.edition, this.rarity,
|
||||
this.artIndex, this.foil, String.valueOf(collectorNumber), this.artist, this.functionalVariant, true);
|
||||
}
|
||||
return this.noSellVersion;
|
||||
}
|
||||
|
||||
public PaperCard copyWithoutFlags() {
|
||||
if(this.flaglessVersion == null) {
|
||||
if(this.flags == PaperCardFlags.IDENTITY_FLAGS)
|
||||
this.flaglessVersion = this;
|
||||
else
|
||||
this.flaglessVersion = new PaperCard(this, null);
|
||||
}
|
||||
return flaglessVersion;
|
||||
}
|
||||
public PaperCard copyWithFlags(Map<String, String> flags) {
|
||||
if(flags == null || flags.isEmpty())
|
||||
return this.copyWithoutFlags();
|
||||
return new PaperCard(this, new PaperCardFlags(flags));
|
||||
}
|
||||
public PaperCard copyWithMarkedColors(ColorSet colors) {
|
||||
if(Objects.equals(colors, this.flags.markedColors))
|
||||
public PaperCard getSellable() {
|
||||
if (!this.noSell)
|
||||
return this;
|
||||
return new PaperCard(this, this.flags.withMarkedColors(colors));
|
||||
|
||||
PaperCard sellable = new PaperCard(this.rules, this.edition, this.rarity,
|
||||
this.artIndex, this.foil, String.valueOf(collectorNumber), this.artist, this.functionalVariant, false);
|
||||
return sellable;
|
||||
}
|
||||
public PaperCard getColorIDVersion(Set<String> colors) {
|
||||
if (colors == null && this.colorID == null)
|
||||
return this;
|
||||
if (this.colorID != null && this.colorID.equals(colors))
|
||||
return this;
|
||||
if (colors != null && colors.equals(this.colorID))
|
||||
return this;
|
||||
return new PaperCard(this.rules, this.edition, this.rarity,
|
||||
this.artIndex, this.foil, String.valueOf(collectorNumber), this.artist, this.functionalVariant, this.noSell, colors);
|
||||
}
|
||||
@Override
|
||||
public String getItemType() {
|
||||
@@ -179,12 +180,8 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
return localizer.getMessage("lblCard");
|
||||
}
|
||||
|
||||
public PaperCardFlags getMarkedFlags() {
|
||||
return this.flags;
|
||||
}
|
||||
|
||||
public boolean hasNoSellValue() {
|
||||
return this.flags.noSellValue;
|
||||
public boolean isNoSell() {
|
||||
return noSell;
|
||||
}
|
||||
public boolean hasImage() {
|
||||
return hasImage(false);
|
||||
@@ -201,41 +198,38 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
IPaperCard.NO_COLLECTOR_NUMBER, IPaperCard.NO_ARTIST_NAME, IPaperCard.NO_FUNCTIONAL_VARIANT);
|
||||
}
|
||||
|
||||
public PaperCard(final PaperCard copyFrom, final PaperCardFlags flags) {
|
||||
this(copyFrom.rules, copyFrom.edition, copyFrom.rarity, copyFrom.artIndex, copyFrom.foil, copyFrom.collectorNumber,
|
||||
copyFrom.artist, copyFrom.functionalVariant, flags);
|
||||
this.flaglessVersion = copyFrom.flaglessVersion;
|
||||
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0,
|
||||
final int artIndex0, final boolean foil0, final String collectorNumber0,
|
||||
final String artist0, final String functionalVariant) {
|
||||
this(rules0, edition0, rarity0, artIndex0, foil0, collectorNumber0, artist0, functionalVariant, false);
|
||||
}
|
||||
|
||||
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0,
|
||||
final int artIndex0, final boolean foil0, final String collectorNumber0,
|
||||
final String artist0, final String functionalVariant) {
|
||||
this(rules0, edition0, rarity0, artIndex0, foil0, collectorNumber0, artist0, functionalVariant, null);
|
||||
final String artist0, final String functionalVariant, final boolean noSell0) {
|
||||
this(rules0, edition0, rarity0, artIndex0, foil0, collectorNumber0, artist0, functionalVariant, noSell0, null);
|
||||
}
|
||||
|
||||
protected PaperCard(final CardRules rules, final String edition, final CardRarity rarity,
|
||||
final int artIndex, final boolean foil, final String collectorNumber,
|
||||
final String artist, final String functionalVariant, final PaperCardFlags flags) {
|
||||
if (rules == null || edition == null || rarity == null) {
|
||||
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0,
|
||||
final int artIndex0, final boolean foil0, final String collectorNumber0,
|
||||
final String artist0, final String functionalVariant, final boolean noSell0, final Set<String> colorID0) {
|
||||
if (rules0 == null || edition0 == null || rarity0 == null) {
|
||||
throw new IllegalArgumentException("Cannot create card without rules, edition or rarity");
|
||||
}
|
||||
this.rules = rules;
|
||||
name = rules.getName();
|
||||
this.edition = edition;
|
||||
this.artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX);
|
||||
this.foil = foil;
|
||||
this.rarity = rarity;
|
||||
this.artist = TextUtil.normalizeText(artist);
|
||||
this.collectorNumber = (collectorNumber != null && !collectorNumber.isEmpty()) ? collectorNumber : IPaperCard.NO_COLLECTOR_NUMBER;
|
||||
rules = rules0;
|
||||
name = rules0.getName();
|
||||
edition = edition0;
|
||||
artIndex = Math.max(artIndex0, IPaperCard.DEFAULT_ART_INDEX);
|
||||
foil = foil0;
|
||||
rarity = rarity0;
|
||||
artist = TextUtil.normalizeText(artist0);
|
||||
collectorNumber = (collectorNumber0 != null) && (collectorNumber0.length() > 0) ? collectorNumber0 : IPaperCard.NO_COLLECTOR_NUMBER;
|
||||
// If the user changes the language this will make cards sort by the old language until they restart the game.
|
||||
// This is a good tradeoff
|
||||
sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(rules.getName()));
|
||||
sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(rules0.getName()));
|
||||
this.functionalVariant = functionalVariant != null ? functionalVariant : IPaperCard.NO_FUNCTIONAL_VARIANT;
|
||||
|
||||
if(flags == null || flags.equals(PaperCardFlags.IDENTITY_FLAGS))
|
||||
this.flags = PaperCardFlags.IDENTITY_FLAGS;
|
||||
else
|
||||
this.flags = flags;
|
||||
noSell = noSell0;
|
||||
colorID = colorID0;
|
||||
}
|
||||
|
||||
public static PaperCard FAKE_CARD = new PaperCard(CardRules.getUnsupportedCardNamed("Fake Card"), "Fake Edition", CardRarity.Common);
|
||||
@@ -262,7 +256,8 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
}
|
||||
if (!getCollectorNumber().equals(other.getCollectorNumber()))
|
||||
return false;
|
||||
if (!Objects.equals(flags, other.flags))
|
||||
// colorID can be NULL
|
||||
if (getColorID() != other.getColorID())
|
||||
return false;
|
||||
return (other.foil == foil) && (other.artIndex == artIndex);
|
||||
}
|
||||
@@ -274,7 +269,13 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, edition, collectorNumber, artIndex, foil, flags);
|
||||
final int code = (name.hashCode() * 11) + (edition.hashCode() * 59) +
|
||||
(artIndex * 2) + (getCollectorNumber().hashCode() * 383);
|
||||
final int id = Optional.ofNullable(colorID).map(Set::hashCode).orElse(0);
|
||||
if (foil) {
|
||||
return code + id + 1;
|
||||
}
|
||||
return code + id;
|
||||
}
|
||||
|
||||
// FIXME: Check
|
||||
@@ -306,7 +307,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
String collectorNumber = collectorNumber0;
|
||||
if (collectorNumber.equals(NO_COLLECTOR_NUMBER))
|
||||
collectorNumber = null;
|
||||
return CardEdition.getSortableCollectorNumber(collectorNumber);
|
||||
return CardEdition.CardInSet.getSortableCollectorNumber(collectorNumber);
|
||||
}
|
||||
|
||||
private String sortableCNKey = null;
|
||||
@@ -338,7 +339,6 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
return Integer.compare(artIndex, o.getArtIndex());
|
||||
}
|
||||
|
||||
@Serial
|
||||
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
|
||||
// default deserialization
|
||||
ois.defaultReadObject();
|
||||
@@ -347,42 +347,13 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
if (pc == null) {
|
||||
pc = StaticData.instance().getVariantCards().getCard(name, edition, artIndex);
|
||||
if (pc == null) {
|
||||
System.out.println("PaperCard: " + name + " not found with set and index " + edition + ", " + artIndex);
|
||||
pc = readObjectAlternate(name, edition);
|
||||
if (pc == null) {
|
||||
throw new IOException(TextUtil.concatWithSpace("Card", name, "not found with set and index", edition, Integer.toString(artIndex)));
|
||||
}
|
||||
System.out.println("Alternate object found: " + pc.getName() + ", " + pc.getEdition() + ", " + pc.getArtIndex());
|
||||
throw new IOException(TextUtil.concatWithSpace("Card", name, "not found"));
|
||||
}
|
||||
}
|
||||
rules = pc.getRules();
|
||||
rarity = pc.getRarity();
|
||||
}
|
||||
|
||||
private IPaperCard readObjectAlternate(String name, String edition) throws ClassNotFoundException, IOException {
|
||||
IPaperCard pc = StaticData.instance().getCommonCards().getCard(name, edition);
|
||||
if (pc == null) {
|
||||
pc = StaticData.instance().getVariantCards().getCard(name, edition);
|
||||
}
|
||||
|
||||
if (pc == null) {
|
||||
pc = StaticData.instance().getCommonCards().getCard(name);
|
||||
if (pc == null) {
|
||||
pc = StaticData.instance().getVariantCards().getCard(name);
|
||||
}
|
||||
}
|
||||
|
||||
return pc;
|
||||
}
|
||||
|
||||
@Serial
|
||||
private Object readResolve() throws ObjectStreamException {
|
||||
//If we deserialize an old PaperCard with no flags, reinitialize as a fresh copy to set default flags.
|
||||
if(this.flags == null)
|
||||
return new PaperCard(this, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImageKey(boolean altState) {
|
||||
String normalizedName = StringUtils.stripAccents(name);
|
||||
@@ -522,88 +493,4 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
public boolean isRebalanced() {
|
||||
return StaticData.instance().isRebalanced(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains properties of a card which distinguish it from an otherwise identical copy of the card with the same
|
||||
* name, edition, and collector number. Examples include permanent markings on the card, and flags for Adventure
|
||||
* mode.
|
||||
*/
|
||||
public static class PaperCardFlags implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = -3924720485840169336L;
|
||||
|
||||
/**
|
||||
* Chosen colors, for cards like Cryptic Spires.
|
||||
*/
|
||||
public final ColorSet markedColors;
|
||||
/**
|
||||
* Removes the sell value of the card in Adventure mode.
|
||||
*/
|
||||
public final boolean noSellValue;
|
||||
|
||||
//TODO: Could probably move foil here.
|
||||
|
||||
static final PaperCardFlags IDENTITY_FLAGS = new PaperCardFlags(Map.of());
|
||||
|
||||
protected PaperCardFlags(Map<String, String> flags) {
|
||||
if(flags.containsKey("markedColors"))
|
||||
markedColors = ColorSet.fromNames(flags.get("markedColors").split(""));
|
||||
else
|
||||
markedColors = null;
|
||||
noSellValue = flags.containsKey("noSellValue");
|
||||
}
|
||||
|
||||
//Copy constructor. There are some better ways to do this, and they should be explored once we have more than 4
|
||||
//or 5 fields here. Just need to ensure it's impossible to accidentally change a field while the PaperCardFlags
|
||||
//object is in use.
|
||||
private PaperCardFlags(PaperCardFlags copyFrom, ColorSet markedColors, Boolean noSellValue) {
|
||||
if(markedColors == null)
|
||||
markedColors = copyFrom.markedColors;
|
||||
else if(markedColors.isColorless())
|
||||
markedColors = null;
|
||||
this.markedColors = markedColors;
|
||||
this.noSellValue = noSellValue != null ? noSellValue : copyFrom.noSellValue;
|
||||
}
|
||||
|
||||
public PaperCardFlags withMarkedColors(ColorSet markedColors) {
|
||||
if(markedColors == null)
|
||||
markedColors = ColorSet.getNullColor();
|
||||
return new PaperCardFlags(this, markedColors, null);
|
||||
}
|
||||
|
||||
public PaperCardFlags withNoSellValueFlag(boolean noSellValue) {
|
||||
return new PaperCardFlags(this, null, noSellValue);
|
||||
}
|
||||
|
||||
private Map<String, String> asMap;
|
||||
public Map<String, String> toMap() {
|
||||
if(asMap != null)
|
||||
return asMap;
|
||||
Map<String, String> out = new HashMap<>();
|
||||
if(markedColors != null && !markedColors.isColorless())
|
||||
out.put("markedColors", markedColors.toString());
|
||||
if(noSellValue)
|
||||
out.put("noSellValue", "true");
|
||||
asMap = out;
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.toMap().entrySet().stream()
|
||||
.map((e) -> e.getKey() + "=" + e.getValue())
|
||||
.collect(Collectors.joining("\t"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof PaperCardFlags that)) return false;
|
||||
return noSellValue == that.noSellValue && Objects.equals(markedColors, that.markedColors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(markedColors, noSellValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,11 @@ import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String name;
|
||||
private String collectorNumber;
|
||||
private String artist;
|
||||
private transient CardEdition edition;
|
||||
private ArrayList<String> imageFileName = new ArrayList<>();
|
||||
private transient CardRules cardRules;
|
||||
@@ -55,31 +54,75 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
||||
return makeTokenFileName(fileName);
|
||||
}
|
||||
|
||||
public PaperToken(final CardRules c, CardEdition edition0, String imageFileName, String collectorNumber, String artist) {
|
||||
public static String makeTokenFileName(final CardRules rules, CardEdition edition) {
|
||||
ArrayList<String> build = new ArrayList<>();
|
||||
|
||||
String subtypes = StringUtils.join(rules.getType().getSubtypes(), " ");
|
||||
if (!rules.getName().equals(subtypes)) {
|
||||
return makeTokenFileName(rules.getName());
|
||||
}
|
||||
|
||||
ColorSet colors = rules.getColor();
|
||||
|
||||
if (colors.isColorless()) {
|
||||
build.add("C");
|
||||
} else {
|
||||
String color = "";
|
||||
if (colors.hasWhite()) color += "W";
|
||||
if (colors.hasBlue()) color += "U";
|
||||
if (colors.hasBlack()) color += "B";
|
||||
if (colors.hasRed()) color += "R";
|
||||
if (colors.hasGreen()) color += "G";
|
||||
|
||||
build.add(color);
|
||||
}
|
||||
|
||||
if (rules.getPower() != null && rules.getToughness() != null) {
|
||||
build.add(rules.getPower());
|
||||
build.add(rules.getToughness());
|
||||
}
|
||||
|
||||
String cardTypes = "";
|
||||
if (rules.getType().isArtifact()) cardTypes += "A";
|
||||
if (rules.getType().isEnchantment()) cardTypes += "E";
|
||||
|
||||
if (!cardTypes.isEmpty()) {
|
||||
build.add(cardTypes);
|
||||
}
|
||||
|
||||
build.add(subtypes);
|
||||
|
||||
// Are these keywords sorted?
|
||||
for (String keyword : rules.getMainPart().getKeywords()) {
|
||||
build.add(keyword);
|
||||
}
|
||||
|
||||
if (edition != null) {
|
||||
build.add(edition.getCode());
|
||||
}
|
||||
|
||||
return StringUtils.join(build, "_").replace('*', 'x').toLowerCase();
|
||||
}
|
||||
|
||||
public PaperToken(final CardRules c, CardEdition edition0, String imageFileName) {
|
||||
this.cardRules = c;
|
||||
this.name = c.getName();
|
||||
this.edition = edition0;
|
||||
this.collectorNumber = collectorNumber;
|
||||
this.artist = artist;
|
||||
|
||||
if (collectorNumber != null && !collectorNumber.isEmpty() && edition != null && edition.getTokens().containsKey(imageFileName)) {
|
||||
int idx = 0;
|
||||
// count the one with the same collectorNumber
|
||||
for (CardEdition.EditionEntry t : edition.getTokens().get(imageFileName)) {
|
||||
++idx;
|
||||
if (!t.collectorNumber().equals(collectorNumber)) {
|
||||
continue;
|
||||
}
|
||||
// TODO make better image file names when collector number is known
|
||||
// for the right index, we need to count the ones with wrong collector number too
|
||||
this.imageFileName.add(String.format("%s|%s|%s|%d", imageFileName, edition.getCode(), collectorNumber, idx));
|
||||
}
|
||||
this.artIndex = this.imageFileName.size();
|
||||
} else if (null == edition || CardEdition.UNKNOWN == edition) {
|
||||
this.imageFileName.add(imageFileName);
|
||||
if (edition != null && edition.getTokens().containsKey(imageFileName)) {
|
||||
this.artIndex = edition.getTokens().get(imageFileName);
|
||||
}
|
||||
|
||||
if (imageFileName == null) {
|
||||
// This shouldn't really happen. We can just use the normalized name again for the base image name
|
||||
this.imageFileName.add(makeTokenFileName(c, edition0));
|
||||
} else {
|
||||
// Fallback if CollectorNumber is not used
|
||||
this.imageFileName.add(String.format("%s|%s", imageFileName, edition.getCode()));
|
||||
String formatEdition = null == edition || CardEdition.UNKNOWN == edition ? "" : "_" + edition.getCode().toLowerCase();
|
||||
|
||||
this.imageFileName.add(String.format("%s%s", imageFileName, formatEdition));
|
||||
for (int idx = 2; idx <= this.artIndex; idx++) {
|
||||
this.imageFileName.add(String.format("%s%d%s", imageFileName, idx, formatEdition));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,14 +138,12 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
||||
|
||||
@Override
|
||||
public String getEdition() {
|
||||
return edition != null ? edition.getCode() : CardEdition.UNKNOWN_CODE;
|
||||
return edition != null ? edition.getCode() : "???";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCollectorNumber() {
|
||||
if (collectorNumber.isEmpty())
|
||||
return IPaperCard.NO_COLLECTOR_NUMBER;
|
||||
return collectorNumber;
|
||||
return IPaperCard.NO_COLLECTOR_NUMBER;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,7 +153,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColorSet getMarkedColors() {
|
||||
public Set<String> getColorID() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -137,8 +178,13 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArtist() {
|
||||
return artist;
|
||||
public String getArtist() { /*TODO*/
|
||||
return "";
|
||||
}
|
||||
|
||||
// Unfortunately this is a property of token, cannot move it outside of class
|
||||
public String getImageFilename() {
|
||||
return getImageFilename(1);
|
||||
}
|
||||
|
||||
public String getImageFilename(int idx) {
|
||||
@@ -213,21 +259,24 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
||||
// InventoryItem
|
||||
@Override
|
||||
public String getImageKey(boolean altState) {
|
||||
String suffix = "";
|
||||
if (hasBackFace() && altState) {
|
||||
if (collectorNumber != null && !collectorNumber.isEmpty() && edition != null) {
|
||||
String name = cardRules.getOtherPart().getName().toLowerCase().replace(" token", "").replace(" ", "_");
|
||||
return ImageKeys.getTokenKey(String.format("%s|%s|%s%s", name, edition.getCode(), collectorNumber, ImageKeys.BACKFACE_POSTFIX));
|
||||
if (hasBackFace()) {
|
||||
String edCode = edition != null ? "_" + edition.getCode().toLowerCase() : "";
|
||||
if (altState) {
|
||||
String name = ImageKeys.TOKEN_PREFIX + cardRules.getOtherPart().getName().toLowerCase().replace(" token", "");
|
||||
name.replace(" ", "_");
|
||||
return name + edCode;
|
||||
} else {
|
||||
suffix = ImageKeys.BACKFACE_POSTFIX;
|
||||
String name = ImageKeys.TOKEN_PREFIX + cardRules.getMainPart().getName().toLowerCase().replace(" token", "");
|
||||
name.replace(" ", "_");
|
||||
return name + edCode;
|
||||
}
|
||||
}
|
||||
int idx = MyRandom.getRandom().nextInt(artIndex);
|
||||
return getImageKey(idx) + suffix;
|
||||
return getImageKey(idx);
|
||||
}
|
||||
|
||||
public String getImageKey(int artIndex) {
|
||||
return ImageKeys.getTokenKey(imageFileName.get(artIndex).replace(" ", "_"));
|
||||
return ImageKeys.TOKEN_PREFIX + imageFileName.get(artIndex).replace(" ", "_");
|
||||
}
|
||||
|
||||
public boolean isRebalanced() {
|
||||
|
||||
@@ -401,68 +401,46 @@ public class BoosterGenerator {
|
||||
System.out.println(numCards + " of type " + slotType);
|
||||
|
||||
// For cards that end in '+', attempt to convert this card to foil.
|
||||
boolean convertAllToFoil = slotType.endsWith("+");
|
||||
if (convertAllToFoil) {
|
||||
boolean convertCardFoil = slotType.endsWith("+");
|
||||
if (convertCardFoil) {
|
||||
slotType = slotType.substring(0, slotType.length() - 1);
|
||||
}
|
||||
|
||||
// Unpack Base
|
||||
BoosterSlot boosterSlot = boosterSlots.get(slotType);
|
||||
Map<String, Integer> slotReplacementCount = bulkSlotReplacement(boosterSlot, numCards);
|
||||
String determineSheet = boosterSlot.replaceSlot();
|
||||
|
||||
List<PaperCard> paperCards = Lists.newArrayList();
|
||||
for(Map.Entry<String, Integer> entry : slotReplacementCount.entrySet()) {
|
||||
String determineSheet = entry.getKey();
|
||||
int numCardsToGenerate = entry.getValue();
|
||||
|
||||
if (determineSheet == null || determineSheet.isEmpty() || numCardsToGenerate == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the sheet ends with a '+', convert all cards in replacement section to foil
|
||||
boolean convertThisToFoil = false;
|
||||
if (determineSheet.endsWith("+")) {
|
||||
determineSheet = determineSheet.substring(0, determineSheet.length() - 1);
|
||||
convertThisToFoil = true;
|
||||
}
|
||||
|
||||
String setCode = template.getEdition();
|
||||
PrintSheet ps;
|
||||
try {
|
||||
// Apply the edition to the sheet name by default. We'll try again if thats not a real sheet
|
||||
ps = getPrintSheet(determineSheet + " " + setCode);
|
||||
} catch (Exception e) {
|
||||
ps = getPrintSheet(determineSheet);
|
||||
}
|
||||
if (convertAllToFoil || convertThisToFoil) {
|
||||
for (PaperCard pc : ps.random(numCardsToGenerate, true)) {
|
||||
paperCards.add(pc.getFoiled());
|
||||
}
|
||||
} else {
|
||||
paperCards.addAll(ps.random(numCardsToGenerate, true));
|
||||
}
|
||||
|
||||
result.addAll(paperCards);
|
||||
if (determineSheet.endsWith("+")) {
|
||||
determineSheet = determineSheet.substring(0, determineSheet.length() - 1);
|
||||
convertCardFoil = true;
|
||||
}
|
||||
|
||||
String setCode = template.getEdition();
|
||||
|
||||
// Ok, so we have a sheet now. Most should be standard sheets, but some named edition sheets
|
||||
List<PaperCard> paperCards;
|
||||
PrintSheet ps;
|
||||
try {
|
||||
// Apply the edition to the sheet name by default. We'll try again if thats not a real sheet
|
||||
ps = getPrintSheet(determineSheet + " " + setCode);
|
||||
} catch(Exception e) {
|
||||
ps = getPrintSheet(determineSheet);
|
||||
}
|
||||
if (convertCardFoil) {
|
||||
paperCards = Lists.newArrayList();
|
||||
for(PaperCard pc : ps.random(numCards, true)) {
|
||||
paperCards.add(pc.getFoiled());
|
||||
}
|
||||
} else {
|
||||
paperCards = ps.random(numCards, true);
|
||||
}
|
||||
|
||||
result.addAll(paperCards);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Map<String, Integer> bulkSlotReplacement(BoosterSlot boosterSlot, int numCards) {
|
||||
Map<String, Integer> slotReplacementCount = new HashMap<>();
|
||||
|
||||
for(int i = 0; i < numCards; i++) {
|
||||
String determineSheet = boosterSlot.replaceSlot();
|
||||
if (slotReplacementCount.containsKey(determineSheet)) {
|
||||
slotReplacementCount.put(determineSheet, slotReplacementCount.get(determineSheet) + 1);
|
||||
} else {
|
||||
slotReplacementCount.put(determineSheet, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return slotReplacementCount;
|
||||
}
|
||||
|
||||
private static void ensureGuaranteedCardInBooster(List<PaperCard> result, SealedTemplate template, String boosterMustContain) {
|
||||
// First, see if there's already a card of the given type
|
||||
String[] types = TextUtil.split(boosterMustContain, ' ');
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
package forge.token;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import forge.card.CardDb;
|
||||
import forge.card.CardEdition;
|
||||
import forge.card.CardRules;
|
||||
import forge.item.IPaperCard;
|
||||
import forge.item.PaperToken;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
@@ -28,8 +23,8 @@ public class TokenDb implements ITokenDatabase {
|
||||
|
||||
// The image names should be the same as the script name + _set
|
||||
// If that isn't found, consider falling back to the original token
|
||||
private final Multimap<String, PaperToken> allTokenByName = HashMultimap.create();
|
||||
private final Map<String, PaperToken> extraTokensByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
private final Map<String, PaperToken> tokensByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
private final CardEdition.Collection editions;
|
||||
private final Map<String, CardRules> rulesByName;
|
||||
@@ -43,87 +38,38 @@ public class TokenDb implements ITokenDatabase {
|
||||
return this.rulesByName.containsKey(rule);
|
||||
|
||||
}
|
||||
@Override
|
||||
public PaperToken getToken(String tokenName) {
|
||||
return getToken(tokenName, CardEdition.UNKNOWN.getName());
|
||||
}
|
||||
|
||||
public void preloadTokens() {
|
||||
for (CardEdition edition : this.editions) {
|
||||
for (Map.Entry<String, Collection<CardEdition.EditionEntry>> inSet : edition.getTokens().asMap().entrySet()) {
|
||||
String name = inSet.getKey();
|
||||
String fullName = String.format("%s_%s", name, edition.getCode().toLowerCase());
|
||||
for (CardEdition.EditionEntry t : inSet.getValue()) {
|
||||
allTokenByName.put(fullName, addTokenInSet(edition, name, t));
|
||||
for (String name : edition.getTokens().keySet()) {
|
||||
try {
|
||||
getToken(name, edition.getCode());
|
||||
} catch(Exception e) {
|
||||
System.out.println(name + "_" + edition.getCode() + " defined in Edition file, but not defined as a token script.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean loadTokenFromSet(CardEdition edition, String name) {
|
||||
String fullName = String.format("%s_%s", name, edition.getCode().toLowerCase());
|
||||
if (allTokenByName.containsKey(fullName)) {
|
||||
return true;
|
||||
}
|
||||
if (!edition.getTokens().containsKey(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (CardEdition.EditionEntry t : edition.getTokens().get(name)) {
|
||||
allTokenByName.put(fullName, addTokenInSet(edition, name, t));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected PaperToken addTokenInSet(CardEdition edition, String name, CardEdition.EditionEntry t) {
|
||||
CardRules rules;
|
||||
if (rulesByName.containsKey(name)) {
|
||||
rules = rulesByName.get(name);
|
||||
} else if ("w_2_2_spirit".equals(name) || "w_3_3_spirit".equals(name)) { // Hotfix for Endure Token
|
||||
rules = rulesByName.get("w_x_x_spirit");
|
||||
} else {
|
||||
throw new RuntimeException("wrong token name:" + name);
|
||||
}
|
||||
return new PaperToken(rules, edition, name, t.collectorNumber(), t.artistName());
|
||||
}
|
||||
|
||||
// try all editions to find token
|
||||
protected PaperToken fallbackToken(String name) {
|
||||
for (CardEdition edition : this.editions) {
|
||||
String fullName = String.format("%s_%s", name, edition.getCode().toLowerCase());
|
||||
if (loadTokenFromSet(edition, name)) {
|
||||
return Aggregates.random(allTokenByName.get(fullName));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaperToken getToken(String tokenName) {
|
||||
return getToken(tokenName, CardEdition.UNKNOWN.getCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaperToken getToken(String tokenName, String edition) {
|
||||
CardEdition realEdition = editions.getEditionByCodeOrThrow(edition);
|
||||
String fullName = String.format("%s_%s", tokenName, realEdition.getCode().toLowerCase());
|
||||
String fullName = String.format("%s_%s", tokenName, edition.toLowerCase());
|
||||
|
||||
// token exist in Set, return one at random
|
||||
if (loadTokenFromSet(realEdition, tokenName)) {
|
||||
return Aggregates.random(allTokenByName.get(fullName));
|
||||
}
|
||||
PaperToken fallback = this.fallbackToken(tokenName);
|
||||
if (fallback != null) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (!extraTokensByName.containsKey(fullName)) {
|
||||
if (!tokensByName.containsKey(fullName)) {
|
||||
try {
|
||||
PaperToken pt = new PaperToken(rulesByName.get(tokenName), realEdition, tokenName, "", IPaperCard.NO_ARTIST_NAME);
|
||||
extraTokensByName.put(fullName, pt);
|
||||
PaperToken pt = new PaperToken(rulesByName.get(tokenName), editions.get(edition), tokenName);
|
||||
tokensByName.put(fullName, pt);
|
||||
return pt;
|
||||
} catch(Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return extraTokensByName.get(fullName);
|
||||
return tokensByName.get(fullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -173,7 +119,7 @@ public class TokenDb implements ITokenDatabase {
|
||||
|
||||
@Override
|
||||
public List<PaperToken> getAllTokens() {
|
||||
return new ArrayList<>(allTokenByName.values());
|
||||
return new ArrayList<>(tokensByName.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -193,6 +139,6 @@ public class TokenDb implements ITokenDatabase {
|
||||
|
||||
@Override
|
||||
public Iterator<PaperToken> iterator() {
|
||||
return allTokenByName.values().iterator();
|
||||
return tokensByName.values().iterator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ import forge.item.IPaperCard;
|
||||
import forge.item.PaperCard;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
|
||||
public class ImageUtil {
|
||||
public static float getNearestHQSize(float baseSize, float actualSize) {
|
||||
//get nearest power of actualSize to baseSize so that the image renders good
|
||||
@@ -102,7 +100,7 @@ public class ImageUtil {
|
||||
|
||||
if (includeSet) {
|
||||
String editionAliased = isDownloadUrl ? StaticData.instance().getEditions().getCode2ByCode(edition) : ImageKeys.getSetFolder(edition);
|
||||
if (editionAliased.isEmpty()) //FIXME: Custom Cards Workaround
|
||||
if (editionAliased == "") //FIXME: Custom Cards Workaround
|
||||
editionAliased = edition;
|
||||
return TextUtil.concatNoSpace(editionAliased, "/", fname);
|
||||
} else {
|
||||
@@ -166,7 +164,7 @@ public class ImageUtil {
|
||||
|
||||
public static String getScryfallDownloadUrl(PaperCard cp, String face, String setCode, String langCode, boolean useArtCrop, boolean hyphenateAlchemy){
|
||||
String editionCode;
|
||||
if (setCode != null && !setCode.isEmpty())
|
||||
if ((setCode != null) && (setCode.length() > 0))
|
||||
editionCode = setCode;
|
||||
else
|
||||
editionCode = cp.getEdition().toLowerCase();
|
||||
@@ -194,33 +192,8 @@ public class ImageUtil {
|
||||
String faceParam = "";
|
||||
if (cp.getRules().getOtherPart() != null) {
|
||||
faceParam = (face.equals("back") ? "&face=back" : "&face=front");
|
||||
} else if (cp.getRules().getSplitType() == CardSplitType.Meld
|
||||
&& !cardCollectorNumber.endsWith("a")
|
||||
&& !cardCollectorNumber.endsWith("b")) {
|
||||
// Only the bottom half of a meld card shares a collector number.
|
||||
// Hanweir Garrison EMN already has a appended.
|
||||
cardCollectorNumber += face.equals("back") ? "b" : "a";
|
||||
}
|
||||
|
||||
String cardCollectorNumberEncoded;
|
||||
try {
|
||||
cardCollectorNumberEncoded = URLEncoder.encode(cardCollectorNumber, "UTF-8");
|
||||
} catch (Exception e) {
|
||||
// Unlikely, for the possibility that "UTF-8" is not supported.
|
||||
System.err.println("UTF-8 encoding not supported on this device.");
|
||||
cardCollectorNumberEncoded = cardCollectorNumber;
|
||||
}
|
||||
|
||||
return String.format("%s/%s/%s?format=image&version=%s%s", editionCode, cardCollectorNumberEncoded,
|
||||
langCode, versionParam, faceParam);
|
||||
}
|
||||
|
||||
public static String getScryfallTokenDownloadUrl(String collectorNumber, String setCode, String langCode, String faceParam) {
|
||||
String versionParam = "normal";
|
||||
if (!faceParam.isEmpty()) {
|
||||
faceParam = (faceParam.equals("back") ? "&face=back" : "&face=front");
|
||||
}
|
||||
return String.format("%s/%s/%s?format=image&version=%s%s", setCode, collectorNumber,
|
||||
return String.format("%s/%s/%s?format=image&version=%s%s", editionCode, cardCollectorNumber,
|
||||
langCode, versionParam, faceParam);
|
||||
}
|
||||
|
||||
@@ -237,4 +210,4 @@ public class ImageUtil {
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,13 +269,6 @@ public class ItemPool<T extends InventoryItem> implements Iterable<Entry<T, Inte
|
||||
// need not set out-of-sync: either remove did set, or nothing was removed
|
||||
}
|
||||
|
||||
public void removeIf(Predicate<T> test) {
|
||||
for (final T item : items.keySet()) {
|
||||
if (test.test(item))
|
||||
remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
items.clear();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.03</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -2,14 +2,12 @@ package forge.game;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.MagicColor;
|
||||
@@ -566,23 +564,13 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
return CardView.get(hostCard);
|
||||
}
|
||||
|
||||
protected List<IHasSVars> getSVarFallback(final String name) {
|
||||
List<IHasSVars> result = Lists.newArrayList();
|
||||
|
||||
protected IHasSVars getSVarFallback() {
|
||||
if (this.getKeyword() != null && this.getKeyword().getStatic() != null) {
|
||||
// only do when the keyword has part of the SVar in ins original string
|
||||
if (name == null || this.getKeyword().getOriginal().contains(name)) {
|
||||
// TODO try to add the keyword instead if possible?
|
||||
result.add(this.getKeyword().getStatic());
|
||||
}
|
||||
return this.getKeyword().getStatic();
|
||||
}
|
||||
if (getCardState() != null)
|
||||
result.add(getCardState());
|
||||
result.add(getHostCard());
|
||||
return result;
|
||||
}
|
||||
protected Optional<IHasSVars> findSVar(final String name) {
|
||||
return getSVarFallback(name).stream().filter(f -> f.hasSVar(name)).findFirst();
|
||||
return getCardState();
|
||||
return getHostCard();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -590,12 +578,12 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
if (sVars.containsKey(name)) {
|
||||
return sVars.get(name);
|
||||
}
|
||||
return findSVar(name).map(o -> o.getSVar(name)).orElse("");
|
||||
return getSVarFallback().getSVar(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSVar(final String name) {
|
||||
return sVars.containsKey(name) || findSVar(name).isPresent();
|
||||
return sVars.containsKey(name) || getSVarFallback().hasSVar(name);
|
||||
}
|
||||
|
||||
public Integer getSVarInt(final String name) {
|
||||
@@ -610,21 +598,22 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSVar(final String name, final String value) {
|
||||
public final void setSVar(final String name, final String value) {
|
||||
sVars.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getSVars() {
|
||||
Map<String, String> res = Maps.newHashMap();
|
||||
// TODO reverse the order
|
||||
for (IHasSVars s : getSVarFallback(null)) {
|
||||
res.putAll(s.getSVars());
|
||||
}
|
||||
Map<String, String> res = Maps.newHashMap(getSVarFallback().getSVars());
|
||||
res.putAll(sVars);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getDirectSVars() {
|
||||
return sVars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSVars(Map<String, String> newSVars) {
|
||||
sVars = Maps.newTreeMap();
|
||||
|
||||
@@ -193,9 +193,7 @@ public class ForgeScript {
|
||||
return sa.isCrew();
|
||||
} else if (property.equals("Saddle")) {
|
||||
return sa.isKeyword(Keyword.SADDLE);
|
||||
} else if (property.equals("Station")) {
|
||||
return sa.isKeyword(Keyword.STATION);
|
||||
}else if (property.equals("Cycling")) {
|
||||
} else if (property.equals("Cycling")) {
|
||||
return sa.isCycling();
|
||||
} else if (property.equals("Dash")) {
|
||||
return sa.isDash();
|
||||
|
||||
@@ -958,9 +958,9 @@ public class Game {
|
||||
// if the player who lost was the Monarch, someone else will be the monarch
|
||||
// TODO need to check rules if it should try the next player if able
|
||||
if (p.equals(getPhaseHandler().getPlayerTurn())) {
|
||||
getAction().becomeMonarch(getNextPlayerAfter(p), p.getMonarchSet());
|
||||
getAction().becomeMonarch(getNextPlayerAfter(p), null);
|
||||
} else {
|
||||
getAction().becomeMonarch(getPhaseHandler().getPlayerTurn(), p.getMonarchSet());
|
||||
getAction().becomeMonarch(getPhaseHandler().getPlayerTurn(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -970,9 +970,9 @@ public class Game {
|
||||
// If the player who has the initiative leaves the game on their own turn,
|
||||
// or the active player left the game at the same time, the next player in turn order takes the initiative.
|
||||
if (p.equals(getPhaseHandler().getPlayerTurn())) {
|
||||
getAction().takeInitiative(getNextPlayerAfter(p), p.getInitiativeSet());
|
||||
getAction().takeInitiative(getNextPlayerAfter(p), null);
|
||||
} else {
|
||||
getAction().takeInitiative(getPhaseHandler().getPlayerTurn(), p.getInitiativeSet());
|
||||
getAction().takeInitiative(getPhaseHandler().getPlayerTurn(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1207,24 +1207,17 @@ public class Game {
|
||||
|
||||
public int getCounterAddedThisTurn(CounterType cType, String validPlayer, String validCard, Card source, Player sourceController, CardTraitBase ctb) {
|
||||
int result = 0;
|
||||
Set<CounterType> types = null;
|
||||
if (cType == null) {
|
||||
types = countersAddedThisTurn.rowKeySet();
|
||||
} else if (!countersAddedThisTurn.containsRow(cType)) {
|
||||
if (!countersAddedThisTurn.containsRow(cType)) {
|
||||
return result;
|
||||
} else {
|
||||
types = Sets.newHashSet(cType);
|
||||
}
|
||||
for (CounterType type : types) {
|
||||
for (Map.Entry<Player, List<Pair<Card, Integer>>> e : countersAddedThisTurn.row(type).entrySet()) {
|
||||
if (e.getKey().isValid(validPlayer.split(","), sourceController, source, ctb)) {
|
||||
for (Pair<Card, Integer> p : e.getValue()) {
|
||||
if (p.getKey().isValid(validCard.split(","), sourceController, source, ctb)) {
|
||||
result += p.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Map.Entry<Player, List<Pair<Card, Integer>>> e : countersAddedThisTurn.row(cType).entrySet()) {
|
||||
if (e.getKey().isValid(validPlayer.split(","), sourceController, source, ctb)) {
|
||||
for (Pair<Card, Integer> p : e.getValue()) {
|
||||
if (p.getKey().isValid(validCard.split(","), sourceController, source, ctb)) {
|
||||
result += p.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import forge.GameCommand;
|
||||
import forge.StaticData;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.CardType.Supertype;
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.GamePieceType;
|
||||
import forge.card.MagicColor;
|
||||
import forge.deck.DeckSection;
|
||||
@@ -44,10 +43,11 @@ import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityPredicates;
|
||||
import forge.game.spellability.SpellPermanent;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
||||
import forge.game.staticability.StaticAbilityContinuous;
|
||||
import forge.game.staticability.StaticAbilityLayer;
|
||||
import forge.game.staticability.StaticAbilityMode;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.PlayerZone;
|
||||
import forge.game.zone.PlayerZoneBattlefield;
|
||||
@@ -386,7 +386,7 @@ public class GameAction {
|
||||
return moveToGraveyard(copied, cause, params);
|
||||
}
|
||||
}
|
||||
attachAuraOnIndirectETB(copied, params);
|
||||
attachAuraOnIndirectEnterBattlefield(copied, params);
|
||||
}
|
||||
|
||||
// Handle merged permanent here so all replacement effects are already applied.
|
||||
@@ -519,7 +519,6 @@ public class GameAction {
|
||||
}
|
||||
card.setZone(zoneTo);
|
||||
}
|
||||
copied.clearMergedCards();
|
||||
} else {
|
||||
storeChangesZoneAll(copied, zoneFrom, zoneTo, params);
|
||||
// "enter the battlefield as a copy" - apply code here
|
||||
@@ -543,26 +542,26 @@ public class GameAction {
|
||||
game.addLeftGraveyardThisTurn(lastKnownInfo);
|
||||
}
|
||||
|
||||
if (c.hasMarkedColor()) {
|
||||
copied.setMarkedColors(c.getMarkedColors());
|
||||
if (c.hasChosenColorSpire()) {
|
||||
copied.setChosenColorID(ImmutableSet.copyOf(c.getChosenColorID()));
|
||||
}
|
||||
|
||||
copied.updateStateForView();
|
||||
|
||||
// we don't want always trigger before counters are placed
|
||||
game.getTriggerHandler().suppressMode(TriggerType.Always);
|
||||
// Need to apply any static effects to produce correct triggers
|
||||
checkStaticAbilities();
|
||||
|
||||
// needed for counters + ascend
|
||||
if (!suppress && toBattlefield) {
|
||||
game.getTriggerHandler().registerActiveTrigger(copied, false);
|
||||
}
|
||||
|
||||
// do ETB counters after zone add
|
||||
table.replaceCounterEffect(game, null, true, true, params);
|
||||
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.Always);
|
||||
if (!table.isEmpty()) {
|
||||
// we don't want always trigger before counters are placed
|
||||
game.getTriggerHandler().suppressMode(TriggerType.Always);
|
||||
// Need to apply any static effects to produce correct triggers
|
||||
checkStaticAbilities();
|
||||
// do ETB counters after zone add
|
||||
table.replaceCounterEffect(game, null, true, true, params);
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.Always);
|
||||
}
|
||||
|
||||
// update static abilities after etb counters have been placed
|
||||
checkStaticAbilities();
|
||||
@@ -646,8 +645,7 @@ public class GameAction {
|
||||
// Ask controller if it wants to be on top or bottom of other meld.
|
||||
unmeldPosition++;
|
||||
}
|
||||
unmeld = changeZone(null, zoneTo, unmeld, position, cause, params);
|
||||
storeChangesZoneAll(unmeld, zoneFrom, zoneTo, params);
|
||||
changeZone(null, zoneTo, unmeld, position, cause, params);
|
||||
}
|
||||
} else if (toBattlefield) {
|
||||
for (Player p : game.getPlayers()) {
|
||||
@@ -799,7 +797,7 @@ public class GameAction {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stAb.checkMode(StaticAbilityMode.CantBlockBy)) {
|
||||
if (stAb.checkMode("CantBlockBy")) {
|
||||
if (!stAb.hasParam("ValidAttacker") || (stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) {
|
||||
continue;
|
||||
}
|
||||
@@ -809,7 +807,7 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stAb.checkMode(StaticAbilityMode.MinMaxBlocker)) {
|
||||
if (stAb.checkMode(StaticAbilityCantAttackBlock.MinMaxBlockerMode)) {
|
||||
for (Card creature : IterableUtil.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.CREATURES)) {
|
||||
if (stAb.matchesValidParam("ValidCard", creature)) {
|
||||
creature.updateAbilityTextForView();
|
||||
@@ -1075,7 +1073,7 @@ public class GameAction {
|
||||
public boolean hasStaticAbilityAffectingZone(ZoneType zone, StaticAbilityLayer layer) {
|
||||
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||
if (!stAb.checkConditions(StaticAbilityMode.Continuous)) {
|
||||
if (!stAb.checkConditions("Continuous")) {
|
||||
continue;
|
||||
}
|
||||
if (layer != null && !stAb.getLayers().contains(layer)) {
|
||||
@@ -1107,6 +1105,10 @@ public class GameAction {
|
||||
// remove old effects
|
||||
game.getStaticEffects().clearStaticEffects(affectedCards);
|
||||
|
||||
for (final Player p : game.getPlayers()) {
|
||||
p.clearStaticAbilities();
|
||||
}
|
||||
|
||||
// search for cards with static abilities
|
||||
final FCollection<StaticAbility> staticAbilities = new FCollection<>();
|
||||
final CardCollection staticList = new CardCollection();
|
||||
@@ -1121,7 +1123,7 @@ public class GameAction {
|
||||
// need to get Card from preList if able
|
||||
final Card co = preList.get(c);
|
||||
for (StaticAbility stAb : co.getStaticAbilities()) {
|
||||
if (stAb.checkMode(StaticAbilityMode.Continuous) && stAb.zonesCheck()) {
|
||||
if (stAb.checkMode("Continuous") && stAb.zonesCheck()) {
|
||||
staticAbilities.add(stAb);
|
||||
}
|
||||
}
|
||||
@@ -1168,7 +1170,7 @@ public class GameAction {
|
||||
if (affectedHere != null) {
|
||||
for (final Card c : affectedHere) {
|
||||
for (final StaticAbility st2 : c.getStaticAbilities()) {
|
||||
if (!staticAbilities.contains(st2) && st2.checkMode(StaticAbilityMode.Continuous) && st2.zonesCheck()) {
|
||||
if (!staticAbilities.contains(st2) && st2.checkMode("Continuous") && st2.zonesCheck()) {
|
||||
toAdd.add(st2);
|
||||
st2.applyContinuousAbilityBefore(layer, preList);
|
||||
}
|
||||
@@ -1465,7 +1467,7 @@ public class GameAction {
|
||||
checkAgainCard |= stateBasedAction_Saga(c, sacrificeList);
|
||||
checkAgainCard |= stateBasedAction_Battle(c, noRegCreats);
|
||||
checkAgainCard |= stateBasedAction_Role(c, unAttachList);
|
||||
checkAgainCard |= stateBasedAction704_attach(c, unAttachList);
|
||||
checkAgainCard |= stateBasedAction704_attach(c, unAttachList); // Attachment
|
||||
checkAgainCard |= stateBasedAction_Contraption(c, noRegCreats);
|
||||
|
||||
checkAgainCard |= stateBasedAction704_5q(c); // annihilate +1/+1 counters with -1/-1 ones
|
||||
@@ -1512,7 +1514,9 @@ public class GameAction {
|
||||
if (!spaceSculptors.isEmpty() && !spaceSculptors.contains(p)) {
|
||||
checkAgain |= stateBasedAction704_5u(p);
|
||||
}
|
||||
checkAgain |= handleLegendRule(p, noRegCreats);
|
||||
if (handleLegendRule(p, noRegCreats)) {
|
||||
checkAgain = true;
|
||||
}
|
||||
|
||||
if ((game.getRules().hasAppliedVariant(GameType.Commander)
|
||||
|| game.getRules().hasAppliedVariant(GameType.Brawl)
|
||||
@@ -1531,12 +1535,13 @@ public class GameAction {
|
||||
checkAgain = true;
|
||||
}
|
||||
|
||||
checkAgain |= handlePlaneswalkerRule(p, noRegCreats);
|
||||
if (handlePlaneswalkerRule(p, noRegCreats)) {
|
||||
checkAgain = true;
|
||||
}
|
||||
}
|
||||
for (Player p : spaceSculptors) {
|
||||
checkAgain |= stateBasedAction704_5u(p);
|
||||
}
|
||||
|
||||
// 704.5m World rule
|
||||
checkAgain |= handleWorldRule(noRegCreats);
|
||||
|
||||
@@ -1571,7 +1576,6 @@ public class GameAction {
|
||||
orderedSacrificeList = true;
|
||||
}
|
||||
sacrifice(sacrificeList, null, true, mapParams);
|
||||
|
||||
setHoldCheckingStaticAbilities(false);
|
||||
|
||||
table.triggerChangesZoneAll(game, null);
|
||||
@@ -1630,7 +1634,7 @@ public class GameAction {
|
||||
|
||||
private boolean stateBasedAction_Saga(Card c, CardCollection sacrificeList) {
|
||||
boolean checkAgain = false;
|
||||
if (!c.isSaga() || !c.hasChapter()) {
|
||||
if (!c.isSaga()) {
|
||||
return false;
|
||||
}
|
||||
// needs to be effect, because otherwise it might be a cost?
|
||||
@@ -1652,27 +1656,10 @@ public class GameAction {
|
||||
if (!c.isBattle()) {
|
||||
return checkAgain;
|
||||
}
|
||||
Player battleController = c.getController();
|
||||
Player battleProtector = c.getProtectingPlayer();
|
||||
/*
|
||||
704.5w If a battle has no player in the game designated as its protector and no attacking creatures are currently
|
||||
attacking that battle, that battle’s controller chooses an appropriate player to be its protector based on its
|
||||
battle type. If no player can be chosen this way, the battle is put into its owner’s graveyard.
|
||||
|
||||
704.5x If a Siege’s controller is also its designated protector, that player chooses an opponent to become its
|
||||
protector. If no player can be chosen this way, the battle is put into its owner’s graveyard.
|
||||
*/
|
||||
if (((battleProtector == null || !battleProtector.isInGame()) &&
|
||||
if (((c.getProtectingPlayer() == null || !c.getProtectingPlayer().isInGame()) &&
|
||||
(game.getCombat() == null || game.getCombat().getAttackersOf(c).isEmpty())) ||
|
||||
(c.getType().hasStringType("Siege") && battleController.equals(battleProtector))) {
|
||||
Player newProtector;
|
||||
if (c.getType().getBattleTypes().contains("Siege"))
|
||||
newProtector = battleController.getController().chooseSingleEntityForEffect(battleController.getOpponents(), new SpellAbility.EmptySa(ApiType.ChoosePlayer, c), "Choose an opponent to protect this battle", null);
|
||||
else {
|
||||
// Fall back to the controller. Technically should fall back to null per the above rules, but no official
|
||||
// cards should use this branch. For now this better supports custom cards. May need to revise this later.
|
||||
newProtector = battleController;
|
||||
}
|
||||
(c.getType().hasStringType("Siege") && c.getController().equals(c.getProtectingPlayer()))) {
|
||||
Player newProtector = c.getController().getController().chooseSingleEntityForEffect(c.getController().getOpponents(), new SpellAbility.EmptySa(ApiType.ChoosePlayer, c), "Choose an opponent to protect this battle", null);
|
||||
// seems unlikely unless range of influence gets implemented
|
||||
if (newProtector == null) {
|
||||
removeList.add(c);
|
||||
@@ -1758,12 +1745,12 @@ public class GameAction {
|
||||
}
|
||||
|
||||
private boolean stateBasedAction_Contraption(Card c, CardCollection removeList) {
|
||||
if (!c.isContraption())
|
||||
if(!c.isContraption())
|
||||
return false;
|
||||
int currentSprocket = c.getSprocket();
|
||||
|
||||
//A contraption that is in the battlefield without being assembled is put into the graveyard or junkyard.
|
||||
if (currentSprocket == 0) {
|
||||
if(currentSprocket == 0) {
|
||||
removeList.add(c);
|
||||
return true;
|
||||
}
|
||||
@@ -1772,7 +1759,7 @@ public class GameAction {
|
||||
//A reassemble effect can handle that on its own. But if it changed controller due to some other effect,
|
||||
//we assign it here. A contraption uses sprocket -1 to signify it has been assembled previously but now needs
|
||||
//a new sprocket.
|
||||
if (currentSprocket > 0 && currentSprocket <= 3)
|
||||
if(currentSprocket > 0 && currentSprocket <= 3)
|
||||
return false;
|
||||
|
||||
int sprocket = c.getController().getController().chooseSprocket(c);
|
||||
@@ -2040,7 +2027,7 @@ public class GameAction {
|
||||
}
|
||||
|
||||
private boolean handleWorldRule(CardCollection noRegCreats) {
|
||||
final List<Card> worlds = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), c -> c.getType().hasSupertype(Supertype.World));
|
||||
final List<Card> worlds = CardLists.getType(game.getCardsIn(ZoneType.Battlefield), "World");
|
||||
if (worlds.size() <= 1) {
|
||||
return false;
|
||||
}
|
||||
@@ -2049,7 +2036,7 @@ public class GameAction {
|
||||
long ts = 0;
|
||||
|
||||
for (final Card crd : worlds) {
|
||||
long crdTs = crd.getWorldTimestamp();
|
||||
long crdTs = crd.getGameTimestamp();
|
||||
if (crdTs > ts) {
|
||||
ts = crdTs;
|
||||
toKeep.clear();
|
||||
@@ -2430,14 +2417,15 @@ public class GameAction {
|
||||
for (Card c : spires) {
|
||||
// TODO: only do this for the AI, for the player part, get the encoded color from the deck file and pass
|
||||
// it to either player or the papercard object so it feels like rule based for the player side..
|
||||
if (!c.hasMarkedColor()) {
|
||||
if (!c.hasChosenColorSpire()) {
|
||||
if (takesAction.isAI()) {
|
||||
List<String> colorChoices = new ArrayList<>(MagicColor.Constant.ONLY_COLORS);
|
||||
String prompt = CardTranslation.getTranslatedName(c.getName()) + ": " +
|
||||
Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(2));
|
||||
SpellAbility sa = new SpellAbility.EmptySa(ApiType.ChooseColor, c, takesAction);
|
||||
sa.putParam("AILogic", "MostProminentInComputerDeck");
|
||||
ColorSet chosenColors = ColorSet.fromNames(takesAction.getController().chooseColors(prompt, sa, 2, 2, MagicColor.Constant.ONLY_COLORS));
|
||||
c.setMarkedColors(chosenColors);
|
||||
Set<String> chosenColors = new HashSet<>(takesAction.getController().chooseColors(prompt, sa, 2, 2, colorChoices));
|
||||
c.setChosenColorID(chosenColors);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2786,36 +2774,21 @@ public class GameAction {
|
||||
* the source
|
||||
* @return true, if successful
|
||||
*/
|
||||
private boolean attachAuraOnIndirectETB(final Card source, Map<AbilityKey, Object> params) {
|
||||
// When an Aura ETB without being cast you can choose a valid card to attach it to
|
||||
if (!source.hasKeyword(Keyword.ENCHANT)) {
|
||||
return false;
|
||||
}
|
||||
public static boolean attachAuraOnIndirectEnterBattlefield(final Card source, Map<AbilityKey, Object> params) {
|
||||
// When an Aura ETB without being cast you can choose a valid card to
|
||||
// attach it to
|
||||
final SpellAbility aura = source.getFirstAttachSpell();
|
||||
|
||||
SpellAbility aura = source.getCurrentState().getAuraSpell();
|
||||
if (aura == null) {
|
||||
return false;
|
||||
}
|
||||
aura.setActivatingPlayer(source.getController());
|
||||
final Game game = source.getGame();
|
||||
final TargetRestrictions tgt = aura.getTargetRestrictions();
|
||||
|
||||
Set<ZoneType> zones = EnumSet.noneOf(ZoneType.class);
|
||||
boolean canTargetPlayer = false;
|
||||
for (KeywordInterface ki : source.getKeywords(Keyword.ENCHANT)) {
|
||||
String o = ki.getOriginal();
|
||||
String m[] = o.split(":");
|
||||
String v = m[1];
|
||||
if (v.contains("inZone")) { // currently the only other zone is Graveyard
|
||||
zones.add(ZoneType.Graveyard);
|
||||
} else {
|
||||
zones.add(ZoneType.Battlefield);
|
||||
}
|
||||
if (v.startsWith("Player") || v.startsWith("Opponent")) {
|
||||
canTargetPlayer = true;
|
||||
}
|
||||
}
|
||||
Player p = source.getController();
|
||||
if (canTargetPlayer) {
|
||||
final FCollection<Player> players = game.getPlayers().filter(PlayerPredicates.canBeAttached(source, null));
|
||||
if (tgt.canTgtPlayer()) {
|
||||
final FCollection<Player> players = game.getPlayers().filter(PlayerPredicates.canBeAttached(source, aura));
|
||||
|
||||
final Player pa = p.getController().chooseSingleEntityForEffect(players, aura,
|
||||
Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
|
||||
@@ -2824,7 +2797,9 @@ public class GameAction {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
List<ZoneType> zones = Lists.newArrayList(tgt.getZone());
|
||||
CardCollection list = new CardCollection();
|
||||
|
||||
if (params != null) {
|
||||
if (zones.contains(ZoneType.Battlefield)) {
|
||||
list.addAll((CardCollectionView) params.get(AbilityKey.LastStateBattlefield));
|
||||
@@ -2837,7 +2812,7 @@ public class GameAction {
|
||||
}
|
||||
list.addAll(game.getCardsIn(zones));
|
||||
|
||||
list = CardLists.filter(list, CardPredicates.canBeAttached(source, null));
|
||||
list = CardLists.filter(list, CardPredicates.canBeAttached(source, aura));
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ import forge.game.spellability.*;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityAlternativeCost;
|
||||
import forge.game.staticability.StaticAbilityLayer;
|
||||
import forge.game.staticability.StaticAbilityMode;
|
||||
import forge.game.zone.Zone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
@@ -419,7 +418,7 @@ public final class GameActionUtil {
|
||||
costSources.addAll(game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES));
|
||||
for (final Card ca : costSources) {
|
||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||
if (!stAb.checkConditions(StaticAbilityMode.OptionalCost)) {
|
||||
if (!stAb.checkConditions("OptionalCost")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -947,16 +946,14 @@ public final class GameActionUtil {
|
||||
}
|
||||
|
||||
if (fromZone != null && !fromZone.is(ZoneType.None)) { // and not a copy
|
||||
// add back to where it came from, hopefully old state
|
||||
// skip GameAction
|
||||
oldCard.getZone().remove(oldCard);
|
||||
|
||||
// might have been an alternative lki host
|
||||
oldCard = ability.getCardState().getCard();
|
||||
|
||||
oldCard.setCastSA(null);
|
||||
oldCard.setCastFrom(null);
|
||||
|
||||
// add back to where it came from, hopefully old state
|
||||
// skip GameAction
|
||||
oldCard.getZone().remove(oldCard);
|
||||
// in some rare cases the old position no longer exists (Panglacial Wurm + Selvala)
|
||||
Integer newPosition = zonePosition >= 0 ? Math.min(zonePosition, fromZone.size()) : null;
|
||||
fromZone.add(oldCard, newPosition, null, true);
|
||||
|
||||
@@ -37,11 +37,11 @@ import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.event.GameEventCardAttachment;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbilityCantAttach;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
@@ -267,18 +267,15 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
}
|
||||
|
||||
protected boolean canBeEnchantedBy(final Card aura) {
|
||||
if (!aura.hasKeyword(Keyword.ENCHANT)) {
|
||||
return false;
|
||||
// TODO need to check for multiple Enchant Keywords
|
||||
|
||||
SpellAbility sa = aura.getFirstAttachSpell();
|
||||
TargetRestrictions tgt = null;
|
||||
if (sa != null) {
|
||||
tgt = sa.getTargetRestrictions();
|
||||
}
|
||||
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
||||
String k = ki.getOriginal();
|
||||
String m[] = k.split(":");
|
||||
String v = m[1];
|
||||
if (!isValid(v.split(","), aura.getController(), aura, null)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
return tgt != null && isValid(tgt.getValidTgts(), aura.getController(), aura, sa);
|
||||
}
|
||||
|
||||
public boolean hasCounters() {
|
||||
|
||||
@@ -21,7 +21,7 @@ import com.google.common.collect.Lists;
|
||||
import forge.StaticData;
|
||||
import forge.card.CardDb;
|
||||
import forge.card.CardEdition;
|
||||
import forge.card.CardEdition.EditionEntry;
|
||||
import forge.card.CardEdition.CardInSet;
|
||||
import forge.card.CardRarity;
|
||||
import forge.deck.CardPool;
|
||||
import forge.deck.Deck;
|
||||
@@ -156,7 +156,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
for (CardRarity cr: this.getAllowedRarities()) {
|
||||
crp.add(StaticData.instance().getCommonCards().wasPrintedAtRarity(cr));
|
||||
}
|
||||
p = p.and(IterableUtil.<PaperCard>or(crp));
|
||||
p = p.and(IterableUtil.or(crp));
|
||||
}
|
||||
if (!this.getAdditionalCards().isEmpty()) {
|
||||
p = p.or(PaperCardPredicates.names(this.getAdditionalCards()));
|
||||
@@ -226,9 +226,9 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
for (String setCode : allowedSetCodes_ro) {
|
||||
CardEdition edition = StaticData.instance().getEditions().get(setCode);
|
||||
if (edition != null) {
|
||||
for (EditionEntry card : edition.getAllCardsInSet()) {
|
||||
if (!bannedCardNames_ro.contains(card.name())) {
|
||||
PaperCard pc = commonCards.getCard(card.name(), setCode, card.collectorNumber());
|
||||
for (CardInSet card : edition.getAllCardsInSet()) {
|
||||
if (!bannedCardNames_ro.contains(card.name)) {
|
||||
PaperCard pc = commonCards.getCard(card.name, setCode, card.collectorNumber);
|
||||
if (pc != null) {
|
||||
cards.add(pc);
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ public class GameSnapshot {
|
||||
newPlayer.setDamageReceivedThisTurn(origPlayer.getDamageReceivedThisTurn());
|
||||
newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn());
|
||||
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
|
||||
newPlayer.setBlessing(origPlayer.hasBlessing(), null);
|
||||
newPlayer.setBlessing(origPlayer.hasBlessing());
|
||||
newPlayer.setRevolt(origPlayer.hasRevolt());
|
||||
newPlayer.setLibrarySearched(origPlayer.getLibrarySearched());
|
||||
newPlayer.setSpellsCastLastTurn(origPlayer.getSpellsCastLastTurn());
|
||||
@@ -322,7 +322,7 @@ public class GameSnapshot {
|
||||
newCard.setLayerTimestamp(fromCard.getLayerTimestamp());
|
||||
newCard.setTapped(fromCard.isTapped());
|
||||
newCard.setFaceDown(fromCard.isFaceDown());
|
||||
newCard.setManifested(fromCard.getManifestedSA());
|
||||
newCard.setManifested(fromCard.isManifested());
|
||||
newCard.setSickness(fromCard.hasSickness());
|
||||
newCard.setState(fromCard.getCurrentStateName(), false);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public interface IHasSVars {
|
||||
//public Set<String> getSVars();
|
||||
|
||||
public Map<String, String> getSVars();
|
||||
public Map<String, String> getDirectSVars();
|
||||
|
||||
public void removeSVar(final String var);
|
||||
}
|
||||
|
||||
@@ -209,6 +209,15 @@ public final class AbilityFactory {
|
||||
}
|
||||
}
|
||||
|
||||
else if (api == ApiType.PermanentCreature || api == ApiType.PermanentNoncreature) {
|
||||
// If API is a permanent type, and creating AF Spell
|
||||
// Clear out the auto created SpellPermanent spell
|
||||
if (type == AbilityRecordType.Spell
|
||||
&& !mapParams.containsKey("SubAbility") && !mapParams.containsKey("NonBasicSpell")) {
|
||||
hostCard.clearFirstSpell();
|
||||
}
|
||||
}
|
||||
|
||||
if (abCost == null) {
|
||||
abCost = parseAbilityCost(state, mapParams, type);
|
||||
}
|
||||
@@ -494,9 +503,8 @@ public final class AbilityFactory {
|
||||
AbilityRecordType leftType = AbilityRecordType.getRecordType(leftMap);
|
||||
ApiType leftApi = leftType.getApiTypeOf(leftMap);
|
||||
leftMap.put("StackDescription", leftMap.get("SpellDescription"));
|
||||
leftMap.put("SpellDescription", "Fuse (You may cast one or both halves of this card from your hand.)");
|
||||
leftMap.put("SpellDescription", "Fuse (you may cast both halves of this card from your hand).");
|
||||
leftMap.put("ActivationZone", "Hand");
|
||||
leftMap.put("Secondary", "True");
|
||||
|
||||
CardState rightState = card.getState(CardStateName.RightSplit);
|
||||
SpellAbility rightAbility = rightState.getFirstAbility();
|
||||
@@ -511,10 +519,8 @@ public final class AbilityFactory {
|
||||
totalCost.add(parseAbilityCost(rightState, rightMap, rightType));
|
||||
|
||||
final SpellAbility left = getAbility(leftType, leftApi, leftMap, totalCost, leftState, leftState);
|
||||
left.setOriginalAbility(leftAbility);
|
||||
left.setCardState(card.getState(CardStateName.Original));
|
||||
final AbilitySub right = (AbilitySub) getAbility(AbilityRecordType.SubAbility, rightApi, rightMap, null, rightState, rightState);
|
||||
right.setOriginalAbility(rightAbility);
|
||||
left.appendSubAbility(right);
|
||||
return left;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ public enum AbilityKey {
|
||||
DefendingPlayer("DefendingPlayer"),
|
||||
Destination("Destination"),
|
||||
Devoured("Devoured"),
|
||||
DicePTExchanges("DicePTExchanges"),
|
||||
Discard("Discard"),
|
||||
DiscardedBefore("DiscardedBefore"),
|
||||
DividedShieldAmount("DividedShieldAmount"),
|
||||
@@ -72,6 +71,7 @@ public enum AbilityKey {
|
||||
Explored("Explored"),
|
||||
Explorer("Explorer"),
|
||||
ExtraTurn("ExtraTurn"),
|
||||
Event("Event"),
|
||||
ETB("ETB"),
|
||||
Fighter("Fighter"),
|
||||
Fighters("Fighters"),
|
||||
@@ -93,8 +93,8 @@ public enum AbilityKey {
|
||||
Mana("Mana"),
|
||||
MergedCards("MergedCards"),
|
||||
Mode("Mode"),
|
||||
Modifier("Modifier"),
|
||||
MonstrosityAmount("MonstrosityAmount"),
|
||||
NaturalResult("NaturalResult"),
|
||||
NewCard("NewCard"),
|
||||
NewCounterAmount("NewCounterAmount"),
|
||||
NoPreventDamage("NoPreventDamage"),
|
||||
@@ -130,6 +130,7 @@ public enum AbilityKey {
|
||||
SourceSA("SourceSA"),
|
||||
SpellAbility("SpellAbility"),
|
||||
SpellAbilityTargets("SpellAbilityTargets"),
|
||||
StackInstance("StackInstance"),
|
||||
StackSa("StackSa"),
|
||||
SurveilNum("SurveilNum"),
|
||||
Target("Target"),
|
||||
@@ -139,7 +140,7 @@ public enum AbilityKey {
|
||||
Valiant("Valiant"),
|
||||
Won("Won"),
|
||||
|
||||
// below shared across different Replacements, don't reuse
|
||||
// below used across different Replacements, don't reuse
|
||||
InternalTriggerTable("InternalTriggerTable"),
|
||||
SimultaneousETB("SimultaneousETB"); // for CR 614.13c
|
||||
|
||||
|
||||
@@ -663,7 +663,7 @@ public class AbilityUtils {
|
||||
Object o = root.getTriggeringObject(AbilityKey.fromString(calcX[0].substring(9)));
|
||||
val = o instanceof Player ? playerXProperty((Player) o, calcX[1], card, ability) : 0;
|
||||
}
|
||||
else if (calcX[0].equals("TriggeredSpellAbility") || calcX[0].equals("SpellTargeted")) {
|
||||
else if (calcX[0].equals("TriggeredSpellAbility") || calcX[0].equals("TriggeredStackInstance") || calcX[0].equals("SpellTargeted")) {
|
||||
final SpellAbility sat = Iterables.getFirst(getDefinedSpellAbilities(card, calcX[0], sa), null);
|
||||
val = sat == null ? 0 : xCount(sat.getHostCard(), calcX[1], sat);
|
||||
}
|
||||
@@ -1281,6 +1281,8 @@ public class AbilityUtils {
|
||||
final Object o = root.getTriggeringObject(AbilityKey.fromString(triggeringType));
|
||||
if (o instanceof SpellAbility) {
|
||||
s = (SpellAbility) o;
|
||||
} else if (o instanceof SpellAbilityStackInstance) {
|
||||
s = ((SpellAbilityStackInstance) o).getSpellAbility();
|
||||
}
|
||||
} else if (defined.endsWith("Targeted") && sa instanceof SpellAbility) {
|
||||
final List<TargetChoices> targets = defined.startsWith("This") ? Arrays.asList(((SpellAbility)sa).getTargets()) : ((SpellAbility)sa).getAllTargetChoices();
|
||||
@@ -1364,7 +1366,7 @@ public class AbilityUtils {
|
||||
|
||||
// do blessing there before condition checks
|
||||
if (source.hasKeyword(Keyword.ASCEND) && controller.getZone(ZoneType.Battlefield).size() >= 10) {
|
||||
controller.setBlessing(true, source.getSetCode());
|
||||
controller.setBlessing(true);
|
||||
}
|
||||
|
||||
if (source.hasKeyword(Keyword.GIFT) && sa.isGiftPromised()) {
|
||||
@@ -1619,8 +1621,7 @@ public class AbilityUtils {
|
||||
|
||||
final String[] sq;
|
||||
sq = l[0].split("\\.");
|
||||
String[] paidparts = l[0].split("\\$", 2);
|
||||
Iterable<Card> someCards = null;
|
||||
|
||||
final Game game = c.getGame();
|
||||
|
||||
if (ctb != null) {
|
||||
@@ -1683,11 +1684,11 @@ public class AbilityUtils {
|
||||
return doXMath(x, expr, c, ctb);
|
||||
} else if (TriggerType.SpellCast.equals(t.getMode())) {
|
||||
// Cast Trigger like Hydroid Krasis
|
||||
SpellAbility castSA = (SpellAbility) root.getTriggeringObject(AbilityKey.SpellAbility);
|
||||
if (castSA == null || castSA.getXManaCostPaid() == null) {
|
||||
SpellAbilityStackInstance castSI = (SpellAbilityStackInstance) root.getTriggeringObject(AbilityKey.StackInstance);
|
||||
if (castSI == null || castSI.getSpellAbility().getXManaCostPaid() == null) {
|
||||
return doXMath(0, expr, c, ctb);
|
||||
}
|
||||
return doXMath(castSA.getXManaCostPaid(), expr, c, ctb);
|
||||
return doXMath(castSI.getSpellAbility().getXManaCostPaid(), expr, c, ctb);
|
||||
} else if (TriggerType.Cycled.equals(t.getMode())) {
|
||||
SpellAbility cycleSA = (SpellAbility) sa.getTriggeringObject(AbilityKey.Cause);
|
||||
if (cycleSA == null || cycleSA.getXManaCostPaid() == null) {
|
||||
@@ -1785,10 +1786,11 @@ public class AbilityUtils {
|
||||
}
|
||||
// Count$NumTimesChoseMode
|
||||
if (sq[0].startsWith("NumTimesChoseMode")) {
|
||||
SpellAbility sub = sa.getRootAbility();
|
||||
int amount = 0;
|
||||
SpellAbility tail = sa.getTailAbility();
|
||||
if (tail.hasSVar("CharmOrder")) {
|
||||
amount = tail.getSVarInt("CharmOrder");
|
||||
while (sub != null) {
|
||||
if (sub.getDirectSVars().containsKey("CharmOrder")) amount++;
|
||||
sub = sub.getSubAbility();
|
||||
}
|
||||
return doXMath(amount, expr, c, ctb);
|
||||
}
|
||||
@@ -1807,25 +1809,27 @@ public class AbilityUtils {
|
||||
}
|
||||
|
||||
if (sq[0].startsWith("LastStateBattlefield")) {
|
||||
final String[] k = paidparts[0].split(" ");
|
||||
final String[] k = l[0].split(" ");
|
||||
CardCollectionView list;
|
||||
// this is only for spells that were cast
|
||||
if (sq[0].contains("WithFallback")) {
|
||||
if (!sa.getHostCard().wasCast()) {
|
||||
return doXMath(0, expr, c, ctb);
|
||||
}
|
||||
someCards = sa.getHostCard().getCastSA().getLastStateBattlefield();
|
||||
list = sa.getHostCard().getCastSA().getLastStateBattlefield();
|
||||
} else {
|
||||
someCards = sa.getLastStateBattlefield();
|
||||
list = sa.getLastStateBattlefield();
|
||||
}
|
||||
if (someCards == null || Iterables.isEmpty(someCards)) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
// LastState is Empty
|
||||
if (sq[0].contains("WithFallback")) {
|
||||
someCards = game.getCardsIn(ZoneType.Battlefield);
|
||||
list = game.getCardsIn(ZoneType.Battlefield);
|
||||
} else {
|
||||
return doXMath(0, expr, c, ctb);
|
||||
}
|
||||
}
|
||||
someCards = CardLists.getValidCards(someCards, k[1], player, c, sa);
|
||||
list = CardLists.getValidCards(list, k[1], player, c, sa);
|
||||
return doXMath(list.size(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].startsWith("LastStateGraveyard")) {
|
||||
@@ -1956,6 +1960,9 @@ public class AbilityUtils {
|
||||
return doXMath(sum, expr, c, ctb);
|
||||
}
|
||||
|
||||
String[] paidparts = l[0].split("\\$", 2);
|
||||
Iterable<Card> someCards = null;
|
||||
|
||||
// count valid cards in any specified zone/s
|
||||
if (sq[0].startsWith("Valid")) {
|
||||
String[] lparts = paidparts[0].split(" ", 2);
|
||||
@@ -2214,7 +2221,7 @@ public class AbilityUtils {
|
||||
// Count$IfCastInOwnMainPhase.<numMain>.<numNotMain> // 7/10
|
||||
if (sq[0].contains("IfCastInOwnMainPhase")) {
|
||||
final PhaseHandler cPhase = game.getPhaseHandler();
|
||||
final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.isPlayerTurn(player) && c.wasCast();
|
||||
final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.isPlayerTurn(player) && c.getCastFrom() != null;
|
||||
return doXMath(Integer.parseInt(sq[isMyMain ? 1 : 2]), expr, c, ctb);
|
||||
}
|
||||
|
||||
@@ -2223,11 +2230,6 @@ public class AbilityUtils {
|
||||
return doXMath(game.getPhaseHandler().getNumUpkeep() - (game.getPhaseHandler().is(PhaseType.UPKEEP) ? 1 : 0), expr, c, ctb);
|
||||
}
|
||||
|
||||
// Count$FinishedEndOfTurnsThisTurn
|
||||
if (sq[0].startsWith("FinishedEndOfTurnsThisTurn")) {
|
||||
return doXMath(game.getPhaseHandler().getNumEndOfTurn() - (game.getPhaseHandler().is(PhaseType.END_OF_TURN) ? 1 : 0), expr, c, ctb);
|
||||
}
|
||||
|
||||
// Count$AttachedTo <restriction>
|
||||
if (sq[0].startsWith("AttachedTo")) {
|
||||
final String[] k = l[0].split(" ");
|
||||
@@ -2322,10 +2324,6 @@ public class AbilityUtils {
|
||||
return doXMath(player.getNumDrawnLastTurn(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].equals("YouFlipThisTurn")) {
|
||||
return doXMath(player.getNumFlipsThisTurn(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].equals("YouRollThisTurn")) {
|
||||
return doXMath(player.getNumRollsThisTurn(), expr, c, ctb);
|
||||
}
|
||||
@@ -2411,10 +2409,6 @@ public class AbilityUtils {
|
||||
return doXMath(player.getMaxOpponentAssignedDamage(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].equals("MaxCombatDamageThisTurn")) {
|
||||
return doXMath(player.getMaxAssignedCombatDamage(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].contains("TotalDamageThisTurn")) {
|
||||
String[] props = l[0].split(" ");
|
||||
int sum = 0;
|
||||
@@ -2482,6 +2476,7 @@ public class AbilityUtils {
|
||||
// But these aren't really things you count so they'll show up in properties most likely
|
||||
}
|
||||
|
||||
|
||||
//Count$TypesSharedWith [defined]
|
||||
if (sq[0].startsWith("TypesSharedWith")) {
|
||||
Set<CardType.CoreType> thisTypes = Sets.newHashSet(c.getType().getCoreTypes());
|
||||
@@ -2711,6 +2706,24 @@ public class AbilityUtils {
|
||||
return doXMath(calculateAmount(c, sq[res.size() > 0 ? 1 : 2], ctb), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].startsWith("CreatureType")) {
|
||||
String[] sqparts = l[0].split(" ", 2);
|
||||
final String[] rest = sqparts[1].split(",");
|
||||
|
||||
final CardCollectionView cardsInZones = sqparts[0].length() > 12
|
||||
? game.getCardsIn(ZoneType.listValueOf(sqparts[0].substring(12)))
|
||||
: game.getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
CardCollection cards = CardLists.getValidCards(cardsInZones, rest, player, c, ctb);
|
||||
final Set<String> creatTypes = Sets.newHashSet();
|
||||
|
||||
for (Card card : cards) {
|
||||
creatTypes.addAll(card.getType().getCreatureTypes());
|
||||
}
|
||||
// filter out fun types?
|
||||
return doXMath(creatTypes.size(), expr, c, ctb);
|
||||
}
|
||||
|
||||
// Count$Chroma.<color name>
|
||||
if (sq[0].startsWith("Chroma")) {
|
||||
final CardCollectionView cards;
|
||||
@@ -2769,6 +2782,16 @@ public class AbilityUtils {
|
||||
return game.getPhaseHandler().getPlanarDiceSpecialActionThisTurn();
|
||||
}
|
||||
|
||||
if (sq[0].equals("AllTypes")) {
|
||||
List<Card> cards = getDefinedCards(c, sq[1], ctb);
|
||||
|
||||
int amount = countCardTypesFromList(cards, false) +
|
||||
countSuperTypesFromList(cards) +
|
||||
countSubTypesFromList(cards);
|
||||
|
||||
return doXMath(amount, expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].equals("TotalTurns")) {
|
||||
return doXMath(game.getPhaseHandler().getTurn(), expr, c, ctb);
|
||||
}
|
||||
@@ -2914,6 +2937,18 @@ public class AbilityUtils {
|
||||
return doXMath(colorSize[colorSize.length - 2], expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].startsWith("ColorsCtrl")) {
|
||||
final String restriction = l[0].substring(11);
|
||||
final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
|
||||
return doXMath(CardUtil.getColorsFromCards(list).countColors(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].startsWith("ColorsDefined")) {
|
||||
final String restriction = l[0].substring(14);
|
||||
final CardCollection list = getDefinedCards(c, restriction, ctb);
|
||||
return doXMath(CardUtil.getColorsFromCards(list).countColors(), expr, c, ctb);
|
||||
}
|
||||
|
||||
// TODO move below to handlePaid
|
||||
if (sq[0].startsWith("SumPower")) {
|
||||
final String[] restrictions = l[0].split("_");
|
||||
@@ -3398,14 +3433,17 @@ public class AbilityUtils {
|
||||
return doXMath(numTied, m, source, ctb);
|
||||
}
|
||||
|
||||
final String[] sq;
|
||||
sq = l[0].split("\\.");
|
||||
|
||||
// the number of players passed in
|
||||
if (l[0].equals("Amount")) {
|
||||
if (sq[0].equals("Amount")) {
|
||||
return doXMath(players.size(), m, source, ctb);
|
||||
}
|
||||
|
||||
if (l[0].startsWith("HasProperty")) {
|
||||
if (sq[0].startsWith("HasProperty")) {
|
||||
int totPlayer = 0;
|
||||
String property = l[0].substring(11);
|
||||
String property = sq[0].substring(11);
|
||||
for (Player p : players) {
|
||||
if (p.hasProperty(property, controller, source, ctb)) {
|
||||
totPlayer++;
|
||||
@@ -3429,7 +3467,7 @@ public class AbilityUtils {
|
||||
return doXMath(totPlayer, m, source, ctb);
|
||||
}
|
||||
|
||||
if (l[0].contains("DamageThisTurn")) {
|
||||
if (sq[0].contains("DamageThisTurn")) {
|
||||
int totDmg = 0;
|
||||
for (Player p : players) {
|
||||
totDmg += p.getAssignedDamage();
|
||||
@@ -3749,10 +3787,6 @@ public class AbilityUtils {
|
||||
return Aggregates.max(paidList, Card::getNetToughness);
|
||||
}
|
||||
|
||||
if (string.startsWith("TapPowerValue")) {
|
||||
return CardLists.getTotalPower(paidList, ctb);
|
||||
}
|
||||
|
||||
if (string.startsWith("SumToughness")) {
|
||||
return Aggregates.sum(paidList, Card::getNetToughness);
|
||||
}
|
||||
@@ -3761,10 +3795,6 @@ public class AbilityUtils {
|
||||
return Aggregates.max(paidList, Card::getCMC);
|
||||
}
|
||||
|
||||
if (string.equals("Colors")) {
|
||||
return CardUtil.getColorsFromCards(paidList).countColors();
|
||||
}
|
||||
|
||||
if (string.equals("DifferentColorPair")) {
|
||||
final Set<ColorSet> diffPair = new HashSet<>();
|
||||
for (final Card card : paidList) {
|
||||
@@ -3794,25 +3824,10 @@ public class AbilityUtils {
|
||||
return doXMath(num, splitString.length > 1 ? splitString[1] : null, source, ctb);
|
||||
}
|
||||
|
||||
if (string.startsWith("AllTypes")) {
|
||||
return countCardTypesFromList(paidList, false) +
|
||||
countSuperTypesFromList(paidList) +
|
||||
countSubTypesFromList(paidList);
|
||||
}
|
||||
|
||||
if (string.startsWith("CardTypes")) {
|
||||
return doXMath(countCardTypesFromList(paidList, string.startsWith("CardTypesPermanent")), CardFactoryUtil.extractOperators(string), source, ctb);
|
||||
}
|
||||
|
||||
if (string.startsWith("CreatureType")) {
|
||||
final Set<String> creatTypes = Sets.newHashSet();
|
||||
for (Card card : paidList) {
|
||||
creatTypes.addAll(card.getType().getCreatureTypes());
|
||||
}
|
||||
// filter out fun types?
|
||||
return doXMath(creatTypes.size(), CardFactoryUtil.extractOperators(string), source, ctb);
|
||||
}
|
||||
|
||||
String filteredString = string;
|
||||
Iterable<Card> filteredList = paidList;
|
||||
final String[] filter = filteredString.split("_");
|
||||
|
||||
@@ -92,7 +92,6 @@ public enum ApiType {
|
||||
ExchangeControlVariant (ControlExchangeVariantEffect.class),
|
||||
ExchangePower (PowerExchangeEffect.class),
|
||||
ExchangeZone (ZoneExchangeEffect.class),
|
||||
ExchangeTextBox (TextBoxExchangeEffect.class),
|
||||
Explore (ExploreEffect.class),
|
||||
Fight (FightEffect.class),
|
||||
FlipACoin (FlipCoinEffect.class),
|
||||
|
||||
@@ -356,7 +356,6 @@ public abstract class SpellAbilityEffect {
|
||||
boolean intrinsic = sa.isIntrinsic();
|
||||
boolean your = location.startsWith("Your");
|
||||
boolean combat = location.endsWith("Combat");
|
||||
boolean upkeep = location.endsWith("Upkeep");
|
||||
|
||||
String desc = sa.getParamOrDefault("AtEOTDesc", "");
|
||||
|
||||
@@ -366,16 +365,11 @@ public abstract class SpellAbilityEffect {
|
||||
if (combat) {
|
||||
location = location.substring(0, location.length() - "Combat".length());
|
||||
}
|
||||
if (upkeep) {
|
||||
location = location.substring(0, location.length() - "Upkeep".length());
|
||||
}
|
||||
|
||||
if (desc.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (location.equals("Hand")) {
|
||||
sb.append("Return ");
|
||||
} else if (location.equals("Library")) {
|
||||
sb.append("Shuffle ");
|
||||
} else if (location.equals("SacrificeCtrl")) {
|
||||
sb.append("Its controller sacrifices ");
|
||||
} else {
|
||||
@@ -384,8 +378,6 @@ public abstract class SpellAbilityEffect {
|
||||
sb.append(Lang.joinHomogenous(crds));
|
||||
if (location.equals("Hand")) {
|
||||
sb.append(" to your hand");
|
||||
} else if (location.equals("Library")) {
|
||||
sb.append(" into your library");
|
||||
}
|
||||
sb.append(" at the ");
|
||||
if (combat) {
|
||||
@@ -393,18 +385,14 @@ public abstract class SpellAbilityEffect {
|
||||
} else {
|
||||
sb.append("beginning of ");
|
||||
sb.append(your ? "your" : "the");
|
||||
if (upkeep) {
|
||||
sb.append(" next upkeep.");
|
||||
} else {
|
||||
sb.append(" next end step.");
|
||||
}
|
||||
sb.append(" next end step.");
|
||||
}
|
||||
desc = sb.toString();
|
||||
}
|
||||
|
||||
StringBuilder delTrig = new StringBuilder();
|
||||
delTrig.append("Mode$ Phase | Phase$ ");
|
||||
delTrig.append(combat ? "EndCombat " : upkeep ? "Upkeep" : "End Of Turn ");
|
||||
delTrig.append(combat ? "EndCombat " : "End Of Turn ");
|
||||
|
||||
if (your) {
|
||||
delTrig.append("| ValidPlayer$ You ");
|
||||
@@ -422,8 +410,6 @@ public abstract class SpellAbilityEffect {
|
||||
String trigSA = "";
|
||||
if (location.equals("Hand")) {
|
||||
trigSA = "DB$ ChangeZone | Defined$ DelayTriggerRememberedLKI | Origin$ Battlefield | Destination$ Hand";
|
||||
} else if (location.equals("Library")) {
|
||||
trigSA = "DB$ ChangeZone | Defined$ DelayTriggerRememberedLKI | Origin$ Battlefield | Destination$ Library | Shuffle$ True";
|
||||
} else if (location.equals("SacrificeCtrl")) {
|
||||
trigSA = "DB$ SacrificeAll | Defined$ DelayTriggerRememberedLKI";
|
||||
} else if (location.equals("Sacrifice")) {
|
||||
@@ -781,11 +767,7 @@ public abstract class SpellAbilityEffect {
|
||||
return combatChanged;
|
||||
}
|
||||
|
||||
protected static void changeZoneUntilCommand(final CardZoneTable triggerList, final SpellAbility sa) {
|
||||
if (!sa.hasParam("Duration")) {
|
||||
return;
|
||||
}
|
||||
|
||||
protected static GameCommand untilHostLeavesPlayCommand(final CardZoneTable triggerList, final SpellAbility sa) {
|
||||
final Card hostCard = sa.getHostCard();
|
||||
final Game game = hostCard.getGame();
|
||||
hostCard.addUntilLeavesBattlefield(triggerList.allCards());
|
||||
@@ -800,7 +782,7 @@ public abstract class SpellAbilityEffect {
|
||||
lki = null;
|
||||
}
|
||||
|
||||
GameCommand gc = new GameCommand() {
|
||||
return new GameCommand() {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -855,13 +837,6 @@ public abstract class SpellAbilityEffect {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// corner case can lead to host exiling itself during the effect
|
||||
if (sa.getParam("Duration").contains("UntilHostLeavesPlay") && !hostCard.isInPlay()) {
|
||||
gc.run();
|
||||
} else {
|
||||
addUntilCommand(sa, gc);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void discard(SpellAbility sa, final boolean effect, Map<Player, CardCollectionView> discardedMap, Map<AbilityKey, Object> params) {
|
||||
@@ -922,8 +897,6 @@ public abstract class SpellAbilityEffect {
|
||||
} else {
|
||||
game.getUpkeep().addUntilEnd(controller, until);
|
||||
}
|
||||
} else if ("UntilTheEndOfYourNextUntap".equals(duration)) {
|
||||
game.getUntap().addUntilEnd(controller, until);
|
||||
} else if ("UntilNextEndStep".equals(duration)) {
|
||||
game.getEndOfTurn().addAt(until);
|
||||
} else if ("UntilYourNextEndStep".equals(duration)) {
|
||||
@@ -1024,9 +997,8 @@ public abstract class SpellAbilityEffect {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Player getNewChooser(final SpellAbility sa, final Player loser) {
|
||||
public static Player getNewChooser(final SpellAbility sa, final Player activator, final Player loser) {
|
||||
// CR 800.4g
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
final PlayerCollection options;
|
||||
if (loser.isOpponentOf(activator)) {
|
||||
options = activator.getOpponents();
|
||||
|
||||
@@ -165,8 +165,14 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
|
||||
// remove abilities
|
||||
final List<SpellAbility> removedAbilities = Lists.newArrayList();
|
||||
if (sa.hasParam("RemoveThisAbility")) {
|
||||
removedAbilities.add(sa.getOriginalAbility());
|
||||
boolean clearSpells = sa.hasParam("OverwriteSpells");
|
||||
|
||||
if (clearSpells) {
|
||||
removedAbilities.addAll(Lists.newArrayList(c.getSpells()));
|
||||
}
|
||||
|
||||
if (sa.hasParam("RemoveThisAbility") && !removedAbilities.contains(sa)) {
|
||||
removedAbilities.add(sa);
|
||||
}
|
||||
|
||||
// give abilities
|
||||
@@ -246,7 +252,9 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (!"Permanent".equals(duration) && !perpetual) {
|
||||
if ("UntilAnimatedFaceup".equals(duration)) {
|
||||
if ("UntilControllerNextUntap".equals(duration)) {
|
||||
game.getUntap().addUntil(c.getController(), unanimate);
|
||||
} else if ("UntilAnimatedFaceup".equals(duration)) {
|
||||
c.addFaceupCommand(unanimate);
|
||||
} else {
|
||||
addUntilCommand(sa, unanimate);
|
||||
|
||||
@@ -39,7 +39,7 @@ public class AscendEffect extends SpellAbilityEffect {
|
||||
}
|
||||
// Player need 10+ permanents on the battlefield
|
||||
if (p.getZone(ZoneType.Battlefield).size() >= 10) {
|
||||
p.setBlessing(true, sa.getOriginalHost().getSetCode());
|
||||
p.setBlessing(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class BecomeMonarchEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
// TODO: improve ai and fix corner cases
|
||||
final String set = sa.getOriginalHost().getSetCode();
|
||||
final String set = sa.getHostCard().getSetCode();
|
||||
|
||||
for (final Player p : getTargetPlayers(sa)) {
|
||||
if (!p.isInGame()) {
|
||||
|
||||
@@ -37,7 +37,7 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
||||
final Player chooser = sa.hasParam("Chooser") ? getDefinedPlayersOrTargeted(sa, "Chooser").get(0) : activator;
|
||||
|
||||
final MagicStack stack = activator.getGame().getStack();
|
||||
|
||||
|
||||
for (final SpellAbility tgtSA : sas) {
|
||||
SpellAbilityStackInstance si = stack.getInstanceMatchingSpellAbilityID(tgtSA);
|
||||
if (si == null) {
|
||||
@@ -72,8 +72,8 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
||||
// 2. prepare new target choices
|
||||
SpellAbilityStackInstance replaceIn = chosenTarget.getKey();
|
||||
GameObject oldTarget = chosenTarget.getValue();
|
||||
TargetChoices newTargetBlock = replaceIn.getTargetChoices();
|
||||
TargetChoices oldTargetBlock = newTargetBlock.clone();
|
||||
TargetChoices oldTargetBlock = replaceIn.getTargetChoices();
|
||||
TargetChoices newTargetBlock = oldTargetBlock.clone();
|
||||
// gets the divided value from old target
|
||||
Integer div = oldTargetBlock.getDividedValue(oldTarget);
|
||||
// 3. test if updated choices would be correct.
|
||||
@@ -87,7 +87,7 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
||||
if (div != null) {
|
||||
newTargetBlock.addDividedAllocation(newTarget, div);
|
||||
}
|
||||
replaceIn.updateTarget(oldTargetBlock, sa.getHostCard());
|
||||
replaceIn.updateTarget(newTargetBlock, sa.getHostCard());
|
||||
}
|
||||
} else {
|
||||
while (changingTgtSI != null) {
|
||||
@@ -104,26 +104,25 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
||||
if (candidates.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
GameEntity choice = Aggregates.random(candidates);
|
||||
TargetChoices oldTarget = changingTgtSA.getTargets();
|
||||
changingTgtSA.resetTargets();
|
||||
GameEntity choice = Aggregates.random(candidates);
|
||||
changingTgtSA.getTargets().add(choice);
|
||||
if (changingTgtSA.isDividedAsYouChoose()) {
|
||||
changingTgtSA.addDividedAllocation(choice, div);
|
||||
}
|
||||
changingTgtSI.updateTarget(oldTarget, sa.getHostCard());
|
||||
|
||||
changingTgtSI.updateTarget(changingTgtSA.getTargets(), sa.getHostCard());
|
||||
}
|
||||
else if (sa.hasParam("DefinedMagnet")) {
|
||||
GameObject newTarget = Iterables.getFirst(getDefinedCardsOrTargeted(sa, "DefinedMagnet"), null);
|
||||
if (newTarget != null && changingTgtSA.canTarget(newTarget)) {
|
||||
int div = changingTgtSA.getTotalDividedValue();
|
||||
TargetChoices oldTarget = changingTgtSA.getTargets();
|
||||
changingTgtSA.resetTargets();
|
||||
changingTgtSA.getTargets().add(newTarget);
|
||||
changingTgtSI.updateTarget(changingTgtSA.getTargets(), sa.getHostCard());
|
||||
if (changingTgtSA.isDividedAsYouChoose()) {
|
||||
changingTgtSA.addDividedAllocation(newTarget, div);
|
||||
}
|
||||
changingTgtSI.updateTarget(oldTarget, sa.getHostCard());
|
||||
}
|
||||
} else {
|
||||
// Update targets, with a potential new target
|
||||
@@ -133,9 +132,8 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
||||
source = changingTgtSA.getTargetCard();
|
||||
}
|
||||
Predicate<GameObject> filter = sa.hasParam("TargetRestriction") ? GameObjectPredicates.restriction(sa.getParam("TargetRestriction").split(","), activator, source, sa) : null;
|
||||
TargetChoices oldTarget = changingTgtSA.getTargets();
|
||||
chooser.getController().chooseNewTargetsFor(changingTgtSA, filter, false);
|
||||
changingTgtSI.updateTarget(oldTarget, sa.getHostCard());
|
||||
TargetChoices newTarget = chooser.getController().chooseNewTargetsFor(changingTgtSA, filter, false);
|
||||
changingTgtSI.updateTarget(newTarget, sa.getHostCard());
|
||||
}
|
||||
}
|
||||
changingTgtSI = changingTgtSI.getSubInstance();
|
||||
|
||||
@@ -186,12 +186,10 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
|
||||
if (sa.hasParam("AtEOT") && !triggerList.isEmpty()) {
|
||||
registerDelayedTrigger(sa, sa.getParam("AtEOT"), triggerList.allCards());
|
||||
if (sa.hasParam("Duration")) {
|
||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, sa));
|
||||
}
|
||||
|
||||
changeZoneUntilCommand(triggerList, sa);
|
||||
|
||||
// CR 701.20d If an effect would cause a player to shuffle a set of objects into a library,
|
||||
// that library is shuffled even if there are no objects in that set.
|
||||
if (sa.hasParam("Shuffle")) {
|
||||
|
||||
@@ -663,24 +663,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
if (sa.isKeyword(Keyword.UNEARTH) && movedCard.isInPlay()) {
|
||||
movedCard.setUnearthed(true);
|
||||
|
||||
final Card eff = createEffect(sa, sa.getActivatingPlayer(), "Unearth Effect", hostCard.getImageKey());
|
||||
|
||||
// It gains haste.
|
||||
String s = "Mode$ Continuous | Affected$ Card.IsRemembered | EffectZone$ Command | AddKeyword$ Haste";
|
||||
eff.addStaticAbility(s);
|
||||
|
||||
// If it would leave the battlefield, exile it instead of putting it anywhere else.
|
||||
addLeaveBattlefieldReplacement(eff, "Exile");
|
||||
|
||||
eff.addRemembered(movedCard);
|
||||
|
||||
movedCard.addLeavesPlayCommand(exileEffectCommand(game, eff));
|
||||
|
||||
game.getAction().moveToCommand(eff, sa);
|
||||
|
||||
// Exile it at the beginning of the next end step.
|
||||
movedCard.addChangedCardKeywords(Lists.newArrayList("Haste"), null, false, game.getNextTimestamp(), null);
|
||||
registerDelayedTrigger(sa, "Exile", Lists.newArrayList(movedCard));
|
||||
addLeaveBattlefieldReplacement(movedCard, sa, "Exile");
|
||||
}
|
||||
if (sa.hasParam("LeaveBattlefield")) {
|
||||
addLeaveBattlefieldReplacement(movedCard, sa, sa.getParam("LeaveBattlefield"));
|
||||
@@ -747,10 +732,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("ForetoldCost")) {
|
||||
movedCard.setForetoldCostByEffect(true);
|
||||
}
|
||||
}
|
||||
// look at the exiled card
|
||||
if (sa.hasParam("WithMayLook") || sa.hasParam("Foretold")) {
|
||||
movedCard.addMayLookFaceDownExile(activator);
|
||||
// look at the exiled card
|
||||
movedCard.addMayLookTemp(activator);
|
||||
}
|
||||
|
||||
// CR 400.7k
|
||||
@@ -834,8 +817,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("AtEOT") && !triggerList.isEmpty()) {
|
||||
registerDelayedTrigger(sa, sa.getParam("AtEOT"), triggerList.allCards());
|
||||
}
|
||||
|
||||
changeZoneUntilCommand(triggerList, sa);
|
||||
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
|
||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, sa));
|
||||
}
|
||||
|
||||
// might set after card is moved again if something has changed
|
||||
if (destination.equals(ZoneType.Exile)) {
|
||||
@@ -1040,9 +1024,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
|
||||
handleCastWhileSearching(fetchList, decider);
|
||||
}
|
||||
if (sa.hasParam("RememberSearched")) {
|
||||
source.addRemembered(player);
|
||||
}
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(decider);
|
||||
runParams.put(AbilityKey.Target, player);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.SearchedLibrary, runParams, false);
|
||||
@@ -1382,11 +1363,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("ForetoldCost")) {
|
||||
movedCard.setForetoldCostByEffect(true);
|
||||
}
|
||||
}
|
||||
|
||||
// look at the exiled card
|
||||
if (sa.hasParam("WithMayLook") || sa.hasParam("Foretold")) {
|
||||
movedCard.addMayLookFaceDownExile(sa.getActivatingPlayer());
|
||||
// look at the exiled card
|
||||
movedCard.addMayLookTemp(sa.getActivatingPlayer());
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -1482,7 +1460,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
|
||||
changeZoneUntilCommand(triggerList, sa);
|
||||
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
|
||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, sa));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCastWhileSearching(final CardCollection fetchList, final Player decider) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -46,7 +45,7 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
choices.removeAll(toRemove);
|
||||
}
|
||||
|
||||
int indx = 1;
|
||||
int indx = 0;
|
||||
// set CharmOrder
|
||||
for (AbilitySub sub : choices) {
|
||||
sub.setSVar("CharmOrder", Integer.toString(indx));
|
||||
@@ -90,13 +89,12 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
boolean limit = sa.hasParam("ActivationLimit");
|
||||
boolean gameLimit = sa.hasParam("GameActivationLimit");
|
||||
boolean oppChooses = "Opponent".equals(sa.getParam("Chooser"));
|
||||
boolean spree = source.hasKeyword(Keyword.SPREE);
|
||||
boolean tiered = source.hasKeyword(Keyword.TIERED);
|
||||
boolean spree = sa.hasParam("Spree");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(sa.getCostDescription());
|
||||
|
||||
if (!spree && !tiered) {
|
||||
if (!spree) {
|
||||
sb.append(oppChooses ? "An opponent chooses " : "Choose ");
|
||||
if (isX) {
|
||||
sb.append(sa.hasParam("MinCharmNum") && min == 0 ? "up to " : "").append("X");
|
||||
@@ -165,7 +163,7 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
if (!includeChosen) {
|
||||
sb.append(num == 1 ? " mode." : " modes.");
|
||||
} else if (!list.isEmpty()) {
|
||||
if (!spree && !tiered) {
|
||||
if (!spree) {
|
||||
if (!repeat && !additionalDesc && !limit && !gameLimit) {
|
||||
sb.append(" \u2014");
|
||||
}
|
||||
@@ -173,10 +171,7 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
}
|
||||
for (AbilitySub sub : list) {
|
||||
if (spree) {
|
||||
sb.append("+ " + new Cost(sub.getParam("ModeCost"), false).toSimpleString() + " \u2014 ");
|
||||
} else if (tiered) {
|
||||
sb.append("\u2022 ").append(sub.getParam("PrecostDesc")).append(" \u2014 ");
|
||||
sb.append(new Cost(sub.getParam("ModeCost"), false).toSimpleString() + " \u2014 ");
|
||||
sb.append("+ " + new Cost(sub.getParam("SpreeCost"), false).toSimpleString() + " \u2014 ");
|
||||
} else if (sub.hasParam("Pawprint")) {
|
||||
sb.append(StringUtils.repeat("{P}", Integer.parseInt(sub.getParam("Pawprint"))) + " \u2014 ");
|
||||
} else {
|
||||
@@ -276,14 +271,10 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
// Sort Chosen by SA order
|
||||
chosen.sort(Comparator.comparingInt(o -> o.getSVarInt("CharmOrder")));
|
||||
|
||||
int indx = 1;
|
||||
for (AbilitySub sub : chosen) {
|
||||
// Clone the chosen, just in case the same subAb gets chosen multiple times
|
||||
AbilitySub clone = (AbilitySub)sub.copy(sa.getActivatingPlayer());
|
||||
|
||||
clone.setSVar("CharmOrder", Integer.toString(indx));
|
||||
indx++;
|
||||
|
||||
// make StackDescription be the SpellDescription if it doesn't already have one
|
||||
if (!clone.hasParam("StackDescription")) {
|
||||
clone.putParam("StackDescription", "SpellDescription");
|
||||
|
||||
@@ -103,7 +103,7 @@ public class ChooseCardEffect extends SpellAbilityEffect {
|
||||
CardCollectionView pChoices = choices;
|
||||
CardCollection chosen = new CardCollection();
|
||||
if (!p.isInGame()) {
|
||||
p = getNewChooser(sa, p);
|
||||
p = getNewChooser(sa, activator, p);
|
||||
}
|
||||
if (sa.hasParam("ControlledByPlayer")) {
|
||||
final String param = sa.getParam("ControlledByPlayer");
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.deck.DeckRecognizer;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Aggregates;
|
||||
@@ -44,13 +41,6 @@ public class ChooseColorEffect extends SpellAbilityEffect {
|
||||
String[] restrictedChoices = sa.getParam("Choices").split(",");
|
||||
colorChoices = Arrays.asList(restrictedChoices);
|
||||
}
|
||||
if (sa.hasParam("ColorsFrom")) {
|
||||
ColorSet cs = CardUtil.getColorsFromCards(AbilityUtils.getDefinedCards(card, sa.getParam("ColorsFrom"), sa));
|
||||
if (cs.isColorless()) {
|
||||
return;
|
||||
}
|
||||
colorChoices = cs.stream().map(Object::toString).collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
if (sa.hasParam("Exclude")) {
|
||||
for (String s : sa.getParam("Exclude").split(",")) {
|
||||
colorChoices.remove(s);
|
||||
@@ -59,22 +49,24 @@ public class ChooseColorEffect extends SpellAbilityEffect {
|
||||
|
||||
for (Player p : getTargetPlayers(sa)) {
|
||||
if (!p.isInGame()) {
|
||||
p = getNewChooser(sa, p);
|
||||
p = getNewChooser(sa, sa.getActivatingPlayer(), p);
|
||||
}
|
||||
List<String> chosenColors = new ArrayList<>();
|
||||
int cntMin = sa.hasParam("UpTo") ? 0 : sa.hasParam("TwoColors") ? 2 : 1;
|
||||
int cntMin = sa.hasParam("TwoColors") ? 2 : 1;
|
||||
int cntMax = sa.hasParam("TwoColors") ? 2 : sa.hasParam("OrColors") ? colorChoices.size() : 1;
|
||||
String prompt = null;
|
||||
if (cntMax == 1) {
|
||||
prompt = Localizer.getInstance().getMessage("lblChooseAColor");
|
||||
} else if (cntMax > cntMin) {
|
||||
if (cntMax >= MagicColor.NUMBER_OR_COLORS) {
|
||||
prompt = Localizer.getInstance().getMessage("lblAtLastChooseNumColors", Lang.getNumeral(cntMin));
|
||||
} else {
|
||||
prompt = Localizer.getInstance().getMessage("lblChooseSpecifiedRangeColors", Lang.getNumeral(cntMin), Lang.getNumeral(cntMax));
|
||||
}
|
||||
} else {
|
||||
prompt = Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(cntMax));
|
||||
if (cntMax > cntMin) {
|
||||
if (cntMax >= MagicColor.NUMBER_OR_COLORS) {
|
||||
prompt = Localizer.getInstance().getMessage("lblAtLastChooseNumColors", Lang.getNumeral(cntMin));
|
||||
} else {
|
||||
prompt = Localizer.getInstance().getMessage("lblChooseSpecifiedRangeColors", Lang.getNumeral(cntMin), Lang.getNumeral(cntMax));
|
||||
}
|
||||
} else {
|
||||
prompt = Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(cntMax));
|
||||
}
|
||||
}
|
||||
Player noNotify = p;
|
||||
if (sa.hasParam("Random")) {
|
||||
|
||||
@@ -62,22 +62,23 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
||||
|
||||
for (Player p : getDefinedPlayersOrTargeted(sa)) {
|
||||
if (!p.isInGame()) {
|
||||
p = getNewChooser(sa, p);
|
||||
p = getNewChooser(sa, sa.getActivatingPlayer(), p);
|
||||
}
|
||||
|
||||
// determine if any of the choices are not valid
|
||||
List<SpellAbility> availableSA = Lists.newArrayList(abilities);
|
||||
List<SpellAbility> saToRemove = Lists.newArrayList();
|
||||
|
||||
for (SpellAbility saChoice : abilities) {
|
||||
if (saChoice.getRestrictions() != null && !saChoice.getRestrictions().checkOtherRestrictions(host, saChoice, sa.getActivatingPlayer())) {
|
||||
availableSA.remove(saChoice);
|
||||
saToRemove.add(saChoice);
|
||||
} else if (saChoice.hasParam("UnlessCost")) {
|
||||
// generic check for if the cost can be paid
|
||||
Cost unlessCost = new Cost(saChoice.getParam("UnlessCost"), false);
|
||||
if (!unlessCost.canPay(sa, p, true)) {
|
||||
availableSA.remove(saChoice);
|
||||
saToRemove.add(saChoice);
|
||||
}
|
||||
}
|
||||
}
|
||||
abilities.removeAll(saToRemove);
|
||||
|
||||
List<SpellAbility> chosenSAs = Lists.newArrayList();
|
||||
String prompt = sa.getParamOrDefault("ChoicePrompt", "Choose");
|
||||
@@ -85,7 +86,7 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("AtRandom")) {
|
||||
random = true;
|
||||
chosenSAs = Aggregates.random(availableSA, amount);
|
||||
chosenSAs = Aggregates.random(abilities, amount);
|
||||
|
||||
int i = 0;
|
||||
while (sa.getParam("AtRandom").equals("Urza") && i < chosenSAs.size()) {
|
||||
@@ -98,8 +99,8 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
||||
chosenSAs.set(i, Aggregates.random(abilities));
|
||||
}
|
||||
}
|
||||
} else if (!availableSA.isEmpty()) {
|
||||
chosenSAs = p.getController().chooseSpellAbilitiesForEffect(availableSA, sa, prompt, amount, ImmutableMap.of());
|
||||
} else if (!abilities.isEmpty()) {
|
||||
chosenSAs = p.getController().chooseSpellAbilitiesForEffect(abilities, sa, prompt, amount, ImmutableMap.of());
|
||||
}
|
||||
|
||||
List<Object> oldRem = Lists.newArrayList(IterableUtil.filter(host.getRemembered(), Player.class));
|
||||
@@ -116,6 +117,7 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
||||
} else if (secretly) {
|
||||
if (record.length() > 0) record.append("\r\n");
|
||||
record.append(Localizer.getInstance().getMessage("lblPlayerChooseValue", p, chosenValue));
|
||||
|
||||
}
|
||||
if (sa.hasParam("SetChosenMode")) {
|
||||
sa.getHostCard().setChosenMode(chosenValue);
|
||||
|
||||
@@ -129,8 +129,6 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
cloneTargets.remove(cardToCopy);
|
||||
}
|
||||
|
||||
final long ts = game.getNextTimestamp();
|
||||
|
||||
for (Card tgtCard : cloneTargets) {
|
||||
if (sa.hasParam("CloneZone") &&
|
||||
!tgtCard.isInZone(ZoneType.smartValueOf(sa.getParam("CloneZone")))) {
|
||||
@@ -143,6 +141,7 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
|
||||
game.getTriggerHandler().clearActiveTriggers(tgtCard, null);
|
||||
|
||||
final long ts = game.getNextTimestamp();
|
||||
tgtCard.addCloneState(CardFactory.getCloneStates(cardToCopy, tgtCard, sa), ts);
|
||||
tgtCard.updateRooms();
|
||||
|
||||
@@ -200,7 +199,7 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
tgtCard.addRemembered(cardToCopy);
|
||||
}
|
||||
// spire
|
||||
tgtCard.setMarkedColors(cardToCopy.getMarkedColors());
|
||||
tgtCard.setChosenColorID(cardToCopy.getChosenColorID());
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(tgtCard));
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.PaperCard;
|
||||
import forge.util.PredicateString.StringOp;
|
||||
|
||||
public class CopyPermanentEffect extends TokenEffectBase {
|
||||
|
||||
@@ -184,15 +185,19 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
System.err.println("Copying random permanent(s): " + tgtCards.toString());
|
||||
}
|
||||
} else if (sa.hasParam("DefinedName")) {
|
||||
List<PaperCard> cards = Lists.newArrayList(StaticData.instance().getCommonCards().getUniqueCards());
|
||||
String name = sa.getParam("DefinedName");
|
||||
if (name.equals("NamedCard")) {
|
||||
if (!host.getNamedCard().isEmpty()) {
|
||||
name = host.getNamedCard();
|
||||
}
|
||||
}
|
||||
PaperCard pc = StaticData.instance().getCommonCards().getUniqueByName(name);
|
||||
if (pc != null) {
|
||||
tgtCards.add(Card.fromPaperCard(pc, controller));
|
||||
|
||||
Predicate<PaperCard> cpp = PaperCardPredicates.fromRules(CardRulesPredicates.name(StringOp.EQUALS, name));
|
||||
cards = Lists.newArrayList(IterableUtil.filter(cards, cpp));
|
||||
|
||||
if (!cards.isEmpty()) {
|
||||
tgtCards.add(Card.fromPaperCard(cards.get(0), controller));
|
||||
}
|
||||
} else if (sa.hasParam("Choices")) {
|
||||
Player chooser = activator;
|
||||
@@ -309,7 +314,7 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
}
|
||||
}
|
||||
// spire
|
||||
copy.setMarkedColors(original.getMarkedColors());
|
||||
copy.setChosenColorID(original.getChosenColorID());
|
||||
|
||||
copy.setTokenSpawningAbility(sa);
|
||||
copy.setGamePieceType(GamePieceType.TOKEN);
|
||||
|
||||
@@ -13,6 +13,7 @@ import forge.game.card.CardFactory;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbilityCantBeCopied;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.*;
|
||||
import forge.util.collect.FCollection;
|
||||
@@ -65,7 +66,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
|
||||
List<SpellAbility> tgtSpells = getTargetSpells(sa);
|
||||
|
||||
tgtSpells.removeIf(SpellAbility::cantBeCopied);
|
||||
tgtSpells.removeIf(tgtSA -> StaticAbilityCantBeCopied.cantBeCopied(tgtSA.getHostCard()));
|
||||
|
||||
if (tgtSpells.isEmpty() || amount == 0) {
|
||||
return;
|
||||
|
||||
@@ -619,6 +619,23 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
for (String k : keywords) {
|
||||
resolvePerType(sa, placer, CounterType.getType(k), counterAmount, table, false);
|
||||
}
|
||||
} else if (sa.hasParam("ForColor")) {
|
||||
Iterable<String> oldColors = card.getChosenColors();
|
||||
CounterType counterType = null;
|
||||
try {
|
||||
counterType = chooseTypeFromList(sa, sa.getParam("CounterType"), null, placer.getController());
|
||||
} catch (Exception e) {
|
||||
System.out.println("Counter type doesn't match, nor does an SVar exist with the type name.");
|
||||
return;
|
||||
}
|
||||
for (String color : MagicColor.Constant.ONLY_COLORS) {
|
||||
card.setChosenColors(Lists.newArrayList(color));
|
||||
if (sa.getOriginalParam("ChoiceTitle") != null) {
|
||||
sa.getMapParams().put("ChoiceTitle", sa.getOriginalParam("ChoiceTitle").replace("chosenColor", color));
|
||||
}
|
||||
resolvePerType(sa, placer, counterType, counterAmount, table, true);
|
||||
}
|
||||
card.setChosenColors(Lists.newArrayList(oldColors));
|
||||
} else {
|
||||
CounterType counterType = null;
|
||||
if (!sa.hasParam("EachExistingCounter") && !sa.hasParam("EachFromSource")
|
||||
@@ -632,19 +649,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("ForColor")) {
|
||||
Iterable<String> oldColors = card.getChosenColors();
|
||||
for (String color : MagicColor.Constant.ONLY_COLORS) {
|
||||
card.setChosenColors(Lists.newArrayList(color));
|
||||
if (sa.getOriginalParam("ChoiceTitle") != null) {
|
||||
sa.getMapParams().put("ChoiceTitle", sa.getOriginalParam("ChoiceTitle").replace("chosenColor", color));
|
||||
}
|
||||
resolvePerType(sa, placer, counterType, counterAmount, table, true);
|
||||
}
|
||||
card.setChosenColors(Lists.newArrayList(oldColors));
|
||||
} else {
|
||||
resolvePerType(sa, placer, counterType, counterAmount, table, true);
|
||||
}
|
||||
resolvePerType(sa, placer, counterType, counterAmount, table, true);
|
||||
}
|
||||
|
||||
table.replaceCounterEffect(game, sa, true);
|
||||
|
||||
@@ -101,12 +101,34 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
boolean rememberAmount = sa.hasParam("RememberAmount");
|
||||
|
||||
int totalRemoved = 0;
|
||||
|
||||
for (final Player tgtPlayer : getTargetPlayers(sa)) {
|
||||
if (!tgtPlayer.isInGame()) {
|
||||
continue;
|
||||
}
|
||||
// Removing energy
|
||||
if (type.equals("All")) {
|
||||
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtPlayer.getCounters().entrySet())) {
|
||||
totalRemoved += tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
|
||||
}
|
||||
} else {
|
||||
if (num.equals("All")) {
|
||||
cntToRemove = tgtPlayer.getCounters(counterType);
|
||||
}
|
||||
if (type.equals("Any")) {
|
||||
totalRemoved += removeAnyType(tgtPlayer, cntToRemove, sa);
|
||||
} else {
|
||||
totalRemoved += tgtPlayer.subtractCounter(counterType, cntToRemove, activator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CardCollectionView srcCards;
|
||||
|
||||
String typeforPrompt = counterType == null ? "" : counterType.getName();
|
||||
String title = Localizer.getInstance().getMessage("lblChooseCardsToTakeTargetCounters", typeforPrompt);
|
||||
title = title.replace(" ", " ");
|
||||
if (sa.hasParam("Choices")) {
|
||||
if (sa.hasParam("Choices") && counterType != null) {
|
||||
ZoneType choiceZone = sa.hasParam("ChoiceZone") ? ZoneType.smartValueOf(sa.getParam("ChoiceZone"))
|
||||
: ZoneType.Battlefield;
|
||||
|
||||
@@ -123,27 +145,6 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
params.put("CounterType", counterType);
|
||||
srcCards = pc.chooseCardsForEffect(choices, sa, title, min, max, min == 0, params);
|
||||
} else {
|
||||
for (final Player tgtPlayer : getTargetPlayers(sa)) {
|
||||
if (!tgtPlayer.isInGame()) {
|
||||
continue;
|
||||
}
|
||||
// Removing energy
|
||||
if (type.equals("All")) {
|
||||
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtPlayer.getCounters().entrySet())) {
|
||||
totalRemoved += tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
|
||||
}
|
||||
} else {
|
||||
if (num.equals("All")) {
|
||||
cntToRemove = tgtPlayer.getCounters(counterType);
|
||||
}
|
||||
if (type.equals("Any")) {
|
||||
totalRemoved += removeAnyType(tgtPlayer, cntToRemove, sa);
|
||||
} else {
|
||||
totalRemoved += tgtPlayer.subtractCounter(counterType, cntToRemove, activator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
srcCards = getTargetCards(sa);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ public class DamageEachEffect extends DamageBaseEffect {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellEffect#resolve(java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
|
||||
@@ -127,8 +127,8 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
final boolean skipReorder = sa.hasParam("SkipReorder");
|
||||
|
||||
// A hack for cards like Explorer's Scope that need to ensure that a card is revealed to the player activating the ability
|
||||
final boolean forceReveal = sa.hasParam("ForceRevealToController")
|
||||
|| sa.hasParam("ForceReveal") || sa.hasParam("WithMayLook");
|
||||
final boolean forceReveal = sa.hasParam("ForceRevealToController") ||
|
||||
sa.hasParam("ForceReveal");
|
||||
|
||||
// These parameters are used to indicate that a dialog box must be show to the player asking if the player wants to proceed
|
||||
// with an optional ability, otherwise the optional ability is skipped.
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.game.ability.effects;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
@@ -123,9 +124,11 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
|
||||
final List<Player> targets = getTargetPlayers(sa),
|
||||
discarders;
|
||||
Player firstTarget = null;
|
||||
if (mode.equals("RevealTgtChoose")) {
|
||||
// In this case the target need not be the discarding player
|
||||
discarders = getDefinedPlayersOrTargeted(sa);
|
||||
firstTarget = Iterables.getFirst(targets, null);
|
||||
} else {
|
||||
discarders = targets;
|
||||
}
|
||||
@@ -137,123 +140,125 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
CardCollectionView toBeDiscarded = new CardCollection();
|
||||
final int numCardsInHand = p.getCardsIn(ZoneType.Hand).size();
|
||||
if (mode.equals("Defined")) {
|
||||
if (!p.canDiscardBy(sa, true)) {
|
||||
continue;
|
||||
if ((mode.equals("RevealTgtChoose") && firstTarget != null) || !sa.usesTargeting() || p.canBeTargetedBy(sa)) {
|
||||
final int numCardsInHand = p.getCardsIn(ZoneType.Hand).size();
|
||||
if (mode.equals("Defined")) {
|
||||
if (!p.canDiscardBy(sa, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean runDiscard = !sa.hasParam("Optional")
|
||||
|| p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, sa.getParam("DiscardMessage"), null);
|
||||
if (runDiscard) {
|
||||
toBeDiscarded = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedCards"), sa);
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
}
|
||||
}
|
||||
|
||||
boolean runDiscard = !sa.hasParam("Optional")
|
||||
|| p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, sa.getParam("DiscardMessage"), null);
|
||||
if (runDiscard) {
|
||||
toBeDiscarded = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedCards"), sa);
|
||||
if (mode.equals("Hand")) {
|
||||
toBeDiscarded = p.getCardsIn(ZoneType.Hand);
|
||||
|
||||
// Empty hand can still be discarded
|
||||
if (!toBeDiscarded.isEmpty() && !p.canDiscardBy(sa, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
}
|
||||
}
|
||||
|
||||
if (mode.equals("Hand")) {
|
||||
toBeDiscarded = p.getCardsIn(ZoneType.Hand);
|
||||
|
||||
// Empty hand can still be discarded
|
||||
if (!toBeDiscarded.isEmpty() && !p.canDiscardBy(sa, true)) {
|
||||
continue;
|
||||
int numCards = 1;
|
||||
if (sa.hasParam("NumCards")) {
|
||||
numCards = AbilityUtils.calculateAmount(source, sa.getParam("NumCards"), sa);
|
||||
numCards = Math.min(numCards, numCardsInHand);
|
||||
}
|
||||
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
}
|
||||
if (mode.equals("Random")) {
|
||||
if (!p.canDiscardBy(sa, true)) {
|
||||
continue;
|
||||
}
|
||||
String message = Localizer.getInstance().getMessage("lblWouldYouLikeRandomDiscardTargetCard", String.valueOf(numCards));
|
||||
boolean runDiscard = !sa.hasParam("Optional") || p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message, null);
|
||||
|
||||
int numCards = 1;
|
||||
if (sa.hasParam("NumCards")) {
|
||||
numCards = AbilityUtils.calculateAmount(source, sa.getParam("NumCards"), sa);
|
||||
numCards = Math.min(numCards, numCardsInHand);
|
||||
}
|
||||
if (runDiscard) {
|
||||
final String valid = sa.getParamOrDefault("DiscardValid", "Card");
|
||||
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), valid, source.getController(), source, sa);
|
||||
|
||||
if (mode.equals("Random")) {
|
||||
if (!p.canDiscardBy(sa, true)) {
|
||||
continue;
|
||||
toBeDiscarded = new CardCollection(Aggregates.random(list, numCards));
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
}
|
||||
}
|
||||
String message = Localizer.getInstance().getMessage("lblWouldYouLikeRandomDiscardTargetCard", String.valueOf(numCards));
|
||||
boolean runDiscard = !sa.hasParam("Optional") || p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message, null);
|
||||
else if (mode.equals("TgtChoose") && sa.hasParam("UnlessType")) {
|
||||
if (!p.canDiscardBy(sa, true)) {
|
||||
continue;
|
||||
}
|
||||
if (numCardsInHand > 0) {
|
||||
CardCollectionView hand = p.getCardsIn(ZoneType.Hand);
|
||||
toBeDiscarded = p.getController().chooseCardsToDiscardUnlessType(Math.min(numCards, numCardsInHand), hand, sa.getParam("UnlessType"), sa);
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game,toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
}
|
||||
}
|
||||
else if (mode.equals("RevealDiscardAll")) {
|
||||
// Reveal
|
||||
final CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand);
|
||||
|
||||
for (final Player opp : p.getAllOtherPlayers()) {
|
||||
opp.getController().reveal(dPHand, ZoneType.Hand, p, Localizer.getInstance().getMessage("lblReveal") + " ");
|
||||
}
|
||||
|
||||
if (!p.canDiscardBy(sa, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String valid = sa.getParamOrDefault("DiscardValid", "Card");
|
||||
|
||||
if (valid.contains("X")) {
|
||||
valid = TextUtil.fastReplace(valid,
|
||||
"X", Integer.toString(AbilityUtils.calculateAmount(source, "X", sa)));
|
||||
}
|
||||
|
||||
toBeDiscarded = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa);
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
} else if (mode.endsWith("YouChoose") || mode.endsWith("TgtChoose")) {
|
||||
CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand);
|
||||
if (dPHand.isEmpty())
|
||||
continue; // for loop over players
|
||||
|
||||
if (sa.hasParam("RevealNumber")) {
|
||||
int amount = AbilityUtils.calculateAmount(source, sa.getParam("RevealNumber"), sa);
|
||||
dPHand = p.getController().chooseCardsToRevealFromHand(amount, amount, dPHand);
|
||||
}
|
||||
|
||||
Player chooser = p;
|
||||
if (mode.endsWith("YouChoose")) {
|
||||
chooser = source.getController();
|
||||
} else if (mode.equals("RevealTgtChoose")) {
|
||||
chooser = firstTarget;
|
||||
}
|
||||
|
||||
if (mode.startsWith("Reveal")) {
|
||||
game.getAction().reveal(dPHand, p);
|
||||
}
|
||||
if (mode.startsWith("Look") && p != chooser) {
|
||||
game.getAction().revealTo(dPHand, chooser);
|
||||
}
|
||||
|
||||
if (!p.canDiscardBy(sa, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (runDiscard) {
|
||||
final String valid = sa.getParamOrDefault("DiscardValid", "Card");
|
||||
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), valid, source.getController(), source, sa);
|
||||
CardCollection validCards = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa);
|
||||
|
||||
int min = sa.hasParam("AnyNumber") || sa.hasParam("Optional") ? 0 : Math.min(validCards.size(), numCards);
|
||||
int max = sa.hasParam("AnyNumber") ? validCards.size() : Math.min(validCards.size(), numCards);
|
||||
|
||||
toBeDiscarded = max == 0 ? CardCollection.EMPTY : chooser.getController().chooseCardsToDiscardFrom(p, sa, validCards, min, max);
|
||||
|
||||
toBeDiscarded = new CardCollection(Aggregates.random(list, numCards));
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
}
|
||||
}
|
||||
else if (mode.equals("TgtChoose") && sa.hasParam("UnlessType")) {
|
||||
if (!p.canDiscardBy(sa, true)) {
|
||||
continue;
|
||||
}
|
||||
if (numCardsInHand > 0) {
|
||||
CardCollectionView hand = p.getCardsIn(ZoneType.Hand);
|
||||
toBeDiscarded = p.getController().chooseCardsToDiscardUnlessType(Math.min(numCards, numCardsInHand), hand, sa.getParam("UnlessType"), sa);
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game,toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
}
|
||||
}
|
||||
else if (mode.equals("RevealDiscardAll")) {
|
||||
// Reveal
|
||||
final CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand);
|
||||
|
||||
for (final Player opp : p.getAllOtherPlayers()) {
|
||||
opp.getController().reveal(dPHand, ZoneType.Hand, p, Localizer.getInstance().getMessage("lblReveal") + " ");
|
||||
}
|
||||
|
||||
if (!p.canDiscardBy(sa, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String valid = sa.getParamOrDefault("DiscardValid", "Card");
|
||||
|
||||
if (valid.contains("X")) {
|
||||
valid = TextUtil.fastReplace(valid,
|
||||
"X", Integer.toString(AbilityUtils.calculateAmount(source, "X", sa)));
|
||||
}
|
||||
|
||||
toBeDiscarded = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa);
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
} else if (mode.endsWith("YouChoose") || mode.endsWith("TgtChoose")) {
|
||||
CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand);
|
||||
if (dPHand.isEmpty())
|
||||
continue; // for loop over players
|
||||
|
||||
if (sa.hasParam("RevealNumber")) {
|
||||
int amount = AbilityUtils.calculateAmount(source, sa.getParam("RevealNumber"), sa);
|
||||
dPHand = p.getController().chooseCardsToRevealFromHand(amount, amount, dPHand);
|
||||
}
|
||||
|
||||
Player chooser = p;
|
||||
if (mode.endsWith("YouChoose")) {
|
||||
chooser = sa.getActivatingPlayer();
|
||||
} else if (mode.equals("RevealTgtChoose")) {
|
||||
chooser = targets.get(0);
|
||||
}
|
||||
|
||||
if (mode.startsWith("Reveal")) {
|
||||
game.getAction().reveal(dPHand, p);
|
||||
}
|
||||
if (mode.startsWith("Look") && p != chooser) {
|
||||
game.getAction().revealTo(dPHand, chooser);
|
||||
}
|
||||
|
||||
if (!p.canDiscardBy(sa, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String valid = sa.getParamOrDefault("DiscardValid", "Card");
|
||||
CardCollection validCards = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa);
|
||||
|
||||
int min = sa.hasParam("AnyNumber") || sa.hasParam("Optional") ? 0 : Math.min(validCards.size(), numCards);
|
||||
int max = sa.hasParam("AnyNumber") ? validCards.size() : Math.min(validCards.size(), numCards);
|
||||
|
||||
toBeDiscarded = max == 0 ? CardCollection.EMPTY : chooser.getController().chooseCardsToDiscardFrom(p, sa, validCards, min, max);
|
||||
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
|
||||
if (mode.startsWith("Reveal") && p != chooser) {
|
||||
p.getController().reveal(toBeDiscarded, ZoneType.Hand, p, Localizer.getInstance().getMessage("lblPlayerHasChosenCardsFrom", chooser.getName()));
|
||||
if (mode.startsWith("Reveal") && p != chooser) {
|
||||
p.getController().reveal(toBeDiscarded, ZoneType.Hand, p, Localizer.getInstance().getMessage("lblPlayerHasChosenCardsFrom", chooser.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
discardedMap.put(p, toBeDiscarded);
|
||||
|
||||
@@ -57,7 +57,7 @@ import java.util.*;
|
||||
// Cardnames that include "," must use ";" instead in Spellbook$ (i.e. Tovolar; Dire Overlord)
|
||||
name = name.replace(";", ",");
|
||||
Card cardOption = Card.fromPaperCard(StaticData.instance().getCommonCards().getUniqueByName(name), player);
|
||||
cardOption.setTokenCard(sa.hasParam("TokenCard"));
|
||||
cardOption.setTokenCard(true);
|
||||
draftOptions.add(cardOption);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public class EndTurnEffect extends SpellAbilityEffect {
|
||||
final List<Player> enders = getDefinedPlayersOrTargeted(sa, "Defined");
|
||||
Player ender = enders.isEmpty() ? sa.getActivatingPlayer() : enders.get(0);
|
||||
if (!ender.isInGame()) {
|
||||
ender = getNewChooser(sa, ender);
|
||||
ender = getNewChooser(sa, sa.getActivatingPlayer(), ender);
|
||||
}
|
||||
|
||||
if (sa.hasParam("Optional") && !ender.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantEndTurn"), null)) {
|
||||
|
||||
@@ -16,7 +16,6 @@ import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerController;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbilityFlipCoinMod;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.MyRandom;
|
||||
@@ -49,26 +48,56 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player player = host.getController();
|
||||
int flipMultiplier = 1; // For multiple copies of Krark's Thumb
|
||||
|
||||
final List<Player> playersToFlip = AbilityUtils.getDefinedPlayers(host, sa.getParam("Flipper"), sa);
|
||||
//final List<Player> caller = AbilityUtils.getDefinedPlayers(host, sa.getParam("Caller"), sa);
|
||||
if (playersToFlip.isEmpty()) {
|
||||
playersToFlip.add(sa.getActivatingPlayer());
|
||||
}
|
||||
|
||||
final List<Player> caller = AbilityUtils.getDefinedPlayers(host, sa.getParam("Caller"), sa);
|
||||
if (caller.isEmpty()) {
|
||||
caller.add(player);
|
||||
}
|
||||
|
||||
final boolean noCall = sa.hasParam("NoCall");
|
||||
final boolean forEachPlayer = sa.hasParam("ForEachPlayer");
|
||||
String varName = sa.getParamOrDefault("SaveNumFlipsToSVar", "X");
|
||||
boolean victory = false;
|
||||
int amount = 1;
|
||||
if (sa.hasParam("Amount")) {
|
||||
amount = AbilityUtils.calculateAmount(host, sa.getParam("Amount"), sa);
|
||||
}
|
||||
|
||||
if (!noCall && !forEachPlayer && amount == 1) {
|
||||
flipMultiplier = getFlipMultiplier(caller.get(0));
|
||||
victory = flipCoinCall(caller.get(0), sa, flipMultiplier, varName);
|
||||
}
|
||||
|
||||
final boolean rememberResult = sa.hasParam("RememberResult");
|
||||
|
||||
for (final Player flipper : playersToFlip) {
|
||||
if (noCall) {
|
||||
int countHeads = flipCoins(flipper, sa, amount);
|
||||
int countTails = Math.abs(countHeads - amount);
|
||||
if (countHeads > 0) {
|
||||
if (sa.hasParam("RememberResult")) {
|
||||
host.addFlipResult(flipper, "Heads");
|
||||
flipMultiplier = getFlipMultiplier(flipper);
|
||||
|
||||
int countHeads = 0;
|
||||
int countTails = 0;
|
||||
|
||||
for (int i = 0; i < amount; ++i) {
|
||||
final boolean resultIsHeads = flipCoinNoCall(sa, flipper, flipMultiplier, varName);
|
||||
|
||||
if (resultIsHeads) {
|
||||
countHeads++;
|
||||
} else {
|
||||
countTails++;
|
||||
}
|
||||
|
||||
if (rememberResult) {
|
||||
host.addFlipResult(flipper, resultIsHeads ? "Heads" : "Tails");
|
||||
}
|
||||
}
|
||||
if (countHeads > 0) {
|
||||
SpellAbility sub = sa.getAdditionalAbility("HeadsSubAbility");
|
||||
if (sub != null) {
|
||||
if (sa.hasParam("Amount")) {
|
||||
@@ -78,9 +107,6 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
if (countTails > 0) {
|
||||
if (sa.hasParam("RememberResult")) {
|
||||
host.addFlipResult(flipper, "Tails");
|
||||
}
|
||||
SpellAbility sub = sa.getAdditionalAbility("TailsSubAbility");
|
||||
if (sub != null) {
|
||||
if (sa.hasParam("Amount")) {
|
||||
@@ -89,7 +115,46 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
||||
AbilityUtils.resolve(sub);
|
||||
}
|
||||
}
|
||||
} else if (amount > 1) {
|
||||
flipMultiplier = getFlipMultiplier(flipper);
|
||||
|
||||
int countWins = 0;
|
||||
int countLosses = 0;
|
||||
|
||||
for (int i = 0; i < amount; ++i) {
|
||||
final boolean win = flipCoinCall(caller.get(0), sa, flipMultiplier, varName);
|
||||
|
||||
if (win) {
|
||||
countWins++;
|
||||
} else {
|
||||
countLosses++;
|
||||
}
|
||||
}
|
||||
if (countWins > 0) {
|
||||
SpellAbility sub = sa.getAdditionalAbility("WinSubAbility");
|
||||
if (sub != null) {
|
||||
sub.setSVar("Wins", "Number$" + countWins);
|
||||
AbilityUtils.resolve(sub);
|
||||
}
|
||||
}
|
||||
if (countLosses > 0) {
|
||||
SpellAbility sub = sa.getAdditionalAbility("LoseSubAbility");
|
||||
if (sub != null) {
|
||||
sub.setSVar("Losses", "Number$" + countLosses);
|
||||
AbilityUtils.resolve(sub);
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("RememberNumber")) {
|
||||
String toRemember = sa.getParam("RememberNumber");
|
||||
if (toRemember.startsWith("Win")) {
|
||||
host.addRemembered(countWins);
|
||||
} else if (toRemember.startsWith("Loss")) {
|
||||
host.addRemembered(countLosses);
|
||||
}
|
||||
}
|
||||
} else if (forEachPlayer) {
|
||||
flipMultiplier = getFlipMultiplier(flipper);
|
||||
|
||||
int countWins = 0;
|
||||
int countLosses = 0;
|
||||
PlayerCollection wonFor = new PlayerCollection();
|
||||
@@ -97,9 +162,9 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
||||
|
||||
for (final Player p : AbilityUtils.getDefinedPlayers(host, sa.getParam("ForEachPlayer"), sa)) {
|
||||
final String info = " (" + p.getName() +")";
|
||||
final int win = flipCoins(flipper, sa, 1, info);
|
||||
final boolean win = flipCoinCall(caller.get(0), sa, flipMultiplier, varName, info);
|
||||
|
||||
if (win > 0) {
|
||||
if (win) {
|
||||
countWins++;
|
||||
wonFor.add(p);
|
||||
} else {
|
||||
@@ -131,59 +196,57 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
||||
host.addRemembered(tempRemembered);
|
||||
}
|
||||
}
|
||||
} else if (victory) {
|
||||
if (sa.hasParam("RememberWinner")) {
|
||||
host.addRemembered(flipper);
|
||||
}
|
||||
|
||||
if (sa.hasAdditionalAbility("WinSubAbility")) {
|
||||
AbilityUtils.resolve(sa.getAdditionalAbility("WinSubAbility"));
|
||||
}
|
||||
} else {
|
||||
int countWins = flipCoins(flipper, sa, amount);
|
||||
int countLosses = Math.abs(countWins - amount);
|
||||
if (countWins > 0) {
|
||||
if (sa.hasParam("RememberWinner")) {
|
||||
host.addRemembered(flipper);
|
||||
}
|
||||
SpellAbility sub = sa.getAdditionalAbility("WinSubAbility");
|
||||
if (sub != null) {
|
||||
sub.setSVar("Wins", "Number$" + countWins);
|
||||
AbilityUtils.resolve(sub);
|
||||
}
|
||||
if (sa.hasParam("RememberLoser")) {
|
||||
host.addRemembered(flipper);
|
||||
}
|
||||
if (countLosses > 0) {
|
||||
if (sa.hasParam("RememberLoser")) {
|
||||
host.addRemembered(flipper);
|
||||
}
|
||||
SpellAbility sub = sa.getAdditionalAbility("LoseSubAbility");
|
||||
if (sub != null) {
|
||||
sub.setSVar("Losses", "Number$" + countLosses);
|
||||
AbilityUtils.resolve(sub);
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("RememberNumber")) {
|
||||
String toRemember = sa.getParam("RememberNumber");
|
||||
if (toRemember.startsWith("Win")) {
|
||||
host.addRemembered(countWins);
|
||||
} else if (toRemember.startsWith("Loss")) {
|
||||
host.addRemembered(countLosses);
|
||||
}
|
||||
|
||||
if (sa.hasAdditionalAbility("LoseSubAbility")) {
|
||||
AbilityUtils.resolve(sa.getAdditionalAbility("LoseSubAbility"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int flipCoins(final Player flipper, final SpellAbility sa, final int amount) {
|
||||
return flipCoins(flipper, sa, amount, "");
|
||||
}
|
||||
public static int flipCoins(final Player flipper, final SpellAbility sa, final int amount, final String info) {
|
||||
int multiplier = getFlipMultiplier(flipper);
|
||||
int result = 0;
|
||||
boolean won = false;
|
||||
/**
|
||||
* <p>
|
||||
* flipCoinNoCall Flip a coin without any call.
|
||||
* </p>
|
||||
*
|
||||
* @param sa the source card.
|
||||
* @param flipper the player flipping the coin.
|
||||
* @param multiplier
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean flipCoinNoCall(final SpellAbility sa, final Player flipper, final int multiplier, final String varName) {
|
||||
boolean result = false;
|
||||
int numSuccesses = 0;
|
||||
|
||||
do {
|
||||
Boolean fixedResult = StaticAbilityFlipCoinMod.fixedResult(flipper);
|
||||
for (int i = 0; i < amount; i++) {
|
||||
won = flipCoin(flipper, sa, multiplier, fixedResult, info);
|
||||
if (won) {
|
||||
result++;
|
||||
}
|
||||
Set<Boolean> flipResults = new HashSet<>();
|
||||
for (int i = 0; i < multiplier; i++) {
|
||||
flipResults.add(MyRandom.getRandom().nextBoolean());
|
||||
}
|
||||
// until is sequential
|
||||
flipper.getGame().fireEvent(new GameEventFlipCoin());
|
||||
result = flipResults.size() == 1 ? flipResults.iterator().next() : flipper.getController().chooseFlipResult(sa, flipper, BOTH_CHOICES, false);
|
||||
if (result) {
|
||||
numSuccesses++;
|
||||
}
|
||||
flipper.getGame().getAction().notifyOfValue(sa, flipper, result ? Localizer.getInstance().getMessage("lblHeads") : Localizer.getInstance().getMessage("lblTails"), null);
|
||||
} while (sa.hasParam("FlipUntilYouLose") && result != false);
|
||||
|
||||
if (sa.hasParam("FlipUntilYouLose") && sa.hasAdditionalAbility("LoseSubAbility")) {
|
||||
sa.getAdditionalAbility("LoseSubAbility").setSVar(varName, "Number$" + numSuccesses);
|
||||
}
|
||||
while (sa.hasParam("FlipUntilYouLose") && won);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -192,50 +255,50 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
||||
* flipCoinCall.
|
||||
* </p>
|
||||
*
|
||||
* @param flipper
|
||||
* @param caller
|
||||
* @param sa
|
||||
* @param multiplier
|
||||
* @return a boolean.
|
||||
*/
|
||||
private static boolean flipCoin(final Player flipper, final SpellAbility sa, int multiplier, final Boolean fixedResult, final String info) {
|
||||
Set<Boolean> flipResults = new HashSet<>();
|
||||
boolean noCall = sa.hasParam("NoCall");
|
||||
boolean choice = true;
|
||||
if (fixedResult != null) {
|
||||
flipResults.add(fixedResult);
|
||||
} 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);
|
||||
}
|
||||
public static boolean flipCoinCall(final Player caller, final SpellAbility sa, final int multiplier) {
|
||||
String varName = sa.getParamOrDefault("SaveNumFlipsToSVar", "X");
|
||||
return flipCoinCall(caller, sa, multiplier, varName, "");
|
||||
}
|
||||
public static boolean flipCoinCall(final Player caller, final SpellAbility sa, final int multiplier, final String varName) {
|
||||
return flipCoinCall(caller, sa, multiplier, varName, "");
|
||||
}
|
||||
public static boolean flipCoinCall(final Player caller, final SpellAbility sa, final int multiplier, final String varName, final String info) {
|
||||
boolean wonFlip = false;
|
||||
int numSuccesses = 0;
|
||||
|
||||
do {
|
||||
Set<Boolean> flipResults = new HashSet<>();
|
||||
final boolean choice = caller.getController().chooseBinary(sa, sa.getHostCard().getName() + " - " + Localizer.getInstance().getMessage("lblCallCoinFlip") + info, PlayerController.BinaryChoiceType.HeadsOrTails);
|
||||
for (int i = 0; i < multiplier; i++) {
|
||||
flipResults.add(MyRandom.getRandom().nextBoolean());
|
||||
}
|
||||
// Play the Flip A Coin sound
|
||||
caller.getGame().fireEvent(new GameEventFlipCoin());
|
||||
boolean result = flipResults.size() == 1 ? flipResults.iterator().next() : caller.getController().chooseFlipResult(sa, caller, BOTH_CHOICES, true);
|
||||
wonFlip = result == choice;
|
||||
|
||||
if (wonFlip) {
|
||||
numSuccesses++;
|
||||
}
|
||||
|
||||
caller.getGame().getAction().notifyOfValue(sa, caller, wonFlip ? Localizer.getInstance().getMessage("lblWin") : Localizer.getInstance().getMessage("lblLose"), null);
|
||||
|
||||
// Run triggers
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(caller);
|
||||
runParams.put(AbilityKey.Result, wonFlip);
|
||||
caller.getGame().getTriggerHandler().runTrigger(TriggerType.FlippedCoin, runParams, false);
|
||||
} while (sa.hasParam("FlipUntilYouLose") && wonFlip);
|
||||
|
||||
if (sa.hasParam("FlipUntilYouLose") && sa.hasAdditionalAbility("LoseSubAbility")) {
|
||||
sa.getAdditionalAbility("LoseSubAbility").setSVar(varName, "Number$" + numSuccesses);
|
||||
}
|
||||
|
||||
boolean result = flipResults.size() == 1 ? flipResults.iterator().next() : flipper.getController().chooseFlipResult(sa, flipper, BOTH_CHOICES, true);
|
||||
boolean wonOrHeads = result == choice;
|
||||
|
||||
String outcome;
|
||||
if (noCall) {
|
||||
outcome = wonOrHeads ? Localizer.getInstance().getMessage("lblHeads") : Localizer.getInstance().getMessage("lblTails");
|
||||
} else {
|
||||
outcome = wonOrHeads ? Localizer.getInstance().getMessage("lblWin") : Localizer.getInstance().getMessage("lblLose");
|
||||
}
|
||||
// Play the Flip A Coin sound
|
||||
flipper.getGame().fireEvent(new GameEventFlipCoin());
|
||||
flipper.getGame().getAction().notifyOfValue(sa, flipper, outcome, null);
|
||||
|
||||
flipper.flip();
|
||||
|
||||
if (!noCall || fixedResult != null) {
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(flipper);
|
||||
runParams.put(AbilityKey.Result, wonOrHeads);
|
||||
flipper.getGame().getTriggerHandler().runTrigger(TriggerType.FlippedCoin, runParams, false);
|
||||
}
|
||||
|
||||
return wonOrHeads;
|
||||
return wonFlip;
|
||||
}
|
||||
|
||||
public static int getFlipMultiplier(final Player flipper) {
|
||||
|
||||
@@ -60,21 +60,18 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
|
||||
if (abMana.isComboMana()) {
|
||||
int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa) : 1;
|
||||
if (amount <= 0)
|
||||
if(amount <= 0)
|
||||
continue;
|
||||
|
||||
String combo = abMana.getComboColors(sa);
|
||||
if (combo.isBlank()) {
|
||||
return;
|
||||
}
|
||||
String[] colorsProduced = combo.split(" ");
|
||||
ColorSet colorOptions = ColorSet.fromNames(colorsProduced);
|
||||
String express = abMana.getExpressChoice();
|
||||
String[] colorsProduced = abMana.getComboColors(sa).split(" ");
|
||||
|
||||
final StringBuilder choiceString = new StringBuilder();
|
||||
final StringBuilder choiceSymbols = new StringBuilder();
|
||||
ColorSet colorOptions = ColorSet.fromNames(colorsProduced);
|
||||
String[] colorsNeeded = express.isEmpty() ? null : express.split(" ");
|
||||
boolean differentChoice = abMana.getOrigProduced().contains("Different");
|
||||
ColorSet fullOptions = colorOptions;
|
||||
final StringBuilder choiceString = new StringBuilder();
|
||||
final StringBuilder choiceSymbols = new StringBuilder();
|
||||
// Use specifyManaCombo if possible
|
||||
if (colorsNeeded == null && amount > 1 && !sa.hasParam("TwoEach")) {
|
||||
Map<Byte, Integer> choices = chooser.getController().specifyManaCombo(sa, colorOptions, amount, differentChoice);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.CardTranslation;
|
||||
import forge.util.Localizer;
|
||||
@@ -13,10 +13,10 @@ public class PermanentCreatureEffect extends PermanentEffect {
|
||||
|
||||
@Override
|
||||
public String getStackDescription(final SpellAbility sa) {
|
||||
final CardState source = sa.getCardState();
|
||||
final Card sourceCard = sa.getHostCard();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(CardTranslation.getTranslatedName(source.getName())).append(" - ").append(Localizer.getInstance().getMessage("lblCreature")).append(" ").append(source.getBasePowerString());
|
||||
sb.append(" / ").append(source.getBaseToughnessString());
|
||||
sb.append(CardTranslation.getTranslatedName(sourceCard.getName())).append(" - ").append(Localizer.getInstance().getMessage("lblCreature")).append(" ").append(sourceCard.getNetPower());
|
||||
sb.append(" / ").append(sourceCard.getNetToughness());
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.game.card.Card;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.CardTranslation;
|
||||
|
||||
@@ -11,7 +12,8 @@ public class PermanentNoncreatureEffect extends PermanentEffect {
|
||||
|
||||
@Override
|
||||
public String getStackDescription(final SpellAbility sa) {
|
||||
final Card sourceCard = sa.getHostCard();
|
||||
//CardView toString return translated name,don't need call CardTranslation.getTranslatedName in this.
|
||||
return CardTranslation.getTranslatedName(sa.getCardState().getName());
|
||||
return CardTranslation.getTranslatedName(sourceCard.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,10 +303,10 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
|
||||
int a = 0;
|
||||
int d = 0;
|
||||
if (sa.hasParam("NumAtt") && !sa.getParam("NumAtt").equals("Double") && !sa.getParam("NumAtt").equals("Triple")) {
|
||||
if (sa.hasParam("NumAtt") && !sa.getParam("NumAtt").equals("Double")) {
|
||||
a = AbilityUtils.calculateAmount(host, sa.getParam("NumAtt"), sa, true);
|
||||
}
|
||||
if (sa.hasParam("NumDef") && !sa.getParam("NumDef").equals("Double") && !sa.getParam("NumDef").equals("Triple")) {
|
||||
if (sa.hasParam("NumDef") && !sa.getParam("NumDef").equals("Double")) {
|
||||
d = AbilityUtils.calculateAmount(host, sa.getParam("NumDef"), sa, true);
|
||||
}
|
||||
|
||||
@@ -488,14 +488,6 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
d = tgtC.getNetToughness();
|
||||
}
|
||||
|
||||
if (sa.hasParam("NumAtt") && sa.getParam("NumAtt").equals("Triple")) {
|
||||
a = tgtC.getNetPower()*2;
|
||||
}
|
||||
if (sa.hasParam("NumDef") && sa.getParam("NumDef").equals("Triple")) {
|
||||
d = tgtC.getNetToughness()*2;
|
||||
}
|
||||
|
||||
|
||||
applyPump(sa, tgtC, a, d, affectedKeywords, timestamp);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package forge.game.ability.effects;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import forge.game.GameObject;
|
||||
import forge.game.PlanarDice;
|
||||
@@ -49,12 +48,6 @@ public class ReplaceEffect extends SpellAbilityEffect {
|
||||
for (Player key : AbilityUtils.getDefinedPlayers(card, sa.getParam("VarKey"), sa)) {
|
||||
m.put(key, m.getOrDefault(key, 0) + AbilityUtils.calculateAmount(card, varValue, sa));
|
||||
}
|
||||
} else if ("CardSet".equals(type)) {
|
||||
Set<Card> cards = (Set<Card>) params.get(varName);
|
||||
List<Card> list = AbilityUtils.getDefinedCards(card, varValue, sa);
|
||||
if (!list.isEmpty()) {
|
||||
cards.add(list.get(0));
|
||||
}
|
||||
} else if (varName != null) {
|
||||
params.put(varName, AbilityUtils.calculateAmount(card, varValue, sa));
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public class RestartGameEffect extends SpellAbilityEffect {
|
||||
p.resetRingTemptedYou();
|
||||
p.clearRingBearer();
|
||||
p.clearTheRing();
|
||||
p.setBlessing(false, null);
|
||||
p.setBlessing(false);
|
||||
p.clearController();
|
||||
|
||||
CardCollection newLibrary = new CardCollection(p.getCardsIn(restartZones, false));
|
||||
|
||||
@@ -23,9 +23,10 @@ public class RingTemptsYouEffect extends EffectEffect {
|
||||
public void resolve(SpellAbility sa) {
|
||||
Player p = sa.getActivatingPlayer();
|
||||
Game game = p.getGame();
|
||||
Card card = sa.getHostCard();
|
||||
|
||||
if (p.getTheRing() == null)
|
||||
p.createTheRing(sa.getOriginalHost().getSetCode());
|
||||
p.createTheRing(card);
|
||||
|
||||
//increment ring tempted you for property
|
||||
p.incrementRingTemptedYou();
|
||||
|
||||
@@ -5,16 +5,13 @@ import com.google.common.collect.Maps;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.event.GameEventRollDie;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerController;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Lang;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.MyRandom;
|
||||
@@ -41,59 +38,6 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static class DieRollResult {
|
||||
private int naturalValue;
|
||||
private int modifiedValue;
|
||||
|
||||
public DieRollResult(int naturalValue, int modifiedValue) {
|
||||
this.naturalValue = naturalValue;
|
||||
this.modifiedValue = modifiedValue;
|
||||
}
|
||||
// Getters
|
||||
public int getNaturalValue() {
|
||||
return naturalValue;
|
||||
}
|
||||
public int getModifiedValue() {
|
||||
return modifiedValue;
|
||||
}
|
||||
// Setters
|
||||
public void setNaturalValue(int naturalValue) {
|
||||
this.naturalValue = naturalValue;
|
||||
}
|
||||
public void setModifiedValue(int modifiedValue) {
|
||||
this.modifiedValue = modifiedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(modifiedValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<DieRollResult> getResultsList(List<Integer> naturalResults) {
|
||||
List<DieRollResult> results = new ArrayList<>();
|
||||
for (int r : naturalResults) {
|
||||
results.add(new DieRollResult(r, r));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public static List<Integer> getNaturalResults(List<DieRollResult> results) {
|
||||
List<Integer> naturalResults = new ArrayList<>();
|
||||
for (DieRollResult r : results) {
|
||||
naturalResults.add(r.getNaturalValue());
|
||||
}
|
||||
return naturalResults;
|
||||
}
|
||||
|
||||
public static List<Integer> getFinalResults(List<DieRollResult> results) {
|
||||
List<Integer> naturalResults = new ArrayList<>();
|
||||
for (DieRollResult r : results) {
|
||||
naturalResults.add(r.getModifiedValue());
|
||||
}
|
||||
return naturalResults;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@@ -136,276 +80,12 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
Map<Player, Integer> ignoreChosenMap = Maps.newHashMap();
|
||||
Set<Card> dicePTExchanges = new HashSet<>();
|
||||
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(player);
|
||||
List<Integer> ignored = new ArrayList<>();
|
||||
List<Integer> naturalRolls = rollAction(amount, sides, ignore, rollsResult, ignored, ignoreChosenMap, dicePTExchanges, player, repParams);
|
||||
|
||||
if (sa != null && sa.hasParam("UseHighestRoll")) {
|
||||
naturalRolls.subList(0, naturalRolls.size() - 1).clear();
|
||||
}
|
||||
|
||||
// Reroll Phase:
|
||||
String monitorKeyword = "Once each turn, you may pay {1} to reroll one or more dice you rolled.";
|
||||
CardCollection canRerollDice = getRerollCards(player, monitorKeyword);
|
||||
while (!canRerollDice.isEmpty()) {
|
||||
List<Integer> diceToReroll = player.getController().chooseDiceToReroll(naturalRolls);
|
||||
if (diceToReroll.isEmpty()) {break;}
|
||||
|
||||
String message = Localizer.getInstance().getMessage("lblChooseRerollCard");
|
||||
Card c = player.getController().chooseSingleEntityForEffect(canRerollDice, sa, message, null);
|
||||
|
||||
String[] parts = c.getSVar("ModsThisTurn").split("\\$");
|
||||
int activationsThisTurn = Integer.parseInt(parts[1]);
|
||||
SpellAbility modifierSA = c.getFirstSpellAbility();
|
||||
Cost cost = new Cost(c.getSVar("RollRerollCost"), false);
|
||||
boolean paid = player.getController().payCostDuringRoll(cost, modifierSA, null);
|
||||
if (paid) {
|
||||
for (Integer roll : diceToReroll) {
|
||||
naturalRolls.remove(roll);
|
||||
}
|
||||
int amountToReroll = diceToReroll.size();
|
||||
List<Integer> rerolls = rollAction(amountToReroll, sides, 0, null, ignored, Maps.newHashMap(), dicePTExchanges, player, repParams);
|
||||
naturalRolls.addAll(rerolls);
|
||||
activationsThisTurn += 1;
|
||||
c.setSVar("ModsThisTurn", "Number$" + activationsThisTurn);
|
||||
canRerollDice.remove(c);
|
||||
}
|
||||
}
|
||||
|
||||
// Modification Phase:
|
||||
List<DieRollResult> resultsList = new ArrayList<>();
|
||||
Integer rollToModify;
|
||||
String xenoKeyword = "After you roll a die, you may remove a +1/+1 counter from Xenosquirrels. If you do, increase or decrease the result by 1.";
|
||||
String nightShiftKeyword = "After you roll a die, you may pay 1 life. If you do, increase or decrease the result by 1. Do this only once each turn.";
|
||||
List<Card> canIncrementDice = getIncrementCards(player, xenoKeyword, nightShiftKeyword);
|
||||
boolean hasBeenModified = false;
|
||||
|
||||
if (!canIncrementDice.isEmpty()) {
|
||||
do {
|
||||
rollToModify = player.getController().chooseRollToModify(naturalRolls);
|
||||
if (rollToModify == null) {break;}
|
||||
|
||||
boolean modified = false;
|
||||
DieRollResult dieResult = new DieRollResult(rollToModify, rollToModify);
|
||||
// canIncrementThisRoll won't be empty the first iteration because canIncrementDice wasn't empty
|
||||
CardCollection canIncrementThisRoll = new CardCollection(canIncrementDice);
|
||||
Card c;
|
||||
do {
|
||||
String message = Localizer.getInstance().getMessage("lblChooseRollIncrementCard", rollToModify);
|
||||
c = player.getController().chooseSingleEntityForEffect(canIncrementThisRoll, sa, message, null);
|
||||
|
||||
String[] parts = c.getSVar("ModsThisTurn").split("\\$");
|
||||
int activationsThisTurn = Integer.parseInt(parts[1]);
|
||||
SpellAbility modifierSA = c.getFirstSpellAbility();
|
||||
String costString = c.getSVar("RollModifyCost");
|
||||
Cost cost = new Cost(costString, false);
|
||||
boolean paid = player.getController().payCostDuringRoll(cost, modifierSA, null);
|
||||
if (paid) {
|
||||
message = Localizer.getInstance().getMessage("lblChooseRollIncrement", rollToModify);
|
||||
boolean isPositive = player.getController().chooseBinary(sa, message, PlayerController.BinaryChoiceType.IncreaseOrDecrease);
|
||||
int increment = isPositive ? 1 : -1;
|
||||
if (!modified) {naturalRolls.remove(rollToModify); modified = true;}
|
||||
rollToModify += increment;
|
||||
activationsThisTurn += 1;
|
||||
c.setSVar("ModsThisTurn", "Number$" + activationsThisTurn);
|
||||
canIncrementThisRoll.remove(c);
|
||||
}
|
||||
} while (!canIncrementThisRoll.isEmpty());
|
||||
if (modified) {
|
||||
dieResult.setModifiedValue(rollToModify);
|
||||
resultsList.add(dieResult);
|
||||
hasBeenModified = true;
|
||||
}
|
||||
canIncrementDice = getIncrementCards(player, xenoKeyword, nightShiftKeyword);
|
||||
} while (!naturalRolls.isEmpty() && !canIncrementDice.isEmpty());
|
||||
}
|
||||
|
||||
// finish roll list
|
||||
for (Integer unmodified : naturalRolls) {
|
||||
// Add all the unmodified rolls into the results
|
||||
resultsList.add(new DieRollResult(unmodified, unmodified));
|
||||
}
|
||||
|
||||
// Vedalken Exchange
|
||||
CardCollection vedalkenSwaps = new CardCollection(dicePTExchanges);
|
||||
if (!vedalkenSwaps.isEmpty()) {
|
||||
DieRollResult rollToSwap;
|
||||
do {
|
||||
rollToSwap = player.getController().chooseRollToSwap(resultsList);
|
||||
if (rollToSwap == null) {break;}
|
||||
|
||||
String message = Localizer.getInstance().getMessage("lblChooseCardToDiceSwap", rollToSwap.getModifiedValue());
|
||||
Card c = player.getController().chooseSingleEntityForEffect(vedalkenSwaps, sa, message, null);
|
||||
int cPower = c.getCurrentPower();
|
||||
int cToughness = c.getCurrentToughness();
|
||||
String labelPower = Localizer.getInstance().getMessage("lblPower");
|
||||
String labelToughness = Localizer.getInstance().getMessage("lblToughness");
|
||||
List<String> choices = Arrays.asList(labelPower, labelToughness);
|
||||
String powerOrToughness = player.getController().chooseRollSwapValue(choices, rollToSwap.getModifiedValue(), cPower, cToughness);
|
||||
if (powerOrToughness != null) {
|
||||
int tempRollValue = rollToSwap.getModifiedValue();
|
||||
if (powerOrToughness.equals(labelPower)) {
|
||||
rollToSwap.setModifiedValue(cPower);
|
||||
c.addNewPT(tempRollValue, cToughness, player.getGame().getNextTimestamp(), 0);
|
||||
} else if (powerOrToughness.equals(labelToughness)) {
|
||||
rollToSwap.setModifiedValue(cToughness);
|
||||
c.addNewPT(cPower, tempRollValue, player.getGame().getNextTimestamp(), 0);
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected value: " + powerOrToughness);
|
||||
}
|
||||
vedalkenSwaps.remove(c);
|
||||
}
|
||||
} while (!vedalkenSwaps.isEmpty());
|
||||
}
|
||||
|
||||
//Notify of results
|
||||
if (amount > 0) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String rollResults = StringUtils.join(getFinalResults(resultsList), ", ");
|
||||
String resultMessage = toVisitAttractions ? "lblAttractionRollResult" : "lblPlayerRolledResult";
|
||||
sb.append(Localizer.getInstance().getMessage(resultMessage, player, rollResults));
|
||||
if (!ignored.isEmpty()) {
|
||||
sb.append("\r\n").append(Localizer.getInstance().getMessage("lblIgnoredRolls",
|
||||
StringUtils.join(ignored, ", ")));
|
||||
}
|
||||
if (hasBeenModified) {
|
||||
sb.append("\r\n").append(Localizer.getInstance().getMessage("lblNaturalRolls",
|
||||
StringUtils.join(getNaturalResults(resultsList), ", ")));
|
||||
}
|
||||
player.getGame().getAction().notifyOfValue(sa, player, sb.toString(), null);
|
||||
player.addDieRollThisTurn(getFinalResults(resultsList));
|
||||
}
|
||||
|
||||
List<Integer> rolls = Lists.newArrayList();
|
||||
int oddResults = 0;
|
||||
int evenResults = 0;
|
||||
int differentResults = 0;
|
||||
int countMaxRolls = 0;
|
||||
for (DieRollResult i : resultsList) {
|
||||
int naturalRoll = i.getNaturalValue();
|
||||
final int modifiedRoll = i.getModifiedValue() + modifier;
|
||||
|
||||
i.setModifiedValue(modifiedRoll);
|
||||
|
||||
if (!rolls.contains(modifiedRoll)) {
|
||||
differentResults++;
|
||||
}
|
||||
rolls.add(modifiedRoll);
|
||||
if (modifiedRoll % 2 == 0) {
|
||||
evenResults++;
|
||||
} else {
|
||||
oddResults++;
|
||||
}
|
||||
if (naturalRoll == sides) {
|
||||
countMaxRolls++;
|
||||
}
|
||||
}
|
||||
if (sa != null) {
|
||||
if (sa.hasParam("EvenOddResults")) {
|
||||
sa.setSVar("EvenResults", Integer.toString(evenResults));
|
||||
sa.setSVar("OddResults", Integer.toString(oddResults));
|
||||
}
|
||||
if (sa.hasParam("DifferentResults")) {
|
||||
sa.setSVar("DifferentResults", Integer.toString(differentResults));
|
||||
}
|
||||
if (sa.hasParam("MaxRollsResults")) {
|
||||
sa.setSVar("MaxRolls", Integer.toString(countMaxRolls));
|
||||
}
|
||||
}
|
||||
|
||||
int rollNum = 1;
|
||||
for (DieRollResult roll : resultsList) {
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(player);
|
||||
runParams.put(AbilityKey.Sides, sides);
|
||||
runParams.put(AbilityKey.Result, roll.getModifiedValue());
|
||||
runParams.put(AbilityKey.NaturalResult, roll.getNaturalValue());
|
||||
runParams.put(AbilityKey.RolledToVisitAttractions, toVisitAttractions);
|
||||
runParams.put(AbilityKey.Number, player.getNumRollsThisTurn() - amount + rollNum);
|
||||
player.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDie, runParams, false);
|
||||
rollNum++;
|
||||
}
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(player);
|
||||
runParams.put(AbilityKey.Sides, sides);
|
||||
runParams.put(AbilityKey.Result, getFinalResults(resultsList));
|
||||
runParams.put(AbilityKey.RolledToVisitAttractions, toVisitAttractions);
|
||||
player.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDieOnce, runParams, false);
|
||||
|
||||
return getFinalResults(resultsList).stream().reduce(0, Integer::sum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of cards that can reroll dice roll results for a given player.
|
||||
* This is currently only Monitor Monitor
|
||||
*
|
||||
* @param player The player whose battlefield is being checked for cards that can modify dice rolls
|
||||
* @param monitorKeyword The keyword text identifying Monitor Monitor cards
|
||||
* @return A list of cards that are currently able to reroll dice
|
||||
*/
|
||||
public static CardCollection getRerollCards(Player player, String monitorKeyword) {
|
||||
CardCollection monitors = CardLists.getKeyword(player.getCardsIn(ZoneType.Battlefield), monitorKeyword);
|
||||
return monitors.filter(card -> {
|
||||
String activationLimit = card.getSVar("RollModificationsLimit");
|
||||
String[] parts = card.getSVar("ModsThisTurn").split("\\$");
|
||||
int activationsThisTurn = Integer.parseInt(parts[1]);
|
||||
return (activationLimit.equals("None") || activationsThisTurn < Integer.parseInt(activationLimit));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of cards that can modify dice roll results for a given player.
|
||||
* This includes both Xenosquirrels (which can remove +1/+1 counters to modify rolls)
|
||||
* and Night Shift cards (which can pay life to modify rolls once per turn).
|
||||
*
|
||||
* @param player The player whose battlefield is being checked for cards that can modify dice rolls
|
||||
* @param xenoKeyword The keyword text identifying Xenosquirrel cards
|
||||
* @param nightShiftKeyword The keyword text identifying Night Shift cards
|
||||
* @return A list of cards that are currently able to modify dice roll results
|
||||
*/
|
||||
public static List<Card> getIncrementCards(Player player, String xenoKeyword, String nightShiftKeyword) {
|
||||
CardCollection xenosquirrels = CardLists.getKeyword(player.getCardsIn(ZoneType.Battlefield), xenoKeyword);
|
||||
CardCollection nightShifts = CardLists.getKeyword(player.getCardsIn(ZoneType.Battlefield), nightShiftKeyword);
|
||||
List<Card> canIncrementDice = new ArrayList<>();
|
||||
for (Card c : xenosquirrels) {
|
||||
// Xenosquirrels must have a P1P1 counter on it to remove in order to modify
|
||||
Integer P1P1Counters = c.getCounters().get(CounterType.get(CounterEnumType.P1P1));
|
||||
if (P1P1Counters != null && P1P1Counters > 0 && c.canRemoveCounters(CounterType.get(CounterEnumType.P1P1))) {
|
||||
canIncrementDice.add(c);
|
||||
}
|
||||
}
|
||||
for (Card c : nightShifts) {
|
||||
// Night Shift of the Living Dead has a limit of once per turn, player must be able to pay the 1 life cost
|
||||
String activationLimit = c.getSVar("RollModificationsLimit");
|
||||
String[] parts = c.getSVar("ModsThisTurn").split("\\$");
|
||||
int activationsThisTurn = Integer.parseInt(parts[1]);
|
||||
if ((activationLimit.equals("None") || activationsThisTurn < Integer.parseInt(activationLimit)) && player.canPayLife(1, true, c.getFirstSpellAbility())) {
|
||||
canIncrementDice.add(c);
|
||||
}
|
||||
}
|
||||
return canIncrementDice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the dice rolling action with support for replacements, ignoring rolls, and tracking results.
|
||||
*
|
||||
* @param amount number of dice to roll
|
||||
* @param sides number of sides on each die
|
||||
* @param ignore number of lowest rolls to automatically ignore
|
||||
* @param rollsResult optional list to store roll results, if null a new list will be created
|
||||
* @param ignored list to store ignored roll results
|
||||
* @param ignoreChosenMap mapping of players to number of rolls they can choose to ignore
|
||||
* @param player the player performing the roll
|
||||
* @param repParams replacement effect parameters
|
||||
* @return list of final roll results after applying ignores and replacements, sorted in ascending order
|
||||
*/
|
||||
private static List<Integer> rollAction(int amount, int sides, int ignore, List<Integer> rollsResult, List<Integer> ignored, Map<Player, Integer> ignoreChosenMap, Set<Card> dicePTExchanges, Player player, Map<AbilityKey, Object> repParams) {
|
||||
|
||||
repParams.put(AbilityKey.Sides, sides);
|
||||
repParams.put(AbilityKey.Number, amount);
|
||||
repParams.put(AbilityKey.Ignore, ignore);
|
||||
repParams.put(AbilityKey.DicePTExchanges, dicePTExchanges);
|
||||
repParams.put(AbilityKey.IgnoreChosen, ignoreChosenMap);
|
||||
|
||||
switch (player.getGame().getReplacementHandler().run(ReplacementType.RollDice, repParams)) {
|
||||
case NotReplaced:
|
||||
break;
|
||||
@@ -430,6 +110,7 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
|
||||
naturalRolls.sort(null);
|
||||
|
||||
List<Integer> ignored = new ArrayList<>();
|
||||
// Ignore lowest rolls
|
||||
if (ignore > 0) {
|
||||
for (int i = ignore - 1; i >= 0; --i) {
|
||||
@@ -446,7 +127,75 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
return naturalRolls;
|
||||
if (sa != null && sa.hasParam("UseHighestRoll")) {
|
||||
naturalRolls.subList(0, naturalRolls.size() - 1).clear();
|
||||
}
|
||||
|
||||
//Notify of results
|
||||
if (amount > 0) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String rollResults = StringUtils.join(naturalRolls, ", ");
|
||||
String resultMessage = toVisitAttractions ? "lblAttractionRollResult" : "lblPlayerRolledResult";
|
||||
sb.append(Localizer.getInstance().getMessage(resultMessage, player, rollResults));
|
||||
if (!ignored.isEmpty()) {
|
||||
sb.append("\r\n").append(Localizer.getInstance().getMessage("lblIgnoredRolls",
|
||||
StringUtils.join(ignored, ", ")));
|
||||
}
|
||||
player.getGame().getAction().notifyOfValue(sa, player, sb.toString(), null);
|
||||
player.addDieRollThisTurn(naturalRolls);
|
||||
}
|
||||
|
||||
List<Integer> rolls = Lists.newArrayList();
|
||||
int oddResults = 0;
|
||||
int evenResults = 0;
|
||||
int differentResults = 0;
|
||||
int countMaxRolls = 0;
|
||||
for (Integer i : naturalRolls) {
|
||||
final int modifiedRoll = i + modifier;
|
||||
if (!rolls.contains(modifiedRoll)) {
|
||||
differentResults++;
|
||||
}
|
||||
rolls.add(modifiedRoll);
|
||||
if (modifiedRoll % 2 == 0) {
|
||||
evenResults++;
|
||||
} else {
|
||||
oddResults++;
|
||||
}
|
||||
if (i == sides) {
|
||||
countMaxRolls++;
|
||||
}
|
||||
}
|
||||
if (sa != null) {
|
||||
if (sa.hasParam("EvenOddResults")) {
|
||||
sa.setSVar("EvenResults", Integer.toString(evenResults));
|
||||
sa.setSVar("OddResults", Integer.toString(oddResults));
|
||||
}
|
||||
if (sa.hasParam("DifferentResults")) {
|
||||
sa.setSVar("DifferentResults", Integer.toString(differentResults));
|
||||
}
|
||||
if (sa.hasParam("MaxRollsResults")) {
|
||||
sa.setSVar("MaxRolls", Integer.toString(countMaxRolls));
|
||||
}
|
||||
}
|
||||
|
||||
int rollNum = 1;
|
||||
for (Integer roll : rolls) {
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(player);
|
||||
runParams.put(AbilityKey.Sides, sides);
|
||||
runParams.put(AbilityKey.Modifier, modifier);
|
||||
runParams.put(AbilityKey.Result, roll);
|
||||
runParams.put(AbilityKey.RolledToVisitAttractions, toVisitAttractions);
|
||||
runParams.put(AbilityKey.Number, player.getNumRollsThisTurn() - amount + rollNum);
|
||||
player.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDie, runParams, false);
|
||||
rollNum++;
|
||||
}
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(player);
|
||||
runParams.put(AbilityKey.Sides, sides);
|
||||
runParams.put(AbilityKey.Result, rolls);
|
||||
runParams.put(AbilityKey.RolledToVisitAttractions, toVisitAttractions);
|
||||
player.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDieOnce, runParams, false);
|
||||
|
||||
return rolls.stream().reduce(0, Integer::sum);
|
||||
}
|
||||
|
||||
private static void resolveSub(SpellAbility sa, int num) {
|
||||
|
||||
@@ -24,7 +24,7 @@ public class TakeInitiativeEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
// TODO: improve ai and fix corner cases
|
||||
final String set = sa.getOriginalHost().getSetCode();
|
||||
final String set = sa.getHostCard().getSetCode();
|
||||
|
||||
for (final Player p : getTargetPlayers(sa)) {
|
||||
if (!p.isInGame()) {
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.GameCommand;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.event.GameEventCardStatsChanged;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Exchanges text boxes between two creatures.
|
||||
*/
|
||||
public class TextBoxExchangeEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
protected String getStackDescription(final SpellAbility sa) {
|
||||
final List<Card> tgtCards = getTargetCards(sa);
|
||||
Card c1;
|
||||
Card c2;
|
||||
if (tgtCards.size() == 1) {
|
||||
c1 = sa.getHostCard();
|
||||
c2 = tgtCards.get(0);
|
||||
} else {
|
||||
c1 = tgtCards.get(0);
|
||||
c2 = tgtCards.get(1);
|
||||
}
|
||||
return c1 + " exchanges text box with " + c2 + ".";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(final SpellAbility sa) {
|
||||
if (!checkValidDuration(sa.getParam("Duration"), sa)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Card> tgtCards = getTargetCards(sa);
|
||||
if (tgtCards.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Card c1 = tgtCards.get(0);
|
||||
final Card c2 = tgtCards.get(1);
|
||||
|
||||
// snapshot the original text boxes before modifying
|
||||
final TextBoxData data1 = captureTextBoxData(c1);
|
||||
final TextBoxData data2 = captureTextBoxData(c2);
|
||||
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
final long ts = game.getNextTimestamp();
|
||||
|
||||
swapTextBox(c1, data2, ts);
|
||||
swapTextBox(c2, data1, ts);
|
||||
|
||||
if (sa.hasParam("Duration")) {
|
||||
final GameCommand revertTextExchange = new GameCommand() {
|
||||
private static final long serialVersionUID = 5331255714437747836L;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Check if the cards are still there
|
||||
Card card1 = game.getCardState(c1, null);
|
||||
Card card2 = game.getCardState(c2, null);
|
||||
|
||||
if (card1 != null && c1.equalsWithGameTimestamp(card1)) {
|
||||
card1.removeChangedCardTraits(ts, 0);
|
||||
card1.removeChangedCardKeywords(ts, 0, false);
|
||||
card1.updateChangedText();
|
||||
card1.updateStateForView();
|
||||
game.fireEvent(new GameEventCardStatsChanged(card1));
|
||||
}
|
||||
|
||||
if (card2 != null && c2.equalsWithGameTimestamp(card2)) {
|
||||
card2.removeChangedCardTraits(ts, 0);
|
||||
card2.removeChangedCardKeywords(ts, 0, false);
|
||||
card2.updateChangedText();
|
||||
card2.updateStateForView();
|
||||
game.fireEvent(new GameEventCardStatsChanged(card2));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
addUntilCommand(sa, revertTextExchange);
|
||||
}
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(c1));
|
||||
game.fireEvent(new GameEventCardStatsChanged(c2));
|
||||
}
|
||||
|
||||
private static void swapTextBox(final Card to, final TextBoxData from, final long ts) {
|
||||
List<SpellAbility> spellabilities = Lists.newArrayList();
|
||||
for (SpellAbility sa : from.spellabilities) {
|
||||
SpellAbility copy = sa.copy(to, false, true);
|
||||
// need to persist any previous word changes
|
||||
copy.changeTextIntrinsic(copy.getChangedTextColors(), copy.getChangedTextTypes());
|
||||
spellabilities.add(copy);
|
||||
}
|
||||
List<Trigger> triggers = Lists.newArrayList();
|
||||
for (Trigger tr : from.triggers) {
|
||||
Trigger copy = tr.copy(to, false, true);
|
||||
copy.changeTextIntrinsic(copy.getChangedTextColors(), copy.getChangedTextTypes());
|
||||
triggers.add(copy);
|
||||
}
|
||||
List<ReplacementEffect> reps = Lists.newArrayList();
|
||||
for (ReplacementEffect re : from.replacements) {
|
||||
ReplacementEffect copy = re.copy(to, false, true);
|
||||
copy.changeTextIntrinsic(copy.getChangedTextColors(), copy.getChangedTextTypes());
|
||||
reps.add(copy);
|
||||
}
|
||||
List<StaticAbility> statics = Lists.newArrayList();
|
||||
for (StaticAbility st : from.statics) {
|
||||
StaticAbility copy = st.copy(to, false, true);
|
||||
copy.changeTextIntrinsic(copy.getChangedTextColors(), copy.getChangedTextTypes());
|
||||
statics.add(copy);
|
||||
}
|
||||
to.addChangedCardTraitsByText(spellabilities, triggers, reps, statics, ts, 0);
|
||||
|
||||
List<KeywordInterface> kws = Lists.newArrayList();
|
||||
for (KeywordInterface kw : from.keywords) {
|
||||
kws.add(kw.copy(to, false));
|
||||
}
|
||||
to.addChangedCardKeywordsByText(kws, ts, 0, false);
|
||||
|
||||
to.updateChangedText();
|
||||
to.updateStateForView();
|
||||
}
|
||||
|
||||
private static TextBoxData captureTextBoxData(final Card card) {
|
||||
TextBoxData data = new TextBoxData();
|
||||
CardState state = card.getCurrentState();
|
||||
|
||||
data.spellabilities = Lists.newArrayList();
|
||||
for (SpellAbility sa : state.getSpellAbilities()) {
|
||||
if (sa.isIntrinsic() && sa.getKeyword() == null) {
|
||||
data.spellabilities.add(sa);
|
||||
}
|
||||
}
|
||||
data.triggers = Lists.newArrayList();
|
||||
for (Trigger tr : state.getTriggers()) {
|
||||
if (tr.isIntrinsic() && tr.getKeyword() == null) {
|
||||
data.triggers.add(tr);
|
||||
}
|
||||
}
|
||||
data.replacements = Lists.newArrayList();
|
||||
for (ReplacementEffect re : state.getReplacementEffects()) {
|
||||
if (re.isIntrinsic() && re.getKeyword() == null) {
|
||||
data.replacements.add(re);
|
||||
}
|
||||
}
|
||||
data.statics = Lists.newArrayList();
|
||||
for (StaticAbility st : state.getStaticAbilities()) {
|
||||
if (st.isIntrinsic() && st.getKeyword() == null) {
|
||||
data.statics.add(st);
|
||||
}
|
||||
}
|
||||
|
||||
data.keywords = Lists.newArrayList();
|
||||
for (KeywordInterface ki : card.getKeywords()) {
|
||||
if (ki.isIntrinsic()) {
|
||||
data.keywords.add(ki);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static class TextBoxData {
|
||||
List<SpellAbility> spellabilities;
|
||||
List<Trigger> triggers;
|
||||
List<ReplacementEffect> replacements;
|
||||
List<StaticAbility> statics;
|
||||
List<KeywordInterface> keywords;
|
||||
}
|
||||
}
|
||||
@@ -20,11 +20,9 @@ package forge.game.card;
|
||||
import com.esotericsoftware.minlog.Log;
|
||||
import com.google.common.collect.*;
|
||||
import forge.GameCommand;
|
||||
import forge.ImageKeys;
|
||||
import forge.StaticData;
|
||||
import forge.card.*;
|
||||
import forge.card.CardDb.CardArtPreference;
|
||||
import forge.card.CardType.Supertype;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostParser;
|
||||
import forge.game.*;
|
||||
@@ -207,8 +205,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
private boolean tributed;
|
||||
private Card suspectedEffect = null;
|
||||
|
||||
private SpellAbility manifestedSA;
|
||||
private SpellAbility cloakedSA;
|
||||
private boolean manifested;
|
||||
private boolean cloaked;
|
||||
|
||||
private boolean foretold;
|
||||
private boolean foretoldCostByEffect;
|
||||
@@ -253,7 +251,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|
||||
private PlayerCollection targetedFromThisTurn = new PlayerCollection();
|
||||
|
||||
private long worldTimestamp = -1;
|
||||
private long bestowTimestamp = -1;
|
||||
private long transformedTimestamp = 0;
|
||||
private long prototypeTimestamp = -1;
|
||||
@@ -297,7 +294,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
private String chosenType2 = "";
|
||||
private List<String> notedTypes = new ArrayList<>();
|
||||
private List<String> chosenColors;
|
||||
private ColorSet markedColor;
|
||||
private Set<String> chosenColorID;
|
||||
private List<String> chosenName = new ArrayList<>();
|
||||
private Integer chosenNumber;
|
||||
private Player chosenPlayer;
|
||||
@@ -410,7 +407,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
view.updateClassLevel(this);
|
||||
view.updateDraftAction(this);
|
||||
if (paperCard != null)
|
||||
setMarkedColors(paperCard.getMarkedColors());
|
||||
setChosenColorID(paperCard.getColorID());
|
||||
}
|
||||
|
||||
public int getHiddenId() {
|
||||
@@ -577,8 +574,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|
||||
// Cleared tests, about to change states
|
||||
if (currentStateName.equals(CardStateName.FaceDown) && state.equals(CardStateName.Original)) {
|
||||
this.setManifested(null);
|
||||
this.setCloaked(null);
|
||||
this.setManifested(false);
|
||||
this.setCloaked(false);
|
||||
}
|
||||
|
||||
currentStateName = state;
|
||||
@@ -733,8 +730,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
} else if (mode.equals("TurnFaceDown")) {
|
||||
CardStateName oldState = getCurrentStateName();
|
||||
if (oldState == CardStateName.Original || oldState == CardStateName.Flipped
|
||||
|| oldState == CardStateName.LeftSplit || oldState == CardStateName.RightSplit || oldState == CardStateName.EmptyRoom) {
|
||||
if (oldState == CardStateName.Original || oldState == CardStateName.Flipped) {
|
||||
return turnFaceDown();
|
||||
}
|
||||
} else if (mode.equals("Meld") && isMeldable()) {
|
||||
@@ -768,12 +764,12 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
setController(p, game.getNextTimestamp());
|
||||
|
||||
// Mark this card as "manifested"
|
||||
setManifested(sa);
|
||||
setManifested(true);
|
||||
|
||||
// Move to p's battlefield
|
||||
Card c = game.getAction().moveToPlay(this, p, sa, params);
|
||||
if (c.isInPlay()) {
|
||||
c.setManifested(sa);
|
||||
c.setManifested(true);
|
||||
c.turnFaceDown(true);
|
||||
c.updateStateForView();
|
||||
}
|
||||
@@ -792,14 +788,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
setController(p, game.getNextTimestamp());
|
||||
|
||||
// Mark this card as "cloaked"
|
||||
setCloaked(sa);
|
||||
setCloaked(true);
|
||||
// give it Ward:2
|
||||
getFaceDownState().addIntrinsicKeyword("Ward:2", true);
|
||||
|
||||
// Move to p's battlefield
|
||||
Card c = game.getAction().moveToPlay(this, p, sa, params);
|
||||
if (c.isInPlay()) {
|
||||
c.setCloaked(sa);
|
||||
c.setCloaked(true);
|
||||
c.turnFaceDown(true);
|
||||
c.updateStateForView();
|
||||
}
|
||||
@@ -871,9 +867,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
// 613.7f A permanent receives a new timestamp each time it turns face up or face down.
|
||||
c.setLayerTimestamp(ts);
|
||||
c.turnedFaceUpThisTurn = true;
|
||||
if (c.isInPlay()) {
|
||||
c.updateRooms();
|
||||
}
|
||||
c.updateRooms();
|
||||
c.updateStateForView(); //fixes cards with backside viewable
|
||||
// need to run faceup commands, currently
|
||||
// it does cleanup the modified facedown state
|
||||
@@ -1407,10 +1401,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
public final CardCollectionView getMergedCards() {
|
||||
return CardCollection.getView(mergedCards);
|
||||
}
|
||||
public final void setMergedCards(Iterable<Card> mc) {
|
||||
mergedCards = new CardCollection(mc);
|
||||
}
|
||||
|
||||
public final Card getTopMergedCard() {
|
||||
return mergedCards.get(0);
|
||||
}
|
||||
@@ -2027,6 +2017,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return currentState.getSVars();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getDirectSVars() {
|
||||
return ImmutableMap.of();
|
||||
}
|
||||
|
||||
public final void setSVars(final Map<String, String> newSVars) {
|
||||
currentState.setSVars(newSVars);
|
||||
}
|
||||
@@ -2239,18 +2234,18 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
public boolean hasChosenColor(String s) {
|
||||
return chosenColors != null && chosenColors.contains(s);
|
||||
}
|
||||
public final ColorSet getMarkedColors() {
|
||||
if (markedColor == null) {
|
||||
return ColorSet.getNullColor();
|
||||
public final Set<String> getChosenColorID() {
|
||||
if (chosenColorID == null) {
|
||||
return Sets.newHashSet();
|
||||
}
|
||||
return markedColor;
|
||||
return chosenColorID;
|
||||
}
|
||||
public final void setMarkedColors(final ColorSet s) {
|
||||
markedColor = s;
|
||||
view.updateMarkedColors(this);
|
||||
public final void setChosenColorID(final Set<String> s) {
|
||||
chosenColorID = s;
|
||||
view.updateChosenColorID(this);
|
||||
}
|
||||
public boolean hasMarkedColor() {
|
||||
return markedColor != null && !markedColor.isColorless();
|
||||
public boolean hasChosenColorSpire() {
|
||||
return chosenColorID != null && !chosenColorID.isEmpty();
|
||||
}
|
||||
public final Card getChosenCard() {
|
||||
return getChosenCards().getFirst();
|
||||
@@ -2430,17 +2425,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
final String[] k = keyword.split(":");
|
||||
sbLong.append(k[2]).append("\r\n");
|
||||
} else if (keyword.startsWith("Enchant")) {
|
||||
String m[] = keyword.split(":");
|
||||
String desc;
|
||||
if (m.length > 2) {
|
||||
desc = m[2];
|
||||
} else {
|
||||
desc = m[1];
|
||||
if (CardType.isACardType(desc) || "Permanent".equals(desc) || "Player".equals(desc) || "Opponent".equals(desc)) {
|
||||
desc = desc.toLowerCase();
|
||||
}
|
||||
}
|
||||
sbLong.append("Enchant ").append(desc).append("\r\n");
|
||||
String k = keyword;
|
||||
k = TextUtil.fastReplace(k, "Curse", "");
|
||||
sbLong.append(k).append("\r\n");
|
||||
} else if (keyword.startsWith("Ripple")) {
|
||||
sbLong.append(TextUtil.fastReplace(keyword, ":", " ")).append("\r\n");
|
||||
} else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph")
|
||||
@@ -2632,7 +2619,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|| keyword.startsWith("Graft") || keyword.startsWith("Fading") || keyword.startsWith("Vanishing:")
|
||||
|| keyword.startsWith("Afterlife") || keyword.startsWith("Hideaway") || keyword.startsWith("Toxic")
|
||||
|| keyword.startsWith("Afflict") || keyword.startsWith ("Poisonous") || keyword.startsWith("Rampage")
|
||||
|| keyword.startsWith("Renown") || keyword.startsWith("Annihilator") || keyword.startsWith("Devour")) {
|
||||
|| keyword.startsWith("Renown") || keyword.startsWith("Annihilator") || keyword.startsWith("Devour")
|
||||
|| keyword.startsWith("Mobilize")) {
|
||||
final String[] k = keyword.split(":");
|
||||
sbLong.append(k[0]).append(" ").append(k[1]).append(" (").append(inst.getReminderText()).append(")");
|
||||
} else if (keyword.startsWith("Crew")) {
|
||||
@@ -2673,13 +2661,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
final String[] k = keyword.split(":");
|
||||
String desc = k.length > 2 ? k[2] : CardType.getPluralType(k[1]);
|
||||
sbLong.append(k[0]).append(" ").append(desc).append(" (").append(inst.getReminderText()).append(")");
|
||||
} else if (keyword.equals("Convoke") || keyword.equals("Dethrone") || keyword.equals("Fear")
|
||||
|| keyword.equals("Melee") || keyword.equals("Improvise") || keyword.equals("Shroud")
|
||||
|| keyword.equals("Banding") || keyword.equals("Intimidate") || keyword.equals("Evolve")
|
||||
|| keyword.equals("Exalted") || keyword.equals("Extort") || keyword.equals("Flanking")
|
||||
|| keyword.equals("Horsemanship") || keyword.equals("Infect") || keyword.equals("Persist")
|
||||
|| keyword.equals("Phasing") || keyword.equals("Shadow") || keyword.equals("Skulk")
|
||||
|| keyword.equals("Undying") || keyword.equals("Wither") || keyword.equals("Bargain")
|
||||
} else if (keyword.equals("Convoke") || keyword.equals("Dethrone")|| keyword.equals("Fear")
|
||||
|| keyword.equals("Melee") || keyword.equals("Improvise")|| keyword.equals("Shroud")
|
||||
|| keyword.equals("Banding") || keyword.equals("Intimidate")|| keyword.equals("Evolve")
|
||||
|| keyword.equals("Exalted") || keyword.equals("Extort")|| keyword.equals("Flanking")
|
||||
|| keyword.equals("Horsemanship") || keyword.equals("Infect")|| keyword.equals("Persist")
|
||||
|| keyword.equals("Phasing") || keyword.equals("Shadow")|| keyword.equals("Skulk")
|
||||
|| keyword.equals("Undying") || keyword.equals("Wither")
|
||||
|| keyword.equals("Bargain")
|
||||
|| keyword.equals("Mentor") || keyword.equals("Training")) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append("\r\n");
|
||||
@@ -2742,9 +2731,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|| keyword.startsWith("Encore") || keyword.startsWith("Mutate") || keyword.startsWith("Dungeon")
|
||||
|| keyword.startsWith("Class") || keyword.startsWith("Blitz")
|
||||
|| keyword.startsWith("Specialize") || keyword.equals("Ravenous")
|
||||
|| keyword.equals("For Mirrodin") || keyword.equals("Job select") || keyword.startsWith("Craft")
|
||||
|| keyword.startsWith("Landwalk") || keyword.startsWith("Visit") || keyword.startsWith("Mobilize")
|
||||
|| keyword.startsWith("Station")) {
|
||||
|| keyword.equals("For Mirrodin") || keyword.startsWith("Craft")
|
||||
|| keyword.startsWith("Landwalk") || keyword.startsWith("Visit")) {
|
||||
// keyword parsing takes care of adding a proper description
|
||||
} else if (keyword.equals("Read ahead")) {
|
||||
sb.append(Localizer.getInstance().getMessage("lblReadAhead")).append(" (").append(Localizer.getInstance().getMessage("lblReadAheadDesc"));
|
||||
@@ -2960,10 +2948,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
if (isSuspected()) {
|
||||
sb.append("Suspected\r\n");
|
||||
}
|
||||
if (isManifested()) {
|
||||
if (manifested) {
|
||||
sb.append("Manifested\r\n");
|
||||
}
|
||||
if (isCloaked()) {
|
||||
if (cloaked) {
|
||||
sb.append("Cloaked\r\n");
|
||||
}
|
||||
String keywordText = keywordsToText(getUnhiddenKeywords(state));
|
||||
@@ -2986,7 +2974,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
}
|
||||
|
||||
if (this.getRules() != null && state.getStateName().equals(CardStateName.Original)) {
|
||||
if (this.getRules() != null && state.getView().getState().equals(CardStateName.Original)) {
|
||||
// try to look which what card this card can be meld to
|
||||
// only show this info if this card does not has the meld Effect itself
|
||||
|
||||
@@ -3119,14 +3107,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
|
||||
boolean found = false;
|
||||
if (stAb.checkMode(StaticAbilityMode.CantBlockBy)) {
|
||||
if (stAb.checkMode("CantBlockBy")) {
|
||||
if (!stAb.hasParam("ValidAttacker") || (stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) {
|
||||
continue;
|
||||
}
|
||||
if (stAb.matchesValidParam("ValidAttacker", this)) {
|
||||
found = true;
|
||||
}
|
||||
} else if (stAb.checkMode(StaticAbilityMode.MinMaxBlocker)) {
|
||||
} else if (stAb.checkMode(StaticAbilityCantAttackBlock.MinMaxBlockerMode)) {
|
||||
if (stAb.matchesValidParam("ValidCard", this)) {
|
||||
found = true;
|
||||
}
|
||||
@@ -3241,8 +3229,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|| keyword.equals("Improvise") || keyword.equals("Retrace")
|
||||
|| keyword.equals("Undaunted") || keyword.equals("Cascade")
|
||||
|| keyword.equals("Devoid") || keyword.equals("Lifelink")
|
||||
|| keyword.equals("Bargain") || keyword.equals("Spree")
|
||||
|| keyword.equals("Tiered") || keyword.equals("Split second")) {
|
||||
|| keyword.equals("Bargain")|| keyword.equals("Spree")
|
||||
|| keyword.equals("Split second")) {
|
||||
sbBefore.append(keyword).append(" (").append(inst.getReminderText()).append(")");
|
||||
sbBefore.append("\r\n\r\n");
|
||||
} else if (keyword.equals("Conspire") || keyword.equals("Epic")
|
||||
@@ -3479,10 +3467,29 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return max_produced;
|
||||
}
|
||||
|
||||
public final void clearFirstSpell() {
|
||||
currentState.clearFirstSpell();
|
||||
}
|
||||
|
||||
public final SpellAbility getFirstSpellAbility() {
|
||||
return Iterables.getFirst(currentState.getNonManaAbilities(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first {@link SpellAbility} marked as a Spell with API type
|
||||
* {@link ApiType#Attach} in this {@link Card}, or {@code null} if no such
|
||||
* object exists.
|
||||
* @see SpellAbility#isSpell()
|
||||
*/
|
||||
public final SpellAbility getFirstAttachSpell() {
|
||||
for (final SpellAbility sa : getSpells()) {
|
||||
if (sa.getApi() == ApiType.Attach && !sa.isSuppressed()) {
|
||||
return sa;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public final SpellPermanent getSpellPermanent() {
|
||||
for (final SpellAbility sa : currentState.getNonManaAbilities()) {
|
||||
if (sa instanceof SpellPermanent) {
|
||||
@@ -3502,6 +3509,18 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public final void removeSpellAbility(final SpellAbility a) {
|
||||
removeSpellAbility(a, true);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public final void removeSpellAbility(final SpellAbility a, final boolean updateView) {
|
||||
if (currentState.removeSpellAbility(a) && updateView) {
|
||||
currentState.getView().updateAbilityText(this, currentState);
|
||||
}
|
||||
}
|
||||
|
||||
public final FCollectionView<SpellAbility> getSpellAbilities() {
|
||||
return currentState.getSpellAbilities();
|
||||
}
|
||||
@@ -3573,7 +3592,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
// add Facedown abilities from Original state but only if this state is face down
|
||||
// need CardStateView#getState or might crash in StackOverflow
|
||||
if (isInPlay()) {
|
||||
if ((null == mana || false == mana) && isFaceDown() && state.getStateName() == CardStateName.FaceDown) {
|
||||
if ((null == mana || false == mana) && isFaceDown() && state.getView().getState() == CardStateName.FaceDown) {
|
||||
for (SpellAbility sa : getState(CardStateName.Original).getNonManaAbilities()) {
|
||||
if (sa.isTurnFaceUp()) {
|
||||
list.add(sa);
|
||||
@@ -3582,7 +3601,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
} else {
|
||||
// Adventure and Omen may only be cast not from Battlefield
|
||||
if (hasState(CardStateName.Secondary) && state.getStateName() == CardStateName.Original) {
|
||||
if (hasState(CardStateName.Secondary) && state.getView().getState() == CardStateName.Original) {
|
||||
for (SpellAbility sa : getState(CardStateName.Secondary).getSpellAbilities()) {
|
||||
if (mana == null || mana == sa.isManaAbility()) {
|
||||
list.add(sa);
|
||||
@@ -4234,12 +4253,12 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return ImmutableList.of();
|
||||
}
|
||||
Iterable<CardChangedType> byText = changedTypeByText == null ? ImmutableList.of() : ImmutableList.of(this.changedTypeByText);
|
||||
return ImmutableList.<CardChangedType>builder()
|
||||
.addAll(changedCardTypesByText.values()) // Layer 3
|
||||
.addAll(byText) // Layer 3 by Word Changes,
|
||||
.addAll(changedCardTypesCharacterDefining.values()) // Layer 4
|
||||
.addAll(changedCardTypes.values()) // Layer 6
|
||||
.build();
|
||||
return ImmutableList.copyOf(Iterables.concat(
|
||||
changedCardTypesByText.values(), // Layer 3
|
||||
byText, // Layer 3 by Word Changes,
|
||||
changedCardTypesCharacterDefining.values(), // Layer 4
|
||||
changedCardTypes.values() // Layer 6
|
||||
));
|
||||
}
|
||||
|
||||
public boolean clearChangedCardTypes() {
|
||||
@@ -4462,13 +4481,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
public final void addCloneState(CardCloneStates states, final long timestamp) {
|
||||
clonedStates.put(timestamp, states);
|
||||
updateCloneState(true);
|
||||
updateWorldTimestamp(timestamp);
|
||||
}
|
||||
|
||||
public final boolean removeCloneState(final long timestamp) {
|
||||
if (clonedStates.remove(timestamp) != null) {
|
||||
updateCloneState(true);
|
||||
updateWorldTimestamp(timestamp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -4807,6 +4824,29 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
perpetual.add(p);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public final void executePerpetual(Map<String, Object> p) {
|
||||
final String category = (String) p.get("Category");
|
||||
if (category.equals("NewPT")) {
|
||||
addNewPT((Integer) p.get("Power"), (Integer) p.get("Toughness"), (long)
|
||||
p.get("Timestamp"), (long) 0);
|
||||
} else if (category.equals("PTBoost")) {
|
||||
addPTBoost((Integer) p.get("Power"), (Integer) p.get("Toughness"), (long)
|
||||
p.get("Timestamp"), (long) 0);
|
||||
} else if (category.equals("Keywords")) {
|
||||
boolean removeAll = p.containsKey("RemoveAll") && (boolean) p.get("RemoveAll") == true;
|
||||
addChangedCardKeywords((List<String>) p.get("AddKeywords"), (List<String>) p.get("RemoveKeywords"),
|
||||
removeAll, (long) p.get("Timestamp"), null);
|
||||
} else if (category.equals("Types")) {
|
||||
addChangedCardTypes((CardType) p.get("AddTypes"), (CardType) p.get("RemoveTypes"),
|
||||
false, (Set<RemoveType>) p.get("RemoveXTypes"),
|
||||
(long) p.get("Timestamp"), (long) 0, true, false);
|
||||
} else if (category.equals("Colors")) {
|
||||
addColor((ColorSet) p.get("Colors"), !(boolean) p.get("Overwrite"), (long) p.get("Timestamp"),
|
||||
(long) 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
public final void removePerpetual(final long timestamp) {
|
||||
Map<String, Object> toRemove = Maps.newHashMap();
|
||||
for (Map<String, Object> p : perpetual) {
|
||||
@@ -4819,14 +4859,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
|
||||
public final void setPerpetual(final Card oldCard) {
|
||||
perpetual = oldCard.getPerpetual();
|
||||
for (Map<String, Object> p : perpetual) {
|
||||
final String category = (String) p.get("Category");
|
||||
if (category.equals("Abilities")) {
|
||||
final List<Map<String, Object>> perp = oldCard.getPerpetual();
|
||||
perpetual = perp;
|
||||
for (Map<String, Object> p : perp) {
|
||||
if (p.get("Category").equals("Abilities")) {
|
||||
long timestamp = (long) p.get("Timestamp");
|
||||
CardTraitChanges ctc = oldCard.getChangedCardTraits().get(timestamp, (long) 0).copy(this, false);
|
||||
addChangedCardTraits(ctc, timestamp, (long) 0);
|
||||
} else if (category.equals("Incorporate")) {
|
||||
} else if (p.get("Category").equals("Incorporate")) {
|
||||
long ts = (long) p.get("Timestamp");
|
||||
final ManaCost cCMC = oldCard.changedCardManaCost.get(ts, (long) 0);
|
||||
addChangedManaCost(cCMC, ts, (long) 0);
|
||||
@@ -4835,24 +4875,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
if (getFirstSpellAbility() != null) {
|
||||
getFirstSpellAbility().getPayCosts().add(new Cost((String) p.get("Incorporate"), false));
|
||||
}
|
||||
} else if (category.equals("NewPT")) {
|
||||
addNewPT((Integer) p.get("Power"), (Integer) p.get("Toughness"), (long)
|
||||
p.get("Timestamp"), (long) 0);
|
||||
} else if (category.equals("PTBoost")) {
|
||||
addPTBoost((Integer) p.get("Power"), (Integer) p.get("Toughness"), (long)
|
||||
p.get("Timestamp"), (long) 0);
|
||||
} else if (category.equals("Keywords")) {
|
||||
boolean removeAll = p.containsKey("RemoveAll") && (boolean) p.get("RemoveAll") == true;
|
||||
addChangedCardKeywords((List<String>) p.get("AddKeywords"), (List<String>) p.get("RemoveKeywords"),
|
||||
removeAll, (long) p.get("Timestamp"), null);
|
||||
} else if (category.equals("Types")) {
|
||||
addChangedCardTypes((CardType) p.get("AddTypes"), (CardType) p.get("RemoveTypes"),
|
||||
false, (Set<RemoveType>) p.get("RemoveXTypes"),
|
||||
(long) p.get("Timestamp"), (long) 0, true, false);
|
||||
} else if (category.equals("Colors")) {
|
||||
addColor((ColorSet) p.get("Colors"), !(boolean) p.get("Overwrite"), (long) p.get("Timestamp"),
|
||||
(long) 0, false);
|
||||
}
|
||||
} else executePerpetual(p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5313,13 +5336,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
}
|
||||
|
||||
public void setChangedCardKeywordsByText(Table<Long, Long, KeywordsChange> changedCardKeywords) {
|
||||
this.changedCardKeywordsByText.clear();
|
||||
for (Table.Cell<Long, Long, KeywordsChange> entry : changedCardKeywords.cellSet()) {
|
||||
this.changedCardKeywordsByText.put(entry.getRowKey(), entry.getColumnKey(), entry.getValue().copy(this, true));
|
||||
}
|
||||
}
|
||||
|
||||
public final void addChangedCardKeywordsInternal(
|
||||
final Collection<KeywordInterface> keywords, final Collection<KeywordInterface> removeKeywords,
|
||||
final boolean removeAllKeywords,
|
||||
@@ -5387,14 +5403,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|
||||
// Layer 1
|
||||
keywords.insertAll(state.getIntrinsicKeywords());
|
||||
if (state.getStateName().equals(CardStateName.Original)) {
|
||||
if (hasState(CardStateName.LeftSplit)) {
|
||||
keywords.insertAll(getState(CardStateName.LeftSplit).getIntrinsicKeywords());
|
||||
}
|
||||
if (hasState(CardStateName.RightSplit)) {
|
||||
keywords.insertAll(getState(CardStateName.RightSplit).getIntrinsicKeywords());
|
||||
}
|
||||
}
|
||||
|
||||
keywords.applyChanges(getChangedCardKeywordsList());
|
||||
|
||||
@@ -5685,7 +5693,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
final String newtxt = AbilityUtils.applyKeywordTextChangeEffects(oldtxt, this);
|
||||
if (!newtxt.equals(oldtxt)) {
|
||||
KeywordInterface newKw = Keyword.getInstance(newtxt);
|
||||
newKw.createTraits(this, true);
|
||||
addKeywords.add(newKw);
|
||||
removeKeywords.add(kw);
|
||||
} else if (oldtxt.startsWith("Class")) {
|
||||
@@ -6555,35 +6562,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return (c != null ? c.getImageKey() : "");
|
||||
}
|
||||
|
||||
public final String getFacedownImageKey() {
|
||||
if (isInZone(ZoneType.Exile)) {
|
||||
if (isForetold()) {
|
||||
return StaticData.instance().getOtherImageKey(ImageKeys.FORETELL_IMAGE, null);
|
||||
}
|
||||
return ImageKeys.getTokenKey(ImageKeys.HIDDEN_CARD);
|
||||
}
|
||||
|
||||
if (isManifested()) {
|
||||
String set = getManifestedSA().getCardState().getSetCode();
|
||||
return StaticData.instance().getOtherImageKey(ImageKeys.MANIFEST_IMAGE, set);
|
||||
}
|
||||
if (isCloaked()) {
|
||||
String set = getCloakedSA().getCardState().getSetCode();
|
||||
return StaticData.instance().getOtherImageKey(ImageKeys.CLOAKED_IMAGE, set);
|
||||
}
|
||||
if (getCastSA() != null) {
|
||||
String set = getCastSA().getCardState().getSetCode();
|
||||
if (getCastSA().isKeyword(Keyword.DISGUISE)) {
|
||||
return StaticData.instance().getOtherImageKey(ImageKeys.CLOAKED_IMAGE, set);
|
||||
} else if (getCastSA().isKeyword(Keyword.MORPH) || getCastSA().isKeyword(Keyword.MEGAMORPH)) {
|
||||
return StaticData.instance().getOtherImageKey(ImageKeys.MORPH_IMAGE, set);
|
||||
}
|
||||
}
|
||||
// TODO add face-down SA to key
|
||||
|
||||
return ImageKeys.getTokenKey(ImageKeys.HIDDEN_CARD);
|
||||
}
|
||||
|
||||
public final boolean isTributed() { return tributed; }
|
||||
public final void setTributed(final boolean b) {
|
||||
tributed = b;
|
||||
@@ -6768,7 +6746,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|
||||
String s = "Mode$ Continuous | AffectedDefined$ RememberedCard | EffectZone$ Command | AddKeyword$ Menace | AddStaticAbility$ SuspectedCantBlockBy";
|
||||
StaticAbility suspectedStatic = suspectedEffect.addStaticAbility(s);
|
||||
String effect = "Mode$ CantBlock | ValidCard$ Creature.Self | Description$ CARDNAME can't block.";
|
||||
String effect = "Mode$ CantBlockBy | ValidBlocker$ Creature.Self | Description$ CARDNAME can't block.";
|
||||
suspectedStatic.setSVar("SuspectedCantBlockBy", effect);
|
||||
|
||||
GameCommand until = SpellAbilityEffect.exileEffectCommand(getGame(), suspectedEffect);
|
||||
@@ -6784,23 +6762,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
|
||||
public final boolean isManifested() {
|
||||
return manifestedSA != null;
|
||||
return manifested;
|
||||
}
|
||||
public final SpellAbility getManifestedSA() {
|
||||
return manifestedSA;
|
||||
}
|
||||
public final void setManifested(final SpellAbility sa) {
|
||||
this.manifestedSA = sa;
|
||||
public final void setManifested(final boolean manifested) {
|
||||
this.manifested = manifested;
|
||||
}
|
||||
|
||||
public final boolean isCloaked() {
|
||||
return cloakedSA != null;
|
||||
return cloaked;
|
||||
}
|
||||
public final SpellAbility getCloakedSA() {
|
||||
return cloakedSA;
|
||||
}
|
||||
public final void setCloaked(final SpellAbility sa) {
|
||||
this.cloakedSA = sa;
|
||||
public final void setCloaked(final boolean cloaked) {
|
||||
this.cloaked = cloaked;
|
||||
}
|
||||
|
||||
public final boolean isForetold() {
|
||||
@@ -6935,7 +6907,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
addChangedCardTypes(new CardType(Collections.singletonList("Aura"), true),
|
||||
new CardType(Collections.singletonList("Creature"), true),
|
||||
false, EnumSet.of(RemoveType.EnchantmentTypes), bestowTimestamp, 0, updateView, false);
|
||||
addChangedCardKeywords(Collections.singletonList("Enchant:Creature"), Lists.newArrayList(),
|
||||
addChangedCardKeywords(Collections.singletonList("Enchant creature"), Lists.newArrayList(),
|
||||
false, bestowTimestamp, null, updateView);
|
||||
}
|
||||
|
||||
@@ -6983,17 +6955,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return equals(c) && c.getGameTimestamp() == gameTimestamp;
|
||||
}
|
||||
|
||||
public long getWorldTimestamp() {
|
||||
return worldTimestamp;
|
||||
}
|
||||
public void updateWorldTimestamp(long ts) {
|
||||
if (!getType().hasSupertype(Supertype.World)) {
|
||||
worldTimestamp = -1;
|
||||
} else if (worldTimestamp == -1) {
|
||||
worldTimestamp = ts;
|
||||
}
|
||||
}
|
||||
|
||||
public String getProtectionKey() {
|
||||
String protectKey = "";
|
||||
boolean pR = false; boolean pG = false; boolean pB = false; boolean pU = false; boolean pW = false;
|
||||
@@ -7128,15 +7089,12 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isPhasedOut()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// CantTarget static abilities
|
||||
if (StaticAbilityCantTarget.cantTarget(this, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return !isInPlay() || !isPhasedOut();
|
||||
}
|
||||
|
||||
public final boolean canBeControlledBy(final Player newController) {
|
||||
@@ -7145,21 +7103,30 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|
||||
@Override
|
||||
protected final boolean canBeEnchantedBy(final Card aura) {
|
||||
if (!aura.hasKeyword(Keyword.ENCHANT)) {
|
||||
return false;
|
||||
SpellAbility sa = aura.getFirstAttachSpell();
|
||||
TargetRestrictions tgt = null;
|
||||
if (sa != null) {
|
||||
tgt = sa.getTargetRestrictions();
|
||||
}
|
||||
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
||||
String k = ki.getOriginal();
|
||||
String m[] = k.split(":");
|
||||
String v = m[1];
|
||||
if (!isValid(v.split(","), aura.getController(), aura, null)) {
|
||||
return false;
|
||||
}
|
||||
if (!v.contains("inZone") && !isInPlay()) {
|
||||
|
||||
if (tgt != null) {
|
||||
boolean zoneValid = false;
|
||||
// check the zone types
|
||||
for (final ZoneType zt : tgt.getZone()) {
|
||||
if (isInZone(zt)) {
|
||||
zoneValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!zoneValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check valid
|
||||
return isValid(tgt.getValidTgts(), aura.getController(), aura, sa);
|
||||
}
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -7221,6 +7188,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return !StaticAbilityCantExile.cantExile(this, source, effect);
|
||||
}
|
||||
|
||||
public final void setStaticAbilities(final List<StaticAbility> a) {
|
||||
currentState.setStaticAbilities(a);
|
||||
}
|
||||
|
||||
public final FCollectionView<StaticAbility> getStaticAbilities() {
|
||||
return currentState.getStaticAbilities();
|
||||
}
|
||||
@@ -7237,6 +7208,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return stAb;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public final void removeStaticAbility(StaticAbility stAb) {
|
||||
currentState.removeStaticAbility(stAb);
|
||||
}
|
||||
|
||||
public void updateStaticAbilities(List<StaticAbility> list, CardState state) {
|
||||
for (final CardTraitChanges ck : getChangedCardTraitsList(state)) {
|
||||
if (ck.isRemoveAll()) {
|
||||
@@ -7289,6 +7265,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return replacementEffect;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void removeReplacementEffect(ReplacementEffect replacementEffect) {
|
||||
currentState.removeReplacementEffect(replacementEffect);
|
||||
}
|
||||
|
||||
public void updateReplacementEffects(List<ReplacementEffect> list, CardState state) {
|
||||
for (final CardTraitChanges ck : getChangedCardTraitsList(state)) {
|
||||
if (ck.isRemoveAll()) {
|
||||
@@ -7959,10 +7940,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
this.savedLastKnownZone = zone;
|
||||
}
|
||||
|
||||
public final boolean hasChapter() {
|
||||
return getCurrentState().hasChapter();
|
||||
}
|
||||
|
||||
public final int getFinalChapterNr() {
|
||||
return getCurrentState().getFinalChapterNr();
|
||||
}
|
||||
@@ -8201,7 +8178,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
|
||||
public boolean attackVigilance() {
|
||||
return StaticAbilityCantAttackBlock.attackVigilance(this);
|
||||
return StaticAbilityAttackVigilance.attackVigilance(this);
|
||||
}
|
||||
|
||||
public boolean isAbilitySick() {
|
||||
@@ -8341,8 +8318,5 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|
||||
this.changedCardNames.putAll(in.changedCardNames);
|
||||
setChangedCardTraits(in.getChangedCardTraits());
|
||||
|
||||
setChangedCardTraitsByText(in.getChangedCardTraitsByText());
|
||||
setChangedCardKeywordsByText(in.getChangedCardKeywordsByText());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,15 +66,8 @@ public class CardCopyService {
|
||||
out.setCollectible(copyFrom.isCollectible());
|
||||
|
||||
// this's necessary for forge.game.GameAction.unattachCardLeavingBattlefield(Card)
|
||||
if (copyFrom.hasCardAttachments()) {
|
||||
out.setAttachedCards(copyFrom.getAttachedCards());
|
||||
}
|
||||
if (copyFrom.isAttachedToEntity()) {
|
||||
out.setEntityAttachedTo(copyFrom.getEntityAttachedTo());
|
||||
}
|
||||
if (copyFrom.hasMergedCard()) {
|
||||
out.setMergedCards(copyFrom.getMergedCards());
|
||||
}
|
||||
out.setAttachedCards(copyFrom.getAttachedCards());
|
||||
out.setEntityAttachedTo(copyFrom.getEntityAttachedTo());
|
||||
|
||||
out.setLeavesPlayCommands(copyFrom.getLeavesPlayCommands());
|
||||
|
||||
@@ -315,7 +308,6 @@ public class CardCopyService {
|
||||
newCopy.setPhasedOut(copyFrom.getPhasedOut());
|
||||
newCopy.setTapped(copyFrom.isTapped());
|
||||
newCopy.setTributed(copyFrom.isTributed());
|
||||
newCopy.setUnearthed(copyFrom.isUnearthed());
|
||||
newCopy.setMonstrous(copyFrom.isMonstrous());
|
||||
newCopy.setRenowned(copyFrom.isRenowned());
|
||||
newCopy.setSolved(copyFrom.isSolved());
|
||||
@@ -360,6 +352,8 @@ public class CardCopyService {
|
||||
}
|
||||
newCopy.setChosenEvenOdd(copyFrom.getChosenEvenOdd());
|
||||
|
||||
newCopy.setUnearthed(copyFrom.isUnearthed());
|
||||
|
||||
newCopy.copyFrom(copyFrom);
|
||||
|
||||
// for getReplacementList (run after setChangedCardKeywords for caching)
|
||||
@@ -367,17 +361,14 @@ public class CardCopyService {
|
||||
newCopy.setStoredReplacements(copyFrom.getStoredReplacements());
|
||||
|
||||
newCopy.copyChangedTextFrom(copyFrom);
|
||||
newCopy.changedTypeByText = copyFrom.changedTypeByText;
|
||||
newCopy.changedCardKeywordsByWord = copyFrom.changedCardKeywordsByWord.copy(newCopy, true);
|
||||
|
||||
newCopy.setGameTimestamp(copyFrom.getGameTimestamp());
|
||||
newCopy.setLayerTimestamp(copyFrom.getLayerTimestamp());
|
||||
|
||||
newCopy.setBestowTimestamp(copyFrom.getBestowTimestamp());
|
||||
|
||||
newCopy.setTurnInZone(copyFrom.getTurnInZone());
|
||||
|
||||
newCopy.setForetold(copyFrom.isForetold());
|
||||
newCopy.setTurnInZone(copyFrom.getTurnInZone());
|
||||
newCopy.setForetoldCostByEffect(copyFrom.isForetoldCostByEffect());
|
||||
|
||||
newCopy.setPlotted(copyFrom.isPlotted());
|
||||
|
||||
@@ -254,7 +254,31 @@ public class CardFactory {
|
||||
|
||||
// ******************************************************************
|
||||
// ************** Link to different CardFactories *******************
|
||||
if (state != CardStateName.Original) {
|
||||
if (state == CardStateName.LeftSplit || state == CardStateName.RightSplit) {
|
||||
for (final SpellAbility sa : card.getSpellAbilities()) {
|
||||
sa.setCardState(card.getState(state));
|
||||
}
|
||||
CardFactoryUtil.setupKeywordedAbilities(card);
|
||||
final CardState original = card.getState(CardStateName.Original);
|
||||
original.addNonManaAbilities(card.getCurrentState().getNonManaAbilities());
|
||||
original.addIntrinsicKeywords(card.getCurrentState().getIntrinsicKeywords()); // Copy 'Fuse' to original side
|
||||
for (Trigger t : card.getCurrentState().getTriggers()) {
|
||||
if (t.isIntrinsic()) {
|
||||
original.addTrigger(t);
|
||||
}
|
||||
}
|
||||
for (StaticAbility st : card.getCurrentState().getStaticAbilities()) {
|
||||
if (st.isIntrinsic()) {
|
||||
original.addStaticAbility(st);
|
||||
}
|
||||
}
|
||||
for (ReplacementEffect re : card.getCurrentState().getReplacementEffects()) {
|
||||
if (re.isIntrinsic()) {
|
||||
original.addReplacementEffect(re);
|
||||
}
|
||||
}
|
||||
original.getSVars().putAll(card.getCurrentState().getSVars()); // Unfortunately need to copy these to (Effect looks for sVars on execute)
|
||||
} else if (state != CardStateName.Original) {
|
||||
CardFactoryUtil.setupKeywordedAbilities(card);
|
||||
}
|
||||
}
|
||||
@@ -282,18 +306,6 @@ public class CardFactory {
|
||||
if (card.getType().hasSubtype("Siege")) {
|
||||
CardFactoryUtil.setupSiegeAbilities(card);
|
||||
}
|
||||
else if (card.getType().getBattleTypes().isEmpty()) {
|
||||
//Probably a custom card? Check if it already has an RE for designating a protector.
|
||||
if(card.getReplacementEffects().stream().anyMatch((re) -> re.hasParam("BattleProtector")))
|
||||
return;
|
||||
//Battles with no battle type enter protected by their controller.
|
||||
String abProtector = "DB$ ChoosePlayer | Choices$ You | Protect$ True | DontNotify$ True";
|
||||
String reText = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated"
|
||||
+ " | BattleProtector$ True | Description$ (As this Battle enters, its controller becomes its protector.)";
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(reText, card, true);
|
||||
re.setOverridingAbility(AbilityFactory.getAbility(abProtector, card));
|
||||
card.addReplacementEffect(re);
|
||||
}
|
||||
}
|
||||
|
||||
public static SpellAbility buildBasicLandAbility(final CardState state, byte color) {
|
||||
@@ -418,6 +430,24 @@ public class CardFactory {
|
||||
|
||||
c.setAttractionLights(face.getAttractionLights());
|
||||
|
||||
// SpellPermanent only for Original State
|
||||
if (c.getCurrentStateName() == CardStateName.Original ||
|
||||
c.getCurrentStateName() == CardStateName.LeftSplit ||
|
||||
c.getCurrentStateName() == CardStateName.RightSplit ||
|
||||
c.getCurrentStateName() == CardStateName.Modal ||
|
||||
c.getCurrentStateName().toString().startsWith("Specialize")) {
|
||||
if (c.isLand()) {
|
||||
SpellAbility sa = new LandAbility(c);
|
||||
sa.setCardState(c.getCurrentState());
|
||||
c.addSpellAbility(sa);
|
||||
} else if (c.isPermanent() && !c.isAura()) {
|
||||
// this is the "default" spell for permanents like creatures and artifacts
|
||||
SpellAbility sa = new SpellPermanent(c);
|
||||
sa.setCardState(c.getCurrentState());
|
||||
c.addSpellAbility(sa);
|
||||
}
|
||||
}
|
||||
|
||||
CardFactoryUtil.addAbilityFactoryAbilities(c, face.getAbilities());
|
||||
}
|
||||
|
||||
@@ -742,7 +772,7 @@ public class CardFactory {
|
||||
TextUtil.fastReplace(host.getName(), ",", ""),
|
||||
" ", "_").toLowerCase();
|
||||
String set = host.getSetCode().toLowerCase();
|
||||
state.setImageKey(ImageKeys.getTokenKey("offspring_" + name + "|" + set));
|
||||
state.setImageKey(ImageKeys.getTokenKey("offspring_" + name + "_" + set));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1409,29 +1409,6 @@ public class CardFactoryUtil {
|
||||
parsedEndTrig.setOverridingAbility(AbilityFactory.getAbility(remove, card));
|
||||
|
||||
inst.addTrigger(parsedEndTrig);
|
||||
} else if (keyword.equals("Job select")) {
|
||||
final StringBuilder sbTrig = new StringBuilder();
|
||||
sbTrig.append("Mode$ ChangesZone | Destination$ Battlefield | ");
|
||||
sbTrig.append("ValidCard$ Card.Self | TriggerDescription$ ");
|
||||
sbTrig.append("Job select (").append(inst.getReminderText()).append(")");
|
||||
|
||||
final String sbHero = "DB$ Token | TokenScript$ c_1_1_hero | TokenOwner$ You | RememberTokens$ True";
|
||||
final SpellAbility saHero= AbilityFactory.getAbility(sbHero, card);
|
||||
|
||||
final String sbAttach = "DB$ Attach | Defined$ Remembered";
|
||||
final AbilitySub saAttach = (AbilitySub) AbilityFactory.getAbility(sbAttach, card);
|
||||
saHero.setSubAbility(saAttach);
|
||||
|
||||
final String sbClear = "DB$ Cleanup | ClearRemembered$ True";
|
||||
final AbilitySub saClear = (AbilitySub) AbilityFactory.getAbility(sbClear, card);
|
||||
saAttach.setSubAbility(saClear);
|
||||
|
||||
final Trigger etbTrigger = TriggerHandler.parseTrigger(sbTrig.toString(), card, intrinsic);
|
||||
|
||||
etbTrigger.setOverridingAbility(saHero);
|
||||
|
||||
saHero.setIntrinsic(intrinsic);
|
||||
inst.addTrigger(etbTrigger);
|
||||
} else if (keyword.equals("Living Weapon")) {
|
||||
final StringBuilder sbTrig = new StringBuilder();
|
||||
sbTrig.append("Mode$ ChangesZone | Destination$ Battlefield | ");
|
||||
@@ -1552,7 +1529,7 @@ public class CardFactoryUtil {
|
||||
final String[] k = keyword.split(":");
|
||||
final String n = k[1];
|
||||
|
||||
final String trigStr = "Mode$ Attacks | ValidCard$ Card.Self"
|
||||
final String trigStr = "Mode$ Attacks | ValidCard$ Card.Self | Secondary$ True"
|
||||
+ " | TriggerDescription$ Mobilize " + n + " (" + inst.getReminderText() + ")";
|
||||
|
||||
final String effect = "DB$ Token | TokenAmount$ " + n + " | TokenScript$ r_1_1_warrior"
|
||||
@@ -2122,13 +2099,23 @@ public class CardFactoryUtil {
|
||||
} else if (keyword.startsWith("Amplify")) {
|
||||
final String[] ampString = keyword.split(":");
|
||||
final String amplifyMagnitude = ampString[1];
|
||||
final String ampTypes = ampString[2];
|
||||
String[] refinedTypes = ampTypes.split(",");
|
||||
final StringBuilder types = new StringBuilder();
|
||||
for (int i = 0; i < refinedTypes.length; i++) {
|
||||
types.append("Card.").append(refinedTypes[i]).append("+YouCtrl");
|
||||
if (i + 1 != refinedTypes.length) {
|
||||
types.append(",");
|
||||
}
|
||||
}
|
||||
|
||||
// Setup ETB replacement effects
|
||||
final String actualRep = "Event$ Moved | Destination$ Battlefield | ValidCard$ Card.Self |"
|
||||
+ " | ReplacementResult$ Updated | Description$ Amplify " + amplifyMagnitude + " ("
|
||||
+ inst.getReminderText() + ")";
|
||||
|
||||
final String abString = "DB$ Reveal | AnyNumber$ True | RevealValid$ Card.YouOwn+sharesCreatureTypeWith+Other+NotDefinedReplacedSimultaneousETB | RememberRevealed$ True";
|
||||
final String abString = "DB$ Reveal | AnyNumber$ True | RevealValid$ "
|
||||
+ types.toString() + " | RememberRevealed$ True";
|
||||
|
||||
SpellAbility saReveal = AbilityFactory.getAbility(abString, card);
|
||||
|
||||
@@ -2782,7 +2769,7 @@ public class CardFactoryUtil {
|
||||
final String[] k = keyword.split(":");
|
||||
final Cost blitzCost = new Cost(k[1], false);
|
||||
|
||||
final SpellAbility newSA = card.getFirstSpellAbilityWithFallback().copyWithManaCostReplaced(host.getController(), blitzCost);
|
||||
final SpellAbility newSA = card.getFirstSpellAbility().copyWithManaCostReplaced(host.getController(), blitzCost);
|
||||
|
||||
if (k.length > 2) {
|
||||
newSA.getMapParams().put("ValidAfterStack", k[2]);
|
||||
@@ -2895,7 +2882,7 @@ public class CardFactoryUtil {
|
||||
}
|
||||
desc += ")";
|
||||
|
||||
final SpellAbility sa = card.getFirstSpellAbilityWithFallback();
|
||||
final SpellAbility sa = card.getFirstSpellAbility();
|
||||
final SpellAbility newSA = sa.copyWithDefinedCost(new Cost(costStr, false));
|
||||
|
||||
newSA.getRestrictions().setIsPresent(validStr + ".YouCtrl+CanBeSacrificedBy");
|
||||
@@ -3125,7 +3112,7 @@ public class CardFactoryUtil {
|
||||
c.setForetold(true);
|
||||
c.turnFaceDown(true);
|
||||
// look at the exiled card
|
||||
c.addMayLookFaceDownExile(getActivatingPlayer());
|
||||
c.addMayLookTemp(getActivatingPlayer());
|
||||
|
||||
// only done when the card is foretold by the static ability
|
||||
getActivatingPlayer().addForetoldThisTurn();
|
||||
@@ -3197,6 +3184,7 @@ public class CardFactoryUtil {
|
||||
inst.addSpellAbility(newSA);
|
||||
} else if (keyword.startsWith("Fuse") && card.getStateName().equals(CardStateName.Original)) {
|
||||
final SpellAbility sa = AbilityFactory.buildFusedAbility(card.getCard());
|
||||
card.addSpellAbility(sa);
|
||||
inst.addSpellAbility(sa);
|
||||
} else if (keyword.startsWith("Haunt")) {
|
||||
if (!host.isCreature() && intrinsic) {
|
||||
@@ -3581,15 +3569,6 @@ public class CardFactoryUtil {
|
||||
sa.setSVar("ScavengeX", "Exiled$CardPower");
|
||||
sa.setIntrinsic(intrinsic);
|
||||
inst.addSpellAbility(sa);
|
||||
} else if (keyword.startsWith("Station")) {
|
||||
String effect = "AB$ PutCounter | Cost$ tapXType<1/Creature.Other> | Defined$ Self " +
|
||||
"| CounterType$ CHARGE | CounterNum$ StationX | SorcerySpeed$ True " +
|
||||
"| CostDesc$ | SpellDescription$ Station (" + inst.getReminderText() + ")";
|
||||
|
||||
final SpellAbility sa = AbilityFactory.getAbility(effect, card);
|
||||
sa.setSVar("StationX", "TappedCards$TapPowerValue");
|
||||
sa.setIntrinsic(intrinsic);
|
||||
inst.addSpellAbility(sa);
|
||||
} else if (keyword.startsWith("Encore")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final String manacost = k[1];
|
||||
@@ -3846,9 +3825,9 @@ public class CardFactoryUtil {
|
||||
final String t = k[1];
|
||||
|
||||
String desc;
|
||||
if (k.length > 2) {
|
||||
if(k.length > 2) {
|
||||
String typeText = k[2];
|
||||
if (typeText.contains(" with "))
|
||||
if(typeText.contains(" with "))
|
||||
desc = typeText.substring(typeText.indexOf(" with ") + 6);
|
||||
else
|
||||
desc = typeText + "s";
|
||||
@@ -3974,7 +3953,7 @@ public class CardFactoryUtil {
|
||||
String effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Daybound | Secondary$ True | Description$ This permanent can't be transformed except by its daybound ability.";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Decayed")) {
|
||||
String effect = "Mode$ CantBlock | ValidCard$ Creature.Self | Secondary$ True | Description$ CARDNAME can't block.";
|
||||
String effect = "Mode$ CantBlockBy | ValidBlocker$ Creature.Self | Secondary$ True | Description$ CARDNAME can't block.";
|
||||
StaticAbility st = StaticAbility.create(effect, state.getCard(), state, intrinsic);
|
||||
inst.addStaticAbility(st);
|
||||
} else if (keyword.equals("Defender")) {
|
||||
@@ -4125,12 +4104,8 @@ public class CardFactoryUtil {
|
||||
String effect = "Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Amount$ Strive | Cost$ "+ manacost +" | EffectZone$ All" +
|
||||
" | Description$ Strive - " + inst.getReminderText();
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Tiered")) {
|
||||
String effect = "Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Secondary$ True | Amount$ Tiered | EffectZone$ All"
|
||||
+ " | Description$ Tiered (" + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Unleash")) {
|
||||
String effect = "Mode$ CantBlock | ValidCard$ Creature.Self+counters_GE1_P1P1 | Secondary$ True | Description$ CARDNAME can't block.";
|
||||
String effect = "Mode$ CantBlockBy | ValidBlocker$ Creature.Self+counters_GE1_P1P1 | Secondary$ True | Description$ CARDNAME can't block.";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Undaunted")) {
|
||||
String effect = "Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Secondary$ True"
|
||||
@@ -4149,7 +4124,7 @@ public class CardFactoryUtil {
|
||||
|
||||
public static void setupSiegeAbilities(Card card) {
|
||||
StringBuilder chooseSB = new StringBuilder();
|
||||
chooseSB.append("Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | BattleProtector$ True");
|
||||
chooseSB.append("Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated");
|
||||
chooseSB.append(" | Description$ (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.)");
|
||||
String chooseProtector = "DB$ ChoosePlayer | Defined$ You | Choices$ Opponent | Protect$ True | ChoiceTitle$ Choose an opponent to protect this battle";
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ public class CardLists {
|
||||
public static CardCollection filterLEPower(final Iterable<Card> in, final int lessthanPower) {
|
||||
return CardLists.filter(in, c -> c.getNetPower() <= lessthanPower);
|
||||
}
|
||||
|
||||
|
||||
public static final Comparator<Card> ToughnessComparator = Comparator.comparingInt(Card::getNetToughness);
|
||||
public static final Comparator<Card> ToughnessComparatorInv = Comparator.comparingInt(Card::getNetToughness).reversed();
|
||||
public static final Comparator<Card> PowerComparator = Comparator.comparingInt(Card::getNetCombatDamage);
|
||||
@@ -421,13 +421,13 @@ public class CardLists {
|
||||
* @param cardList the list of creature cards for which to sum the power
|
||||
* @param crew for cards that crew with toughness rather than power
|
||||
*/
|
||||
public static int getTotalPower(Iterable<Card> cardList, CardTraitBase ctb) {
|
||||
public static int getTotalPower(Iterable<Card> cardList, SpellAbility sa) {
|
||||
int total = 0;
|
||||
for (final Card crd : cardList) {
|
||||
if (StaticAbilityTapPowerValue.withToughness(crd, ctb)) {
|
||||
if (StaticAbilityTapPowerValue.withToughness(crd, sa)) {
|
||||
total += Math.max(0, crd.getNetToughness());
|
||||
} else {
|
||||
int m = StaticAbilityTapPowerValue.getMod(crd, ctb);
|
||||
int m = StaticAbilityTapPowerValue.getMod(crd, sa);
|
||||
total += Math.max(0, crd.getNetPower() + m);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user