Merge remote-tracking branch 'upstream/master' into collector-number-in-card-list-and-card-db-refactoring

This commit is contained in:
leriomaggio
2021-07-23 22:38:23 +01:00
299 changed files with 3647 additions and 1286 deletions

View File

@@ -148,7 +148,6 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
return getParamOrDefault("Secondary", "False").equals("True");
}
public final boolean isClassAbility() {
return hasParam("ClassLevel");
}

View File

@@ -19,7 +19,6 @@ public class ForgeScript {
public static boolean cardStateHasProperty(CardState cardState, String property, Player sourceController,
Card source, CardTraitBase spellAbility) {
final boolean isColorlessSource = cardState.getCard().hasKeyword("Colorless Damage Source", cardState);
final ColorSet colors = cardState.getCard().determineColor(cardState);
if (property.contains("White") || property.contains("Blue") || property.contains("Black")
@@ -123,7 +122,6 @@ public class ForgeScript {
return Expressions.compare(y, property, x);
} else return cardState.getTypeWithChanges().hasStringType(property);
}
public static boolean spellAbilityHasProperty(SpellAbility sa, String property, Player sourceController,
@@ -194,8 +192,7 @@ public class ForgeScript {
// spell was on the stack
if (sa.getCardState().getCard().isInZone(ZoneType.Stack)) {
y = sa.getHostCard().getCMC();
}
else {
} else {
y = sa.getPayCosts().getTotalMana().getCMC();
}
int x = AbilityUtils.calculateAmount(spellAbility.getHostCard(), property.substring(5), spellAbility);

View File

@@ -805,7 +805,7 @@ public class Game {
getTriggerHandler().clearDelayedTrigger(c);
} else {
// return stolen permanents
if ((c.getController().equals(p) || c.getZone().getPlayer().equals(p)) && c.isInZone(ZoneType.Battlefield)) {
if (c.isInZone(ZoneType.Battlefield) && (c.getController().equals(p) || c.getZone().getPlayer().equals(p))) {
c.removeTempController(p);
getAction().controllerChangeZoneCorrection(c);
}

View File

@@ -118,7 +118,7 @@ public final class GameActionUtil {
continue;
}
// non basic are only allowed if PayManaCost is yes
if (!sa.isBasicSpell() && o.getPayManaCost() == PayManaCost.NO) {
if ((!sa.isBasicSpell() || (sa.costHasManaX() && !sa.getPayCosts().getCostMana().canXbe0())) && o.getPayManaCost() == PayManaCost.NO) {
continue;
}
final Card host = o.getHost();

View File

@@ -42,6 +42,7 @@ import forge.game.card.CardCollectionView;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardState;
import forge.game.card.CardUtil;
import forge.game.card.CounterType;
import forge.game.card.CardPredicates.Presets;
@@ -56,6 +57,7 @@ import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.LandAbility;
import forge.game.spellability.OptionalCost;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
@@ -2856,24 +2858,45 @@ public class AbilityUtils {
public static final List<SpellAbility> getBasicSpellsFromPlayEffect(final Card tgtCard, final Player controller) {
List<SpellAbility> sas = new ArrayList<>();
List<SpellAbility> list = Lists.newArrayList(tgtCard.getBasicSpells());
if (tgtCard.isModal()) {
list.addAll(Lists.newArrayList(tgtCard.getBasicSpells(tgtCard.getState(CardStateName.Modal))));
CardState original = tgtCard.getState(CardStateName.Original);
if (tgtCard.isLand()) {
LandAbility la = new LandAbility(tgtCard, controller, null);
la.setCardState(original);
list.add(la);
}
if (tgtCard.isModal()) {
CardState modal = tgtCard.getState(CardStateName.Modal);
list.addAll(Lists.newArrayList(tgtCard.getBasicSpells(modal)));
if (modal.getType().isLand()) {
LandAbility la = new LandAbility(tgtCard, controller, null);
la.setCardState(modal);
list.add(la);
}
}
for (SpellAbility s : list) {
final Spell newSA = (Spell) s.copy();
newSA.setActivatingPlayer(controller);
SpellAbilityRestriction res = new SpellAbilityRestriction();
// timing restrictions still apply
res.setPlayerTurn(s.getRestrictions().getPlayerTurn());
res.setOpponentTurn(s.getRestrictions().getOpponentTurn());
res.setPhases(s.getRestrictions().getPhases());
res.setZone(null);
newSA.setRestrictions(res);
// timing restrictions still apply
if (res.checkTimingRestrictions(tgtCard, newSA)
// still need to check the other restrictions like Aftermath
&& res.checkOtherRestrictions(tgtCard, newSA, controller)) {
sas.add(newSA);
if (s instanceof LandAbility) {
// CR 305.3
if (controller.getGame().getPhaseHandler().isPlayerTurn(controller) && controller.canPlayLand(tgtCard, true, s)) {
sas.add(s);
}
} else {
final Spell newSA = (Spell) s.copy();
newSA.setActivatingPlayer(controller);
SpellAbilityRestriction res = new SpellAbilityRestriction();
// timing restrictions still apply
res.setPlayerTurn(s.getRestrictions().getPlayerTurn());
res.setOpponentTurn(s.getRestrictions().getOpponentTurn());
res.setPhases(s.getRestrictions().getPhases());
res.setZone(null);
newSA.setRestrictions(res);
// timing restrictions still apply
if (res.checkTimingRestrictions(tgtCard, newSA)
// still need to check the other restrictions like Aftermath
&& res.checkOtherRestrictions(tgtCard, newSA, controller)) {
sas.add(newSA);
}
}
}
return sas;

View File

@@ -11,7 +11,7 @@ import forge.game.spellability.TargetRestrictions;
public class SpellApiBased extends Spell {
private static final long serialVersionUID = -6741797239508483250L;
private final SpellAbilityEffect effect;
public SpellApiBased(ApiType api0, Card sourceCard, Cost abCost, TargetRestrictions tgt, Map<String, String> params0) {
super(sourceCard, abCost);
this.setTargetRestrictions(tgt);

View File

@@ -31,6 +31,7 @@ import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.AlternativeCost;
import forge.game.spellability.LandAbility;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.trigger.TriggerType;
@@ -167,7 +168,7 @@ public class PlayEffect extends SpellAbilityEffect {
}
if (sa.hasParam("ValidSA")) {
final String valid[] = {sa.getParam("ValidSA")};
final String valid[] = sa.getParam("ValidSA").split(",");
Iterator<Card> it = tgtCards.iterator();
while (it.hasNext()) {
Card c = it.next();
@@ -244,8 +245,8 @@ public class PlayEffect extends SpellAbilityEffect {
tgtCards.remove(tgtCard);
}
final Card original = tgtCard;
if (sa.hasParam("CopyCard")) {
final Card original = tgtCard;
final Zone zone = tgtCard.getZone();
tgtCard = Card.fromPaperCard(tgtCard.getPaperCard(), sa.getActivatingPlayer());
@@ -258,23 +259,10 @@ public class PlayEffect extends SpellAbilityEffect {
}
}
// lands will be played
if (tgtCard.isLand()) {
if (controller.playLand(tgtCard, true)) {
amount--;
if (remember) {
source.addRemembered(tgtCard);
}
} else {
tgtCards.remove(tgtCard);
}
continue;
}
// get basic spells (no flashback, etc.)
List<SpellAbility> sas = AbilityUtils.getBasicSpellsFromPlayEffect(tgtCard, controller);
if (sa.hasParam("ValidSA")) {
final String valid[] = {sa.getParam("ValidSA")};
final String valid[] = sa.getParam("ValidSA").split(",");
sas = Lists.newArrayList(Iterables.filter(sas, SpellAbilityPredicates.isValid(valid, controller , source, sa)));
}
if (hasTotalCMCLimit) {
@@ -290,11 +278,6 @@ public class PlayEffect extends SpellAbilityEffect {
continue;
}
// play copied cards with linked abilities, e.g. Elite Arcanist
if (sa.hasParam("CopyOnce")) {
tgtCards.remove(original);
}
SpellAbility tgtSA;
if (!sa.hasParam("CastFaceDown")) {
@@ -313,8 +296,22 @@ public class PlayEffect extends SpellAbilityEffect {
continue;
}
// lands will be played
if (tgtSA instanceof LandAbility) {
tgtSA.resolve();
amount--;
if (remember) {
source.addRemembered(tgtCard);
}
continue;
}
final int tgtCMC = tgtSA.getPayCosts().getTotalMana().getCMC();
// illegal action, cancel early
if ((sa.hasParam("WithoutManaCost") || sa.hasParam("PlayCost")) && tgtSA.costHasManaX() && !tgtSA.getPayCosts().getCostMana().canXbe0()) {
continue;
}
if (sa.hasParam("WithoutManaCost")) {
tgtSA = tgtSA.copyWithNoManaCost();
} else if (sa.hasParam("PlayCost")) {

View File

@@ -12,16 +12,12 @@ import forge.card.CardRulesPredicates;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardFactory;
import forge.game.card.CardUtil;
import forge.game.event.GameEventLandPlayed;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.util.Aggregates;
@@ -60,28 +56,18 @@ public class PlayLandVariantEffect extends SpellAbilityEffect {
}, PaperCard.FN_GET_NAME);
cards = Lists.newArrayList(Iterables.filter(cards, cp));
// get a random basic land
PaperCard ran = Aggregates.random(cards);
Card random = CardFactory.getCard(ran, activator, source.getGame());
Card random;
// if activator cannot play the random land, loop
while (!activator.canPlayLand(random, false) && !cards.isEmpty()) {
cards.remove(ran);
do {
if (cards.isEmpty()) return;
ran = Aggregates.random(cards);
PaperCard ran = Aggregates.random(cards);
random = CardFactory.getCard(ran, activator, game);
}
cards.remove(ran);
} while (!activator.canPlayLand(random, false));
source.addCloneState(CardFactory.getCloneStates(random, source, sa), game.getNextTimestamp());
source.updateStateForView();
source.setController(activator, 0);
game.getAction().moveTo(activator.getZone(ZoneType.Battlefield), source, sa);
// play a sound
game.fireEvent(new GameEventLandPlayed(activator, source));
// Run triggers
game.getTriggerHandler().runTrigger(TriggerType.LandPlayed, AbilityKey.mapFromCard(source), false);
game.getStack().unfreezeStack();
activator.addLandPlayedThisTurn();
activator.playLandNoCheck(source, sa);
}
}

View File

@@ -1832,10 +1832,10 @@ public class CardFactoryUtil {
if (sa.hasParam("NextRoom")) {
boolean first = true;
StringBuilder nextRoomParam = new StringBuilder();
trigStr.append(" ( ");
trigStr.append(" (Leads to: ");
for (String nextRoomSVar : sa.getParam("NextRoom").split(",")) {
if (!first) {
trigStr.append(" or ");
trigStr.append(", ");
nextRoomParam.append(",");
}
String nextRoomName = saMap.get(nextRoomSVar).getParam("RoomName");

View File

@@ -80,7 +80,7 @@ public final class CardPlayOption {
return toString(true);
}
public String toString( final boolean withPlayer) {
public String toString(final boolean withPlayer) {
StringBuilder sb = new StringBuilder(withPlayer ? this.player.toString() : StringUtils.EMPTY);
switch (getPayManaCost()) {

View File

@@ -1695,8 +1695,7 @@ public class Player extends GameEntity implements Comparable<Player> {
for (int i = 0; i < max; i++) {
if (bottom) {
milled.add(lib.get(lib.size() - i - 1));
}
else {
} else {
milled.add(lib.get(i));
}
}
@@ -1758,7 +1757,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)) {
playLandNoCheck(land);
playLandNoCheck(land, null);
return true;
}
@@ -1766,20 +1765,22 @@ public class Player extends GameEntity implements Comparable<Player> {
return false;
}
public final Card playLandNoCheck(final Card land) {
public final Card playLandNoCheck(final Card land, SpellAbility cause) {
land.setController(this, 0);
if (land.isFaceDown()) {
land.turnFaceUp(null);
}
game.copyLastState();
final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null);
final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, cause);
game.updateLastStateForCard(c);
// play a sound
game.fireEvent(new GameEventLandPlayed(this, land));
// Run triggers
game.getTriggerHandler().runTrigger(TriggerType.LandPlayed, AbilityKey.mapFromCard(land), false);
Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(land);
runParams.put(AbilityKey.SpellAbility, cause);
game.getTriggerHandler().runTrigger(TriggerType.LandPlayed, runParams, false);
game.getStack().unfreezeStack();
addLandPlayedThisTurn();

View File

@@ -71,7 +71,7 @@ public class LandAbility extends Ability {
@Override
public void resolve() {
getHostCard().setSplitStateToPlayAbility(this);
final Card result = getActivatingPlayer().playLandNoCheck(getHostCard());
final Card result = getActivatingPlayer().playLandNoCheck(getHostCard(), this);
// increase mayplay used
if (getMayPlay() != null) {

View File

@@ -1116,7 +1116,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
}
public SpellAbility copyWithManaCostReplaced(Player active, Cost abCost) {
final SpellAbility newSA = copy(active);
if (newSA == null) {
return null; // the ability was not copyable, e.g. a Suspend SA may get here
@@ -1995,6 +1994,16 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return false;
}
}
else if (incR[0].equals("Instant")) {
if (!root.getCardState().getType().isInstant()) {
return false;
}
}
else if (incR[0].equals("Sorcery")) {
if (!root.getCardState().getType().isSorcery()) {
return false;
}
}
else if (incR[0].equals("Triggered")) {
if (!root.isTrigger()) {
return false;

View File

@@ -877,7 +877,9 @@ public final class StaticAbilityContinuous {
mayPlayAltCost = mayPlayAltCost.replace("ConvertedManaCost", costcmc);
}
Player mayPlayController = params.containsKey("MayPlayCardOwner") ? affectedCard.getOwner() : controller;
Player mayPlayController = params.containsKey("MayPlayPlayer") ?
AbilityUtils.getDefinedPlayers(affectedCard, params.get("MayPlayPlayer"), stAb).get(0) :
controller;
affectedCard.setMayPlay(mayPlayController, mayPlayWithoutManaCost,
mayPlayAltCost != null ? new Cost(mayPlayAltCost, false) : null,
mayPlayWithFlash, mayPlayGrantZonePermissions, stAb);

View File

@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -28,7 +28,7 @@ import forge.util.Localizer;
* <p>
* Trigger_LandPlayed class.
* </p>
*
*
* @author Forge
* @version $Id$
*/
@@ -38,7 +38,7 @@ public class TriggerLandPlayed extends Trigger {
* <p>
* Constructor for Trigger_LandPlayed.
* </p>
*
*
* @param params
* a {@link java.util.HashMap} object.
* @param host
@@ -71,6 +71,10 @@ public class TriggerLandPlayed extends Trigger {
return false;
}
if (!matchesValidParam("ValidSA", runParams.get(AbilityKey.SpellAbility))) {
return false;
}
if (hasParam("NotFirstLand")) {
Card land = (Card) runParams.get(AbilityKey.Card);
if (land.getController().getLandsPlayedThisTurn() < 1) {