mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 10:48:00 +00:00
Merge remote-tracking branch 'upstream/master' into collector-number-in-card-list-and-card-db-refactoring
This commit is contained in:
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user