Merge branch 'master' into boosterQoL

This commit is contained in:
JMAilan
2024-10-15 11:36:57 -04:00
committed by GitHub
448 changed files with 733 additions and 160 deletions

View File

@@ -25,7 +25,7 @@ This file is tarball, and may need to be extracted twice depending on which prog
We recommend extracting to a new folder rather than on top of an existing installation.
**For users who have played Forge before all of your user data is stored separately so you don't have to worry about losing it on upgrade.**
Java 8 or later is required to run Forge. Please make sure is the right version is installed in your enviroment. Check the user guide for more info.
Java 8 or later is required to run Forge. Please make sure is the right version is installed in your environment. Check the user guide for more info.
For Android users, download the APK file from [Snapshot Build](https://downloads.cardforge.org/dailysnapshots/) to your device.
On first run, Forge will download all needed data.

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-ai</artifactId>

View File

@@ -923,6 +923,10 @@ public class AiController {
if (checkCurseEffects(sa)) {
return AiPlayDecision.CurseEffects;
}
// TODO maybe other location for this?
if (!sa.isLegalAfterStack()) {
return AiPlayDecision.AnotherTime;
}
Card spellHost = card;
if (sa.isSpell()) {
spellHost = CardCopyService.getLKICopy(spellHost);
@@ -930,10 +934,6 @@ public class AiController {
spellHost.setLastKnownZone(game.getStackZone()); // need to add to stack to make check Restrictions respect stack cmc
spellHost.setCastFrom(card.getZone());
}
// TODO maybe other location for this?
if (!sa.isLegalAfterStack()) {
return AiPlayDecision.AnotherTime;
}
if (!sa.checkRestrictions(spellHost, player)) {
return AiPlayDecision.AnotherTime;
}
@@ -946,7 +946,7 @@ public class AiController {
}
}
if (sa instanceof Spell) {
if (card.isPermanent()) {
if (sa.getApi() == ApiType.PermanentCreature || sa.getApi() == ApiType.PermanentNoncreature) {
return canPlayFromEffectAI((Spell) sa, false, true);
}
if (!player.cantLoseForZeroOrLessLife() && player.canLoseLife() &&

View File

@@ -651,7 +651,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
// TODO sort negatives to remove from best Cards first?
for (final Card crd : negatives) {
for (Map.Entry<CounterType, Integer> e : table.filterToRemove(crd).entrySet()) {
if (ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
if (ComputerUtil.isNegativeCounter(e.getKey(), crd) && crd.canRemoveCounters(e.getKey())) {
int over = Math.min(e.getValue(), c - toRemove);
if (over > 0) {
toRemove += over;
@@ -762,7 +762,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
}
}
// if table is empty, than no counter was removed
// if table is empty, then no counter was removed
return table.isEmpty() ? null : PaymentDecision.counters(table);
}

View File

@@ -836,10 +836,9 @@ public class ComputerUtil {
}
public static CardCollection chooseUntapType(final Player ai, final String type, final Card activate, final boolean untap, final int amount, SpellAbility sa) {
CardCollection typeList =
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
typeList = CardLists.filter(typeList, Presets.TAPPED);
typeList = CardLists.filter(typeList, Presets.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(CounterType.get(CounterEnumType.STUN)));
if (untap) {
typeList.remove(activate);
@@ -851,12 +850,7 @@ public class ComputerUtil {
CardLists.sortByPowerDesc(typeList);
final CardCollection untapList = new CardCollection();
for (int i = 0; i < amount; i++) {
untapList.add(typeList.get(i));
}
return untapList;
return typeList.subList(0, amount);
}
public static CardCollection chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount, SpellAbility sa) {

View File

@@ -222,6 +222,8 @@ public class CountersRemoveAi extends SpellAbilityAi {
return true;
}
// TODO stun counters with canRemoveCounters check
// remove P1P1 counters from opposing creatures
CardCollection oppP1P1List = CardLists.filter(list,
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.isControlledByAnyOf(ai.getOpponents())),

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-core</artifactId>

View File

@@ -224,7 +224,7 @@ public class DeckHints {
return true;
}
for (String tok : card.getTokens()) {
if (tdb != null && tdb.containsRule(tok) && predicate.apply(tdb.getToken(tok).getRules())) {
if (tdb != null && tdb.containsRule(tok) && rulesWithTokens(predicate).apply(tdb.getToken(tok).getRules())) {
return true;
}
}

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-game</artifactId>

View File

@@ -309,8 +309,10 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
abstract public void setCounters(final Map<CounterType, Integer> allCounters);
abstract public boolean canRemoveCounters(final CounterType type);
abstract public boolean canReceiveCounters(final CounterType type);
abstract public void subtractCounter(final CounterType counterName, final int n, final Player remover);
abstract public int subtractCounter(final CounterType counterName, final int n, final Player remover);
abstract public void clearCounters();
public boolean canReceiveCounters(final CounterEnumType type) {
@@ -331,8 +333,8 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
addCounter(CounterType.get(counterType), n, source, table);
}
public void subtractCounter(final CounterEnumType counterName, final int n, final Player remover) {
subtractCounter(CounterType.get(counterName), n, remover);
public int subtractCounter(final CounterEnumType counterName, final int n, final Player remover) {
return subtractCounter(CounterType.get(counterName), n, remover);
}
abstract public void addCounterInternal(final CounterType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params);

View File

@@ -4,7 +4,6 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.card.CardStateName;
import forge.card.CardType;
import forge.game.*;
@@ -27,7 +26,9 @@ import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class ChangeZoneEffect extends SpellAbilityEffect {
@@ -1085,8 +1086,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
String selectPrompt = sa.hasParam("SelectPrompt") ? sa.getParam("SelectPrompt") : MessageUtil.formatMessage(Localizer.getInstance().getMessage("lblSelectCardFromPlayerZone", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase()), decider, player);
final String totalcmc = sa.getParam("WithTotalCMC");
final String totalpower = sa.getParam("WithTotalPower");
final String totalCardTypes = sa.getParam("WithTotalCardTypes");
int totcmc = AbilityUtils.calculateAmount(source, totalcmc, sa);
int totpower = AbilityUtils.calculateAmount(source, totalpower, sa);
int totCardTypes = AbilityUtils.calculateAmount(source, totalCardTypes, sa);
CardCollection chosenCards = new CardCollection();
if (changeType.startsWith("EACH")) {
@@ -1160,6 +1163,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
}
// If we're choosing multiple cards, only need to show the reveal dialog the first time through.
boolean shouldReveal = (i == 0);
Card c = null;
@@ -1170,6 +1174,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
c = Aggregates.random(fetchList);
} else if (defined && !chooseFromDef) {
c = Iterables.getFirst(fetchList, null);
} else if (totalCardTypes != null) {
String title = selectPrompt;
title += "\nCard types left: " + Math.max(totCardTypes, 0);
c = decider.getController().chooseSingleCardForZoneChange(destination, origin, sa, fetchList, shouldReveal ? delayedReveal : null, title, !mandatory, decider);
} else {
String title = selectPrompt;
if (changeNum > 1) { //indicate progress if multiple cards being chosen
@@ -1202,6 +1210,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (totalpower != null) {
totpower -= c.getCurrentPower();
}
if (totalCardTypes != null) {
totCardTypes -= Iterables.size(c.getType().getCoreTypes());
}
}
if (totalCardTypes != null && totCardTypes > 0) {
chosenCards.clear();
}
}
@@ -1537,7 +1552,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
&& !sa.hasParam("AtRandom")
&& (!sa.hasParam("Defined") || sa.hasParam("ChooseFromDefined"))
&& !sa.hasParam("WithTotalCMC")
&& !sa.hasParam("WithTotalPower");
&& !sa.hasParam("WithTotalPower")
&& !sa.hasParam("WithTotalCardTypes");
}
/**

View File

@@ -101,8 +101,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
// uses for multi sources -> one defined/target
// this needs given counter type
if (sa.hasParam("ValidSource")) {
CardCollectionView srcCards = game.getCardsIn(ZoneType.Battlefield);
srcCards = CardLists.getValidCards(srcCards, sa.getParam("ValidSource"), activator, host, sa);
CardCollectionView srcCards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("ValidSource"), activator, host, sa);
List<Card> tgtCards = getDefinedCardsOrTargeted(sa);
if (tgtCards.isEmpty()) {
@@ -147,11 +146,6 @@ public class CountersMoveEffect extends SpellAbilityEffect {
Map<CounterType, Integer> countersToAdd = Maps.newHashMap();
for (Card src : srcCards) {
// rule 121.5: If the first and second objects are the same object, nothing happens
if (src.equals(dest)) {
continue;
}
if ("All".equals(counterName)) {
final Map<CounterType, Integer> tgtCounters = Maps.newHashMap(src.getCounters());
for (Map.Entry<CounterType, Integer> e : tgtCounters.entrySet()) {
@@ -183,8 +177,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
params.put("CounterType", cType);
params.put("Source", source);
CardCollectionView tgtCards = game.getCardsIn(ZoneType.Battlefield);
tgtCards = CardLists.getValidCards(tgtCards, sa.getParam("ValidDefined"), activator, host, sa);
CardCollectionView tgtCards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("ValidDefined"), activator, host, sa);
if (counterNum.equals("Any")) {
tgtCards = activator.getController().chooseCardsForEffect(
@@ -203,6 +196,9 @@ public class CountersMoveEffect extends SpellAbilityEffect {
if (!dest.canReceiveCounters(cType)) {
continue;
}
if (!source.canRemoveCounters(cType)) {
continue;
}
Card cur = game.getCardState(dest, null);
if (cur == null || !cur.equalsWithGameTimestamp(dest)) {
@@ -287,7 +283,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
final List<CounterType> typeChoices = Lists.newArrayList();
// get types of counters
for (CounterType ct : tgtCounters.keySet()) {
if (dest.canReceiveCounters(ct)) {
if (dest.canReceiveCounters(ct) && source.canRemoveCounters(cType)) {
typeChoices.add(ct);
}
}
@@ -337,6 +333,9 @@ public class CountersMoveEffect extends SpellAbilityEffect {
if (!dest.canReceiveCounters(cType)) {
return;
}
if (!src.canRemoveCounters(cType)) {
return;
}
int cmax = src.getCounters(cType);
if (cmax <= 0) {

View File

@@ -126,9 +126,20 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
final int operandValue = AbilityUtils.calculateAmount(host, operand, sa);
putCounter = !Expressions.compare(value, operator, operandValue);
} else {
boolean canReceive = tgtCard.canReceiveCounters(ctype);
boolean canRemove = tgtCard.canRemoveCounters(ctype);
if (!canReceive && !canRemove) {
return;
}
if (canReceive && !canRemove) {
putCounter = true;
} else if (!canReceive && canRemove) {
putCounter = false;
} else {
putCounter = pc.chooseBinary(sa, prompt, BinaryChoiceType.AddOrRemove, params);
}
}
if (putCounter) {
tgtCard.addCounter(chosenType, counterAmount, pl, table);

View File

@@ -63,8 +63,7 @@ public class CountersRemoveAllEffect extends SpellAbilityEffect {
for (final Card tgtCard : cards) {
if (sa.hasParam("AllCounterTypes")) {
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtCard.getCounters().entrySet())) {
numberRemoved += e.getValue();
tgtCard.subtractCounter(e.getKey(), e.getValue(), sa.getActivatingPlayer());
numberRemoved += tgtCard.subtractCounter(e.getKey(), e.getValue(), sa.getActivatingPlayer());
}
//tgtCard.getCounters().clear();
continue;
@@ -74,7 +73,7 @@ public class CountersRemoveAllEffect extends SpellAbilityEffect {
}
if (counterAmount > 0) {
tgtCard.subtractCounter(CounterType.getType(type), counterAmount, sa.getActivatingPlayer());
numberRemoved += tgtCard.subtractCounter(CounterType.getType(type), counterAmount, sa.getActivatingPlayer());
game.updateLastStateForCard(tgtCard);
}
}

View File

@@ -110,8 +110,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
// Removing energy
if (type.equals("All")) {
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtPlayer.getCounters().entrySet())) {
tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
totalRemoved += e.getValue();
totalRemoved += tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
}
} else {
if (num.equals("All")) {
@@ -120,8 +119,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
if (type.equals("Any")) {
totalRemoved += removeAnyType(tgtPlayer, cntToRemove, sa);
} else {
tgtPlayer.subtractCounter(counterType, cntToRemove, activator);
totalRemoved += cntToRemove;
totalRemoved += tgtPlayer.subtractCounter(counterType, cntToRemove, activator);
}
}
}
@@ -165,11 +163,11 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
if (gameCard == null || !tgtCard.equalsWithGameTimestamp(gameCard)) {
continue;
}
final Zone zone = game.getZoneOf(gameCard);
if (type.equals("All")) {
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(gameCard.getCounters().entrySet())) {
gameCard.subtractCounter(e.getKey(), e.getValue(), activator);
totalRemoved += e.getValue();
totalRemoved += gameCard.subtractCounter(e.getKey(), e.getValue(), activator);
}
game.updateLastStateForCard(gameCard);
continue;
@@ -180,6 +178,9 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
if (type.equals("Any")) {
totalRemoved += removeAnyType(gameCard, cntToRemove, sa);
} else {
if (!tgtCard.canRemoveCounters(counterType)) {
continue;
}
cntToRemove = Math.min(cntToRemove, gameCard.getCounters(counterType));
if (zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Exile)) {
@@ -221,14 +222,18 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
final Player activator = sa.getActivatingPlayer();
final PlayerController pc = activator.getController();
final Map<CounterType, Integer> tgtCounters = Maps.newHashMap(entity.getCounters());
for (CounterType ct : ImmutableList.copyOf(tgtCounters.keySet())) {
if (!entity.canRemoveCounters(ct)) {
tgtCounters.remove(ct);
}
}
while (cntToRemove > 0 && !tgtCounters.isEmpty()) {
Map<String, Object> params = Maps.newHashMap();
params.put("Target", entity);
String prompt = Localizer.getInstance().getMessage("lblSelectCountersTypeToRemove");
CounterType chosenType = pc.chooseCounterType(
ImmutableList.copyOf(tgtCounters.keySet()), sa, prompt, params);
CounterType chosenType = pc.chooseCounterType(ImmutableList.copyOf(tgtCounters.keySet()), sa, prompt, params);
int max = Math.min(cntToRemove, tgtCounters.get(chosenType));
// remove selection so player can't cheat additional trigger by choosing the same type multiple times

View File

@@ -1600,6 +1600,21 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
return true;
}
@Override
public final boolean canRemoveCounters(final CounterType type) {
if (isPhasedOut()) {
return false;
}
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.CounterType, type);
repParams.put(AbilityKey.Result, 0);
repParams.put(AbilityKey.IsDamage, false);
if (game.getReplacementHandler().cantHappenCheck(ReplacementType.RemoveCounter, repParams)) {
return false;
}
return true;
}
@Override
public void addCounterInternal(final CounterType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params) {
int addAmount = n;
@@ -1736,11 +1751,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
}
@Override
public final void subtractCounter(final CounterType counterName, final int n, final Player remover) {
subtractCounter(counterName, n, remover, false);
public final int subtractCounter(final CounterType counterName, final int n, final Player remover) {
return subtractCounter(counterName, n, remover, false);
}
public final void subtractCounter(final CounterType counterName, final int n, final Player remover, final boolean isDamage) {
public final int subtractCounter(final CounterType counterName, final int n, final Player remover, final boolean isDamage) {
int oldValue = getCounters(counterName);
int newValue = Math.max(oldValue - n, 0);
@@ -1758,12 +1773,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
newValue = 0;
}
break;
case Replaced:
return 0;
default:
break;
}
final int delta = oldValue - newValue;
if (delta == 0) { return; }
if (delta == 0) { return 0; }
int powerBonusBefore = getPowerBonusFromCounters();
int toughnessBonusBefore = getToughnessBonusFromCounters();
@@ -1800,6 +1817,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
runParams.put(AbilityKey.CounterAmount, delta);
runParams.put(AbilityKey.NewCounterAmount, newValue);
getGame().getTriggerHandler().runTrigger(TriggerType.CounterRemovedOnce, runParams, false);
return delta;
}
@Override

View File

@@ -70,10 +70,16 @@ public class CostRemoveAnyCounter extends CostPart {
int allCounters = 0;
for (Card c : validCards) {
if (this.counter != null) {
if (!c.canRemoveCounters(this.counter)) {
continue;
}
allCounters += c.getCounters(this.counter);
} else {
for (Integer value : c.getCounters().values()) {
allCounters += value;
for (Map.Entry<CounterType, Integer> entry : c.getCounters().entrySet()) {
if (!c.canRemoveCounters(entry.getKey())) {
continue;
}
allCounters += entry.getValue();
}
}
}

View File

@@ -21,6 +21,8 @@ import com.google.common.collect.Maps;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
@@ -78,7 +80,8 @@ public class CostUntap extends CostPart {
@Override
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
return source.isTapped() && !source.isAbilitySick();
return source.isTapped() && !source.isAbilitySick() &&
(source.getCounters(CounterEnumType.STUN) == 0 || source.canRemoveCounters(CounterType.get(CounterEnumType.STUN)));
}
@Override

View File

@@ -23,6 +23,8 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.card.CardPredicates.Presets;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -90,7 +92,7 @@ public class CostUntapType extends CostPartWithList {
if (!canUntapSource) {
typeList.remove(source);
}
typeList = CardLists.filter(typeList, Presets.TAPPED);
typeList = CardLists.filter(typeList, Presets.TAPPED, c -> c.getCounters(CounterEnumType.STUN) == 0 || c.canRemoveCounters(CounterType.get(CounterEnumType.STUN)));
final int amount = this.getAbilityAmount(ability);
return (typeList.size() != 0) && (typeList.size() >= amount);

View File

@@ -1049,6 +1049,7 @@ public class PhaseHandler implements java.io.Serializable {
for (SpellAbility sa : chosenSa) {
Card saHost = sa.getHostCard();
final Zone originZone = saHost.getZone();
final CardZoneTable triggerList = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
if (pPlayerPriority.getController().playChosenSpellAbility(sa)) {
// 117.3c If a player has priority when they cast a spell, activate an ability, [play a land]
@@ -1064,7 +1065,6 @@ public class PhaseHandler implements java.io.Serializable {
// Need to check if Zone did change
if (currentZone != null && originZone != null && !currentZone.equals(originZone) && (sa.isSpell() || sa.isLandAbility())) {
// currently there can be only one Spell put on the Stack at once, or Land Abilities be played
final CardZoneTable triggerList = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), saHost);
triggerList.triggerChangesZoneAll(game, sa);
}

View File

@@ -855,6 +855,14 @@ public class Player extends GameEntity implements Comparable<Player> {
return true;
}
public final boolean canRemoveCounters(final CounterType type) {
if (!isInGame()) {
return false;
}
// no RE affecting players currently, skip check for performance
return true;
}
@Override
public void addCounterInternal(final CounterType counterType, final int n, final Player source, final boolean fireEvents, GameEntityCounterTable table, Map<AbilityKey, Object> params) {
int addAmount = n;
@@ -896,12 +904,12 @@ public class Player extends GameEntity implements Comparable<Player> {
}
@Override
public void subtractCounter(CounterType counterName, int num, final Player remover) {
public int subtractCounter(CounterType counterName, int num, final Player remover) {
int oldValue = getCounters(counterName);
int newValue = Math.max(oldValue - num, 0);
final int delta = oldValue - newValue;
if (delta == 0) { return; }
if (delta == 0) { return 0; }
setCounters(counterName, newValue, null, true);
@@ -917,6 +925,7 @@ public class Player extends GameEntity implements Comparable<Player> {
getGame().getTriggerHandler().runTrigger(TriggerType.CounterRemoved, runParams, false);
}
*/
return delta;
}
public final void clearCounters() {

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="forge.app"
android:versionCode="106650000"
android:versionName="1.6.65" > <!-- versionName should be updated and it's used for Sentry releases tag -->
android:versionCode="106660000"
android:versionName="1.6.66" > <!-- versionName should be updated and it's used for Sentry releases tag -->
<uses-sdk
android:minSdkVersion="26"

View File

@@ -7,7 +7,7 @@
<packaging.type>jar</packaging.type>
<build.min.memory>-Xms1024m</build.min.memory>
<build.max.memory>-Xmx1536m</build.max.memory>
<alpha-version>1.6.65-SNAPSHOT</alpha-version>
<alpha-version>1.6.66-SNAPSHOT</alpha-version>
<sign.keystore>keystore</sign.keystore>
<sign.alias>alias</sign.alias>
<sign.storepass>storepass</sign.storepass>
@@ -20,7 +20,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-android</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-desktop</artifactId>
@@ -118,7 +118,7 @@
</goals>
<configuration>
<!-- TODO: insert placeholder for latest version tag -->
<fromRef>forge-1.6.64</fromRef>
<fromRef>forge-1.6.65</fromRef>
<file>../forge-gui/release-files/CHANGES.txt</file>
<templateContent>
<![CDATA[

View File

@@ -12,7 +12,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-ios</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-mobile-dev</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-mobile</artifactId>

View File

@@ -1,11 +1,7 @@
package forge;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.*;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.controllers.Controller;
import com.badlogic.gdx.controllers.ControllerAdapter;
import com.badlogic.gdx.controllers.ControllerListener;
@@ -54,7 +50,7 @@ import java.nio.file.Paths;
import java.util.*;
public class Forge implements ApplicationListener {
public static final String CURRENT_VERSION = "1.6.65-SNAPSHOT";
public static final String CURRENT_VERSION = "1.6.66-SNAPSHOT";
private static ApplicationListener app = null;
static Scene currentScene = null;

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.65-SNAPSHOT</version>
<version>1.6.66-SNAPSHOT</version>
</parent>
<artifactId>forge-gui</artifactId>

View File

@@ -3,7 +3,7 @@ ManaCost:4 R
Types:Creature Minotaur Shaman
PT:3/4
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ ABImpulse | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME attacks, you may put an instant or sorcery card from your graveyard on the bottom of your library. If you do, exile the top two cards of your library. You may play those cards this turn.
SVar:ABImpulse:AB$ Dig | Cost$ PutCardToLibFromGrave<1/-1/Sorcery;Instant> | Defined$ You | DigNum$ 2 | ChangeNum$ All | DestinationZone$ Exile | SubAbility$ DBEffect | RememberChanged$ True | SubAbility$ DBEffect | SpellDescription$ Exile the top two cards of your library. You may play those cards this turn.
SVar:ABImpulse:AB$ Dig | Cost$ PutCardToLibFromGrave<1/-1/Sorcery;Instant> | Defined$ You | DigNum$ 2 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect | SpellDescription$ Exile the top two cards of your library. You may play those cards this turn.
SVar:DBEffect:DB$ Effect | RememberObjects$ RememberedCard | StaticAbilities$ Play | SubAbility$ DBCleanup | ForgetOnMoved$ Exile
SVar:Play:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play the exiled cards this turn.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True

View File

@@ -4,8 +4,8 @@ Types:Legendary Creature Human Berserker
PT:3/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl+attacking | TriggerZones$ Battlefield | Execute$ TrigChangeZoneBis | TriggerDescription$ Whenever a creature you control attacks or a creature you control enters attacking, you may pay {1}{R}. If you do, you may put a creature card with mana value less than that creature's mana value from your hand onto the battlefield tapped and attacking.
T:Mode$ Attacks | ValidCard$ Creature.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigChangeZone | Secondary$ True | TriggerDescription$ Whenever a creature you control attacks or a creature you control enters attacking, you may pay {1}{R}. If you do, you may put a creature card with mana value less than that creature's mana value from your hand onto the battlefield tapped and attacking.
SVar:TrigChangeZone:AB$ ChangeZone | Cost$ 1 R | Origin$ Hand | ChangeNum$ 1 | Destination$ Battlefield | ChangeType$ Creature.cmcLTX+YouCtrl | ChangeNum$ 1 | Tapped$ True | Attacking$ True
SVar:TrigChangeZoneBis:AB$ ChangeZone | Cost$ 1 R | Origin$ Hand | ChangeNum$ 1 | Destination$ Battlefield | ChangeType$ Creature.cmcLTY+YouCtrl | ChangeNum$ 1 | Tapped$ True | Attacking$ True
SVar:TrigChangeZone:AB$ ChangeZone | Cost$ 1 R | Origin$ Hand | Destination$ Battlefield | ChangeType$ Creature.cmcLTX+YouCtrl | ChangeNum$ 1 | Tapped$ True | Attacking$ True
SVar:TrigChangeZoneBis:AB$ ChangeZone | Cost$ 1 R | Origin$ Hand | Destination$ Battlefield | ChangeType$ Creature.cmcLTY+YouCtrl | ChangeNum$ 1 | Tapped$ True | Attacking$ True
SVar:X:TriggeredAttacker$CardManaCost
SVar:Y:TriggeredCard$CardManaCost
Oracle:Whenever a creature you control attacks or a creature you control enters attacking, you may pay {1}{R}. If you do, you may put a creature card with mana value less than that creature's mana value from your hand onto the battlefield tapped and attacking.

View File

@@ -3,6 +3,6 @@ ManaCost:3 R
Types:Creature Wurm
PT:4/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Land.YouCtrl | Execute$ TrigPump | TriggerZones$ Battlefield | TriggerDescription$ Landfall — Whenever a land you control enters, CARDNAME gains first strike until end of turn.
SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ First Strike | Defined$ Self
SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ First Strike
SVar:BuffedBy:Land
Oracle:Landfall — Whenever a land you control enters, Belligerent Whiptail gains first strike until end of turn.

View File

@@ -2,7 +2,7 @@ Name:Blight Herder
ManaCost:5
Types:Creature Eldrazi Processor
PT:4/5
T:Mode$ SpellCast | ValidCard$ Card.Self | OptionalDecider$ You | Execute$ TrigToken | OptionalDecider$ You | TriggerDescription$ When you cast this spell, you may put two cards your opponents own from exile into their owners' graveyards. If you do, create three 1/1 colorless Eldrazi Scion creature tokens. They have "Sacrifice this creature: Add {C}."
T:Mode$ SpellCast | ValidCard$ Card.Self | Execute$ TrigToken | OptionalDecider$ You | TriggerDescription$ When you cast this spell, you may put two cards your opponents own from exile into their owners' graveyards. If you do, create three 1/1 colorless Eldrazi Scion creature tokens. They have "Sacrifice this creature: Add {C}."
SVar:TrigToken:AB$ Token | Cost$ ExiledMoveToGrave<2/Card.OppOwn/cards your opponents own> | TokenAmount$ 3 | TokenScript$ c_1_1_eldrazi_scion_sac | TokenOwner$ You
DeckHints:Keyword$Ingest & Type$Eldrazi
DeckHas:Ability$Mana.Colorless|Token

View File

@@ -4,7 +4,7 @@ Types:Creature Squirrel Druid
PT:3/3
K:Toxic:2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ WWen CARDNAME enters, you and target opponent each create a Treasure token.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | ValidTgts$ Opponent | TokenOwner$ TargetedAndYou | TokenAmount$ 1 | TokenScript$ c_a_treasure_sac | SpellDescription$ You and target opponent each create a Food token.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | ValidTgts$ Opponent | TokenOwner$ TargetedAndYou | TokenScript$ c_a_treasure_sac | SpellDescription$ You and target opponent each create a Food token.
T:Mode$ Sacrificed | ValidCard$ Permanent.token+nonCreature | ValidPlayer$ Opponent | Execute$ TrigPoison | TriggerZones$ Battlefield | TriggerDescription$ Whenever an opponent sacrifices a noncreature token, that player gets two poison counters.
SVar:TrigPoison:DB$ Poison | Defined$ TriggeredCardController | Num$ 2
Oracle:Toxic 2 (Players dealt combat damage by this creature also get two poison counters. A player with ten or more poison counters loses the game.)\nWhen Bloodroot Apothecary enters, you and target opponent each create a Treasure token.\nWhenever an opponent sacrifices a noncreature token, that player gets two poison counters.

View File

@@ -3,6 +3,6 @@ ManaCost:3 R
Types:Creature Minotaur Warrior
PT:4/3
T:Mode$ Attacks | ValidCard$ Card.Self | IsPresent$ Warrior.Other+YourTeamCtrl | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME attacks, if your team controls another Warrior, CARDNAME gains first strike until end of turn.
SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ First Strike | Defined$ Self
SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ First Strike
SVar:BuffedBy:Warrior
Oracle:Whenever Bull-Rush Bruiser attacks, if your team controls another Warrior, Bull-Rush Bruiser gains first strike until end of turn.

View File

@@ -3,7 +3,7 @@ ManaCost:1 B
Types:Enchantment
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You.descended | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ At the beginning of your end step, if you descended this turn, put a +1/+1 counter on target creature you control. (You descended if a permanent card was put into your graveyard from anywhere.)
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterNum$ 1 | CounterType$ P1P1
A:AB$ Token | Cost$ 5 B B Sac<1/CARDNAME> | TokenOwner$ You | TokenScript$ wb_4_3_vampire_demon_flying | TokenOwner$ You | SpellDescription$ Create a 4/3 white and black Vampire Demon creature token with flying.
A:AB$ Token | Cost$ 5 B B Sac<1/CARDNAME> | TokenScript$ wb_4_3_vampire_demon_flying | TokenOwner$ You | SpellDescription$ Create a 4/3 white and black Vampire Demon creature token with flying.
DeckHas:Ability$Sacrifice|Token|Counters & Type$Vampire|Demon
DeckHints:Ability$Mill|Sacrifice
Oracle:At the beginning of your end step, if you descended this turn, put a +1/+1 counter on target creature you control. (You descended if a permanent card was put into your graveyard from anywhere.)\n{5}{B}{B}, Sacrifice Canonized in Blood: Create a 4/3 white and black Vampire Demon creature token with flying.

View File

@@ -7,7 +7,7 @@ SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1
T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | Execute$ TrigCreateAndMill | TriggerDescription$ At the beginning of your end step, create a 5/5 black Demon creature token with flying, then mill two cards. If two cards that share all their card types were milled this way, sacrifice CARDNAME.
SVar:TrigCreateAndMill:DB$ Token | TokenAmount$ 1 | TokenScript$ b_5_5_demon_flying | TokenOwner$ You | SubAbility$ DBMill
SVar:DBMill:DB$ Mill | NumCards$ 2 | RememberMilled$ True | ShowMilledCards$ True | SubAbility$ DBSacrifice
SVar:DBSacrifice:DB$ Sacrifice | SacValid$ Self | ShowSacrificedCards$ True | ConditionCheckSVar$ MilledSharesAllTypes | ConditionSVarCompare$ GE2 | SubAbility$ Cleanup
SVar:DBSacrifice:DB$ Sacrifice | SacValid$ Self | ShowSacrificedCards$ True | ConditionCheckSVar$ MilledSharesAllTypes | ConditionSVarCompare$ GE2 | SubAbility$ DBCleanup
SVar:MilledSharesAllTypes:Remembered$Valid Card.sharesAllCardTypesWithOther Remembered
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
DeckHas:Ability$Token|Mill|Sacrifice

Some files were not shown because too many files have changed in this diff Show More