Merge master

This commit is contained in:
Lyu Zong-Hong
2021-02-09 17:15:06 +09:00
345 changed files with 340 additions and 174 deletions

View File

@@ -177,6 +177,12 @@ public class ForgeScript {
return sa.getActivatingPlayer().equals(sourceController);
} else if (property.equals("OppCtrl")) {
return sa.getActivatingPlayer().isOpponentOf(sourceController);
} else if (property.startsWith("cmc")) {
int y = sa.getPayCosts().getTotalMana().getCMC();
int x = AbilityUtils.calculateAmount(spellAbility.getHostCard(), property.substring(5), spellAbility);
if (!Expressions.compare(y, property, x)) {
return false;
}
} else if (sa.getHostCard() != null) {
return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility);
}

View File

@@ -106,6 +106,14 @@ public class GameAction {
}
private Card changeZoneWrapped(final Zone zoneFrom, Zone zoneTo, final Card c, Integer position, SpellAbility cause, Map<AbilityKey, Object> params) {
// 111.11. A copy of a permanent spell becomes a token as it resolves.
// The token has the characteristics of the spell that became that token.
// The token is not “created” for the purposes of any replacement effects or triggered abilities that refer to creating a token.
if (c.isCopiedSpell() && zoneTo.is(ZoneType.Battlefield) && c.isPermanent() && cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
c.setCopiedSpell(false);
c.setToken(true);
}
if (c.isCopiedSpell() || (c.isImmutable() && zoneTo.is(ZoneType.Exile))) {
// Remove Effect from command immediately, this is essential when some replacement
// effects happen during the resolving of a spellability ("the next time ..." effect)
@@ -637,8 +645,11 @@ public class GameAction {
}
public final Card moveToStack(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
final Zone stack = game.getStackZone();
return moveTo(stack, c, cause, params);
Card result = moveTo(game.getStackZone(), c, cause, params);
if (cause != null && cause.isSpell() && result.equals(cause.getHostCard())) {
result.setSplitStateToPlayAbility(cause);
}
return result;
}
public final Card moveToGraveyard(final Card c, SpellAbility cause) {

View File

@@ -3,7 +3,10 @@ package forge.game.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
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.card.ColorSet;
import forge.card.MagicColor;
@@ -1820,7 +1823,11 @@ public class AbilityUtils {
public static final List<SpellAbility> getBasicSpellsFromPlayEffect(final Card tgtCard, final Player controller) {
List<SpellAbility> sas = new ArrayList<>();
for (SpellAbility s : tgtCard.getBasicSpells()) {
List<SpellAbility> list = Lists.newArrayList(tgtCard.getBasicSpells());
if (tgtCard.isModal()) {
list.addAll(Lists.newArrayList(tgtCard.getBasicSpells(tgtCard.getState(CardStateName.Modal))));
}
for (SpellAbility s : list) {
final Spell newSA = (Spell) s.copy();
newSA.setActivatingPlayer(controller);
SpellAbilityRestriction res = new SpellAbilityRestriction();

View File

@@ -10,6 +10,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardZoneTable;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
@@ -25,27 +26,27 @@ public class AttachEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard();
final Game game = host.getGame();
if (host.isAura() && sa.isSpell()) {
final Player ap = sa.getActivatingPlayer();
CardZoneTable table = new CardZoneTable();
host.setController(sa.getActivatingPlayer(), 0);
ZoneType previousZone = host.getZone().getZoneType();
// The Spell_Permanent (Auras) version of this AF needs to
// move the card into play before Attaching
host.setController(ap, 0);
// 111.11. A copy of a permanent spell becomes a token as it resolves.
// The token has the characteristics of the spell that became that token.
// The token is not “created” for the purposes of any replacement effects or triggered abilities that refer to creating a token.
if (host.isCopiedSpell()) {
host.setCopiedSpell(false);
host.setToken(true);
}
final Card c = ap.getGame().getAction().moveToPlay(host, ap, sa);
final Card c = game.getAction().moveToPlay(host, sa);
sa.setHostCard(c);
ZoneType newZone = c.getZone().getZoneType();
if (newZone != previousZone) {
table.put(previousZone, newZone, c);
}
table.triggerChangesZoneAll(game);
}
final Card source = sa.getHostCard();
final Game game = source.getGame();
CardCollection attachments;
final List<GameObject> targets = getDefinedOrTargeted(sa, "Defined");

View File

@@ -2,10 +2,12 @@ package forge.game.ability.effects;
import com.google.common.collect.Lists;
import forge.game.Game;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.card.CardZoneTable;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class PermanentEffect extends SpellAbilityEffect {
@@ -18,25 +20,26 @@ public class PermanentEffect extends SpellAbilityEffect {
*/
@Override
public void resolve(SpellAbility sa) {
Player p = sa.getActivatingPlayer();
sa.getHostCard().setController(p, 0);
final Card host = sa.getHostCard();
final Game game = host.getGame();
CardZoneTable table = new CardZoneTable();
ZoneType previousZone = host.getZone().getZoneType();
// 111.11. A copy of a permanent spell becomes a token as it resolves.
// The token has the characteristics of the spell that became that token.
// The token is not “created” for the purposes of any replacement effects or triggered abilities that refer to creating a token.
if (host.isCopiedSpell()) {
host.setCopiedSpell(false);
host.setToken(true);
}
host.setController(sa.getActivatingPlayer(), 0);
final Card c = p.getGame().getAction().moveToPlay(host, p, sa);
final Card c = game.getAction().moveToPlay(host, sa);
sa.setHostCard(c);
// some extra for Dashing
if (sa.isDash()) {
if (sa.isDash() && c.isInPlay()) {
c.setSVar("EndOfTurnLeavePlay", "Dash");
registerDelayedTrigger(sa, "Hand", Lists.newArrayList(c));
}
ZoneType newZone = c.getZone().getZoneType();
if (newZone != previousZone) {
table.put(previousZone, newZone, c);
}
table.triggerChangesZoneAll(game);
}
}

View File

@@ -1,6 +1,7 @@
package forge.game.ability.effects;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
@@ -28,6 +29,7 @@ import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.AlternativeCost;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.trigger.TriggerType;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
@@ -82,7 +84,7 @@ public class PlayEffect extends SpellAbilityEffect {
tgtCards = new CardCollection(
AbilityUtils.filterListByType(game.getCardsIn(zones), sa.getParam("Valid"), sa)
);
if ( sa.hasParam("ShowCards") ) {
if (sa.hasParam("ShowCards")) {
showCards = new CardCollection(AbilityUtils.filterListByType(game.getCardsIn(zones), sa.getParam("ShowCards"), sa));
}
}
@@ -102,7 +104,7 @@ public class PlayEffect extends SpellAbilityEffect {
final CardCollection choice = new CardCollection();
final String num = sa.hasParam("RandomNum") ? sa.getParam("RandomNum") : "1";
int ncopied = AbilityUtils.calculateAmount(source, num, sa);
while(ncopied > 0) {
while (ncopied > 0) {
final PaperCard cp = Aggregates.random(copysource);
final Card possibleCard = Card.fromPaperCard(cp, sa.getActivatingPlayer());
// Need to temporarily set the Owner so the Game is set
@@ -145,6 +147,21 @@ public class PlayEffect extends SpellAbilityEffect {
return;
}
if (sa.hasParam("ValidSA")) {
final String valid[] = {sa.getParam("ValidSA")};
final Iterator<Card> itr = tgtCards.iterator();
while (itr.hasNext()) {
final Card c = itr.next();
final List<SpellAbility> validSA = Lists.newArrayList(Iterables.filter(AbilityUtils.getBasicSpellsFromPlayEffect(c, controller), SpellAbilityPredicates.isValid(valid, controller , c, sa)));
if (validSA.size() == 0) {
itr.remove();
}
}
if (tgtCards.isEmpty()) {
return;
}
}
if (sa.hasParam("Amount") && sa.getParam("Amount").equals("All")) {
amount = tgtCards.size();
}
@@ -214,7 +231,12 @@ public class PlayEffect extends SpellAbilityEffect {
}
// get basic spells (no flashback, etc.)
final List<SpellAbility> sas = AbilityUtils.getBasicSpellsFromPlayEffect(tgtCard, controller);
List<SpellAbility> sas = AbilityUtils.getBasicSpellsFromPlayEffect(tgtCard, controller);
if (sa.hasParam("ValidSA")) {
final String valid[] = {sa.getParam("ValidSA")};
sas = Lists.newArrayList(Iterables.filter(sas, SpellAbilityPredicates.isValid(valid, controller , source, sa)));
}
if (sas.isEmpty()) {
continue;
}
@@ -233,6 +255,10 @@ public class PlayEffect extends SpellAbilityEffect {
// For Illusionary Mask effect
tgtSA = CardFactoryUtil.abilityMorphDown(tgtCard);
}
// in case player canceled from choice dialog
if (tgtSA == null) {
continue;
}
final boolean noManaCost = sa.hasParam("WithoutManaCost");
if (noManaCost) {

View File

@@ -3,24 +3,40 @@ package forge.game.card;
import java.io.Serializable;
public class CardFaceView implements Serializable, Comparable<CardFaceView> {
private String name;
/**
*
*/
private static final long serialVersionUID = 1874016432028306386L;
private String displayName;
private String oracleName;
public CardFaceView(String faceName) {
this.name = faceName;
public CardFaceView(String displayName) {
this(displayName, displayName);
}
public String getName() { return name;}
public CardFaceView(String displayFaceName, String oracleFaceName ) {
this.displayName = displayFaceName;
this.oracleName = oracleFaceName;
}
public String getName() { return displayName;}
public void setName(String name) {
this.name = name;
this.displayName = name;
}
public String getOracleName() { return oracleName; }
public void setOracleName(String name) {
this.oracleName = name;
}
public String toString() {
return name;
return displayName;
}
@Override
public int compareTo(CardFaceView o) {
return this.getName().compareTo(o.getName());
return this.getOracleName().compareTo(o.getOracleName());
}
}

View File

@@ -651,24 +651,28 @@ public class CardProperty {
if ((property.endsWith("Source") || property.equals("DamagedBy")) &&
!card.getReceivedDamageFromThisTurn().containsKey(source)) {
return false;
} else if (property.endsWith("Remembered")) {
boolean matched = false;
for (final Object obj : source.getRemembered()) {
if (!(obj instanceof Card)) {
continue;
} else {
String prop = property.substring("DamagedBy".length());
boolean found = false;
for (Card d : card.getReceivedDamageFromThisTurn().keySet()) {
if (d.isValid(prop, sourceController, source, spellAbility)) {
found = true;
break;
}
matched |= card.getReceivedDamageFromThisTurn().containsKey(obj);
}
if (!matched)
return false;
} else if (property.endsWith("Equipped")) {
final Card equipee = source.getEquipping();
if (equipee == null || !card.getReceivedDamageFromThisTurn().containsKey(equipee))
return false;
} else if (property.endsWith("Enchanted")) {
final Card equipee = source.getEnchantingCard();
if (equipee == null || !card.getReceivedDamageFromThisTurn().containsKey(equipee))
if (!found) {
for (Card d : AbilityUtils.getDefinedCards(source, prop, spellAbility)) {
if (card.getReceivedDamageFromThisTurn().containsKey(d)) {
found = true;
break;
}
}
}
if (!found) {
return false;
}
}
} else if (property.startsWith("Damaged")) {
if (!card.getDealtDamageToThisTurn().containsKey(source)) {
@@ -1035,17 +1039,21 @@ public class CardProperty {
}
}
return false;
} else if (property.startsWith("ThisTurnEntered")) {
final String restrictions = property.split("ThisTurnEntered_")[1];
final String[] res = restrictions.split("_");
final ZoneType destination = ZoneType.smartValueOf(res[0]);
ZoneType origin = null;
if (res.length > 1 && res[1].equals("from")) {
origin = ZoneType.smartValueOf(res[2]);
} else if (property.equals("ThisTurnEntered")) {
// only check if it entered the Zone this turn
if (card.getTurnInZone() != game.getPhaseHandler().getTurn()) {
return false;
}
List<Card> cards = CardUtil.getThisTurnEntered(destination,
origin, "Card", source);
if (!cards.contains(card)) {
} else if (property.startsWith("ThisTurnEnteredFrom")) {
final String restrictions = property.split("ThisTurnEnteredFrom_")[1];
final String[] res = restrictions.split("_");
final ZoneType origin = ZoneType.smartValueOf(res[0]);
if (card.getTurnInZone() != game.getPhaseHandler().getTurn()) {
return false;
}
if (!card.getZone().isCardAddedThisTurn(card, origin)) {
return false;
}
} else if (property.equals("DiscardedThisTurn")) {

View File

@@ -25,6 +25,7 @@ import forge.util.Lang;
import forge.util.Localizer;
import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
import forge.util.CardTranslation;
import org.apache.commons.lang3.StringUtils;
import java.util.EnumSet;
@@ -933,7 +934,7 @@ public class CardView extends GameEntityView {
}
}
}
return (zone + ' ' + name + " (" + getId() + ")").trim();
return (zone + ' ' + CardTranslation.getTranslatedName(name) + " (" + getId() + ")").trim();
}
public class CardStateView extends TrackableObject {

View File

@@ -211,6 +211,13 @@ public class Zone implements java.io.Serializable, Iterable<Card> {
return getCardsAdded(cardsAddedLastTurn, origin);
}
public final boolean isCardAddedThisTurn(final Card card, final ZoneType origin) {
if (!cardsAddedThisTurn.containsKey(origin)) {
return false;
}
return cardsAddedThisTurn.get(origin).contains(card);
}
private static List<Card> getCardsAdded(final MapOfLists<ZoneType, Card> cardsAdded, final ZoneType origin) {
if (origin != null) {
final Collection<Card> cards = cardsAdded.get(origin);