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)'
|
||||
required: false
|
||||
default: false
|
||||
upload_package:
|
||||
type: boolean
|
||||
description: 'Upload the completed Android package'
|
||||
required: false
|
||||
default: true
|
||||
#upload_package:
|
||||
# type: boolean
|
||||
# description: 'Upload the completed Android package'
|
||||
# required: false
|
||||
# default: true
|
||||
schedule:
|
||||
# * is a special character in YAML so you have to quote this string
|
||||
- cron: '00 19 * * *'
|
||||
@@ -89,41 +89,23 @@ jobs:
|
||||
- name: Build/Install/Publish to GitHub Packages Apache Maven
|
||||
run: |
|
||||
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
|
||||
mkdir -p forge-gui-android/target/upload
|
||||
mv forge-gui-android/target/*-signed-aligned.apk forge-gui-android/target/upload/
|
||||
mv forge-gui-android/target/assets.zip forge-gui-android/target/upload/
|
||||
cd forge-gui-android/target/upload/
|
||||
# Get the first APK file in the folder
|
||||
mkdir upload
|
||||
mv /home/runner/work/forge/forge/forge-gui-android/target/*-signed-aligned.apk upload/
|
||||
mv /home/runner/work/forge/forge/forge-gui-android/target/assets.zip upload/
|
||||
mv /home/runner/work/forge/forge/forge-gui-android/target/classes/assets/version.txt upload/
|
||||
cd upload
|
||||
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:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: 📂 Sync files
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.4
|
||||
if: ${{ inputs.upload_package }}
|
||||
#if: ${{ inputs.upload_package }}
|
||||
with:
|
||||
server: ftp.cardforge.org
|
||||
username: ${{ secrets.FTP_USERNAME }}
|
||||
password: ${{ secrets.FTP_PASSWORD }}
|
||||
local-dir: forge-gui-android/target/upload/
|
||||
local-dir: upload/
|
||||
server-dir: downloads/dailysnapshots/
|
||||
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.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.AiCardMemory.MemorySet;
|
||||
import forge.ai.ability.ChangeZoneAi;
|
||||
import forge.ai.ability.LearnAi;
|
||||
import forge.ai.simulation.SpellAbilityPicker;
|
||||
@@ -845,6 +847,13 @@ public class AiController {
|
||||
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
|
||||
// are willing to play the SA
|
||||
return AiPlayDecision.WillPlay;
|
||||
|
||||
@@ -2,6 +2,8 @@ package forge.ai;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.AiCardMemory.MemorySet;
|
||||
import forge.card.CardType;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
@@ -32,6 +34,10 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
|
||||
discarded = new CardCollection();
|
||||
tapped = new CardCollection();
|
||||
Set<Card> tappedForMana = AiCardMemory.getMemorySet(ai0, MemorySet.PAYS_TAP_COST);
|
||||
if (tappedForMana != null) {
|
||||
tapped.addAll(tappedForMana);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -440,21 +446,6 @@ 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, card -> {
|
||||
for (final SpellAbility sa : card.getSpellAbilities()) {
|
||||
if (sa.isManaAbility() && sa.getPayCosts().hasTapCost()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
exclude.addAll(toExclude);
|
||||
}
|
||||
|
||||
String totalP = "";
|
||||
CardCollectionView totap;
|
||||
if (isVehicle) {
|
||||
|
||||
@@ -782,6 +782,7 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
CardLists.sortByPowerAsc(typeList);
|
||||
// TODO prefer noncreatures without tap abilities
|
||||
|
||||
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.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -450,7 +451,7 @@ public class ComputerUtilCost {
|
||||
* the source
|
||||
* @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) {
|
||||
return true;
|
||||
}
|
||||
@@ -487,8 +488,8 @@ public class ComputerUtilCost {
|
||||
c = AbilityUtils.calculateAmount(source, part.getAmount(), sa);
|
||||
}
|
||||
CardCollection exclude = new CardCollection();
|
||||
if (AiCardMemory.getMemorySet(ai, MemorySet.PAYS_TAP_COST) != null) {
|
||||
exclude.addAll(AiCardMemory.getMemorySet(ai, MemorySet.PAYS_TAP_COST));
|
||||
if (alreadyTapped != null) {
|
||||
exclude.addAll(alreadyTapped);
|
||||
}
|
||||
// trying to produce mana that includes tapping source that will already be tapped
|
||||
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);
|
||||
if (tapChoices != null) {
|
||||
for (Card choice : tapChoices) {
|
||||
AiCardMemory.rememberCard(ai, choice, MemorySet.PAYS_TAP_COST);
|
||||
}
|
||||
if (alreadyTapped != null) {
|
||||
alreadyTapped.addAll(tapChoices);
|
||||
// if manasource gets tapped to produce it also can't help paying another
|
||||
if (cost.hasTapCost()) {
|
||||
AiCardMemory.rememberCard(ai, source, MemorySet.PAYS_TAP_COST);
|
||||
alreadyTapped.add(source);
|
||||
}
|
||||
}
|
||||
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
|
||||
if (sa.getHostCard().hasKeyword(Keyword.CASUALTY)) {
|
||||
for (final CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
|
||||
@@ -268,6 +268,7 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
for (final SpellAbility ma : saList) {
|
||||
// this rarely seems like a good idea
|
||||
if (ma.getHostCard() == saHost) {
|
||||
continue;
|
||||
}
|
||||
@@ -276,7 +277,7 @@ public class ComputerUtilMana {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ public final class ImageKeys {
|
||||
}
|
||||
|
||||
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() {
|
||||
missingCards.clear();
|
||||
}
|
||||
|
||||
@@ -2464,7 +2464,7 @@ public class GameAction {
|
||||
game.getAction().reveal(milledPlayer, destination, p, false, message, addSuffix);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@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
|
||||
public void recieve(GameEvent ev) {
|
||||
GameLogEntry le = ev.visit(this);
|
||||
|
||||
@@ -1640,9 +1640,15 @@ public class AbilityUtils {
|
||||
return doXMath(calculateAmount(c, sq[v ? 1 : 2], ctb), expr, c, ctb);
|
||||
}
|
||||
|
||||
SpellAbility sa = null;
|
||||
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
|
||||
if (sq[0].contains("xPaid")) {
|
||||
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,
|
||||
// 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"))) {
|
||||
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())) {
|
||||
// Cast Trigger like Hydroid Krasis
|
||||
SpellAbilityStackInstance castSI = (SpellAbilityStackInstance) root.getTriggeringObject(AbilityKey.StackInstance);
|
||||
@@ -1696,7 +1703,8 @@ public class AbilityUtils {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -4111,9 +4111,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
|
||||
public final boolean isModified() {
|
||||
if (!isCreature()) {
|
||||
return false;
|
||||
}
|
||||
if (this.isEquipped() || this.hasCounters()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3101,8 +3101,6 @@ public class CardFactoryUtil {
|
||||
// because it doesn't work other wise
|
||||
c.setForetoldCostByEffect(true);
|
||||
}
|
||||
String sb = TextUtil.concatWithSpace(getActivatingPlayer().toString(), "has foretold.");
|
||||
game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
|
||||
game.fireEvent(new GameEventCardForetold(getActivatingPlayer()));
|
||||
}
|
||||
};
|
||||
@@ -3425,8 +3423,6 @@ public class CardFactoryUtil {
|
||||
|
||||
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()));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -105,6 +105,7 @@ public interface ICostVisitor<T> {
|
||||
public T visit(CostFlipCoin cost) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(CostForage cost) {
|
||||
return null;
|
||||
@@ -146,7 +147,9 @@ public interface ICostVisitor<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(CostPromiseGift cost) { return null; }
|
||||
public T visit(CostPromiseGift cost) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T visit(CostPutCardToLib cost) {
|
||||
|
||||
@@ -92,9 +92,6 @@ public class ReplaceDamage extends ReplacementEffect {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (hasParam("IsEquipping") && !getHostCard().isEquipping()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasParam("DamageTarget")) {
|
||||
//Lava Burst and Whippoorwill check
|
||||
|
||||
@@ -665,6 +665,7 @@ public class ReplacementHandler {
|
||||
}
|
||||
|
||||
List<ReplacementEffect> possibleReplacers = new ArrayList<>(replaceCandidateMap.keySet());
|
||||
// TODO should be able to choose different order for each entity
|
||||
ReplacementEffect chosenRE = decider.getController().chooseSingleReplacementEffect(possibleReplacers);
|
||||
List<Map<AbilityKey, Object>> runParamList = replaceCandidateMap.get(chosenRE);
|
||||
|
||||
|
||||
@@ -54,6 +54,19 @@
|
||||
<failIfNoMatch>false</failIfNoMatch>
|
||||
</configuration>
|
||||
</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>
|
||||
</plugin>
|
||||
<plugin>
|
||||
@@ -170,26 +183,6 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</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>
|
||||
<groupId>se.bjurr.gitchangelog</groupId>
|
||||
<artifactId>git-changelog-maven-plugin</artifactId>
|
||||
|
||||
@@ -78,9 +78,8 @@ public class CCombat implements ICDoc {
|
||||
}
|
||||
|
||||
display.append("\n");
|
||||
PlayerView controller = null;
|
||||
if (defender instanceof CardView) {
|
||||
controller = ((CardView) defender).getController();
|
||||
PlayerView controller = ((CardView) defender).getController();
|
||||
if (controller == null)
|
||||
//shouldn't be null but display card's + controller ie Black Knight's controller
|
||||
display.append(Lang.getInstance().getPossesive(defender.getName())).append(" controller");
|
||||
|
||||
@@ -70,7 +70,6 @@ public class VAssignGenericAmount extends FDialog {
|
||||
|
||||
/** Constructor.
|
||||
*
|
||||
* @param attacker0 {@link forge.game.card.Card}
|
||||
* @param targets Map<GameEntity, Integer>, map of GameEntity and its maximum assignable amount
|
||||
* @param amount Total amount to be assigned
|
||||
* @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));
|
||||
} else if (entity instanceof PlayerView) {
|
||||
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) {
|
||||
FSkinImageInterface manaSymbol;
|
||||
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
|
||||
manaSymbol = Forge.getAssets().images().get(FSkinProp.IMG_MANA_COLORLESS);
|
||||
}
|
||||
obj = add(new MiscTargetPanel("", manaSymbol));
|
||||
obj = add(new MiscTargetPanel("", manaSymbol, entity));
|
||||
} 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());
|
||||
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 static final FSkinFont FONT = FSkinFont.get(18);
|
||||
private static FSkinColor getForeColor() {
|
||||
private class MiscTargetPanel extends FDisplayObject {
|
||||
private final FSkinFont FONT = FSkinFont.get(18);
|
||||
private FSkinColor getForeColor() {
|
||||
if (Forge.isMobileAdventureMode)
|
||||
return FSkinColor.get(Colors.ADV_CLR_TEXT);
|
||||
return FSkinColor.get(Colors.CLR_TEXT);
|
||||
}
|
||||
private final String name;
|
||||
private final FImage image;
|
||||
private final Object entity;
|
||||
|
||||
private MiscTargetPanel(String name0, FImage image0) {
|
||||
private MiscTargetPanel(String name0, FImage image0, Object entity0) {
|
||||
name = name0;
|
||||
image = image0;
|
||||
entity = entity0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -254,6 +255,24 @@ public class VAssignGenericAmount extends FDialog {
|
||||
g.drawImage(image, 0, 0, w, w);
|
||||
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) {
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Infernal Vessel
|
||||
ManaCost:2 B
|
||||
Types:Creature Human Cleric
|
||||
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: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.
|
||||
@@ -3,7 +3,7 @@ ManaCost:2 W B
|
||||
Types:Legendary Creature Spirit Noble
|
||||
PT:3/3
|
||||
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
|
||||
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
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Needletooth Pack
|
||||
ManaCost:3 G G
|
||||
Types:Creature Dinosaur
|
||||
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: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.
|
||||
@@ -2,6 +2,6 @@ Name:Pariah's Shield
|
||||
ManaCost:5
|
||||
Types:Artifact Equipment
|
||||
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
|
||||
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
|
||||
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.
|
||||
SVar:AIPaymentPreference:DontPayTapCostWithManaSources
|
||||
K:Flying
|
||||
K:Lifelink
|
||||
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
|
||||
PT:10/1
|
||||
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: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.
|
||||
|
||||
@@ -3,6 +3,5 @@ ManaCost:2 W
|
||||
Types:Creature Human Soldier
|
||||
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.
|
||||
SVar:AIPaymentPreference:DontPayTapCostWithManaSources
|
||||
DeckHints:Type$Artifact
|
||||
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
|
||||
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.
|
||||
# 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
|
||||
|
||||
@@ -443,11 +443,7 @@ public abstract class GameLobby implements IHasGameType {
|
||||
Deck deck = slot.getDeck();
|
||||
RegisteredPlayer rp = new RegisteredPlayer(deck);
|
||||
|
||||
if (variantTypes.isEmpty()) {
|
||||
rp.setTeamNumber(team);
|
||||
players.add(rp.setPlayer(lobbyPlayer));
|
||||
}
|
||||
else {
|
||||
if (!variantTypes.isEmpty()) {
|
||||
if (isCommanderMatch) {
|
||||
final GameType commanderGameType =
|
||||
isOathbreakerMatch ? GameType.Oathbreaker :
|
||||
@@ -481,7 +477,6 @@ public abstract class GameLobby implements IHasGameType {
|
||||
Iterable<PaperCard> schemes = null;
|
||||
Iterable<PaperCard> planes = null;
|
||||
|
||||
//Archenemy
|
||||
if (variantTypes.contains(GameType.ArchenemyRumble)
|
||||
|| (variantTypes.contains(GameType.Archenemy) && isArchenemy)) {
|
||||
final CardPool schemePool = deck.get(DeckSection.Schemes);
|
||||
@@ -495,7 +490,6 @@ public abstract class GameLobby implements IHasGameType {
|
||||
schemes = schemePool == null ? Collections.emptyList() : schemePool.toFlatList();
|
||||
}
|
||||
|
||||
//Planechase
|
||||
if (variantTypes.contains(GameType.Planechase)) {
|
||||
final CardPool planePool = deck.get(DeckSection.Planes);
|
||||
if (checkLegality) {
|
||||
@@ -508,7 +502,6 @@ public abstract class GameLobby implements IHasGameType {
|
||||
planes = planePool == null ? Collections.emptyList() : planePool.toFlatList();
|
||||
}
|
||||
|
||||
//Vanguard
|
||||
if (variantTypes.contains(GameType.Vanguard)) {
|
||||
if (avatarPool == null || avatarPool.countAll() == 0) { //ERROR! null if avatar deselected on list
|
||||
SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblNoSelectedVanguardAvatarForPlayer", name));
|
||||
@@ -517,9 +510,10 @@ public abstract class GameLobby implements IHasGameType {
|
||||
}
|
||||
|
||||
rp = RegisteredPlayer.forVariants(activeSlots.size(), variantTypes, deck, schemes, isArchenemy, planes, avatarPool);
|
||||
}
|
||||
|
||||
rp.setTeamNumber(team);
|
||||
players.add(rp.setPlayer(lobbyPlayer));
|
||||
}
|
||||
|
||||
if (!isAI) {
|
||||
guis.put(rp, gui);
|
||||
|
||||
@@ -56,7 +56,8 @@ public class InputQueue extends Observable {
|
||||
|
||||
if (topMostInput != inp) {
|
||||
System.out.println("Cannot remove input " + inp.getClass().getSimpleName() + " because it's not on top of stack. Stack = " + inputStack );
|
||||
} else {
|
||||
} else if (topMostInput != null) {
|
||||
// if topMostInput is null then it means the inputstack is already empty, why this is called twice?
|
||||
inputStack.pop();
|
||||
}
|
||||
updateObservers();
|
||||
|
||||
@@ -119,9 +119,9 @@ public enum ItemManagerConfig {
|
||||
null, null, 3, 0),
|
||||
NET_ARCHIVE_BLOCK_DECKS(SColumnUtil.getDecksDefaultColumns(false, false), false, false, false,
|
||||
null, null, 3, 0),
|
||||
ADVENTURE_EDITOR_POOL(SColumnUtil.getConquestCollectionDefaultColumns(), false, false, false,
|
||||
ADVENTURE_EDITOR_POOL(SColumnUtil.getAdventureCollectionDefaultColumns(), false, false, false,
|
||||
null, null, 6, 0),
|
||||
ADVENTURE_STORE_POOL(SColumnUtil.getConquestCollectionDefaultColumns(), false, false, true,
|
||||
ADVENTURE_STORE_POOL(SColumnUtil.getAdventureCollectionDefaultColumns(), false, false, true,
|
||||
null, null, 6, 0),
|
||||
ADVENTURE_SIDEBOARD(SColumnUtil.getDeckEditorDefaultColumns(), false, false, true,
|
||||
null, null, 6, 0),
|
||||
|
||||
@@ -143,6 +143,13 @@ public final class SColumnUtil {
|
||||
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() {
|
||||
//Similar to special card pool, but show the collector number and hide the type.
|
||||
List<ColumnDef> colDefs = new ArrayList<>();
|
||||
|
||||
@@ -228,6 +228,11 @@ public abstract class ImageFetcher {
|
||||
}
|
||||
if (filename.equalsIgnoreCase("null.jpg"))
|
||||
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");
|
||||
tokenUrl = String.format("%s%s", ForgeConstants.URL_TOKEN_DOWNLOAD, filename);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user