...
 
Commits (345)
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>
......@@ -231,3 +231,6 @@ forge-gui/tools/oracleScript.log
/release.properties
/target
/test-output
.settings
.classpath
.project
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>forge</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>
add_header=true
add_todo=false
eclipse.preferences.version=1
header_text=/*\n * Forge\: Play Magic\: the Gathering.\n * Copyright (C) 2011 Forge Team\n *\n * This program is free software\: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n * \n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n * \n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http\://www.gnu.org/licenses/>.\n */
project_specific_settings=true
replacements=<?xml version\="1.0" standalone\="yes"?>\n\n<replacements>\n<replacement key\="get" scope\="1" mode\="0">Gets the</replacement>\n<replacement key\="set" scope\="1" mode\="0">Sets the</replacement>\n<replacement key\="add" scope\="1" mode\="0">Adds the</replacement>\n<replacement key\="edit" scope\="1" mode\="0">Edits the</replacement>\n<replacement key\="remove" scope\="1" mode\="0">Removes the</replacement>\n<replacement key\="init" scope\="1" mode\="0">Inits the</replacement>\n<replacement key\="parse" scope\="1" mode\="0">Parses the</replacement>\n<replacement key\="create" scope\="1" mode\="0">Creates the</replacement>\n<replacement key\="build" scope\="1" mode\="0">Builds the</replacement>\n<replacement key\="is" scope\="1" mode\="0">Checks if is</replacement>\n<replacement key\="print" scope\="1" mode\="0">Prints the</replacement>\n<replacement key\="has" scope\="1" mode\="0">Checks for</replacement>\n</replacements>\n\n
visibility_private=false
eclipse.preferences.version=1
encoding/<project>=UTF-8
This diff is collapsed.
#Sun Apr 28 22:56:26 CEST 2013
eclipse.preferences.version=1
formatter_profile=_Forge Java Code Style - Formatter Settings
formatter_settings_version=12
org.eclipse.jdt.ui.javadoc=true
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\r\n * @return the ${bare_field_name}\r\n */</template><template autoinsert\="false" context\="settercomment_context" deleted\="false" description\="Comment for setter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\r\n * @param ${param}0 the ${bare_field_name} to set\r\n */</template><template autoinsert\="false" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\r\n * ${todo}\: Write javadoc for Constructor.\r\n * ${tags}\r\n */</template><template autoinsert\="false" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment"/><template autoinsert\="false" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.typecomment" name\="typecomment">/** \r\n * ${todo}\: Write javadoc for this type.\r\n *\r\n * ${tags}\r\n */</template><template autoinsert\="false" context\="fieldcomment_context" deleted\="false" description\="Comment for fields" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment"/><template autoinsert\="false" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\r\n * ${todo}\: Write javadoc for this method.\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="overridecomment_context" deleted\="false" description\="Comment for overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.overridecomment" name\="overridecomment">/* (non-Javadoc)\r\n * ${see_to_overridden}\r\n */</template><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\r\n * ${tags}\r\n * ${see_to_target}\r\n */</template><template autoinsert\="true" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.newtype" name\="newtype">${filecomment}\r\n${package_declaration}\r\n\r\n${typecomment}\r\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.classbody" name\="classbody">\r\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.interfacebody" name\="interfacebody">\r\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.enumbody" name\="enumbody">\r\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.annotationbody" name\="annotationbody">\r\n</template><template autoinsert\="false" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block ignores the exception, but sends it to System.err and probably forge.log.\r\n${exception_var}.printStackTrace();</template><template autoinsert\="false" context\="methodbody_context" deleted\="false" description\="Code in created method stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated method stub\r\n${body_statement}</template><template autoinsert\="false" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\r\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="false" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param}0; // ${todo}\: Add 0 to parameter's name.</template></templates>
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
......@@ -9,7 +9,9 @@ Discord channel [here](https://discordapp.com/channels/267367946135928833/267742
# Requirements / Tools
- Java IDE such as IntelliJ or Eclipse
- Java JDK 8 or later
- Git
- Git client (optional)
- Maven
- Gitlab account
- Libgdx (optional: familiarity with this library is helpful for mobile platform development)
......@@ -26,48 +28,140 @@ Discord channel [here](https://discordapp.com/channels/267367946135928833/267742
# Eclipse
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
## Project Setup
- Follow the instructions for cloning from Gitlab. You'll need a Gitlab account setup and an SSH key defined. If you are on a
Windows machine you can use Putty with TortoiseGit. Run puttygen.exe to generate the key -- save the private key and export
- Follow the instructions for cloning from Gitlab. You'll need a Gitlab account setup and an SSH key defined.
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 Gitlab profile under
"SSH keys".
Run pageant.exe and add the private key generated earlier. TortoiseGit will use this for accessing Gitlab.
"SSH keys". Run pageant.exe and add the private key generated earlier. TortoiseGit will use this for accessing Gitlab.
- Fork the Forge git repo to your Gitlab account.
- Clone your forked repo to your local machine.
- Make sure the Java SDK is installed -- not just the JRE. Java 8 or newer required. At the time of this writing, JDK 11 works as expected.
- You need maven to load in dependencies and build. Obtain that [from here](https://maven.apache.org/download.cgi). Execute the following from the root repo dir to download dependencies, etc:
- Make sure the Java SDK is installed -- not just the JRE. Java 8 or newer required. If you execute `java -version` at the shell or command prompt, it should report version 1.8 or later.
`mvn -U -B clean -P windows-linux install`
For the desktop, this will create a populated directory at `forge-gui-desktop/target/forge-gui-desktop-<release-name>` containing typical release files such as the jar, Windows executable, resource files, etc.
- Install Eclipse 2018-12 or later for Java. Launch it.
- Install Eclipse for Java. Launch it. At the time of this writing, Eclipse 2018-12 works as expected. YMMV for other versions.
- Create a workspace. Go to the workbench. Right-click inside of Package Explorer > Import... > General > Existing Projects into Workspace > Navigate to local forge repo >
Check "Search for nested projects" > Uncheck 'forge', check the rest > Finish.
- Create a workspace. Go to the workbench. Right-click inside of Package Explorer > Import... > Maven > Existing Maven Projects > Navigate to root path of the local forge repo and
ensure everything is checked > Finish.
- Let Eclipse run through building the project. You may be prompted for resolving any missing Maven plugins -- accept the ones offered. You may see errors appear in the "Problems" tab. These should
be automatically resolved as plug-ins are installed and Eclipse continues the build process. If this is the first time for some plug-in installs, Eclipse may prompt you to restart. Do so. Be patient
for this first time through.
- Let Eclipse run through building the project.
- Once everything builds, all errors should disappear. You can now advance to Project launch.
## Project Launch
### Desktop
- Right-click on forge-gui-desktop > Run As... > Java Application > "Main - forge.view" > Proceed
This is the standard configuration used for releasing to Windows / Linux / MacOS.
- Right-click on forge-gui-desktop > Run As... > Java Application > "Main - forge.view" > Ok
- The familiar Forge splash screen, etc. should appear. Enjoy!
### Mobile (Desktop dev)
- Right-click on forge-gui-mobile-dev > Run As... > Java Application > "Main - forge.app" > Proceed
This is the configuration used for doing mobile development using the Windows / Linux / MacOS front-end. Knowledge of libgdx is helpful here.
- Right-click on forge-gui-mobile-dev > Run As... > Java Application > "Main - forge.app" > Ok.
- A view similar to a mobile phone should appear. Enjoy!
## Eclipse / Android SDK Integration
Google no longer supports Android SDK releases for Eclipse. That said, it is still possible to build and debug Android platforms.
### Android SDK
Reference SO for obtaining a specific release: https://stackoverflow.com/questions/27043522/where-can-i-download-an-older-version-of-the-android-sdk
#### Windows
Download the following archived version of the Android SDK: http://dl-ssl.google.com/android/repository/tools_r25.2.3-windows.zip. Install it somewhere on your machine. This is referenced
in the following instructions as your 'Android SDK Install' path.
#### Linux / Mac OSX
TBD
### Android Plugin for Eclipse
Google's last plugin release does not work completely with target's running Android 7.0 or later. Download the ADT-24.2.0-20160729.zip plugin
from: https://github.com/khaledev/ADT/releases
In Eclipse go to: Help > Install New Software... > Add > Name: ADT Update, Click on the "Archive:" button and navigate to the downloaded ADT-24.2.0-20160729.zip file > Add. Install all "Developer Tools". Eclipse
should restart and prompt you to run the SDK Manager. Launch it and continue to the next steps below.
### Android Platform
In Eclipse, if the SDK Manager is not already running, go to Window > Android SDK Manager. Install the following options / versions:
- Android SDK Build-tools 26.0.1
- Android 7.1.1 (API 25) SDK Platform
- Google USB Driver 11
Note that this will populate additional tools in the Android SDK install path extracted above.
### Proguard update
The Proguard included with the Android SDK Build-tools is outdated and does not work with Java 1.8. Download Proguard 6.0.3 from https://sourceforge.net/projects/proguard/files/proguard/6.0/.
- Go to the Android SDK install path. Rename the tools/proguard/ path to tools/proguard4.7/.
- Extract Proguard 6.0.3 to the Android SDK install path under tools/. You will need to rename the dir proguard6.0.3/ to proguard/.
### Android Build
The Eclipse plug-ins do NOT support building things for Android. They do however allow you to use the debugger so you can still set breakpoints and trace
things out. The steps below show how to generate a debug Android build.
Right-click on the forge-gui-android project. Run as.. > Maven build...
On the Main tab, set Goals: install, Profiles: android-debug
On the Environment tab, you may need to define the variable ANDROID_HOME with the value containing the path to your Android SDK installation.
For example, Variable: ANDROID_HOME, Value: Your Android SDK install path here.
You should now be able to "run" the forge-gui-android Maven build. This may take a few minutes. If everything worked, you should see "BUILD SUCCESS" in the Console View.
Assuming you got this far, you should have an Android forge-android-[version].apk in the forge-gui-android/target path.
### Android Deploy
You'll need to have the Android SDK install path platform-tools/ path in your command search path to easily deploy builds.
- Open a command prompt. Navigate to the forge-gui-android/target/ path.
- Connect your Android device to your dev machine.
- Ensure the device is visible using `adb devices`
- Remove the old Forge install if present: `adb uninstall forge.app`
- Install the new apk: `adb install forge-android-[version].apk`
### Android Debugging
Assuming the apk is installed, launch it from the device.
In Eclipse, launch the DDMS. Window > Perspective > Open Perspective > Other... > DDMS. You should see the forge app in the list. Highlight the app, click on the green debug button and a
green debug button should appear next to the app's name. You can now set breakpoints and step through the source code.
# IntelliJ
TBD
# Card Scripting
Visit [this page](https://www.slightlymagic.net/wiki/Forge_API) for information on scripting.
Card scripting resources are found in the forge-gui/res/ path.
# General Notes
## Project Hierarchy
......@@ -95,6 +189,8 @@ The platform-specific projects are:
### forge-gui
The forge-gui project includes the scripting resource definitions in the res/ path.
### forge-gui-android
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
......
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry combineaccessrules="false" kind="src" path="/forge-core"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>forge-ai</name>
<comment></comment>
<projects>
<project>forge-game</project>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>
eclipse.preferences.version=1
encoding//src/main/java=ISO-8859-1
encoding/<project>=UTF-8
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.7
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
......@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.20-SNAPSHOT</version>
<version>1.6.22-SNAPSHOT</version>
</parent>
<artifactId>forge-ai</artifactId>
......
......@@ -73,6 +73,7 @@ public class ComputerUtil {
public static boolean handlePlayingSpellAbility(final Player ai, SpellAbility sa, final Game game, Runnable chooseTargets) {
game.getStack().freezeStack();
final Card source = sa.getHostCard();
source.setSplitStateToPlayAbility(sa);
if (sa.isSpell() && !source.isCopiedSpell()) {
if (source.getType().hasStringType("Arcane")) {
......
......@@ -95,9 +95,15 @@ public class ComputerUtilAbility {
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
final List<SpellAbility> newAbilities = Lists.newArrayList();
List<SpellAbility> originListWithAddCosts = Lists.newArrayList();
for (SpellAbility sa : originList) {
// If this spell has alternative additional costs, add them instead of the unmodified SA itself
sa.setActivatingPlayer(player);
originListWithAddCosts.addAll(GameActionUtil.getAdditionalCostSpell(sa));
}
for (SpellAbility sa : originListWithAddCosts) {
// determine which alternative costs are cheaper than the original and prioritize them
List<SpellAbility> saAltCosts = GameActionUtil.getAlternativeCosts(sa, player);
List<SpellAbility> priorityAltSa = Lists.newArrayList();
......
......@@ -233,8 +233,15 @@ public class AttachAi extends SpellAbilityAi {
boolean alternativeConsiderations = hasFloatMana || willDiscardNow || willDieNow || willRespondToStack || willCastAtEOT || willCastEarly;
if (!alternativeConsiderations && (combat == null || game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) || (!combat.isAttacking(attachTarget) && !combat.isBlocking(attachTarget))) {
return false;
if (!alternativeConsiderations) {
if (combat == null ||
game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
if (!(combat.isAttacking(attachTarget) || combat.isBlocking(attachTarget))) {
return false;
}
}
return true;
......
......@@ -311,13 +311,20 @@ public class CountersPutAi extends SpellAbilityAi {
return false;
}
if (sa.hasParam("Adapt") && source.getCounters(CounterType.P1P1) > 0) {
return false;
}
// TODO handle proper calculation of X values based on Cost
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
if (sa.hasParam("Adapt")) {
Game game = ai.getGame();
Combat combat = game.getCombat();
if (!source.canReceiveCounters(CounterType.P1P1) || source.getCounters(CounterType.P1P1) > 0) {
return false;
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return doCombatAdaptLogic(source, amount, combat);
}
}
if ("Fight".equals(logic)) {
int nPump = 0;
if (type.equals("P1P1")) {
......@@ -1049,4 +1056,39 @@ public class CountersPutAi extends SpellAbilityAi {
return false;
}
private boolean doCombatAdaptLogic(Card source, int amount, Combat combat) {
if (combat.isAttacking(source)) {
if (!combat.isBlocked(source)) {
return true;
} else {
for (Card blockedBy : combat.getBlockers(source)) {
if (blockedBy.getNetToughness() > source.getNetPower()
&& blockedBy.getNetToughness() <= source.getNetPower() + amount) {
return true;
}
}
int totBlkPower = Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
if (source.getNetToughness() <= totBlkPower
&& source.getNetToughness() + amount > totBlkPower) {
return true;
}
}
} else if (combat.isBlocking(source)) {
for (Card blocked : combat.getAttackersBlockedBy(source)) {
if (blocked.getNetToughness() > source.getNetPower()
&& blocked.getNetToughness() <= source.getNetPower() + amount) {
return true;
}
}
int totAtkPower = Aggregates.sum(combat.getAttackersBlockedBy(source), CardPredicates.Accessors.fnGetNetPower);
if (source.getNetToughness() <= totAtkPower
&& source.getNetToughness() + amount > totAtkPower) {
return true;
}
}
return false;
}
}
......@@ -8,8 +8,10 @@ import forge.game.GameEntity;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.TokenEffect;
import forge.game.card.*;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.token.TokenInfo;
import forge.game.combat.Combat;
import forge.game.cost.CostPart;
......@@ -58,20 +60,9 @@ public class TokenAi extends SpellAbilityAi {
private void readParameters(final SpellAbility mapParams) {
this.tokenAmount = mapParams.getParamOrDefault("TokenAmount", "1");
TokenEffect effect = new TokenEffect();
this.actualToken = effect.loadTokenPrototype(mapParams);
this.actualToken = TokenInfo.getProtoType(mapParams.getParam("TokenScript"), mapParams);
if (actualToken == null) {
String[] keywords;
if (mapParams.hasParam("TokenKeywords")) {
// TODO: Change this Split to a semicolon or something else
keywords = mapParams.getParam("TokenKeywords").split("<>");
} else {
keywords = new String[0];
}
this.tokenPower = mapParams.getParam("TokenPower");
this.tokenToughness = mapParams.getParam("TokenToughness");
} else {
......@@ -340,6 +331,7 @@ public class TokenAi extends SpellAbilityAi {
@Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
// TODO: AILogic
readParameters(sa); // remember to call this somewhere!
Combat combat = ai.getGame().getCombat();
// TokenAttacking
if (combat != null && sa.hasParam("TokenAttacking")) {
......@@ -359,6 +351,7 @@ public class TokenAi extends SpellAbilityAi {
@Override
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) {
// TODO: AILogic
readParameters(sa); // remember to call this somewhere!
Combat combat = ai.getGame().getCombat();
// TokenAttacking
if (combat != null && sa.hasParam("TokenAttacking")) {
......@@ -389,6 +382,7 @@ public class TokenAi extends SpellAbilityAi {
* @param sa Token SpellAbility
* @return token creature created by ability
*/
@Deprecated
public static Card spawnToken(Player ai, SpellAbility sa) {
return spawnToken(ai, sa, false);
}
......@@ -401,9 +395,17 @@ public class TokenAi extends SpellAbilityAi {
* @return token creature created by ability
*/
// TODO Is this just completely copied from TokenEffect? Let's just call that thing
@Deprecated
public static Card spawnToken(Player ai, SpellAbility sa, boolean notNull) {
final Card host = sa.getHostCard();
Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa);
if (result != null) {
result.setController(ai, 0);
return result;
}
String[] tokenKeywords = sa.hasParam("TokenKeywords") ? sa.getParam("TokenKeywords").split("<>") : new String[0];
String tokenPower = sa.getParam("TokenPower");
String tokenToughness = sa.getParam("TokenToughness");
......
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>forge-core</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>
eclipse.preferences.version=1
encoding//src/main/java=ISO-8859-1
encoding//src/test/java=ISO-8859-1
encoding/<project>=UTF-8
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.7
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
......@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.20-SNAPSHOT</version>
<version>1.6.22-SNAPSHOT</version>
</parent>
<artifactId>forge-core</artifactId>
......
......@@ -103,19 +103,17 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
return StringUtils.join(build, "_").replace('*', 'x').toLowerCase();
}
public PaperToken(final CardRules c) { this(c, null, null); }
public PaperToken(final CardRules c, final String fileName) { this(c, null, fileName); }
public PaperToken(final CardRules c, CardEdition edition) { this(c, edition, null); }
public PaperToken(final CardRules c, CardEdition edition0, String imageFileName) {
this.card = c;
this.name = c.getName();
this.edition = edition0;
if (imageFileName == null) {
// This shouldn't really happen. We can just use the normalized name again for the base image name
this.imageFileName = makeTokenFileName(c, edition0);
} else {
String formatEdition = null == edition || CardEdition.UNKNOWN == edition ? "" : edition.getCode();
this.imageFileName = String.format("%s%s", formatEdition, imageFileName);
String formatEdition = null == edition || CardEdition.UNKNOWN == edition ? "" : "_" + edition.getCode().toLowerCase();
this.imageFileName = String.format("%s%s", imageFileName, formatEdition);
}
}
......
......@@ -2,7 +2,9 @@ package forge.token;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
import forge.card.*;
import forge.card.CardDb;
import forge.card.CardEdition;
import forge.card.CardRules;
import forge.item.PaperToken;
import java.util.*;
......@@ -43,7 +45,7 @@ public class TokenDb implements ITokenDatabase {
if (!tokensByName.containsKey(fullName)) {
try {
PaperToken pt = new PaperToken(rulesByName.get(tokenName), editions.get(edition));
PaperToken pt = new PaperToken(rulesByName.get(tokenName), editions.get(edition), tokenName);
tokensByName.put(fullName, pt);
return pt;
} catch(Exception e) {
......
......@@ -37,7 +37,8 @@ public class Localizer {
MessageFormat formatter = null;
try {
formatter = new MessageFormat(resourceBundle.getString(key.toLowerCase()), locale);
//formatter = new MessageFormat(resourceBundle.getString(key.toLowerCase()), locale);
formatter = new MessageFormat(resourceBundle.getString(key.toString()), locale);
} catch (final IllegalArgumentException | MissingResourceException e) {
e.printStackTrace();
}
......
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="src" path="/forge-core"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>forge-game</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>
eclipse.preferences.version=1
encoding//src/main/java=ISO-8859-1
encoding//src/test/java=ISO-8859-1
encoding/<project>=UTF-8
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.7
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
......@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.20-SNAPSHOT</version>
<version>1.6.22-SNAPSHOT</version>
</parent>
<artifactId>forge-game</artifactId>
......
......@@ -147,13 +147,6 @@ public class GameAction {
}
}
// Cards returned from exile face-down must be reset to their original state, otherwise
// all sort of funky shenanigans may happen later (e.g. their ETB replacement effects are set
// up on the wrong card state etc.).
if (wasFacedown && (fromBattlefield || (toHand && zoneFrom.is(ZoneType.Exile)))) {
c.setState(CardStateName.Original, true);
c.runFaceupCommands();
}
// Clean up the temporary Dash SVar when the Dashed card leaves the battlefield
// Clean up the temporary AtEOT SVar
......@@ -191,6 +184,14 @@ public class GameAction {
lastKnownInfo = CardUtil.getLKICopy(c);
}
// Cards returned from exile face-down must be reset to their original state, otherwise
// all sort of funky shenanigans may happen later (e.g. their ETB replacement effects are set
// up on the wrong card state etc.).
if (wasFacedown && (fromBattlefield || (toHand && zoneFrom.is(ZoneType.Exile)))) {
c.setState(CardStateName.Original, true);
c.runFaceupCommands();
}
if (!c.isToken()) {
if (c.isCloned()) {
c.switchStates(CardStateName.Original, CardStateName.Cloner, false);
......@@ -215,15 +216,15 @@ public class GameAction {
c.updateStateForView();
}
if (fromBattlefield && c.getCurrentStateName() != CardStateName.Original) {
copied = CardFactory.copyCard(c, false);
if (fromBattlefield && copied.getCurrentStateName() != CardStateName.Original) {
// when a card leaves the battlefield, ensure it's in its original state
// (we need to do this on the object before copying it, or it won't work correctly e.g.
// on Transformed objects)
c.setState(CardStateName.Original, false);
copied.setState(CardStateName.Original, false);
}
copied = CardFactory.copyCard(c, false);
copied.setUnearthed(c.isUnearthed());
copied.setTapped(false);
......@@ -250,26 +251,25 @@ public class GameAction {
// special rule for Worms of the Earth
if (toBattlefield && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLandBattlefield)) {
// something that is already a Land cant enter the battlefield
if (c.isLand()) {
return c;
}
// check if something would be a land
Card noLandLKI = CardUtil.getLKICopy(c);
// this check needs to check if this card would be on the battlefield
noLandLKI.setLastKnownZone(zoneTo);
CardCollection preList = new CardCollection(noLandLKI);
checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList);
// fake etb counters thing, then if something changed,
// need to apply checkStaticAbilities again
if(!noLandLKI.isLand()) {
if (noLandLKI.putEtbCounters()) {
// counters are added need to check again
checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList);
Card noLandLKI = c;
if (!c.isLand()) {
// check if something would be a land
noLandLKI = CardUtil.getLKICopy(c);
// this check needs to check if this card would be on the battlefield
noLandLKI.setLastKnownZone(zoneTo);
CardCollection preList = new CardCollection(noLandLKI);
checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList);
// fake etb counters thing, then if something changed,
// need to apply checkStaticAbilities again
if(!noLandLKI.isLand()) {
if (noLandLKI.putEtbCounters()) {
// counters are added need to check again
checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList);
}
}
}
if(noLandLKI.isLand()) {
// if it isn't on the Stack, it stays in that Zone
if (!c.getZone().is(ZoneType.Stack)) {
......@@ -278,6 +278,7 @@ public class GameAction {
// if something would only be a land when entering the battlefield and not before
// put it into the graveyard instead
zoneTo = c.getOwner().getZone(ZoneType.Graveyard);
// reset facedown
copied.setState(CardStateName.Original, false);
copied.setManifested(false);
......@@ -285,6 +286,26 @@ public class GameAction {
// not to battlefield anymore!
toBattlefield = false;
if (copied.isCloned()) {
copied.switchStates(CardStateName.Original, CardStateName.Cloner, false);
copied.setState(CardStateName.Original, false);
copied.clearStates(CardStateName.Cloner, false);
if (copied.isFlipCard()) {
copied.clearStates(CardStateName.Flipped, false);
}
if (copied.getStates().contains(CardStateName.OriginalText)) {
copied.clearStates(CardStateName.OriginalText, false);
copied.removeSVar("GainingTextFrom");
copied.removeSVar("GainingTextFromTimestamp");
}
}
if (copied.getCurrentStateName() != CardStateName.Original) {
copied.setState(CardStateName.Original, false);
}
copied.updateStateForView();
}
}
......@@ -1127,7 +1148,7 @@ public class GameAction {
if (c.isAttachedToEntity()) {
final GameEntity ge = c.getEntityAttachedTo();
if (!ge.canBeAttached(c)) {
if (!ge.canBeAttached(c, true)) {
c.unattachFromEntity(ge);
checkAgain = true;
}
......@@ -1853,7 +1874,7 @@ public class GameAction {
// Assumes that the list of players is in APNAP order, which should be the case
// Optional here as well to handle the way that mulligans do the choice
// 701.17. Scry
// 701.17a To “scry N” means to look at the top N cards of your library, then put any number of them
// 701.17a To "scry N" means to look at the top N cards of your library, then put any number of them
// on the bottom of your library in any order and the rest on top of your library in any order.
// 701.17b If a player is instructed to scry 0, no scry event occurs. Abilities that trigger whenever a
// player scries won’t trigger.
......
......@@ -10,9 +10,11 @@ import forge.game.trigger.TriggerType;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
public class BecomesBlockedEffect extends SpellAbilityEffect {
......@@ -38,10 +40,11 @@ public class BecomesBlockedEffect extends SpellAbilityEffect {
game.getCombat().setBlocked(c, true);
if (!c.getDamageHistory().getCreatureGotBlockedThisCombat()) {
isCombatChanged = true;
final HashMap<String, Object> runParams = new HashMap<String, Object>();
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Attacker", c);
runParams.put("Blockers", new ArrayList<Card>());
runParams.put("Blockers", Lists.<Card>newArrayList());
runParams.put("NumBlockers", 0);
runParams.put("Defender", game.getCombat().getDefenderByAttacker(c));
runParams.put("DefendingPlayer", game.getCombat().getDefenderPlayerByAttacker(c));
game.getTriggerHandler().runTrigger(TriggerType.AttackerBlocked, runParams, false);
}
......
......@@ -606,6 +606,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (remember != null) {
hostCard.addRemembered(movedCard);
// addRememberedFromCardState ?
if (tgtC.getMeldedWith() != null) {
Card meld = game.getCardState(tgtC.getMeldedWith(), null);
if (meld != null) {
hostCard.addRemembered(meld);
}
}
}
if (forget != null) {
hostCard.removeRemembered(movedCard);
......@@ -1121,6 +1128,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (remember) {
source.addRemembered(movedCard);
// addRememberedFromCardState ?
if (c.getMeldedWith() != null) {
Card meld = game.getCardState(c.getMeldedWith(), null);
if (meld != null) {
source.addRemembered(meld);
}
}
}
if (forget) {
source.removeRemembered(movedCard);
......
......@@ -150,14 +150,14 @@ public class CharmEffect extends SpellAbilityEffect {
return "";
}
public static void makeChoices(SpellAbility sa) {
public static boolean makeChoices(SpellAbility sa) {
//this resets all previous choices
sa.setSubAbility(null);
// Entwine does use all Choices
if (sa.isEntwine()) {
chainAbilities(sa, makePossibleOptions(sa));
return;
return true;
}
final int num = sa.hasParam("CharmNumOnResolve") ?
......@@ -181,6 +181,7 @@ public class CharmEffect extends SpellAbilityEffect {
List<AbilitySub> chosen = chooser.getController().chooseModeForAbility(sa, min, num, sa.hasParam("CanRepeatModes"));
chainAbilities(sa, chosen);
return chosen != null && !chosen.isEmpty();
}
private static void chainAbilities(SpellAbility sa, List<AbilitySub> chosen) {
......
......@@ -240,39 +240,41 @@ public class DigEffect extends SpellAbilityEffect {
}
} else {
String prompt;
if (sa.hasParam("PrimaryPrompt")) {
prompt = sa.getParam("PrimaryPrompt");
} else {
prompt = "Choose card(s) to put into " + destZone1.name();
if (destZone1.equals(ZoneType.Library)) {
if (libraryPosition == -1) {
prompt = "Choose card(s) to put on the bottom of {player's} library";
} else if (libraryPosition == 0) {
prompt = "Choose card(s) to put on top of {player's} library";
}
}
}
movedCards = new CardCollection();
if (valid.isEmpty()) {
chooser.getController().notifyOfValue(sa, null, "No valid cards");
} else {
if ( p == chooser ) { // the digger can still see all the dug cards when choosing
chooser.getController().tempShowCards(top);
}
List<Card> chosen;
if (!andOrValid.equals("")) {
valid.removeAll(andOrCards); //pfps remove andOr cards to get two two choices set up correctly
chosen = chooser.getController().chooseFromTwoListsForEffect(valid, andOrCards, optional, delayedReveal, sa, prompt, p);
} else {
int max = anyNumber ? valid.size() : Math.min(valid.size(),destZone1ChangeNum);
int min = (anyNumber || optional) ? 0 : max;
chosen = chooser.getController().chooseEntitiesForEffect(valid, min, max, delayedReveal, sa, prompt, p);
}
chooser.getController().endTempShowCards();
movedCards.addAll(chosen);
}
if (sa.hasParam("PrimaryPrompt")) {
prompt = sa.getParam("PrimaryPrompt");
} else {
prompt = "Choose card(s) to put into " + destZone1.name();
if (destZone1.equals(ZoneType.Library)) {
if (libraryPosition == -1) {
prompt = "Choose card(s) to put on the bottom of {player's} library";
} else if (libraryPosition == 0) {
prompt = "Choose card(s) to put on top of {player's} library";
}
}
}
movedCards = new CardCollection();
if (valid.isEmpty()) {
chooser.getController().notifyOfValue(sa, null, "No valid cards");
} else {
if ( p == chooser ) { // the digger can still see all the dug cards when choosing
chooser.getController().tempShowCards(top);
}
List<Card> chosen = new ArrayList<Card>();
if (!andOrValid.equals("")) {
valid.removeAll(andOrCards); //pfps remove andOr cards to get two two choices set up correctly
chosen = chooser.getController().chooseFromTwoListsForEffect(valid, andOrCards, optional, delayedReveal, sa, prompt, p);