diff --git a/.github/workflows/snapshots-android.yml b/.github/workflows/snapshots-android.yml index 04fdf794e11..793f4cddd7f 100644 --- a/.github/workflows/snapshots-android.yml +++ b/.github/workflows/snapshots-android.yml @@ -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 diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 14915df4a79..e20e9718b34 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -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 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; diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index 662a42b7318..9ebcff668a4 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -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 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) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 0d83d91aefe..2d4b7308c8c 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -782,6 +782,7 @@ public class ComputerUtil { } CardLists.sortByPowerAsc(typeList); + // TODO prefer noncreatures without tap abilities final CardCollection tapList = new CardCollection(); diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index bfee0f90eb7..dfd27b69b04 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -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 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 manasource gets tapped to produce it also can't help paying another - if (cost.hasTapCost()) { - AiCardMemory.rememberCard(ai, source, 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()) { + 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()) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index b23d4d6b250..1dd1c1448e8 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -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; } diff --git a/forge-core/src/main/java/forge/ImageKeys.java b/forge-core/src/main/java/forge/ImageKeys.java index aa0d4912565..803d437aa83 100644 --- a/forge-core/src/main/java/forge/ImageKeys.java +++ b/forge-core/src/main/java/forge/ImageKeys.java @@ -85,7 +85,7 @@ public final class ImageKeys { } private static final Map cachedCards = new HashMap<>(50000); - private static HashSet missingCards = new HashSet<>(); + public static HashSet missingCards = new HashSet<>(); public static void clearMissingCards() { missingCards.clear(); } @@ -310,7 +310,7 @@ public final class ImageKeys { } // 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 missingCards.add(filename); return null; diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index d9e52576309..c2969e4ca72 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -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 + "."); } } diff --git a/forge-game/src/main/java/forge/game/GameLogFormatter.java b/forge-game/src/main/java/forge/game/GameLogFormatter.java index 4599f4c17b3..911cd2e7f37 100644 --- a/forge-game/src/main/java/forge/game/GameLogFormatter.java +++ b/forge-game/src/main/java/forge/game/GameLogFormatter.java @@ -306,6 +306,17 @@ public class GameLogFormatter extends IGameEventVisitor.Base { 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); diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index 6f3475832fe..d0e727a2bed 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -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); diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 2d00f281ea6..1f5819e494b 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -4111,9 +4111,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr } public final boolean isModified() { - if (!isCreature()) { - return false; - } if (this.isEquipped() || this.hasCounters()) { return true; } diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index be1af91202f..4f9cc1b0551 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -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())); } }; diff --git a/forge-game/src/main/java/forge/game/cost/ICostVisitor.java b/forge-game/src/main/java/forge/game/cost/ICostVisitor.java index 1ee90baa331..190e1c806ce 100644 --- a/forge-game/src/main/java/forge/game/cost/ICostVisitor.java +++ b/forge-game/src/main/java/forge/game/cost/ICostVisitor.java @@ -105,6 +105,7 @@ public interface ICostVisitor { public T visit(CostFlipCoin cost) { return null; } + @Override public T visit(CostForage cost) { return null; @@ -146,7 +147,9 @@ public interface ICostVisitor { } @Override - public T visit(CostPromiseGift cost) { return null; } + public T visit(CostPromiseGift cost) { + return null; + } @Override public T visit(CostPutCardToLib cost) { diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceDamage.java b/forge-game/src/main/java/forge/game/replacement/ReplaceDamage.java index cbd590a9d9f..32d3b393a80 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplaceDamage.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplaceDamage.java @@ -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 diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java index cd28fa4316a..75262669cfe 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -665,6 +665,7 @@ public class ReplacementHandler { } List possibleReplacers = new ArrayList<>(replaceCandidateMap.keySet()); + // TODO should be able to choose different order for each entity ReplacementEffect chosenRE = decider.getController().chooseSingleReplacementEffect(possibleReplacers); List> runParamList = replaceCandidateMap.get(chosenRE); diff --git a/forge-gui-desktop/pom.xml b/forge-gui-desktop/pom.xml index 1aeb7a74877..6bd211ca954 100644 --- a/forge-gui-desktop/pom.xml +++ b/forge-gui-desktop/pom.xml @@ -54,6 +54,19 @@ false + + released-version + validate + + released-version + + + + parse-version + + parse-version + + @@ -170,26 +183,6 @@ - - org.codehaus.mojo - build-helper-maven-plugin - 3.6.0 - - - released-version - validate - - released-version - - - - parse-version - - parse-version - - - - se.bjurr.gitchangelog git-changelog-maven-plugin diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CCombat.java b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CCombat.java index 750d7403a42..9ee50bc9fc1 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CCombat.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CCombat.java @@ -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"); diff --git a/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java b/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java index ca738800da4..9fc02058b6a 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java @@ -70,7 +70,6 @@ public class VAssignGenericAmount extends FDialog { /** Constructor. * - * @param attacker0 {@link forge.game.card.Card} * @param targets Map, 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) { diff --git a/forge-gui/res/cardsfolder/i/infernal_vessel.txt b/forge-gui/res/cardsfolder/i/infernal_vessel.txt index 8aec8a859df..064ca619fab 100644 --- a/forge-gui/res/cardsfolder/i/infernal_vessel.txt +++ b/forge-gui/res/cardsfolder/i/infernal_vessel.txt @@ -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. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/k/king_of_the_oathbreakers.txt b/forge-gui/res/cardsfolder/k/king_of_the_oathbreakers.txt index 9e400b406ba..4a93802da32 100644 --- a/forge-gui/res/cardsfolder/k/king_of_the_oathbreakers.txt +++ b/forge-gui/res/cardsfolder/k/king_of_the_oathbreakers.txt @@ -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 diff --git a/forge-gui/res/cardsfolder/n/needletooth_pack.txt b/forge-gui/res/cardsfolder/n/needletooth_pack.txt index 4c7b222a5c3..81e7abaf07b 100644 --- a/forge-gui/res/cardsfolder/n/needletooth_pack.txt +++ b/forge-gui/res/cardsfolder/n/needletooth_pack.txt @@ -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. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/p/pariahs_shield.txt b/forge-gui/res/cardsfolder/p/pariahs_shield.txt index 1166af2a770..ce73061b4d3 100644 --- a/forge-gui/res/cardsfolder/p/pariahs_shield.txt +++ b/forge-gui/res/cardsfolder/p/pariahs_shield.txt @@ -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} diff --git a/forge-gui/res/cardsfolder/s/sephara_skys_blade.txt b/forge-gui/res/cardsfolder/s/sephara_skys_blade.txt index a1873fea7ce..4c2bc985e56 100644 --- a/forge-gui/res/cardsfolder/s/sephara_skys_blade.txt +++ b/forge-gui/res/cardsfolder/s/sephara_skys_blade.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/t/the_mindskinner.txt b/forge-gui/res/cardsfolder/t/the_mindskinner.txt index fce38f7d439..74bcacb712a 100644 --- a/forge-gui/res/cardsfolder/t/the_mindskinner.txt +++ b/forge-gui/res/cardsfolder/t/the_mindskinner.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/w/warlords_elite.txt b/forge-gui/res/cardsfolder/w/warlords_elite.txt index 615cf18e4a1..81de84233b6 100644 --- a/forge-gui/res/cardsfolder/w/warlords_elite.txt +++ b/forge-gui/res/cardsfolder/w/warlords_elite.txt @@ -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. diff --git a/forge-gui/res/cardsfolder/z/zahid_djinn_of_the_lamp.txt b/forge-gui/res/cardsfolder/z/zahid_djinn_of_the_lamp.txt index 75c52775ad7..997b8c36bfd 100644 --- a/forge-gui/res/cardsfolder/z/zahid_djinn_of_the_lamp.txt +++ b/forge-gui/res/cardsfolder/z/zahid_djinn_of_the_lamp.txt @@ -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 diff --git a/forge-gui/src/main/java/forge/gamemodes/match/GameLobby.java b/forge-gui/src/main/java/forge/gamemodes/match/GameLobby.java index c92bc865263..340a9633b46 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/GameLobby.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/GameLobby.java @@ -443,17 +443,13 @@ 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 : - isTinyLeadersMatch ? GameType.TinyLeaders : - isBrawlMatch ? GameType.Brawl : - GameType.Commander; + isTinyLeadersMatch ? GameType.TinyLeaders : + isBrawlMatch ? GameType.Brawl : + GameType.Commander; if (checkLegality) { final String errMsg = commanderGameType.getDeckFormat().getDeckConformanceProblem(deck); if (errMsg != null) { @@ -481,7 +477,6 @@ public abstract class GameLobby implements IHasGameType { Iterable schemes = null; Iterable 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,10 +510,11 @@ 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)); } + rp.setTeamNumber(team); + players.add(rp.setPlayer(lobbyPlayer)); + if (!isAI) { guis.put(rp, gui); } diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputQueue.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputQueue.java index b669697d598..7319d31e2d2 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputQueue.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputQueue.java @@ -56,8 +56,9 @@ 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 { - inputStack.pop(); + } else if (topMostInput != null) { + // if topMostInput is null then it means the inputstack is already empty, why this is called twice? + inputStack.pop(); } updateObservers(); } diff --git a/forge-gui/src/main/java/forge/itemmanager/ItemManagerConfig.java b/forge-gui/src/main/java/forge/itemmanager/ItemManagerConfig.java index 685f1d6deda..7f4adf97539 100644 --- a/forge-gui/src/main/java/forge/itemmanager/ItemManagerConfig.java +++ b/forge-gui/src/main/java/forge/itemmanager/ItemManagerConfig.java @@ -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), @@ -376,4 +376,4 @@ public enum ItemManagerConfig { e.printStackTrace(); } } -} \ No newline at end of file +} diff --git a/forge-gui/src/main/java/forge/itemmanager/SColumnUtil.java b/forge-gui/src/main/java/forge/itemmanager/SColumnUtil.java index ff0328f4e28..b2309f45346 100644 --- a/forge-gui/src/main/java/forge/itemmanager/SColumnUtil.java +++ b/forge-gui/src/main/java/forge/itemmanager/SColumnUtil.java @@ -143,6 +143,13 @@ public final class SColumnUtil { return columns; } + public static Map getAdventureCollectionDefaultColumns() { + Map 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 getAttractionPoolDefaultColumns() { //Similar to special card pool, but show the collector number and hide the type. List colDefs = new ArrayList<>(); diff --git a/forge-gui/src/main/java/forge/util/ImageFetcher.java b/forge-gui/src/main/java/forge/util/ImageFetcher.java index 516988793c2..45f87d9dfc8 100644 --- a/forge-gui/src/main/java/forge/util/ImageFetcher.java +++ b/forge-gui/src/main/java/forge/util/ImageFetcher.java @@ -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); }