mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Merge branch 'master' into CrypticSpire
This commit is contained in:
42
.github/workflows/snapshots-android.yml
vendored
42
.github/workflows/snapshots-android.yml
vendored
@@ -8,11 +8,11 @@ on:
|
|||||||
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
||||||
required: false
|
required: false
|
||||||
default: false
|
default: false
|
||||||
upload_package:
|
#upload_package:
|
||||||
type: boolean
|
# type: boolean
|
||||||
description: 'Upload the completed Android package'
|
# description: 'Upload the completed Android package'
|
||||||
required: false
|
# required: false
|
||||||
default: true
|
# default: true
|
||||||
schedule:
|
schedule:
|
||||||
# * is a special character in YAML so you have to quote this string
|
# * is a special character in YAML so you have to quote this string
|
||||||
- cron: '00 19 * * *'
|
- cron: '00 19 * * *'
|
||||||
@@ -89,41 +89,23 @@ jobs:
|
|||||||
- name: Build/Install/Publish to GitHub Packages Apache Maven
|
- name: Build/Install/Publish to GitHub Packages Apache Maven
|
||||||
run: |
|
run: |
|
||||||
export _JAVA_OPTIONS="-Xmx2g"
|
export _JAVA_OPTIONS="-Xmx2g"
|
||||||
d=$(date +%m.%d)
|
|
||||||
# Replace date in forge-gui-mobile/src/forge/Forge.java
|
|
||||||
# sed -i -e "s/-SNAPSHOT/-SNAPSHOT-${d}/g" forge-gui-mobile/src/forge/Forge.java
|
|
||||||
mvn -U -B -P android-release-build install -e -Dcardforge-repo.username=${{ secrets.FTP_USERNAME }} -Dcardforge-repo.password=${{ secrets.FTP_PASSWORD }} -Dandroid.sdk.path=/usr/local/lib/android/sdk -Dandroid.buildToolsVersion=35.0.0 -Dmaven.test.skip=true
|
mvn -U -B -P android-release-build install -e -Dcardforge-repo.username=${{ secrets.FTP_USERNAME }} -Dcardforge-repo.password=${{ secrets.FTP_PASSWORD }} -Dandroid.sdk.path=/usr/local/lib/android/sdk -Dandroid.buildToolsVersion=35.0.0 -Dmaven.test.skip=true
|
||||||
mkdir -p forge-gui-android/target/upload
|
mkdir upload
|
||||||
mv forge-gui-android/target/*-signed-aligned.apk forge-gui-android/target/upload/
|
mv /home/runner/work/forge/forge/forge-gui-android/target/*-signed-aligned.apk upload/
|
||||||
mv forge-gui-android/target/assets.zip forge-gui-android/target/upload/
|
mv /home/runner/work/forge/forge/forge-gui-android/target/assets.zip upload/
|
||||||
cd forge-gui-android/target/upload/
|
mv /home/runner/work/forge/forge/forge-gui-android/target/classes/assets/version.txt upload/
|
||||||
# Get the first APK file in the folder
|
cd upload
|
||||||
ls
|
ls
|
||||||
apk_file=$(find . -maxdepth 1 -type f -name '*.apk' -print -quit)
|
|
||||||
|
|
||||||
if [ -n "$apk_file" ]; then
|
|
||||||
version=$(echo "$apk_file" | grep -oP 'forge-android-\K\d+\.\d+\.\d+-SNAPSHOT' | sed 's/-signed-aligned.apk//')
|
|
||||||
echo "APK File: $apk_file"
|
|
||||||
echo "Version: $version"
|
|
||||||
# mv *.apk "forge-android-$version-$d-signed-aligned.apk"
|
|
||||||
|
|
||||||
echo "$version-$d" > version.txt
|
|
||||||
else
|
|
||||||
echo "No .apk files found in the specified folder."
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd -
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
|
||||||
- name: 📂 Sync files
|
- name: 📂 Sync files
|
||||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.4
|
uses: SamKirkland/FTP-Deploy-Action@v4.3.4
|
||||||
if: ${{ inputs.upload_package }}
|
#if: ${{ inputs.upload_package }}
|
||||||
with:
|
with:
|
||||||
server: ftp.cardforge.org
|
server: ftp.cardforge.org
|
||||||
username: ${{ secrets.FTP_USERNAME }}
|
username: ${{ secrets.FTP_USERNAME }}
|
||||||
password: ${{ secrets.FTP_PASSWORD }}
|
password: ${{ secrets.FTP_PASSWORD }}
|
||||||
local-dir: forge-gui-android/target/upload/
|
local-dir: upload/
|
||||||
server-dir: downloads/dailysnapshots/
|
server-dir: downloads/dailysnapshots/
|
||||||
state-name: .ftp-deploy-android-sync-state.json
|
state-name: .ftp-deploy-android-sync-state.json
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiCardMemory.MemorySet;
|
||||||
import forge.ai.ability.ChangeZoneAi;
|
import forge.ai.ability.ChangeZoneAi;
|
||||||
import forge.ai.ability.LearnAi;
|
import forge.ai.ability.LearnAi;
|
||||||
import forge.ai.simulation.SpellAbilityPicker;
|
import forge.ai.simulation.SpellAbilityPicker;
|
||||||
@@ -845,6 +847,13 @@ public class AiController {
|
|||||||
return AiPlayDecision.CantAfford;
|
return AiPlayDecision.CantAfford;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if enough left (pass memory indirectly because we don't want to include those)
|
||||||
|
Set<Card> tappedForMana = AiCardMemory.getMemorySet(player, MemorySet.PAYS_TAP_COST);
|
||||||
|
if (tappedForMana != null && tappedForMana.isEmpty() &&
|
||||||
|
!ComputerUtilCost.checkTapTypeCost(player, sa.getPayCosts(), host, sa, new CardCollection(tappedForMana))) {
|
||||||
|
return AiPlayDecision.CantAfford;
|
||||||
|
}
|
||||||
|
|
||||||
// if we got here, looks like we can play the final cost and we could properly set up and target the API and
|
// if we got here, looks like we can play the final cost and we could properly set up and target the API and
|
||||||
// are willing to play the SA
|
// are willing to play the SA
|
||||||
return AiPlayDecision.WillPlay;
|
return AiPlayDecision.WillPlay;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package forge.ai;
|
|||||||
|
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiCardMemory.MemorySet;
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -32,6 +34,10 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
|
|
||||||
discarded = new CardCollection();
|
discarded = new CardCollection();
|
||||||
tapped = new CardCollection();
|
tapped = new CardCollection();
|
||||||
|
Set<Card> tappedForMana = AiCardMemory.getMemorySet(ai0, MemorySet.PAYS_TAP_COST);
|
||||||
|
if (tappedForMana != null) {
|
||||||
|
tapped.addAll(tappedForMana);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -440,21 +446,6 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
return null;
|
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, card -> {
|
|
||||||
for (final SpellAbility sa : card.getSpellAbilities()) {
|
|
||||||
if (sa.isManaAbility() && sa.getPayCosts().hasTapCost()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
exclude.addAll(toExclude);
|
|
||||||
}
|
|
||||||
|
|
||||||
String totalP = "";
|
String totalP = "";
|
||||||
CardCollectionView totap;
|
CardCollectionView totap;
|
||||||
if (isVehicle) {
|
if (isVehicle) {
|
||||||
|
|||||||
@@ -782,6 +782,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CardLists.sortByPowerAsc(typeList);
|
CardLists.sortByPowerAsc(typeList);
|
||||||
|
// TODO prefer noncreatures without tap abilities
|
||||||
|
|
||||||
final CardCollection tapList = new CardCollection();
|
final CardCollection tapList = new CardCollection();
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import forge.util.collect.FCollectionView;
|
|||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -450,7 +451,7 @@ public class ComputerUtilCost {
|
|||||||
* the source
|
* the source
|
||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
public static boolean checkTapTypeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sa, final CardCollection alreadyTapped) {
|
public static boolean checkTapTypeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sa, final Collection<Card> alreadyTapped) {
|
||||||
if (cost == null) {
|
if (cost == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -487,8 +488,8 @@ public class ComputerUtilCost {
|
|||||||
c = AbilityUtils.calculateAmount(source, part.getAmount(), sa);
|
c = AbilityUtils.calculateAmount(source, part.getAmount(), sa);
|
||||||
}
|
}
|
||||||
CardCollection exclude = new CardCollection();
|
CardCollection exclude = new CardCollection();
|
||||||
if (AiCardMemory.getMemorySet(ai, MemorySet.PAYS_TAP_COST) != null) {
|
if (alreadyTapped != null) {
|
||||||
exclude.addAll(AiCardMemory.getMemorySet(ai, MemorySet.PAYS_TAP_COST));
|
exclude.addAll(alreadyTapped);
|
||||||
}
|
}
|
||||||
// trying to produce mana that includes tapping source that will already be tapped
|
// trying to produce mana that includes tapping source that will already be tapped
|
||||||
if (exclude.contains(source) && cost.hasTapCost()) {
|
if (exclude.contains(source) && cost.hasTapCost()) {
|
||||||
@@ -500,12 +501,12 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
CardCollection tapChoices = ComputerUtil.chooseTapType(ai, type, source, cost.hasTapCost(), c, exclude, sa);
|
CardCollection tapChoices = ComputerUtil.chooseTapType(ai, type, source, cost.hasTapCost(), c, exclude, sa);
|
||||||
if (tapChoices != null) {
|
if (tapChoices != null) {
|
||||||
for (Card choice : tapChoices) {
|
if (alreadyTapped != null) {
|
||||||
AiCardMemory.rememberCard(ai, choice, MemorySet.PAYS_TAP_COST);
|
alreadyTapped.addAll(tapChoices);
|
||||||
}
|
// if manasource gets tapped to produce it also can't help paying another
|
||||||
// if manasource gets tapped to produce it also can't help paying another
|
if (cost.hasTapCost()) {
|
||||||
if (cost.hasTapCost()) {
|
alreadyTapped.add(source);
|
||||||
AiCardMemory.rememberCard(ai, source, MemorySet.PAYS_TAP_COST);
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -600,33 +601,6 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Alternate costs which involve both paying mana and tapping a card, e.g. Zahid, Djinn of the Lamp
|
|
||||||
// Current AI decides on each part separately, thus making it possible for the AI to cheat by
|
|
||||||
// tapping a mana source for mana and for the tap cost at the same time. Until this is improved, AI
|
|
||||||
// will not consider mana sources valid for paying the tap cost to avoid this exact situation.
|
|
||||||
if ("DontPayTapCostWithManaSources".equals(sa.getHostCard().getSVar("AIPaymentPreference"))) {
|
|
||||||
for (final CostPart part : sa.getPayCosts().getCostParts()) {
|
|
||||||
if (part instanceof CostTapType) {
|
|
||||||
CardCollectionView nonManaSources =
|
|
||||||
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), part.getType().split(";"),
|
|
||||||
sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
|
||||||
nonManaSources = CardLists.filter(nonManaSources, card -> {
|
|
||||||
boolean hasManaSa = false;
|
|
||||||
for (final SpellAbility sa1 : card.getSpellAbilities()) {
|
|
||||||
if (sa1.isManaAbility() && sa1.getPayCosts().hasTapCost()) {
|
|
||||||
hasManaSa = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return !hasManaSa;
|
|
||||||
});
|
|
||||||
if (nonManaSources.size() < part.convertAmount()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bail early on Casualty in case there are no cards that would make sense to pay with
|
// Bail early on Casualty in case there are no cards that would make sense to pay with
|
||||||
if (sa.getHostCard().hasKeyword(Keyword.CASUALTY)) {
|
if (sa.getHostCard().hasKeyword(Keyword.CASUALTY)) {
|
||||||
for (final CostPart part : sa.getPayCosts().getCostParts()) {
|
for (final CostPart part : sa.getPayCosts().getCostParts()) {
|
||||||
|
|||||||
@@ -268,6 +268,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final SpellAbility ma : saList) {
|
for (final SpellAbility ma : saList) {
|
||||||
|
// this rarely seems like a good idea
|
||||||
if (ma.getHostCard() == saHost) {
|
if (ma.getHostCard() == saHost) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -276,7 +277,7 @@ public class ComputerUtilMana {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa, new CardCollection())) {
|
if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa, AiCardMemory.getMemorySet(ai, MemorySet.PAYS_TAP_COST))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ public final class ImageKeys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<String, File> cachedCards = new HashMap<>(50000);
|
private static final Map<String, File> cachedCards = new HashMap<>(50000);
|
||||||
private static HashSet<String> missingCards = new HashSet<>();
|
public static HashSet<String> missingCards = new HashSet<>();
|
||||||
public static void clearMissingCards() {
|
public static void clearMissingCards() {
|
||||||
missingCards.clear();
|
missingCards.clear();
|
||||||
}
|
}
|
||||||
@@ -310,7 +310,7 @@ public final class ImageKeys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// System.out.println("File not found, no image created: " + key);
|
// System.out.println("File not found, no image created: " + key);
|
||||||
//add missing cards - disable for desktop version for compatibility reasons with autodownloader
|
// add missing cards - disable for desktop version for compatibility reasons with autodownloader
|
||||||
if (isLibGDXPort && !hasSetLookup(filename)) //missing cards with setlookup is handled differently
|
if (isLibGDXPort && !hasSetLookup(filename)) //missing cards with setlookup is handled differently
|
||||||
missingCards.add(filename);
|
missingCards.add(filename);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -2464,7 +2464,7 @@ public class GameAction {
|
|||||||
game.getAction().reveal(milledPlayer, destination, p, false, message, addSuffix);
|
game.getAction().reveal(milledPlayer, destination, p, false, message, addSuffix);
|
||||||
}
|
}
|
||||||
game.getGameLog().add(GameLogEntryType.ZONE_CHANGE, p + " milled " +
|
game.getGameLog().add(GameLogEntryType.ZONE_CHANGE, p + " milled " +
|
||||||
Lang.joinHomogenous(milled) + toZoneStr + ".");
|
Lang.joinHomogenous(milledPlayer) + toZoneStr + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -306,6 +306,17 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
|||||||
return new GameLogEntry(GameLogEntryType.MULLIGAN, message);
|
return new GameLogEntry(GameLogEntryType.MULLIGAN, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GameLogEntry visit(GameEventCardForetold ev) {
|
||||||
|
String sb = TextUtil.concatWithSpace(ev.activatingPlayer.toString(), "has foretold.");
|
||||||
|
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GameLogEntry visit(GameEventCardPlotted ev) {
|
||||||
|
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, ev.toString());
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void recieve(GameEvent ev) {
|
public void recieve(GameEvent ev) {
|
||||||
GameLogEntry le = ev.visit(this);
|
GameLogEntry le = ev.visit(this);
|
||||||
|
|||||||
@@ -1640,9 +1640,15 @@ public class AbilityUtils {
|
|||||||
return doXMath(calculateAmount(c, sq[v ? 1 : 2], ctb), expr, c, ctb);
|
return doXMath(calculateAmount(c, sq[v ? 1 : 2], ctb), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpellAbility sa = null;
|
||||||
if (ctb instanceof SpellAbility) {
|
if (ctb instanceof SpellAbility) {
|
||||||
final SpellAbility sa = (SpellAbility) ctb;
|
sa = (SpellAbility) ctb;
|
||||||
|
} else if (sq[0].contains("xPaid") && ctb instanceof TriggerReplacementBase) {
|
||||||
|
// try avoid fallback
|
||||||
|
sa = ((TriggerReplacementBase) ctb).getOverridingAbility();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa != null) {
|
||||||
// special logic for xPaid in SpellAbility
|
// special logic for xPaid in SpellAbility
|
||||||
if (sq[0].contains("xPaid")) {
|
if (sq[0].contains("xPaid")) {
|
||||||
SpellAbility root = sa.getRootAbility();
|
SpellAbility root = sa.getRootAbility();
|
||||||
@@ -1672,7 +1678,8 @@ public class AbilityUtils {
|
|||||||
// and the spell that became that object as it resolved had a value of X chosen for any of its costs,
|
// and the spell that became that object as it resolved had a value of X chosen for any of its costs,
|
||||||
// the value of X for that ability is the same as the value of X for that spell, although the value of X for that permanent is 0.
|
// the value of X for that ability is the same as the value of X for that spell, although the value of X for that permanent is 0.
|
||||||
if (TriggerType.ChangesZone.equals(t.getMode()) && ZoneType.Battlefield.name().equals(t.getParam("Destination"))) {
|
if (TriggerType.ChangesZone.equals(t.getMode()) && ZoneType.Battlefield.name().equals(t.getParam("Destination"))) {
|
||||||
return doXMath(c.getXManaCostPaid(), expr, c, ctb);
|
int x = isUnlinkedFromCastSA(ctb, c) ? 0 : c.getXManaCostPaid();
|
||||||
|
return doXMath(x, expr, c, ctb);
|
||||||
} else if (TriggerType.SpellCast.equals(t.getMode())) {
|
} else if (TriggerType.SpellCast.equals(t.getMode())) {
|
||||||
// Cast Trigger like Hydroid Krasis
|
// Cast Trigger like Hydroid Krasis
|
||||||
SpellAbilityStackInstance castSI = (SpellAbilityStackInstance) root.getTriggeringObject(AbilityKey.StackInstance);
|
SpellAbilityStackInstance castSI = (SpellAbilityStackInstance) root.getTriggeringObject(AbilityKey.StackInstance);
|
||||||
@@ -1696,7 +1703,8 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (root.isReplacementAbility() && sa.hasParam("ETB")) {
|
if (root.isReplacementAbility() && sa.hasParam("ETB")) {
|
||||||
return doXMath(c.getXManaCostPaid(), expr, c, ctb);
|
int x = isUnlinkedFromCastSA(ctb, c) ? 0 : c.getXManaCostPaid();
|
||||||
|
return doXMath(x, expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
return doXMath(0, expr, c, ctb);
|
return doXMath(0, expr, c, ctb);
|
||||||
|
|||||||
@@ -4111,9 +4111,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isModified() {
|
public final boolean isModified() {
|
||||||
if (!isCreature()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.isEquipped() || this.hasCounters()) {
|
if (this.isEquipped() || this.hasCounters()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3101,8 +3101,6 @@ public class CardFactoryUtil {
|
|||||||
// because it doesn't work other wise
|
// because it doesn't work other wise
|
||||||
c.setForetoldCostByEffect(true);
|
c.setForetoldCostByEffect(true);
|
||||||
}
|
}
|
||||||
String sb = TextUtil.concatWithSpace(getActivatingPlayer().toString(), "has foretold.");
|
|
||||||
game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
|
|
||||||
game.fireEvent(new GameEventCardForetold(getActivatingPlayer()));
|
game.fireEvent(new GameEventCardForetold(getActivatingPlayer()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -3425,8 +3423,6 @@ public class CardFactoryUtil {
|
|||||||
|
|
||||||
c.setPlotted(true);
|
c.setPlotted(true);
|
||||||
|
|
||||||
String sb = TextUtil.concatWithSpace(getActivatingPlayer().toString(), "has plotted", c.toString());
|
|
||||||
game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
|
|
||||||
game.fireEvent(new GameEventCardPlotted(c, getActivatingPlayer()));
|
game.fireEvent(new GameEventCardPlotted(c, getActivatingPlayer()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ public interface ICostVisitor<T> {
|
|||||||
public T visit(CostFlipCoin cost) {
|
public T visit(CostFlipCoin cost) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T visit(CostForage cost) {
|
public T visit(CostForage cost) {
|
||||||
return null;
|
return null;
|
||||||
@@ -146,7 +147,9 @@ public interface ICostVisitor<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T visit(CostPromiseGift cost) { return null; }
|
public T visit(CostPromiseGift cost) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T visit(CostPutCardToLib cost) {
|
public T visit(CostPutCardToLib cost) {
|
||||||
|
|||||||
@@ -92,9 +92,6 @@ public class ReplaceDamage extends ReplacementEffect {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasParam("IsEquipping") && !getHostCard().isEquipping()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasParam("DamageTarget")) {
|
if (hasParam("DamageTarget")) {
|
||||||
//Lava Burst and Whippoorwill check
|
//Lava Burst and Whippoorwill check
|
||||||
|
|||||||
@@ -665,6 +665,7 @@ public class ReplacementHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<ReplacementEffect> possibleReplacers = new ArrayList<>(replaceCandidateMap.keySet());
|
List<ReplacementEffect> possibleReplacers = new ArrayList<>(replaceCandidateMap.keySet());
|
||||||
|
// TODO should be able to choose different order for each entity
|
||||||
ReplacementEffect chosenRE = decider.getController().chooseSingleReplacementEffect(possibleReplacers);
|
ReplacementEffect chosenRE = decider.getController().chooseSingleReplacementEffect(possibleReplacers);
|
||||||
List<Map<AbilityKey, Object>> runParamList = replaceCandidateMap.get(chosenRE);
|
List<Map<AbilityKey, Object>> runParamList = replaceCandidateMap.get(chosenRE);
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,19 @@
|
|||||||
<failIfNoMatch>false</failIfNoMatch>
|
<failIfNoMatch>false</failIfNoMatch>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>released-version</id>
|
||||||
|
<phase>validate</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>released-version</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>parse-version</id>
|
||||||
|
<goals>
|
||||||
|
<goal>parse-version</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
@@ -170,26 +183,6 @@
|
|||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
|
||||||
<artifactId>build-helper-maven-plugin</artifactId>
|
|
||||||
<version>3.6.0</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>released-version</id>
|
|
||||||
<phase>validate</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>released-version</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>parse-version</id>
|
|
||||||
<goals>
|
|
||||||
<goal>parse-version</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>se.bjurr.gitchangelog</groupId>
|
<groupId>se.bjurr.gitchangelog</groupId>
|
||||||
<artifactId>git-changelog-maven-plugin</artifactId>
|
<artifactId>git-changelog-maven-plugin</artifactId>
|
||||||
|
|||||||
@@ -78,9 +78,8 @@ public class CCombat implements ICDoc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
display.append("\n");
|
display.append("\n");
|
||||||
PlayerView controller = null;
|
|
||||||
if (defender instanceof CardView) {
|
if (defender instanceof CardView) {
|
||||||
controller = ((CardView) defender).getController();
|
PlayerView controller = ((CardView) defender).getController();
|
||||||
if (controller == null)
|
if (controller == null)
|
||||||
//shouldn't be null but display card's + controller ie Black Knight's controller
|
//shouldn't be null but display card's + controller ie Black Knight's controller
|
||||||
display.append(Lang.getInstance().getPossesive(defender.getName())).append(" controller");
|
display.append(Lang.getInstance().getPossesive(defender.getName())).append(" controller");
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ public class VAssignGenericAmount extends FDialog {
|
|||||||
|
|
||||||
/** Constructor.
|
/** Constructor.
|
||||||
*
|
*
|
||||||
* @param attacker0 {@link forge.game.card.Card}
|
|
||||||
* @param targets Map<GameEntity, Integer>, map of GameEntity and its maximum assignable amount
|
* @param targets Map<GameEntity, Integer>, map of GameEntity and its maximum assignable amount
|
||||||
* @param amount Total amount to be assigned
|
* @param amount Total amount to be assigned
|
||||||
* @param atLeastOne Must assign at least one amount to each target
|
* @param atLeastOne Must assign at least one amount to each target
|
||||||
@@ -168,7 +167,7 @@ public class VAssignGenericAmount extends FDialog {
|
|||||||
obj = add(new EffectSourcePanel((CardView)entity));
|
obj = add(new EffectSourcePanel((CardView)entity));
|
||||||
} else if (entity instanceof PlayerView) {
|
} else if (entity instanceof PlayerView) {
|
||||||
PlayerView player = (PlayerView)entity;
|
PlayerView player = (PlayerView)entity;
|
||||||
obj = add(new MiscTargetPanel(player.getName(), MatchController.getPlayerAvatar(player)));
|
obj = add(new MiscTargetPanel(player.getName(), MatchController.getPlayerAvatar(player), null));
|
||||||
} else if (entity instanceof Byte) {
|
} else if (entity instanceof Byte) {
|
||||||
FSkinImageInterface manaSymbol;
|
FSkinImageInterface manaSymbol;
|
||||||
byte color = (Byte) entity;
|
byte color = (Byte) entity;
|
||||||
@@ -185,9 +184,9 @@ public class VAssignGenericAmount extends FDialog {
|
|||||||
} else { // Should never come here, but add this to avoid compile error
|
} else { // Should never come here, but add this to avoid compile error
|
||||||
manaSymbol = Forge.getAssets().images().get(FSkinProp.IMG_MANA_COLORLESS);
|
manaSymbol = Forge.getAssets().images().get(FSkinProp.IMG_MANA_COLORLESS);
|
||||||
}
|
}
|
||||||
obj = add(new MiscTargetPanel("", manaSymbol));
|
obj = add(new MiscTargetPanel("", manaSymbol, entity));
|
||||||
} else {
|
} else {
|
||||||
obj = add(new MiscTargetPanel(entity.toString(), FSkinImage.UNKNOWN));
|
obj = add(new MiscTargetPanel(entity.toString(), FSkinImage.UNKNOWN, null));
|
||||||
}
|
}
|
||||||
label = add(new FLabel.Builder().text("0").font(FSkinFont.get(18)).align(Align.center).build());
|
label = add(new FLabel.Builder().text("0").font(FSkinFont.get(18)).align(Align.center).build());
|
||||||
btnSubtract = add(new FLabel.ButtonBuilder().icon(FSkinImage.MINUS).command(e -> assignAmountTo(entity, false)).build());
|
btnSubtract = add(new FLabel.ButtonBuilder().icon(FSkinImage.MINUS).command(e -> assignAmountTo(entity, false)).build());
|
||||||
@@ -232,19 +231,21 @@ public class VAssignGenericAmount extends FDialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MiscTargetPanel extends FDisplayObject {
|
private class MiscTargetPanel extends FDisplayObject {
|
||||||
private static final FSkinFont FONT = FSkinFont.get(18);
|
private final FSkinFont FONT = FSkinFont.get(18);
|
||||||
private static FSkinColor getForeColor() {
|
private FSkinColor getForeColor() {
|
||||||
if (Forge.isMobileAdventureMode)
|
if (Forge.isMobileAdventureMode)
|
||||||
return FSkinColor.get(Colors.ADV_CLR_TEXT);
|
return FSkinColor.get(Colors.ADV_CLR_TEXT);
|
||||||
return FSkinColor.get(Colors.CLR_TEXT);
|
return FSkinColor.get(Colors.CLR_TEXT);
|
||||||
}
|
}
|
||||||
private final String name;
|
private final String name;
|
||||||
private final FImage image;
|
private final FImage image;
|
||||||
|
private final Object entity;
|
||||||
|
|
||||||
private MiscTargetPanel(String name0, FImage image0) {
|
private MiscTargetPanel(String name0, FImage image0, Object entity0) {
|
||||||
name = name0;
|
name = name0;
|
||||||
image = image0;
|
image = image0;
|
||||||
|
entity = entity0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -254,6 +255,24 @@ public class VAssignGenericAmount extends FDialog {
|
|||||||
g.drawImage(image, 0, 0, w, w);
|
g.drawImage(image, 0, 0, w, w);
|
||||||
g.drawText(name, FONT, getForeColor(), 0, w, w, h - w, false, Align.center, true);
|
g.drawText(name, FONT, getForeColor(), 0, w, w, h - w, false, Align.center, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean tap(float x, float y, int count) {
|
||||||
|
if (count > 1 && entity != null) {
|
||||||
|
AssignTarget at = targetsMap.get(entity);
|
||||||
|
int assigned = at.amount;
|
||||||
|
int leftToAssign = Math.max(0, at.max - assigned);
|
||||||
|
int amountToAdd = Math.min(getRemainingAmount(), leftToAssign);
|
||||||
|
|
||||||
|
if (0 == amountToAdd || amountToAdd + assigned < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
addAssignedAmount(at, amountToAdd);
|
||||||
|
updateLabels();
|
||||||
|
}
|
||||||
|
return super.tap(x, y, count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assignAmountTo(Object source, boolean isAdding) {
|
private void assignAmountTo(Object source, boolean isAdding) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Name:Infernal Vessel
|
|||||||
ManaCost:2 B
|
ManaCost:2 B
|
||||||
Types:Creature Human Cleric
|
Types:Creature Human Cleric
|
||||||
PT:2/1
|
PT:2/1
|
||||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+notDemon | Execute$ TrigReturn | TriggerDescription$ When this creature dies, if it wasn't a Demon, return it to the battlefield under its owner's control with two +1/+1 counters on it. It's a Demon in addition to its other types.
|
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+nonDemon | Execute$ TrigReturn | TriggerDescription$ When this creature dies, if it wasn't a Demon, return it to the battlefield under its owner's control with two +1/+1 counters on it. It's a Demon in addition to its other types.
|
||||||
SVar:TrigReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ TriggeredNewCardLKICopy | WithCountersType$ P1P1 | WithCountersAmount$ 2 | AnimateSubAbility$ DBAnimate
|
SVar:TrigReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ TriggeredNewCardLKICopy | WithCountersType$ P1P1 | WithCountersAmount$ 2 | AnimateSubAbility$ DBAnimate
|
||||||
SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Types$ Demon | Duration$ Permanent
|
SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Types$ Demon | Duration$ Permanent
|
||||||
Oracle:When this creature dies, if it wasn't a Demon, return it to the battlefield under its owner's control with two +1/+1 counters on it. It's a Demon in addition to its other types.
|
Oracle:When this creature dies, if it wasn't a Demon, return it to the battlefield under its owner's control with two +1/+1 counters on it. It's a Demon in addition to its other types.
|
||||||
@@ -3,7 +3,7 @@ ManaCost:2 W B
|
|||||||
Types:Legendary Creature Spirit Noble
|
Types:Legendary Creature Spirit Noble
|
||||||
PT:3/3
|
PT:3/3
|
||||||
K:Flying
|
K:Flying
|
||||||
T:Mode$ BecomesTarget | ValidTarget$ Card.Self,Spirit.YouCtrl+Other | ValidSource$ Spell | TriggerZones$ Battlefield | Execute$ TrigPhaseOut | TriggerDescription$ Whenever CARDNAME or another Spirit you control becomes the target of a spell, it phases out. (Treat it and anything attached to it as though they don't exist until your next turn.)
|
T:Mode$ BecomesTarget | ValidTarget$ Card.Self,Spirit.YouCtrl+Other+inZoneBattlefield | ValidSource$ Spell | TriggerZones$ Battlefield | Execute$ TrigPhaseOut | TriggerDescription$ Whenever CARDNAME or another Spirit you control becomes the target of a spell, it phases out. (Treat it and anything attached to it as though they don't exist until your next turn.)
|
||||||
SVar:TrigPhaseOut:DB$ Phases | Defined$ TriggeredTargetLKICopy
|
SVar:TrigPhaseOut:DB$ Phases | Defined$ TriggeredTargetLKICopy
|
||||||
T:Mode$ PhaseIn | ValidCard$ Card.Self,Spirit.YouCtrl+Other | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever CARDNAME or another Spirit you control phases in, create a tapped 1/1 white Spirit creature token with flying.
|
T:Mode$ PhaseIn | ValidCard$ Card.Self,Spirit.YouCtrl+Other | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever CARDNAME or another Spirit you control phases in, create a tapped 1/1 white Spirit creature token with flying.
|
||||||
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_spirit_flying | TokenOwner$ You | TokenTapped$ True
|
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_spirit_flying | TokenOwner$ You | TokenTapped$ True
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Name:Needletooth Pack
|
|||||||
ManaCost:3 G G
|
ManaCost:3 G G
|
||||||
Types:Creature Dinosaur
|
Types:Creature Dinosaur
|
||||||
PT:4/5
|
PT:4/5
|
||||||
T:Mode$ Phase | Phase$ End of Turn | CheckSVar$ Morbid | SVarCompare$ GE1 | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Morbid — At the beginning of your end step, if a creature died this turn, put two +1/+1 counters on target creature you control.
|
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | CheckSVar$ Morbid | SVarCompare$ GE1 | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Morbid — At the beginning of your end step, if a creature died this turn, put two +1/+1 counters on target creature you control.
|
||||||
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ P1P1 | CounterNum$ 2
|
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ P1P1 | CounterNum$ 2
|
||||||
SVar:Morbid:Count$Morbid.1.0
|
SVar:Morbid:Count$Morbid.1.0
|
||||||
Oracle:Morbid — At the beginning of your end step, if a creature died this turn, put two +1/+1 counters on target creature you control.
|
Oracle:Morbid — At the beginning of your end step, if a creature died this turn, put two +1/+1 counters on target creature you control.
|
||||||
@@ -2,6 +2,6 @@ Name:Pariah's Shield
|
|||||||
ManaCost:5
|
ManaCost:5
|
||||||
Types:Artifact Equipment
|
Types:Artifact Equipment
|
||||||
K:Equip:3
|
K:Equip:3
|
||||||
R:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ You | ReplaceWith$ DmgEquipped | IsEquipping$ True | DamageTarget$ Equipped | Description$ All damage that would be dealt to you is dealt to equipped creature instead.
|
R:Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ You | ReplaceWith$ DmgEquipped | IsPresent$ Card.equipping | PresentDefined$ Self | DamageTarget$ Equipped | Description$ All damage that would be dealt to you is dealt to equipped creature instead.
|
||||||
SVar:DmgEquipped:DB$ ReplaceEffect | VarName$ Affected | VarValue$ Equipped | VarType$ Card
|
SVar:DmgEquipped:DB$ ReplaceEffect | VarName$ Affected | VarValue$ Equipped | VarType$ Card
|
||||||
Oracle:All damage that would be dealt to you is dealt to equipped creature instead.\nEquip {3}
|
Oracle:All damage that would be dealt to you is dealt to equipped creature instead.\nEquip {3}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ ManaCost:4 W W W
|
|||||||
Types:Legendary Creature Angel
|
Types:Legendary Creature Angel
|
||||||
PT:7/7
|
PT:7/7
|
||||||
S:Mode$ AlternativeCost | ValidSA$ Spell.Self | EffectZone$ All | Cost$ W tapXType<4/Creature.withFlying> | Description$ You may pay {W} and tap four untapped creatures you control with flying rather than pay this spell's mana cost.
|
S:Mode$ AlternativeCost | ValidSA$ Spell.Self | EffectZone$ All | Cost$ W tapXType<4/Creature.withFlying> | Description$ You may pay {W} and tap four untapped creatures you control with flying rather than pay this spell's mana cost.
|
||||||
SVar:AIPaymentPreference:DontPayTapCostWithManaSources
|
|
||||||
K:Flying
|
K:Flying
|
||||||
K:Lifelink
|
K:Lifelink
|
||||||
S:Mode$ Continuous | Affected$ Creature.withFlying+Other+YouCtrl | AddKeyword$ Indestructible | Description$ Other creatures you control with flying have indestructible.
|
S:Mode$ Continuous | Affected$ Creature.withFlying+Other+YouCtrl | AddKeyword$ Indestructible | Description$ Other creatures you control with flying have indestructible.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ ManaCost:U U U
|
|||||||
Types:Legendary Enchantment Creature Nightmare
|
Types:Legendary Enchantment Creature Nightmare
|
||||||
PT:10/1
|
PT:10/1
|
||||||
S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | Description$ CARDNAME can't be blocked.
|
S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | Description$ CARDNAME can't be blocked.
|
||||||
R:Event$ DamageDone | ActiveZones$ Battlefield | ValidSource$ Card.YouCtrl,Emblem.YouCtrl | ValidTarget$ Opponent | ReplaceWith$ Mill | PreventionEffect$ True | ExecuteMode$ PerTarget | Description$ If a source you control would deal damage to an opponent, prevent that damage and each opponent mills that many cards.
|
R:Event$ DamageDone | ActiveZones$ Battlefield | ValidSource$ Card.YouCtrl,Emblem.YouCtrl | ValidTarget$ Opponent | ReplaceWith$ Mill | PreventionEffect$ True | ExecuteMode$ PerSource | Description$ If a source you control would deal damage to an opponent, prevent that damage and each opponent mills that many cards.
|
||||||
SVar:Mill:DB$ Mill | Defined$ Opponent | NumCards$ X
|
SVar:Mill:DB$ Mill | Defined$ Opponent | NumCards$ X
|
||||||
SVar:X:ReplaceCount$DamageAmount
|
SVar:X:ReplaceCount$DamageAmount
|
||||||
Oracle:The Mindskinner can't be blocked.\nIf a source you control would deal damage to an opponent, prevent that damage and each opponent mills that many cards.
|
Oracle:The Mindskinner can't be blocked.\nIf a source you control would deal damage to an opponent, prevent that damage and each opponent mills that many cards.
|
||||||
|
|||||||
@@ -3,6 +3,5 @@ ManaCost:2 W
|
|||||||
Types:Creature Human Soldier
|
Types:Creature Human Soldier
|
||||||
PT:4/4
|
PT:4/4
|
||||||
A:SP$ PermanentCreature | Cost$ 2 W tapXType<2/Artifact;Creature;Land/artifacts, creatures, and/or lands> | SpellDescription$ As an additional cost to cast this spell, tap two untapped artifacts, creatures, and/or lands you control.
|
A:SP$ PermanentCreature | Cost$ 2 W tapXType<2/Artifact;Creature;Land/artifacts, creatures, and/or lands> | SpellDescription$ As an additional cost to cast this spell, tap two untapped artifacts, creatures, and/or lands you control.
|
||||||
SVar:AIPaymentPreference:DontPayTapCostWithManaSources
|
|
||||||
DeckHints:Type$Artifact
|
DeckHints:Type$Artifact
|
||||||
Oracle:As an additional cost to cast this spell, tap two untapped artifacts, creatures, and/or lands you control.
|
Oracle:As an additional cost to cast this spell, tap two untapped artifacts, creatures, and/or lands you control.
|
||||||
|
|||||||
@@ -4,6 +4,4 @@ Types:Legendary Creature Djinn
|
|||||||
PT:5/6
|
PT:5/6
|
||||||
K:Flying
|
K:Flying
|
||||||
S:Mode$ AlternativeCost | ValidSA$ Spell.Self | EffectZone$ All | Cost$ 3 U tapXType<1/Artifact> | Description$ You may pay {3}{U} and tap an untapped artifact you control rather than pay this spell's mana cost.
|
S:Mode$ AlternativeCost | ValidSA$ Spell.Self | EffectZone$ All | Cost$ 3 U tapXType<1/Artifact> | Description$ You may pay {3}{U} and tap an untapped artifact you control rather than pay this spell's mana cost.
|
||||||
# TODO: Currently the AI may cheat without the following flag by tapping the same artifact for mana and for the tap cost, e.g. 2 Islands + Sol Ring. Remove this flag once the AI is smart enough not to do that.
|
|
||||||
SVar:AIPaymentPreference:DontPayTapCostWithManaSources
|
|
||||||
Oracle:You may pay {3}{U} and tap an untapped artifact you control rather than pay this spell's mana cost.\nFlying
|
Oracle:You may pay {3}{U} and tap an untapped artifact you control rather than pay this spell's mana cost.\nFlying
|
||||||
|
|||||||
@@ -443,17 +443,13 @@ public abstract class GameLobby implements IHasGameType {
|
|||||||
Deck deck = slot.getDeck();
|
Deck deck = slot.getDeck();
|
||||||
RegisteredPlayer rp = new RegisteredPlayer(deck);
|
RegisteredPlayer rp = new RegisteredPlayer(deck);
|
||||||
|
|
||||||
if (variantTypes.isEmpty()) {
|
if (!variantTypes.isEmpty()) {
|
||||||
rp.setTeamNumber(team);
|
|
||||||
players.add(rp.setPlayer(lobbyPlayer));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (isCommanderMatch) {
|
if (isCommanderMatch) {
|
||||||
final GameType commanderGameType =
|
final GameType commanderGameType =
|
||||||
isOathbreakerMatch ? GameType.Oathbreaker :
|
isOathbreakerMatch ? GameType.Oathbreaker :
|
||||||
isTinyLeadersMatch ? GameType.TinyLeaders :
|
isTinyLeadersMatch ? GameType.TinyLeaders :
|
||||||
isBrawlMatch ? GameType.Brawl :
|
isBrawlMatch ? GameType.Brawl :
|
||||||
GameType.Commander;
|
GameType.Commander;
|
||||||
if (checkLegality) {
|
if (checkLegality) {
|
||||||
final String errMsg = commanderGameType.getDeckFormat().getDeckConformanceProblem(deck);
|
final String errMsg = commanderGameType.getDeckFormat().getDeckConformanceProblem(deck);
|
||||||
if (errMsg != null) {
|
if (errMsg != null) {
|
||||||
@@ -481,7 +477,6 @@ public abstract class GameLobby implements IHasGameType {
|
|||||||
Iterable<PaperCard> schemes = null;
|
Iterable<PaperCard> schemes = null;
|
||||||
Iterable<PaperCard> planes = null;
|
Iterable<PaperCard> planes = null;
|
||||||
|
|
||||||
//Archenemy
|
|
||||||
if (variantTypes.contains(GameType.ArchenemyRumble)
|
if (variantTypes.contains(GameType.ArchenemyRumble)
|
||||||
|| (variantTypes.contains(GameType.Archenemy) && isArchenemy)) {
|
|| (variantTypes.contains(GameType.Archenemy) && isArchenemy)) {
|
||||||
final CardPool schemePool = deck.get(DeckSection.Schemes);
|
final CardPool schemePool = deck.get(DeckSection.Schemes);
|
||||||
@@ -495,7 +490,6 @@ public abstract class GameLobby implements IHasGameType {
|
|||||||
schemes = schemePool == null ? Collections.emptyList() : schemePool.toFlatList();
|
schemes = schemePool == null ? Collections.emptyList() : schemePool.toFlatList();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Planechase
|
|
||||||
if (variantTypes.contains(GameType.Planechase)) {
|
if (variantTypes.contains(GameType.Planechase)) {
|
||||||
final CardPool planePool = deck.get(DeckSection.Planes);
|
final CardPool planePool = deck.get(DeckSection.Planes);
|
||||||
if (checkLegality) {
|
if (checkLegality) {
|
||||||
@@ -508,7 +502,6 @@ public abstract class GameLobby implements IHasGameType {
|
|||||||
planes = planePool == null ? Collections.emptyList() : planePool.toFlatList();
|
planes = planePool == null ? Collections.emptyList() : planePool.toFlatList();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Vanguard
|
|
||||||
if (variantTypes.contains(GameType.Vanguard)) {
|
if (variantTypes.contains(GameType.Vanguard)) {
|
||||||
if (avatarPool == null || avatarPool.countAll() == 0) { //ERROR! null if avatar deselected on list
|
if (avatarPool == null || avatarPool.countAll() == 0) { //ERROR! null if avatar deselected on list
|
||||||
SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblNoSelectedVanguardAvatarForPlayer", name));
|
SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblNoSelectedVanguardAvatarForPlayer", name));
|
||||||
@@ -517,10 +510,11 @@ public abstract class GameLobby implements IHasGameType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rp = RegisteredPlayer.forVariants(activeSlots.size(), variantTypes, deck, schemes, isArchenemy, planes, avatarPool);
|
rp = RegisteredPlayer.forVariants(activeSlots.size(), variantTypes, deck, schemes, isArchenemy, planes, avatarPool);
|
||||||
rp.setTeamNumber(team);
|
|
||||||
players.add(rp.setPlayer(lobbyPlayer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rp.setTeamNumber(team);
|
||||||
|
players.add(rp.setPlayer(lobbyPlayer));
|
||||||
|
|
||||||
if (!isAI) {
|
if (!isAI) {
|
||||||
guis.put(rp, gui);
|
guis.put(rp, gui);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,8 +56,9 @@ public class InputQueue extends Observable {
|
|||||||
|
|
||||||
if (topMostInput != inp) {
|
if (topMostInput != inp) {
|
||||||
System.out.println("Cannot remove input " + inp.getClass().getSimpleName() + " because it's not on top of stack. Stack = " + inputStack );
|
System.out.println("Cannot remove input " + inp.getClass().getSimpleName() + " because it's not on top of stack. Stack = " + inputStack );
|
||||||
} else {
|
} else if (topMostInput != null) {
|
||||||
inputStack.pop();
|
// if topMostInput is null then it means the inputstack is already empty, why this is called twice?
|
||||||
|
inputStack.pop();
|
||||||
}
|
}
|
||||||
updateObservers();
|
updateObservers();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,9 +119,9 @@ public enum ItemManagerConfig {
|
|||||||
null, null, 3, 0),
|
null, null, 3, 0),
|
||||||
NET_ARCHIVE_BLOCK_DECKS(SColumnUtil.getDecksDefaultColumns(false, false), false, false, false,
|
NET_ARCHIVE_BLOCK_DECKS(SColumnUtil.getDecksDefaultColumns(false, false), false, false, false,
|
||||||
null, null, 3, 0),
|
null, null, 3, 0),
|
||||||
ADVENTURE_EDITOR_POOL(SColumnUtil.getConquestCollectionDefaultColumns(), false, false, false,
|
ADVENTURE_EDITOR_POOL(SColumnUtil.getAdventureCollectionDefaultColumns(), false, false, false,
|
||||||
null, null, 6, 0),
|
null, null, 6, 0),
|
||||||
ADVENTURE_STORE_POOL(SColumnUtil.getConquestCollectionDefaultColumns(), false, false, true,
|
ADVENTURE_STORE_POOL(SColumnUtil.getAdventureCollectionDefaultColumns(), false, false, true,
|
||||||
null, null, 6, 0),
|
null, null, 6, 0),
|
||||||
ADVENTURE_SIDEBOARD(SColumnUtil.getDeckEditorDefaultColumns(), false, false, true,
|
ADVENTURE_SIDEBOARD(SColumnUtil.getDeckEditorDefaultColumns(), false, false, true,
|
||||||
null, null, 6, 0),
|
null, null, 6, 0),
|
||||||
@@ -376,4 +376,4 @@ public enum ItemManagerConfig {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,13 @@ public final class SColumnUtil {
|
|||||||
return columns;
|
return columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Map<ColumnDef, ItemColumnConfig> getAdventureCollectionDefaultColumns() {
|
||||||
|
Map<ColumnDef, ItemColumnConfig> columns = getCardColumns(ColumnDef.QUANTITY, false, false, false, true, false);
|
||||||
|
columns.get(ColumnDef.NEW).setSortPriority(1);
|
||||||
|
columns.get(ColumnDef.NAME).setSortPriority(2);
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
public static Map<ColumnDef, ItemColumnConfig> getAttractionPoolDefaultColumns() {
|
public static Map<ColumnDef, ItemColumnConfig> getAttractionPoolDefaultColumns() {
|
||||||
//Similar to special card pool, but show the collector number and hide the type.
|
//Similar to special card pool, but show the collector number and hide the type.
|
||||||
List<ColumnDef> colDefs = new ArrayList<>();
|
List<ColumnDef> colDefs = new ArrayList<>();
|
||||||
|
|||||||
@@ -228,6 +228,11 @@ public abstract class ImageFetcher {
|
|||||||
}
|
}
|
||||||
if (filename.equalsIgnoreCase("null.jpg"))
|
if (filename.equalsIgnoreCase("null.jpg"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (ImageKeys.missingCards.contains(filename))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImageKeys.missingCards.add(filename);
|
||||||
System.err.println("No specified file for '" + filename + "'.. Attempting to download from default Url");
|
System.err.println("No specified file for '" + filename + "'.. Attempting to download from default Url");
|
||||||
tokenUrl = String.format("%s%s", ForgeConstants.URL_TOKEN_DOWNLOAD, filename);
|
tokenUrl = String.format("%s%s", ForgeConstants.URL_TOKEN_DOWNLOAD, filename);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user