Commits (1352)

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

# Forge
Gitlab repo is found [here](https://git.cardforge.org/core-developers/forge).
Dev instructions here: [Getting Started](https://www.slightlymagic.net/wiki/Forge:How_to_Get_Started_Developing_Forge) (Somewhat outdated)
Discord channel [here](https://discordapp.com/channels/267367946135928833/267742313390931968)
# Requirements / Tools
- Java IDE such as IntelliJ or Eclipse
- Git
- Maven
- Gitlab account
- Libgdx (optional: familiarity with this library is helpful for mobile platform development)
- Android SDK (optional: for Android releases)
- RoboVM (optional: for iOS releases) (TBD: Current status of support by libgdx)
# Project Quick Setup
- Log in to gitlab with your user account and fork the project.
- Clone your forked project to your local machine
- Go to the project location on your machine. Run Maven to download all dependencies and build a snapshot. Example for Windows & Linux: `mvn -U -B clean -P windows-linux install`
# Eclipse
## 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
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.
- 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:
`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 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.
- Let Eclipse run through building the project.
## Project Launch
### Desktop
- Right-click on forge-gui-desktop > Run As... > Java Application > "Main - forge.view" > Proceed
### Mobile (Desktop dev)
- Right-click on forge-gui-mobile-dev > Run As... > Java Application > "Main - forge.app" > Proceed
# IntelliJ
# General Notes
## Project Hierarchy
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:
- forge-ai
- forge-core
- forge-game
- forge-gui
The platform-specific projects are:
- forge-gui-android
- forge-gui-desktop
- forge-gui-ios
- forge-gui-mobile
- forge-gui-mobile-dev
### forge-ai
### forge-core
### forge-game
### forge-gui
### forge-gui-android
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.
### forge-gui-ios
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.
### forge-gui-mobile-dev
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
......@@ -6,7 +6,7 @@
......@@ -30,30 +30,4 @@
......@@ -22,6 +22,7 @@ import com.google.common.base.Predicates;
import forge.card.CardStateName;
import forge.game.CardTraitBase;
import forge.game.GameEntity;
import forge.game.GlobalRuleChange;
import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
......@@ -771,6 +772,16 @@ public class AiBlockController {
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
// Don't add any blockers that won't kill the attacker because the damage would be prevented by a static effect
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention)) {
blockers = CardLists.filter(blockers, new Predicate<Card>() {
public boolean apply(Card blocker) {
return !ComputerUtilCombat.isCombatDamagePrevented(blocker, attacker, blocker.getNetCombatDamage());
// Try to use safe blockers first
if (blockers.size() > 0) {
safeBlockers = getSafeBlockers(combat, attacker, blockers);
......@@ -40,16 +40,39 @@ import java.util.Set;
public class AiCardMemory {
* Defines the memory set in which the card is remembered
* (which, in its turn, defines how the AI utilizes the information
* about remembered cards).
public enum MemorySet {
MANDATORY_ATTACKERS, // These creatures must attack this turn
TRICK_ATTACKERS, // These creatures will attack to try to provoke the opponent to block them into a combat trick
HELD_MANA_SOURCES_FOR_MAIN2, // These mana sources will not be used before Main 2
HELD_MANA_SOURCES_FOR_DECLBLK, // These mana sources will not be used before Combat - Declare Blockers
HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK, // These mana sources will not be used before the opponent's Combat - Declare Blockers
HELD_MANA_SOURCES_FOR_NEXT_SPELL, // These mana sources will not be used until the next time the AI chooses a spell to cast
ATTACHED_THIS_TURN, // These equipments were attached to something already this turn
ANIMATED_THIS_TURN, // These cards had their AF Animate effect activated this turn
BOUNCED_THIS_TURN, // These cards were bounced this turn
ACTIVATED_THIS_TURN, // These cards had their ability activated this turn
CHOSEN_FOG_EFFECT, // These cards are marked as the Fog-like effect the AI is planning to cast this turn
MARKED_TO_AVOID_REENTRY // These cards may cause a stack smash when processed recursively, and are thus marked to avoid a crash
//REVEALED_CARDS // stub, not linked to AI code yet
private final Set<Card> memMandatoryAttackers;
private final Set<Card> memTrickAttackers;
private final Set<Card> memHeldManaSources;
private final Set<Card> memHeldManaSourcesForCombat;
private final Set<Card> memHeldManaSourcesForEnemyCombat;
private final Set<Card> memHeldManaSourcesForNextSpell;
private final Set<Card> memAttachedThisTurn;
private final Set<Card> memAnimatedThisTurn;
private final Set<Card> memBouncedThisTurn;
private final Set<Card> memActivatedThisTurn;
private final Set<Card> memChosenFogEffect;
private final Set<Card> memMarkedToAvoidReentry;
public AiCardMemory() {
this.memMandatoryAttackers = new HashSet<>();
......@@ -62,25 +85,8 @@ public class AiCardMemory {
this.memActivatedThisTurn = new HashSet<>();
this.memTrickAttackers = new HashSet<>();
this.memChosenFogEffect = new HashSet<>();
* Defines the memory set in which the card is remembered
* (which, in its turn, defines how the AI utilizes the information
* about remembered cards).
public enum MemorySet {
//REVEALED_CARDS // stub, not linked to AI code yet
this.memMarkedToAvoidReentry = new HashSet<>();
this.memHeldManaSourcesForNextSpell = new HashSet<>();
private Set<Card> getMemorySet(MemorySet set) {
......@@ -95,6 +101,8 @@ public class AiCardMemory {
return memHeldManaSourcesForCombat;
return memHeldManaSourcesForEnemyCombat;
return memHeldManaSourcesForNextSpell;
return memAttachedThisTurn;
......@@ -105,6 +113,8 @@ public class AiCardMemory {
return memActivatedThisTurn;
return memChosenFogEffect;
return memMarkedToAvoidReentry;
// return memRevealedCards;
......@@ -273,35 +283,66 @@ public class AiCardMemory {
* Clears all memory sets stored in this card memory for the given player.
public void clearAllRemembered() {
for (MemorySet memSet : MemorySet.values()) {
// Static functions to simplify access to AI card memory of a given AI player.
public static void rememberCard(Player ai, Card c, MemorySet set) {
if (!ai.getController().isAI()) {
((PlayerControllerAi)ai.getController()).getAi().getCardMemory().rememberCard(c, set);
public static void rememberCard(AiController aic, Card c, MemorySet set) {
aic.getCardMemory().rememberCard(c, set);
public static void forgetCard(Player ai, Card c, MemorySet set) {
if (!ai.getController().isAI()) {
((PlayerControllerAi)ai.getController()).getAi().getCardMemory().forgetCard(c, set);
public static void forgetCard(AiController aic, Card c, MemorySet set) {
aic.getCardMemory().forgetCard(c, set);
public static boolean isRememberedCard(Player ai, Card c, MemorySet set) {
if (!ai.getController().isAI()) {
return false;
return ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isRememberedCard(c, set);
public static boolean isRememberedCard(AiController aic, Card c, MemorySet set) {
return aic.getCardMemory().isRememberedCard(c, set);
public static boolean isRememberedCardByName(Player ai, String name, MemorySet set) {
if (!ai.getController().isAI()) {
return false;
return ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isRememberedCardByName(name, set);
public static boolean isRememberedCardByName(AiController aic, String name, MemorySet set) {
return aic.getCardMemory().isRememberedCardByName(name, set);
public static void clearMemorySet(Player ai, MemorySet set) {
if (!ai.getController().isAI()) {
public static void clearMemorySet(AiController aic, MemorySet set) {
if (!isMemorySetEmpty(aic, set)) {
public static boolean isMemorySetEmpty(Player ai, MemorySet set) {
if (!ai.getController().isAI()) {
return false;
return ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isMemorySetEmpty(set);
public static boolean isMemorySetEmpty(AiController aic, MemorySet set) {
return aic.getCardMemory().isMemorySetEmpty(set);
\ No newline at end of file
......@@ -459,6 +459,10 @@ public class AiCostDecision extends CostDecisionMakerBase {
Integer c = cost.convertAmount();
String type = cost.getType();
boolean isVehicle = type.contains("+withTotalPowerGE");
CardCollection exclude = new CardCollection();
if (c == null) {
final String sVar = ability.getSVar(amount);
if (sVar.equals("XChoice")) {
......@@ -482,18 +486,36 @@ public class AiCostDecision extends CostDecisionMakerBase {
return null;
if ("DontPayTapCostWithManaSources".equals(source.getSVar("AIPaymentPreference"))) {
CardCollectionView toExclude =
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"),
ability.getActivatingPlayer(), ability.getHostCard(), ability);
toExclude = CardLists.filter(toExclude, new Predicate<Card>() {
public boolean apply(Card card) {
for (final SpellAbility sa : card.getSpellAbilities()) {
if (sa.isManaAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
return true;
return false;
String totalP = "";
CardCollectionView totap;
if (isVehicle) {
totalP = type.split("withTotalPowerGE")[1];
type = TextUtil.fastReplace(type, "+withTotalPowerGE",