Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master

This commit is contained in:
Michael Kamensky
2021-06-11 07:50:33 +03:00
813 changed files with 2245 additions and 1611 deletions

View File

@@ -807,6 +807,8 @@ public class AbilityUtils {
final SpellAbility root = sa.getRootAbility();
list = new CardCollection((Card) root.getReplacingObject(AbilityKey.fromString(calcX[0].substring(8))));
}
// there could be null inside!
list = Iterables.filter(list, Card.class);
if (list != null) {
val = handlePaid(list, calcX[1], card, ability);
}
@@ -3424,7 +3426,6 @@ public class AbilityUtils {
} else {
return size;
}
}
if (string.startsWith("DifferentCMC")) {
@@ -3437,14 +3438,13 @@ public class AbilityUtils {
if (string.startsWith("SumCMC")) {
int sumCMC = 0;
for(Card c : paidList) {
for (Card c : paidList) {
sumCMC += c.getCMC();
}
return sumCMC;
}
if (string.startsWith("Valid")) {
final String[] splitString = string.split("/", 2);
String valid = splitString[0].substring(6);
final List<Card> list = CardLists.getValidCardsAsList(paidList, valid, source.getController(), source, ctb);
@@ -3464,6 +3464,7 @@ public class AbilityUtils {
}
int tot = 0;
for (final Card c : filteredList) {
tot += xCount(c, filteredString, ctb);
}

View File

@@ -704,8 +704,8 @@ public abstract class SpellAbilityEffect {
Card host = sa.getHostCard();
final Game game = host.getGame();
final String duration = sa.getParam("Duration");
// in case host was LKI
if (host.isLKI()) {
// in case host was LKI or still resolving
if (host.isLKI() || host.getZone().is(ZoneType.Stack)) {
host = game.getCardState(host);
}

View File

@@ -243,5 +243,4 @@ public class CharmEffect extends SpellAbilityEffect {
}
}

View File

@@ -1,5 +1,6 @@
package forge.game.ability.effects;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -62,6 +63,7 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
boolean randomChoice = sa.hasParam("AtRandom");
boolean chooseFromDefined = sa.hasParam("ChooseFromDefinedCards");
boolean chooseFromOneTimeList = sa.hasParam("ChooseFromOneTimeList");
if (!randomChoice) {
if (sa.hasParam("SelectPrompt")) {
@@ -100,7 +102,7 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
} else if (chooseFromDefined) {
CardCollection choices = AbilityUtils.getDefinedCards(host, sa.getParam("ChooseFromDefinedCards"), sa);
choices = CardLists.getValidCards(choices, valid, host.getController(), host, sa);
List<ICardFace> faces = Lists.newArrayList();
List<ICardFace> faces = new ArrayList<>();
// get Card
for (final Card c : choices) {
final CardRules rules = c.getRules();
@@ -114,6 +116,22 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
}
Collections.sort(faces);
chosen = p.getController().chooseCardName(sa, faces, message);
} else if (chooseFromOneTimeList) {
String [] names = sa.getParam("ChooseFromOneTimeList").split(",");
List<ICardFace> faces = new ArrayList<>();
for (String name : names) {
faces.add(StaticData.instance().getCommonCards().getFaceByName(name));
}
chosen = p.getController().chooseCardName(sa, faces, message);
// Remove chosen Name from List
StringBuilder sb = new StringBuilder();
for (String name : names) {
if (chosen.equals(name)) continue;
if (sb.length() > 0) sb.append(',');
sb.append(name);
}
sa.putParam("ChooseFromOneTimeList", sb.toString());
} else {
// use CardFace because you might name a alternate names
Predicate<ICardFace> cpp = Predicates.alwaysTrue();

View File

@@ -99,9 +99,9 @@ public class CopyPermanentEffect extends TokenEffectBase {
if (sa.hasParam("RandomCopied")) {
List<PaperCard> copysource = Lists.newArrayList(cards);
List<Card> choice = Lists.newArrayList();
final String num = sa.hasParam("RandomNum") ? sa.getParam("RandomNum") : "1";
final String num = sa.getParamOrDefault("RandomNum","1");
int ncopied = AbilityUtils.calculateAmount(host, num, sa);
while(ncopied > 0 && !copysource.isEmpty()) {
while (ncopied > 0 && !copysource.isEmpty()) {
final PaperCard cp = Aggregates.random(copysource);
Card possibleCard = Card.fromPaperCard(cp, activator); // Need to temporarily set the Owner so the Game is set

View File

@@ -22,7 +22,6 @@ public class MustAttackEffect extends SpellAbilityEffect {
final Card host = sa.getHostCard();
final StringBuilder sb = new StringBuilder();
// end standard pre-
final List<Player> tgtPlayers = getTargetPlayers(sa);
@@ -67,7 +66,6 @@ public class MustAttackEffect extends SpellAbilityEffect {
entity = defPWs.getFirst();
}
}
//System.out.println("Setting mustAttackEntity to: "+entity);
for (final Player p : tgtPlayers) {
if ((tgt == null) || p.canBeTargetedBy(sa)) {

View File

@@ -35,6 +35,7 @@ import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.util.Aggregates;
import forge.util.CardTranslation;
import forge.util.Lang;
import forge.util.Localizer;
@@ -93,8 +94,7 @@ public class PlayEffect extends SpellAbilityEffect {
if (sa.hasParam("ShowCards")) {
showCards = new CardCollection(AbilityUtils.filterListByType(game.getCardsIn(zones), sa.getParam("ShowCards"), sa));
}
}
else if (sa.hasParam("AnySupportedCard")) {
} else if (sa.hasParam("AnySupportedCard")) {
final String valid = sa.getParam("AnySupportedCard");
List<PaperCard> cards = null;
if (valid.startsWith("Names:")){
@@ -139,8 +139,14 @@ public class PlayEffect extends SpellAbilityEffect {
else {
return;
}
}
else {
} else if (sa.hasParam("CopyFromChosenName")) {
String name = source.getChosenName();
if (name.trim().isEmpty()) return;
Card card = Card.fromPaperCard(StaticData.instance().getCommonCards().getUniqueByName(name), controller);
card.setToken(true);
tgtCards = new CardCollection();
tgtCards.add(card);
} else {
tgtCards = new CardCollection();
// filter only cards that didn't changed zones
for (Card c : getTargetCards(sa)) {
@@ -179,7 +185,16 @@ public class PlayEffect extends SpellAbilityEffect {
while (!tgtCards.isEmpty() && amount > 0) {
activator.getController().tempShowCards(showCards);
Card tgtCard = controller.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblSelectCardToPlay"), optional, null);
Card tgtCard = null;
if (tgtCards.size() == 1 && amount == 1 && optional) {
tgtCard = tgtCards.get(0);
if (!controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", CardTranslation.getTranslatedName(tgtCard.getName())))) {
break;
}
} else {
tgtCard = controller.getController().chooseSingleEntityForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblSelectCardToPlay"), optional, null);
}
activator.getController().endTempShowCards();
if (tgtCard == null) {
break;

View File

@@ -3,6 +3,7 @@ package forge.game.ability.effects;
import java.util.Collections;
import java.util.List;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.GameCommand;
@@ -154,20 +155,27 @@ public class RepeatEachEffect extends SpellAbilityEffect {
game.getUntap().addUntil(p, new GameCommand() {
@Override
public void run() {
List<Object> tempRemembered = Lists.newArrayList(Iterables.filter(source.getRemembered(), Player.class));
source.removeRemembered(tempRemembered);
source.addRemembered(p);
AbilityUtils.resolve(repeat);
source.removeRemembered(p);
source.addRemembered(tempRemembered);
}
});
} else {
// to avoid risk of collision with other abilities swap out other Remembered Player while resolving
List<Object> tempRemembered = Lists.newArrayList(Iterables.filter(source.getRemembered(), Player.class));
source.removeRemembered(tempRemembered);
source.addRemembered(p);
AbilityUtils.resolve(repeat);
source.removeRemembered(p);
source.addRemembered(tempRemembered);
}
}
}
if(sa.hasParam("DamageMap")) {
if (sa.hasParam("DamageMap")) {
game.getAction().dealDamage(false, sa.getDamageMap(), sa.getPreventMap(), sa.getCounterTable(), sa);
}
if (sa.hasParam("ChangeZoneTable")) {

View File

@@ -82,7 +82,7 @@ public class ReplaceManaEffect extends SpellAbilityEffect {
// need to log Updated events there, or the log is wrong order
String message = sa.getReplacementEffect().toString();
if ( !StringUtils.isEmpty(message)) {
if (!StringUtils.isEmpty(message)) {
message = TextUtil.fastReplace(message, "CARDNAME", card.getName());
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
}

View File

@@ -1708,6 +1708,9 @@ public class CardProperty {
}
} else if (property.startsWith("set")) {
final String setCode = property.substring(3, 6);
if (card.getName().isEmpty()) {
return false;
}
final PaperCard setCard = StaticData.instance().getCommonCards().getCardFromEdition(card.getName(), CardDb.SetPreference.Earliest);
if (setCard != null && !setCard.getEdition().equals(setCode)) {
return false;

View File

@@ -1086,7 +1086,7 @@ public class CardView extends GameEntityView {
}
public CardTypeView getType() {
if (isFaceDown() && !isInZone(EnumSet.of(ZoneType.Battlefield, ZoneType.Stack))) {
if (getState() != CardStateName.Original && isFaceDown() && !isInZone(EnumSet.of(ZoneType.Battlefield, ZoneType.Stack))) {
return CardType.EMPTY;
}
return get(TrackableProperty.Type);

View File

@@ -31,6 +31,7 @@ import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
import forge.game.CardTraitBase;
import forge.game.card.Card;
import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
@@ -876,15 +877,30 @@ public class Cost implements Serializable {
costParts.add(0, new CostPartMana(oldManaCost.toManaCost(), r));
}
} else if (part instanceof CostDiscard || part instanceof CostTapType ||
part instanceof CostAddMana || part instanceof CostPayLife) {
part instanceof CostAddMana || part instanceof CostPayLife
|| part instanceof CostPutCounter) {
boolean alreadyAdded = false;
for (final CostPart other : costParts) {
if (other.getClass().equals(part.getClass()) &&
if ((other.getClass().equals(part.getClass()) || (part instanceof CostPutCounter && ((CostPutCounter)part).getCounter().is(CounterEnumType.LOYALTY))) &&
part.getType().equals(other.getType()) &&
StringUtils.isNumeric(part.getAmount()) &&
StringUtils.isNumeric(other.getAmount())) {
final String amount = String.valueOf(Integer.parseInt(part.getAmount()) + Integer.parseInt(other.getAmount()));
if (part instanceof CostDiscard) {
String amount = String.valueOf(Integer.parseInt(part.getAmount()) + Integer.parseInt(other.getAmount()));
if (part instanceof CostPutCounter) { // path for Carth
if (other instanceof CostPutCounter && ((CostPutCounter)other).getCounter().is(CounterEnumType.LOYALTY)) {
costParts.add(new CostPutCounter(amount, CounterType.get(CounterEnumType.LOYALTY), part.getType(), part.getTypeDescription()));
} else if (other instanceof CostRemoveCounter && ((CostRemoveCounter)other).counter.is(CounterEnumType.LOYALTY)) {
Integer counters = Integer.parseInt(other.getAmount()) - Integer.parseInt(part.getAmount());
// the cost can turn positive if multiple Carth raise it
if (counters < 0) {
costParts.add(new CostPutCounter(String.valueOf(counters *-1), CounterType.get(CounterEnumType.LOYALTY), part.getType(), part.getTypeDescription()));
} else {
costParts.add(new CostRemoveCounter(String.valueOf(counters), CounterType.get(CounterEnumType.LOYALTY), part.getType(), part.getTypeDescription(), ZoneType.Battlefield));
}
} else {
continue;
}
} else if (part instanceof CostDiscard) {
costParts.add(new CostDiscard(amount, part.getType(), part.getTypeDescription()));
} else if (part instanceof CostTapType) {
CostTapType tappart = (CostTapType)part;

View File

@@ -86,11 +86,12 @@ public class CostPayment extends ManaConversionMatrix {
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
public static boolean canPayAdditionalCosts(final Cost cost, final SpellAbility ability) {
public static boolean canPayAdditionalCosts(Cost cost, final SpellAbility ability) {
if (cost == null) {
return true;
}
cost = CostAdjustment.adjust(cost, ability);
return cost.canPay(ability);
}

View File

@@ -75,7 +75,7 @@ public class CostPutCounter extends CostPartWithList {
}
@Override
public int paymentOrder() { return 8; }
public int paymentOrder() { return 6; }
@Override
public boolean isReusable() {

View File

@@ -1762,7 +1762,7 @@ public class Player extends GameEntity implements Comparable<Player> {
public final boolean playLand(final Card land, final boolean ignoreZoneAndTiming) {
// Dakkon Blackblade Avatar will use a similar effect
if (canPlayLand(land, ignoreZoneAndTiming)) {
this.playLandNoCheck(land);
playLandNoCheck(land);
return true;
}
@@ -1775,8 +1775,8 @@ public class Player extends GameEntity implements Comparable<Player> {
if (land.isFaceDown()) {
land.turnFaceUp(null);
}
final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null);
game.copyLastState();
final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null);
game.updateLastStateForCard(c);
// play a sound

View File

@@ -31,7 +31,6 @@ public class ReplaceProduceMana extends ReplacementEffect {
*/
@Override
public boolean canReplace(Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Affected))) {
return false;
}

View File

@@ -765,6 +765,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
resetTriggeringObjects();
resetTriggerRemembered();
if (isActivatedAbility()) {
setXManaCostPaid(null);
}
// reset last state when finished resolving
setLastStateBattlefield(CardCollection.EMPTY);
setLastStateGraveyard(CardCollection.EMPTY);

View File

@@ -195,7 +195,6 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
* @return a boolean.
*/
public final boolean checkZoneRestrictions(final Card c, final SpellAbility sa) {
final Player activator = sa.getActivatingPlayer();
final Zone cardZone = c.getLastKnownZone();
Card cp = c;

View File

@@ -366,7 +366,6 @@ public class TargetRestrictions {
return this.saValidTargeting;
}
/**
* <p>
* canOnlyTgtOpponent.

View File

@@ -57,6 +57,9 @@ public class StaticAbilityCantBeCast {
}
public static boolean cantBeActivatedAbility(final SpellAbility spell, final Card card, final Player activator) {
if (spell.isTrigger()) {
return false;
}
final Game game = activator.getGame();
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
@@ -98,7 +101,6 @@ public class StaticAbilityCantBeCast {
* @return true, if successful
*/
public static boolean applyCantBeCastAbility(final StaticAbility stAb, final SpellAbility spell, final Card card, final Player activator) {
if (!stAb.matchesValidParam("ValidCard", card)) {
return false;
}
@@ -153,7 +155,6 @@ public class StaticAbilityCantBeCast {
* @return true, if successful
*/
public static boolean applyCantBeActivatedAbility(final StaticAbility stAb, final SpellAbility spellAbility, final Card card, final Player activator) {
if (!stAb.matchesValidParam("ValidCard", card)) {
return false;
}

View File

@@ -543,7 +543,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
}
private final void finishResolving(final SpellAbility sa, final boolean fizzle) {
// SpellAbility is removed from the stack here
// temporarily removed removing SA after resolution
final SpellAbilityStackInstance si = getInstanceFromSpellAbility(sa);