mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Merge branch 'Card-Forge:master' into charming
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.50-SNAPSHOT</version>
|
||||
<version>1.6.54-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -20,6 +20,7 @@ import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.card.CardView;
|
||||
import forge.game.card.IHasCardView;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
@@ -34,6 +35,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
/** The host card. */
|
||||
protected Card hostCard;
|
||||
protected CardState cardState = null;
|
||||
protected KeywordInterface keyword = null;
|
||||
|
||||
/** The map params. */
|
||||
protected Map<String, String> originalMapParams = Maps.newHashMap(),
|
||||
@@ -55,15 +57,13 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
/** Keys of descriptive (text) parameters. */
|
||||
private static final ImmutableList<String> descriptiveKeys = ImmutableList.<String>builder()
|
||||
.add("Description", "SpellDescription", "StackDescription", "TriggerDescription").build();
|
||||
/** Keys to be followed as SVar names when changing text. */
|
||||
private static final ImmutableList<String> mutableKeys = ImmutableList.<String>builder()
|
||||
.add("AddAbility").build();
|
||||
|
||||
/**
|
||||
* Keys that should not changed
|
||||
*/
|
||||
private static final ImmutableList<String> noChangeKeys = ImmutableList.<String>builder()
|
||||
.add("TokenScript", "LegacyImage", "TokenImage", "NewName", "ChooseFromList").build();
|
||||
.add("TokenScript", "LegacyImage", "TokenImage", "NewName", "ChooseFromList")
|
||||
.add("AddAbility").build();
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -140,6 +140,14 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
this.hostCard = c;
|
||||
}
|
||||
|
||||
public KeywordInterface getKeyword() {
|
||||
return this.keyword;
|
||||
}
|
||||
|
||||
public void setKeyword(final KeywordInterface kw) {
|
||||
this.keyword = kw;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isSecondary.
|
||||
@@ -259,6 +267,18 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
}
|
||||
if (params.containsKey("Revolt")) {
|
||||
if ("True".equalsIgnoreCase(params.get("Revolt")) != hostController.hasRevolt()) return false;
|
||||
else if ("None".equalsIgnoreCase(params.get("Revolt"))) {
|
||||
boolean none = true;
|
||||
for (Player p : game.getRegisteredPlayers()) {
|
||||
if (p.hasRevolt()) {
|
||||
none = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!none) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (params.containsKey("Desert")) {
|
||||
if ("True".equalsIgnoreCase(params.get("Desert")) != hostController.hasDesert()) return false;
|
||||
@@ -422,6 +442,16 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
if (!Expressions.compare(sVar, svarOperator, operandValue)) {
|
||||
return false;
|
||||
}
|
||||
if (hasParam("CheckSecondSVar")) {
|
||||
final int sVar2 = AbilityUtils.calculateAmount(this.hostCard, getParam("CheckSecondSVar"), this);
|
||||
final String comparator2 = getParamOrDefault("SecondSVarCompare", "GE1");
|
||||
final String svarOperator2 = comparator2.substring(0, 2);
|
||||
final String svarOperand2 = comparator2.substring(2);
|
||||
final int operandValue2 = AbilityUtils.calculateAmount(this.hostCard, svarOperand2, this);
|
||||
if (!Expressions.compare(sVar2, svarOperator2, operandValue2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (params.containsKey("ManaSpent")) {
|
||||
@@ -489,11 +519,6 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
} else if (descriptiveKeys.contains(key)) {
|
||||
// change descriptions differently
|
||||
newValue = AbilityUtils.applyDescriptionTextChangeEffects(value, this);
|
||||
} else if (mutableKeys.contains(key)) {
|
||||
// follow SVar and change it
|
||||
final String originalSVarValue = hostCard.getSVar(value);
|
||||
hostCard.changeSVar(value, AbilityUtils.applyAbilityTextChangeEffects(originalSVarValue, this));
|
||||
newValue = null;
|
||||
} else if (this.getHostCard().hasSVar(value)) {
|
||||
// don't change literal SVar names!
|
||||
newValue = null;
|
||||
@@ -652,6 +677,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
copy.setCardState(cardState);
|
||||
// dont use setHostCard to not trigger the not copied parts yet
|
||||
copy.hostCard = host;
|
||||
copy.keyword = this.keyword;
|
||||
}
|
||||
|
||||
abstract public List<Object> getTriggerRemembered();
|
||||
|
||||
@@ -202,6 +202,11 @@ public class ForgeScript {
|
||||
return sa.hasParam("Nightbound");
|
||||
} else if (property.equals("paidPhyrexianMana")) {
|
||||
return sa.getSpendPhyrexianMana();
|
||||
} else if (property.startsWith("ManaSpent")) {
|
||||
String[] k = property.split(" ", 2);
|
||||
String comparator = k[1].substring(0, 2);
|
||||
int y = AbilityUtils.calculateAmount(sa.getHostCard(), k[1].substring(2), sa);
|
||||
return Expressions.compare(sa.getPayingMana().size(), comparator, y);
|
||||
} else if (property.startsWith("ManaFrom")) {
|
||||
final String fromWhat = property.substring(8);
|
||||
boolean found = false;
|
||||
@@ -247,7 +252,7 @@ public class ForgeScript {
|
||||
} else if (property.startsWith("cmc")) {
|
||||
int y = 0;
|
||||
// spell was on the stack
|
||||
if (sa.getCardState().getCard().isInZone(ZoneType.Stack)) {
|
||||
if (sa.getHostCard().isInZone(ZoneType.Stack)) {
|
||||
y = sa.getHostCard().getCMC();
|
||||
} else {
|
||||
y = sa.getPayCosts().getTotalMana().getCMC();
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -47,6 +48,7 @@ import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardDamageHistory;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardUtil;
|
||||
@@ -78,6 +80,7 @@ import forge.trackable.Tracker;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.Visitor;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
/**
|
||||
* Represents the state of a <i>single game</i>, a new instance is created for each game.
|
||||
@@ -119,10 +122,14 @@ public class Game {
|
||||
|
||||
private Table<CounterType, Player, List<Pair<Card, Integer>>> countersAddedThisTurn = HashBasedTable.create();
|
||||
|
||||
private FCollection<CardDamageHistory> globalDamageHistory = new FCollection<>();
|
||||
private IdentityHashMap<Pair<Integer, Boolean>, Pair<Card, GameEntity>> damageThisTurnLKI = new IdentityHashMap<>();
|
||||
|
||||
private Map<Player, Card> topLibsCast = Maps.newHashMap();
|
||||
private Map<Card, Integer> facedownWhileCasting = Maps.newHashMap();
|
||||
|
||||
private Player monarch = null;
|
||||
private Player initiative = null;
|
||||
private Player monarchBeginTurn = null;
|
||||
private Player startingPlayer;
|
||||
|
||||
@@ -173,6 +180,13 @@ public class Game {
|
||||
this.monarchBeginTurn = monarchBeginTurn;
|
||||
}
|
||||
|
||||
public Player getHasInitiative() {
|
||||
return initiative;
|
||||
}
|
||||
public void setHasInitiative(final Player p ) {
|
||||
initiative = p;
|
||||
}
|
||||
|
||||
public CardCollectionView getLastStateBattlefield() {
|
||||
return lastStateBattlefield;
|
||||
}
|
||||
@@ -243,7 +257,7 @@ public class Game {
|
||||
if (c == null) {
|
||||
return null;
|
||||
}
|
||||
return changeZoneLKIInfo.containsKey(c.getId()) ? changeZoneLKIInfo.get(c.getId()) : c;
|
||||
return changeZoneLKIInfo.getOrDefault(c.getId(), c);
|
||||
}
|
||||
public final void clearChangeZoneLKIInfo() {
|
||||
changeZoneLKIInfo.clear();
|
||||
@@ -846,6 +860,18 @@ public class Game {
|
||||
}
|
||||
}
|
||||
|
||||
if (p.hasInitiative()) {
|
||||
// The third way to take the initiative is if the player who currently has the initiative leaves the game.
|
||||
// When that happens, the player whose turn it is takes the initiative.
|
||||
// If the player who has the initiative leaves the game on their own turn,
|
||||
// or the active player left the game at the same time, the next player in turn order takes the initiative.
|
||||
if (p.equals(getPhaseHandler().getPlayerTurn())) {
|
||||
getAction().takeInitiative(getNextPlayerAfter(p), null);
|
||||
} else {
|
||||
getAction().takeInitiative(getPhaseHandler().getPlayerTurn(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove leftover items from
|
||||
getStack().removeInstancesControlledBy(p);
|
||||
|
||||
@@ -1068,6 +1094,7 @@ public class Game {
|
||||
|
||||
public void onCleanupPhase() {
|
||||
clearCounterAddedThisTurn();
|
||||
clearGlobalDamageHistory();
|
||||
// some cards need this info updated even after a player lost, so don't skip them
|
||||
for (Player player : getRegisteredPlayers()) {
|
||||
player.onCleanupPhase();
|
||||
@@ -1109,6 +1136,48 @@ public class Game {
|
||||
countersAddedThisTurn.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the damage instances done this turn.
|
||||
* @param isCombat if true only combat damage matters, pass null for both
|
||||
* @param anyIsEnough if true returns early once result has an entry
|
||||
* @param validSourceCard
|
||||
* @param validTargetEntity
|
||||
* @param source
|
||||
* @param sourceController
|
||||
* @param ctb
|
||||
* @return List<Integer> for each source
|
||||
*/
|
||||
public List<Integer> getDamageDoneThisTurn(Boolean isCombat, boolean anyIsEnough, String validSourceCard, String validTargetEntity, Card source, Player sourceController, CardTraitBase ctb) {
|
||||
final List<Integer> dmgList = Lists.newArrayList();
|
||||
for (CardDamageHistory cdh : globalDamageHistory) {
|
||||
int dmg = cdh.getDamageDoneThisTurn(isCombat, anyIsEnough, validSourceCard, validTargetEntity, source, sourceController, ctb);
|
||||
if (dmg == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dmgList.add(dmg);
|
||||
|
||||
if (anyIsEnough) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return dmgList;
|
||||
}
|
||||
|
||||
public void addGlobalDamageHistory(CardDamageHistory cdh, Pair<Integer, Boolean> dmg, Card source, GameEntity target) {
|
||||
globalDamageHistory.add(cdh);
|
||||
damageThisTurnLKI.put(dmg, Pair.of(source, target));
|
||||
}
|
||||
public void clearGlobalDamageHistory() {
|
||||
globalDamageHistory.clear();
|
||||
damageThisTurnLKI.clear();
|
||||
}
|
||||
|
||||
public Pair<Card, GameEntity> getDamageLKI(Pair<Integer, Boolean> dmg) {
|
||||
return damageThisTurnLKI.get(dmg);
|
||||
}
|
||||
|
||||
public Card getTopLibForPlayer(Player P) {
|
||||
return topLibsCast.get(P);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.util.Set;
|
||||
import forge.util.*;
|
||||
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
@@ -37,6 +36,7 @@ import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
import forge.GameCommand;
|
||||
import forge.StaticData;
|
||||
@@ -66,6 +66,7 @@ import forge.game.event.GameEventGameStarted;
|
||||
import forge.game.event.GameEventScry;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.keyword.KeywordsChange;
|
||||
import forge.game.mulligan.MulliganService;
|
||||
import forge.game.player.GameLossReason;
|
||||
import forge.game.player.Player;
|
||||
@@ -161,14 +162,32 @@ public class GameAction {
|
||||
// need to check before it enters
|
||||
if (c.isAura() && !c.isAttachedToEntity() && toBattlefield && (zoneFrom == null || !zoneFrom.is(ZoneType.Stack))) {
|
||||
boolean found = false;
|
||||
if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(c, null))) {
|
||||
found = true;
|
||||
try {
|
||||
if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(c, null))) {
|
||||
found = true;
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
found = false;
|
||||
}
|
||||
else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(c, null))) {
|
||||
found = true;
|
||||
|
||||
if (!found) {
|
||||
try {
|
||||
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(c, null))) {
|
||||
found = true;
|
||||
}
|
||||
} catch (Exception e2) {
|
||||
found = false;
|
||||
}
|
||||
}
|
||||
else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(c, null))) {
|
||||
found = true;
|
||||
|
||||
if (!found) {
|
||||
try {
|
||||
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(c, null))) {
|
||||
found = true;
|
||||
}
|
||||
} catch (Exception e3) {
|
||||
found = false;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
c.clearControllers();
|
||||
@@ -233,13 +252,6 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up the temporary Dash/Blitz SVar when the card leaves the battlefield
|
||||
// Clean up the temporary AtEOT SVar
|
||||
String endofTurn = c.getSVar("EndOfTurnLeavePlay");
|
||||
if (fromBattlefield && (endofTurn.equals("Dash") || endofTurn.equals("Blitz") || endofTurn.equals("AtEOT"))) {
|
||||
c.removeSVar("EndOfTurnLeavePlay");
|
||||
}
|
||||
|
||||
if (fromBattlefield && !toBattlefield) {
|
||||
c.getController().setRevolt(true);
|
||||
}
|
||||
@@ -253,10 +265,6 @@ public class GameAction {
|
||||
lastKnownInfo = CardUtil.getLKICopy(c);
|
||||
}
|
||||
|
||||
if (!suppress) {
|
||||
copied.setTimestamp(game.getNextTimestamp());
|
||||
}
|
||||
|
||||
if (!lastKnownInfo.hasKeyword("Counters remain on CARDNAME as it moves to any zone other than a player's hand or library.")) {
|
||||
copied.clearCounters();
|
||||
}
|
||||
@@ -281,6 +289,8 @@ public class GameAction {
|
||||
copied = CardFactory.copyCard(c, false);
|
||||
}
|
||||
|
||||
copied.setTimestamp(c.getTimestamp());
|
||||
|
||||
if (zoneTo.is(ZoneType.Stack)) {
|
||||
// when moving to stack, copy changed card information
|
||||
copied.setChangedCardColors(c.getChangedCardColorsTable());
|
||||
@@ -293,7 +303,6 @@ public class GameAction {
|
||||
copied.setDrawnThisTurn(c.getDrawnThisTurn());
|
||||
|
||||
copied.copyChangedTextFrom(c);
|
||||
copied.setTimestamp(c.getTimestamp());
|
||||
|
||||
// clean up changes that come from its own static abilities
|
||||
copied.cleanupCopiedChangesFrom(c);
|
||||
@@ -305,15 +314,20 @@ public class GameAction {
|
||||
|
||||
// copy bestow timestamp
|
||||
copied.setBestowTimestamp(c.getBestowTimestamp());
|
||||
|
||||
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
|
||||
copied.setCastSA(cause);
|
||||
KeywordInterface kw = cause.getKeyword();
|
||||
if (kw != null) {
|
||||
copied.addKeywordForStaticAbility(kw);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// when a card leaves the battlefield, ensure it's in its original state
|
||||
// (we need to do this on the object before copying it, or it won't work correctly e.g.
|
||||
// on Transformed objects)
|
||||
copied.setState(CardStateName.Original, false);
|
||||
copied.setBackSide(false);
|
||||
|
||||
// reset timestamp in changezone effects so they have same timestamp if ETB simultaneously
|
||||
copied.setTimestamp(game.getNextTimestamp());
|
||||
}
|
||||
|
||||
copied.setUnearthed(c.isUnearthed());
|
||||
@@ -337,7 +351,7 @@ public class GameAction {
|
||||
CardCollectionView comCards = c.getOwner().getCardsIn(ZoneType.Command);
|
||||
for (final Card effCard : comCards) {
|
||||
for (final ReplacementEffect re : effCard.getReplacementEffects()) {
|
||||
if (re.hasSVar("CommanderMoveReplacement") && effCard.getEffectSource().getName().equals(c.getRealCommander().getName())) {
|
||||
if (re.hasParam("CommanderMoveReplacement") && c.getMergedCards().contains(effCard.getEffectSource())) {
|
||||
commanderEffect = effCard;
|
||||
break;
|
||||
}
|
||||
@@ -345,8 +359,10 @@ public class GameAction {
|
||||
if (commanderEffect != null) break;
|
||||
}
|
||||
// Disable the commander replacement effect
|
||||
for (final ReplacementEffect re : commanderEffect.getReplacementEffects()) {
|
||||
re.setSuppressed(true);
|
||||
if (commanderEffect != null) {
|
||||
for (final ReplacementEffect re : commanderEffect.getReplacementEffects()) {
|
||||
re.setSuppressed(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +381,7 @@ public class GameAction {
|
||||
}
|
||||
|
||||
ReplacementResult repres = game.getReplacementHandler().run(ReplacementType.Moved, repParams);
|
||||
if (repres != ReplacementResult.NotReplaced) {
|
||||
if (repres != ReplacementResult.NotReplaced && repres != ReplacementResult.Updated) {
|
||||
// reset failed manifested Cards back to original
|
||||
if (c.isManifested() && !c.isInPlay()) {
|
||||
c.forceTurnFaceUp();
|
||||
@@ -395,6 +411,11 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
if (!zoneTo.is(ZoneType.Stack) && !suppress) {
|
||||
// reset timestamp in changezone effects so they have same timestamp if ETB simultaneously
|
||||
copied.setTimestamp(game.getNextTimestamp());
|
||||
}
|
||||
|
||||
copied.getOwner().removeInboundToken(copied);
|
||||
|
||||
// Aura entering as Copy from stack
|
||||
@@ -489,6 +510,37 @@ public class GameAction {
|
||||
if (!zoneTo.is(ZoneType.Exile) && !zoneTo.is(ZoneType.Stack)) {
|
||||
c.cleanupExiledWith();
|
||||
}
|
||||
|
||||
// 400.7a Effects from static abilities that give a permanent spell on the stack an ability
|
||||
// that allows it to be cast for an alternative cost continue to apply to the permanent that spell becomes.
|
||||
if (zoneFrom.is(ZoneType.Stack) && toBattlefield) {
|
||||
List<KeywordInterface> newKw = Lists.newArrayList();
|
||||
for (Table.Cell<Long, Long, KeywordsChange> cell : c.getChangedCardKeywords().cellSet()) {
|
||||
// comes from a static ability
|
||||
if (cell.getColumnKey() == 0) {
|
||||
continue;
|
||||
}
|
||||
for (KeywordInterface ki : cell.getValue().getKeywords()) {
|
||||
boolean keepKeyword = false;
|
||||
for (SpellAbility sa : ki.getAbilities()) {
|
||||
if (!sa.isSpell()) {
|
||||
continue;
|
||||
}
|
||||
if (sa.getAlternativeCost() != null) {
|
||||
keepKeyword = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (keepKeyword) {
|
||||
ki.setHostCard(copied);
|
||||
newKw.add(ki);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!newKw.isEmpty()) {
|
||||
copied.addChangedCardKeywordsInternal(newKw, null, false, copied.getTimestamp(), 0, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if an adventureCard is put from Stack somewhere else, need to reset to Original State
|
||||
@@ -498,15 +550,6 @@ public class GameAction {
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
|
||||
// need to suspend cards own replacement effects
|
||||
if (!suppress) {
|
||||
if (toBattlefield && !copied.getEtbCounters().isEmpty()) {
|
||||
for (final ReplacementEffect re : copied.getReplacementEffects()) {
|
||||
re.setSuppressed(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mergedCards != null) {
|
||||
// Move components of merged permanent here
|
||||
// Also handle 723.3e and 903.9a
|
||||
@@ -553,14 +596,9 @@ public class GameAction {
|
||||
}
|
||||
|
||||
// do ETB counters after zone add
|
||||
if (!suppress) {
|
||||
if (toBattlefield) {
|
||||
copied.putEtbCounters(table);
|
||||
// enable replacement effects again
|
||||
for (final ReplacementEffect re : copied.getReplacementEffects()) {
|
||||
re.setSuppressed(false);
|
||||
}
|
||||
}
|
||||
if (!suppress && toBattlefield && !copied.getEtbCounters().isEmpty()) {
|
||||
game.getTriggerHandler().registerActiveTrigger(copied, false);
|
||||
copied.putEtbCounters(table);
|
||||
copied.clearEtbCounters();
|
||||
}
|
||||
|
||||
@@ -581,7 +619,7 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
table.replaceCounterEffect(game, null, true);
|
||||
table.replaceCounterEffect(game, null, true, true, params);
|
||||
|
||||
// Need to apply any static effects to produce correct triggers
|
||||
checkStaticAbilities();
|
||||
@@ -1059,6 +1097,24 @@ public class GameAction {
|
||||
return holdCheckingStaticAbilities;
|
||||
}
|
||||
|
||||
// This doesn't check layers or if the ability gets removed by other effects
|
||||
public boolean hasStaticAbilityAffectingZone(ZoneType zone, StaticAbilityLayer layer) {
|
||||
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||
if (!stAb.getParam("Mode").equals("Continuous") || stAb.isSuppressed() || !stAb.checkConditions()) {
|
||||
continue;
|
||||
}
|
||||
if (layer != null && !stAb.getLayers().contains(layer)) {
|
||||
continue;
|
||||
}
|
||||
if (ZoneType.listValueOf(stAb.getParamOrDefault("AffectedZone", ZoneType.Battlefield.toString())).contains(zone)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public final void checkStaticAbilities() {
|
||||
checkStaticAbilities(true);
|
||||
}
|
||||
@@ -1183,26 +1239,26 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
for (Player p : game.getPlayers()) {
|
||||
for (Card c : p.getCardsIn(ZoneType.Battlefield).threadSafeIterable()) {
|
||||
if (!c.getController().equals(p)) {
|
||||
controllerChangeZoneCorrection(c);
|
||||
affectedCards.add(c);
|
||||
}
|
||||
if (c.isCreature() && c.isPaired()) {
|
||||
Card partner = c.getPairedWith();
|
||||
if (!partner.isCreature() || c.getController() != partner.getController() || !c.isInPlay()) {
|
||||
c.setPairedWith(null);
|
||||
partner.setPairedWith(null);
|
||||
affectedCards.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// preList means that this is run by a pre Check with LKI objects
|
||||
// in that case Always trigger should not Run
|
||||
if (preList.isEmpty()) {
|
||||
for (Player p : game.getPlayers()) {
|
||||
for (Card c : p.getCardsIn(ZoneType.Battlefield).threadSafeIterable()) {
|
||||
if (!c.getController().equals(p)) {
|
||||
controllerChangeZoneCorrection(c);
|
||||
affectedCards.add(c);
|
||||
}
|
||||
if (c.isCreature() && c.isPaired()) {
|
||||
Card partner = c.getPairedWith();
|
||||
if (!partner.isCreature() || c.getController() != partner.getController() || !c.isInPlay()) {
|
||||
c.setPairedWith(null);
|
||||
partner.setPairedWith(null);
|
||||
affectedCards.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Always, runParams, false);
|
||||
|
||||
@@ -1217,6 +1273,8 @@ public class GameAction {
|
||||
c.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering
|
||||
}
|
||||
|
||||
// TODO filter out old copies from zone change
|
||||
|
||||
if (runEvents && !affectedCards.isEmpty()) {
|
||||
game.fireEvent(new GameEventCardStatsChanged(affectedCards));
|
||||
}
|
||||
@@ -1293,33 +1351,13 @@ public class GameAction {
|
||||
noRegCreats.add(c);
|
||||
checkAgain = true;
|
||||
} else if (c.hasKeyword("CARDNAME can't be destroyed by lethal damage unless lethal damage dealt by a single source is marked on it.")) {
|
||||
// merge entries with same source
|
||||
List<Integer> dmgList = Lists.newArrayList();
|
||||
List<Pair<Card, Integer>> remainingDamaged = Lists.newArrayList(c.getReceivedDamageFromThisTurn());
|
||||
while (!remainingDamaged.isEmpty()) {
|
||||
Pair <Card, Integer> damaged = remainingDamaged.get(0);
|
||||
int sum = damaged.getRight();
|
||||
remainingDamaged.remove(damaged);
|
||||
for (Pair<Card, Integer> other : Lists.newArrayList(remainingDamaged)) {
|
||||
if (other.getLeft().equalsWithTimestamp(damaged.getLeft())) {
|
||||
sum += other.getRight();
|
||||
// once it got counted keep it out
|
||||
remainingDamaged.remove(other);
|
||||
}
|
||||
}
|
||||
dmgList.add(sum);
|
||||
}
|
||||
|
||||
for (final Integer dmg : dmgList) {
|
||||
if (c.getLethal() <= dmg.intValue() || c.hasBeenDealtDeathtouchDamage()) {
|
||||
if (desCreats == null) {
|
||||
desCreats = new CardCollection();
|
||||
}
|
||||
desCreats.add(c);
|
||||
c.setHasBeenDealtDeathtouchDamage(false);
|
||||
checkAgain = true;
|
||||
break;
|
||||
if (c.getLethal() <= c.getMaxDamageFromSource() || c.hasBeenDealtDeathtouchDamage()) {
|
||||
if (desCreats == null) {
|
||||
desCreats = new CardCollection();
|
||||
}
|
||||
desCreats.add(c);
|
||||
c.setHasBeenDealtDeathtouchDamage(false);
|
||||
checkAgain = true;
|
||||
}
|
||||
}
|
||||
// Rule 704.5g - Destroy due to lethal damage
|
||||
@@ -1546,7 +1584,7 @@ public class GameAction {
|
||||
c.getGame().getTracker().flush();
|
||||
|
||||
c.setMoveToCommandZone(false);
|
||||
if (c.getOwner().getController().confirmAction(c.getFirstSpellAbility(), PlayerActionConfirmMode.ChangeZoneToAltDestination, c.getName() + ": If a commander is in a graveyard or in exile and that card was put into that zone since the last time state-based actions were checked, its owner may put it into the command zone.")) {
|
||||
if (c.getOwner().getController().confirmAction(c.getFirstSpellAbility(), PlayerActionConfirmMode.ChangeZoneToAltDestination, c.getName() + ": If a commander is in a graveyard or in exile and that card was put into that zone since the last time state-based actions were checked, its owner may put it into the command zone.", null)) {
|
||||
moveTo(c.getOwner().getZone(ZoneType.Command), c, null);
|
||||
return true;
|
||||
}
|
||||
@@ -2203,6 +2241,32 @@ public class GameAction {
|
||||
game.getTriggerHandler().runTrigger(TriggerType.BecomeMonarch, runParams, false);
|
||||
}
|
||||
|
||||
public void takeInitiative(final Player p, final String set) {
|
||||
final Player previous = game.getHasInitiative();
|
||||
if (p == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!p.equals(previous)) {
|
||||
if (previous != null) {
|
||||
previous.removeInitiativeEffect();
|
||||
}
|
||||
|
||||
if (p.hasLost()) { // the person who should take initiative is gone, it goes to next player
|
||||
takeInitiative(game.getNextPlayerAfter(p), set);
|
||||
}
|
||||
|
||||
game.setHasInitiative(p);
|
||||
p.createInitiativeEffect(set);
|
||||
}
|
||||
|
||||
// You can take the initiative even if you already have it
|
||||
// Run triggers
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, p);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.TakesInitiative, runParams, false);
|
||||
}
|
||||
|
||||
// Make scry an action function so that it can be used for mulligans (with a null cause)
|
||||
// Assumes that the list of players is in APNAP order, which should be the case
|
||||
// Optional here as well to handle the way that mulligans do the choice
|
||||
@@ -2271,13 +2335,16 @@ public class GameAction {
|
||||
final Player p = e.getKey();
|
||||
final CardCollection toTop = e.getValue().getLeft();
|
||||
final CardCollection toBottom = e.getValue().getRight();
|
||||
int numLookedAt = 0;
|
||||
if (toTop != null) {
|
||||
numLookedAt += toTop.size();
|
||||
Collections.reverse(toTop); // reverse to get the correct order
|
||||
for (Card c : toTop) {
|
||||
moveToLibrary(c, cause, null);
|
||||
}
|
||||
}
|
||||
if (toBottom != null) {
|
||||
numLookedAt += toBottom.size();
|
||||
for (Card c : toBottom) {
|
||||
moveToBottomOfLibrary(c, cause, null);
|
||||
}
|
||||
@@ -2287,6 +2354,7 @@ public class GameAction {
|
||||
// set up triggers (but not actually do them until later)
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, p);
|
||||
runParams.put(AbilityKey.ScryNum, numLookedAt);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Scry, runParams, false);
|
||||
}
|
||||
}
|
||||
@@ -2308,6 +2376,7 @@ public class GameAction {
|
||||
game.getReplacementHandler().runReplaceDamage(isCombat, damageMap, preventMap, counterTable, cause);
|
||||
|
||||
Map<Card, Integer> lethalDamage = Maps.newHashMap();
|
||||
Map<Integer, Card> lkiCache = Maps.newHashMap();
|
||||
|
||||
// Actually deal damage according to replaced damage map
|
||||
for (Map.Entry<Card, Map<GameEntity, Integer>> et : damageMap.rowMap().entrySet()) {
|
||||
@@ -2334,6 +2403,8 @@ public class GameAction {
|
||||
|
||||
e.setValue(Integer.valueOf(e.getKey().addDamageAfterPrevention(e.getValue(), sourceLKI, isCombat, counterTable)));
|
||||
sum += e.getValue();
|
||||
|
||||
sourceLKI.getDamageHistory().registerDamage(e.getValue(), isCombat, sourceLKI, e.getKey(), lkiCache);
|
||||
}
|
||||
|
||||
if (sum > 0 && sourceLKI.hasKeyword(Keyword.LIFELINK)) {
|
||||
|
||||
@@ -42,17 +42,12 @@ import forge.game.player.PlayerController;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementHandler;
|
||||
import forge.game.replacement.ReplacementLayer;
|
||||
import forge.game.spellability.AbilityManaPart;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.AlternativeCost;
|
||||
import forge.game.spellability.OptionalCost;
|
||||
import forge.game.spellability.OptionalCostValue;
|
||||
import forge.game.spellability.Spell;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityRestriction;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.staticability.StaticAbilityLayer;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.Zone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Lang;
|
||||
import forge.util.TextUtil;
|
||||
@@ -91,7 +86,11 @@ public final class GameActionUtil {
|
||||
Card source = sa.getHostCard();
|
||||
final Game game = source.getGame();
|
||||
|
||||
if (sa.isSpell() && !source.isInPlay()) {
|
||||
if (sa.isSpell() && source.isInPlay()) {
|
||||
return alternatives;
|
||||
}
|
||||
|
||||
if (sa.isSpell()) {
|
||||
boolean lkicheck = false;
|
||||
|
||||
Card newHost = ((Spell)sa).getAlternateHost(source);
|
||||
@@ -179,12 +178,13 @@ public final class GameActionUtil {
|
||||
|
||||
SpellAbility newSA;
|
||||
if (source.getAlternateState().getType().hasSubtype("Aura")) {
|
||||
newSA = source.getAlternateState().getFirstAbility().copyWithManaCostReplaced(activator,
|
||||
disturbCost);
|
||||
newSA = source.getAlternateState().getFirstAbility().copyWithManaCostReplaced(activator, disturbCost);
|
||||
} else {
|
||||
newSA = sa.copyWithManaCostReplaced(activator, disturbCost);
|
||||
newSA = new SpellPermanent(source);
|
||||
newSA.setCardState(source.getAlternateState());
|
||||
newSA.setPayCosts(disturbCost);
|
||||
newSA.setActivatingPlayer(activator);
|
||||
}
|
||||
newSA.setActivatingPlayer(activator);
|
||||
|
||||
newSA.putParam("PrecostDesc", "Disturb —");
|
||||
newSA.putParam("CostDesc", disturbCost.toString());
|
||||
@@ -210,7 +210,6 @@ public final class GameActionUtil {
|
||||
final Cost escapeCost = new Cost(k[1], true);
|
||||
|
||||
final SpellAbility newSA = sa.copyWithManaCostReplaced(activator, escapeCost);
|
||||
newSA.setActivatingPlayer(activator);
|
||||
|
||||
newSA.putParam("PrecostDesc", "Escape—");
|
||||
newSA.putParam("CostDesc", escapeCost.toString());
|
||||
@@ -290,6 +289,35 @@ public final class GameActionUtil {
|
||||
foretold.putParam("AfterDescription", "(Foretold)");
|
||||
alternatives.add(foretold);
|
||||
}
|
||||
|
||||
// some needs to check after ability was put on the stack
|
||||
// Currently this is only checked for Toolbox and that only cares about creature spells
|
||||
if (source.isCreature() && game.getAction().hasStaticAbilityAffectingZone(ZoneType.Stack, StaticAbilityLayer.ABILITIES)) {
|
||||
Zone oldZone = source.getLastKnownZone();
|
||||
Card blitzCopy = source;
|
||||
if (!source.isLKI()) {
|
||||
blitzCopy = CardUtil.getLKICopy(source);
|
||||
}
|
||||
blitzCopy.setLastKnownZone(game.getStackZone());
|
||||
lkicheck = true;
|
||||
|
||||
blitzCopy.clearStaticChangedCardKeywords(false);
|
||||
CardCollection preList = new CardCollection(blitzCopy);
|
||||
game.getAction().checkStaticAbilities(false, Sets.newHashSet(blitzCopy), preList);
|
||||
|
||||
// currently only for Keyword BLitz, but should affect Dash probably too
|
||||
for (final KeywordInterface inst : blitzCopy.getKeywords(Keyword.BLITZ)) {
|
||||
// TODO with mana value 4 or greater has blitz.
|
||||
for (SpellAbility iSa : inst.getAbilities()) {
|
||||
// do only non intrinsic
|
||||
if (!iSa.isIntrinsic()) {
|
||||
alternatives.add(iSa);
|
||||
}
|
||||
}
|
||||
}
|
||||
// need to reset to Old Zone, or canPlay would fail
|
||||
blitzCopy.setLastKnownZone(oldZone);
|
||||
}
|
||||
}
|
||||
|
||||
// reset static abilities
|
||||
@@ -300,74 +328,73 @@ public final class GameActionUtil {
|
||||
// need to unfreeze tracker
|
||||
game.getTracker().unfreeze();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (sa.isManaAbility() && sa.isActivatedAbility() && activator.hasKeyword("Piracy") && source.isLand() && source.isInPlay() && !activator.equals(source.getController()) && sa.getPayCosts().hasTapCost()) {
|
||||
SpellAbility newSA = sa.copy(activator);
|
||||
// to bypass Activator restriction, set Activator to Player
|
||||
newSA.getRestrictions().setActivator("Player");
|
||||
|
||||
if (sa.isManaAbility() && sa.isActivatedAbility() && activator.hasKeyword("Piracy") && source.isLand() && source.isInPlay() && !activator.equals(source.getController()) && sa.getPayCosts().hasTapCost()) {
|
||||
SpellAbility newSA = sa.copy(activator);
|
||||
// to bypass Activator restriction, set Activator to Player
|
||||
newSA.getRestrictions().setActivator("Player");
|
||||
|
||||
// extra Mana restriction to only Spells
|
||||
for (AbilityManaPart mp : newSA.getAllManaParts()) {
|
||||
mp.setExtraManaRestriction("Spell");
|
||||
}
|
||||
alternatives.add(newSA);
|
||||
}
|
||||
|
||||
// below are for some special cases of activated abilities
|
||||
if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) {
|
||||
for (final KeywordInterface inst : source.getKeywords()) {
|
||||
// need to find the correct Keyword from which this Ability is from
|
||||
if (!inst.getAbilities().contains(sa)) {
|
||||
continue;
|
||||
// extra Mana restriction to only Spells
|
||||
for (AbilityManaPart mp : newSA.getAllManaParts()) {
|
||||
mp.setExtraManaRestriction("Spell");
|
||||
}
|
||||
|
||||
// set the cost to this directly to bypass non mana cost
|
||||
final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>");
|
||||
newSA.setActivatingPlayer(activator);
|
||||
newSA.putParam("CostDesc", ManaCostParser.parse("0"));
|
||||
|
||||
// need to build a new Keyword to get better Reminder Text
|
||||
String data[] = inst.getOriginal().split(":");
|
||||
data[1] = "0";
|
||||
KeywordInterface newKi = Keyword.getInstance(StringUtils.join(data, ":"));
|
||||
|
||||
// makes new SpellDescription
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(newSA.getCostDescription());
|
||||
sb.append("(").append(newKi.getReminderText()).append(")");
|
||||
newSA.setDescription(sb.toString());
|
||||
|
||||
alternatives.add(newSA);
|
||||
}
|
||||
}
|
||||
if (sa.isEquip() && activator.hasKeyword("You may pay 0 rather than pay equip costs.")) {
|
||||
for (final KeywordInterface inst : source.getKeywords()) {
|
||||
// need to find the correct Keyword from which this Ability is from
|
||||
if (!inst.getAbilities().contains(sa)) {
|
||||
continue;
|
||||
|
||||
// below are for some special cases of activated abilities
|
||||
if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) {
|
||||
for (final KeywordInterface inst : source.getKeywords()) {
|
||||
// need to find the correct Keyword from which this Ability is from
|
||||
if (!inst.getAbilities().contains(sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// set the cost to this directly to bypass non mana cost
|
||||
final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>");
|
||||
newSA.setActivatingPlayer(activator);
|
||||
newSA.putParam("CostDesc", ManaCostParser.parse("0"));
|
||||
|
||||
// need to build a new Keyword to get better Reminder Text
|
||||
String data[] = inst.getOriginal().split(":");
|
||||
data[1] = "0";
|
||||
KeywordInterface newKi = Keyword.getInstance(StringUtils.join(data, ":"));
|
||||
|
||||
// makes new SpellDescription
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(newSA.getCostDescription());
|
||||
sb.append("(").append(newKi.getReminderText()).append(")");
|
||||
newSA.setDescription(sb.toString());
|
||||
|
||||
alternatives.add(newSA);
|
||||
}
|
||||
}
|
||||
if (sa.isEquip() && activator.hasKeyword("You may pay 0 rather than pay equip costs.")) {
|
||||
for (final KeywordInterface inst : source.getKeywords()) {
|
||||
// need to find the correct Keyword from which this Ability is from
|
||||
if (!inst.getAbilities().contains(sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// set the cost to this directly to bypass non mana cost
|
||||
SpellAbility newSA = sa.copyWithDefinedCost("0");
|
||||
newSA.setActivatingPlayer(activator);
|
||||
newSA.putParam("CostDesc", ManaCostParser.parse("0"));
|
||||
// set the cost to this directly to bypass non mana cost
|
||||
SpellAbility newSA = sa.copyWithDefinedCost("0");
|
||||
newSA.setActivatingPlayer(activator);
|
||||
newSA.putParam("CostDesc", ManaCostParser.parse("0"));
|
||||
|
||||
// need to build a new Keyword to get better Reminder Text
|
||||
String data[] = inst.getOriginal().split(":");
|
||||
data[1] = "0";
|
||||
KeywordInterface newKi = Keyword.getInstance(StringUtils.join(data, ":"));
|
||||
// need to build a new Keyword to get better Reminder Text
|
||||
String data[] = inst.getOriginal().split(":");
|
||||
data[1] = "0";
|
||||
KeywordInterface newKi = Keyword.getInstance(StringUtils.join(data, ":"));
|
||||
|
||||
// makes new SpellDescription
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(newSA.getCostDescription());
|
||||
sb.append("(").append(newKi.getReminderText()).append(")");
|
||||
newSA.setDescription(sb.toString());
|
||||
// makes new SpellDescription
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(newSA.getCostDescription());
|
||||
sb.append("(").append(newKi.getReminderText()).append(")");
|
||||
newSA.setDescription(sb.toString());
|
||||
|
||||
alternatives.add(newSA);
|
||||
alternatives.add(newSA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return alternatives;
|
||||
}
|
||||
|
||||
@@ -677,13 +704,12 @@ public final class GameActionUtil {
|
||||
if (!StringUtils.isNumeric(amount)) {
|
||||
sa.setSVar(amount, sourceCard.getSVar(amount));
|
||||
}
|
||||
CardFactoryUtil.setupETBReplacementAbility(sa);
|
||||
|
||||
String desc = "It enters the battlefield with ";
|
||||
desc += Lang.nounWithNumeral(amount, CounterType.getType(counter).getName() + " counter");
|
||||
desc += " on it.";
|
||||
|
||||
String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc;
|
||||
String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | ReplacementResult$ Updated | Description$ " + desc;
|
||||
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
|
||||
re.setLayer(ReplacementLayer.Other);
|
||||
|
||||
@@ -17,9 +17,13 @@
|
||||
*/
|
||||
package forge.game;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -46,6 +50,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
private String name = "";
|
||||
protected CardCollection attachedCards = new CardCollection();
|
||||
protected Map<CounterType, Integer> counters = Maps.newHashMap();
|
||||
protected List<Pair<Integer, Boolean>> damageReceivedThisTurn = Lists.newArrayList();
|
||||
|
||||
protected GameEntity(int id0) {
|
||||
id = id0;
|
||||
@@ -330,6 +335,30 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
addCounterInternal(CounterType.get(counterType), n, source, fireEvents, table);
|
||||
}
|
||||
|
||||
public void receiveDamage(Pair<Integer, Boolean> dmg) {
|
||||
damageReceivedThisTurn.add(dmg);
|
||||
}
|
||||
|
||||
public final int getAssignedDamage() {
|
||||
return getAssignedDamage(null, null);
|
||||
}
|
||||
public final int getAssignedCombatDamage() {
|
||||
return getAssignedDamage(true, null);
|
||||
}
|
||||
public final int getAssignedDamage(Boolean isCombat, final Card source) {
|
||||
int num = 0;
|
||||
for (Pair<Integer, Boolean> dmg : damageReceivedThisTurn) {
|
||||
if (isCombat != null && dmg.getRight() != isCombat) {
|
||||
continue;
|
||||
}
|
||||
if (source != null && !getGame().getDamageLKI(dmg).getLeft().equalsWithTimestamp(source)) {
|
||||
continue;
|
||||
}
|
||||
num += dmg.getLeft();
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o) {
|
||||
if (o == null) { return false; }
|
||||
|
||||
@@ -94,7 +94,9 @@ public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, Ga
|
||||
for (Map<CounterType, Integer> cm : gm.getValue().values()) {
|
||||
Integer old = ObjectUtils.firstNonNull(result.get(gm.getKey()), 0);
|
||||
Integer v = ObjectUtils.firstNonNull(cm.get(type), 0);
|
||||
result.put(gm.getKey(), old + v);
|
||||
if (old + v > 0) {
|
||||
result.put(gm.getKey(), old + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,8 +122,12 @@ public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, Ga
|
||||
game.getTriggerHandler().runTrigger(TriggerType.CounterAddedAll, runParams, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void replaceCounterEffect(final Game game, final SpellAbility cause, final boolean effect) {
|
||||
replaceCounterEffect(game, cause, effect, false, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void replaceCounterEffect(final Game game, final SpellAbility cause, final boolean effect, final boolean etb, Map<AbilityKey, Object> params) {
|
||||
if (isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -133,6 +139,10 @@ public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, Ga
|
||||
repParams.put(AbilityKey.Cause, cause);
|
||||
repParams.put(AbilityKey.EffectOnly, effect);
|
||||
repParams.put(AbilityKey.CounterMap, values);
|
||||
repParams.put(AbilityKey.ETB, etb);
|
||||
if (params != null) {
|
||||
repParams.putAll(params);
|
||||
}
|
||||
|
||||
switch (game.getReplacementHandler().run(ReplacementType.AddCounter, repParams)) {
|
||||
case NotReplaced:
|
||||
|
||||
@@ -48,7 +48,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
public enum FormatType {
|
||||
SANCTIONED,
|
||||
CASUAL,
|
||||
HISTORIC,
|
||||
ARCHIVED,
|
||||
DIGITAL,
|
||||
CUSTOM
|
||||
}
|
||||
@@ -290,7 +290,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
if (other.formatSubType != formatSubType){
|
||||
return formatSubType.compareTo(other.formatSubType);
|
||||
}
|
||||
if (formatType.equals(FormatType.HISTORIC)){
|
||||
if (formatType.equals(FormatType.ARCHIVED)){
|
||||
int compareDates = this.effectiveDate.compareTo(other.effectiveDate);
|
||||
if (compareDates != 0)
|
||||
return compareDates;
|
||||
@@ -306,7 +306,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
|
||||
public static class Reader extends StorageReaderRecursiveFolderWithUserFolder<GameFormat> {
|
||||
List<GameFormat> naturallyOrdered = new ArrayList<>();
|
||||
boolean includeHistoric;
|
||||
boolean includeArchived;
|
||||
private List<String> coreFormats = new ArrayList<>();
|
||||
{
|
||||
coreFormats.add("Standard.txt");
|
||||
@@ -321,14 +321,14 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
coreFormats.add("Oathbreaker.txt");
|
||||
}
|
||||
|
||||
public Reader(File forgeFormats, File customFormats, boolean includeHistoric) {
|
||||
public Reader(File forgeFormats, File customFormats, boolean includeArchived) {
|
||||
super(forgeFormats, customFormats, GameFormat.FN_GET_NAME);
|
||||
this.includeHistoric=includeHistoric;
|
||||
this.includeArchived=includeArchived;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GameFormat read(File file) {
|
||||
if (!includeHistoric && !coreFormats.contains(file.getName())) {
|
||||
if (!includeArchived && !coreFormats.contains(file.getName())) {
|
||||
return null;
|
||||
}
|
||||
final Map<String, List<String>> contents = FileSection.parseSections(FileUtil.readFile(file));
|
||||
@@ -348,7 +348,12 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
try {
|
||||
formatType = FormatType.valueOf(section.get("type").toUpperCase());
|
||||
} catch (Exception e) {
|
||||
formatType = FormatType.CUSTOM;
|
||||
if ("HISTORIC".equals(section.get("type").toUpperCase())) {
|
||||
System.out.println("Historic is no longer used as a format Type. Please update " + file.getAbsolutePath() + " to use 'Archived' instead");
|
||||
formatType = FormatType.ARCHIVED;
|
||||
} else {
|
||||
formatType = FormatType.CUSTOM;
|
||||
}
|
||||
}
|
||||
FormatSubType formatsubType;
|
||||
try {
|
||||
@@ -450,7 +455,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
public Iterable<GameFormat> getFilterList() {
|
||||
List<GameFormat> coreList = new ArrayList<>();
|
||||
for (GameFormat format: naturallyOrdered) {
|
||||
if (!format.getFormatType().equals(FormatType.HISTORIC)
|
||||
if (!format.getFormatType().equals(FormatType.ARCHIVED)
|
||||
&&!format.getFormatType().equals(FormatType.DIGITAL)){
|
||||
coreList.add(format);
|
||||
}
|
||||
@@ -458,10 +463,10 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
return coreList;
|
||||
}
|
||||
|
||||
public Iterable<GameFormat> getHistoricList() {
|
||||
public Iterable<GameFormat> getArchivedList() {
|
||||
List<GameFormat> coreList = new ArrayList<>();
|
||||
for (GameFormat format: naturallyOrdered) {
|
||||
if (format.getFormatType().equals(FormatType.HISTORIC)){
|
||||
if (format.getFormatType().equals(FormatType.ARCHIVED)){
|
||||
coreList.add(format);
|
||||
}
|
||||
}
|
||||
@@ -470,7 +475,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
|
||||
public Iterable<GameFormat> getBlockList() {
|
||||
List<GameFormat> blockFormats = new ArrayList<>();
|
||||
for (GameFormat format : this.getHistoricList()){
|
||||
for (GameFormat format : this.getArchivedList()){
|
||||
if (format.getFormatSubType() != GameFormat.FormatSubType.BLOCK)
|
||||
continue;
|
||||
if (!format.getName().endsWith("Block"))
|
||||
@@ -481,10 +486,10 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
return blockFormats;
|
||||
}
|
||||
|
||||
public Map<String, List<GameFormat>> getHistoricMap() {
|
||||
public Map<String, List<GameFormat>> getArchivedMap() {
|
||||
Map<String, List<GameFormat>> coreList = new HashMap<>();
|
||||
for (GameFormat format: naturallyOrdered){
|
||||
if (format.getFormatType().equals(FormatType.HISTORIC)){
|
||||
if (format.getFormatType().equals(FormatType.ARCHIVED)){
|
||||
String alpha = format.getName().substring(0,1);
|
||||
if (!coreList.containsKey(alpha)) {
|
||||
coreList.put(alpha,new ArrayList<>());
|
||||
@@ -557,9 +562,9 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
//exclude Commander format as other deck checks are not performed here
|
||||
continue;
|
||||
}
|
||||
if (gf.getFormatType().equals(FormatType.HISTORIC) && coveredTypes.contains(gf.getFormatSubType())
|
||||
if (gf.getFormatType().equals(FormatType.ARCHIVED) && coveredTypes.contains(gf.getFormatSubType())
|
||||
&& !exhaustive){
|
||||
//exclude duplicate formats - only keep first of e.g. Standard historical
|
||||
//exclude duplicate formats - only keep first of e.g. Standard archived
|
||||
continue;
|
||||
}
|
||||
if (gf.isPoolLegal(allCards)) {
|
||||
@@ -590,7 +595,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
if (gf2.formatSubType != gf1.formatSubType){
|
||||
return gf1.formatSubType.compareTo(gf2.formatSubType);
|
||||
}
|
||||
if (gf1.formatType.equals(FormatType.HISTORIC)){
|
||||
if (gf1.formatType.equals(FormatType.ARCHIVED)){
|
||||
if (gf1.effectiveDate!=gf2.effectiveDate) {//for matching dates or default dates default to name sorting
|
||||
return gf1.effectiveDate.compareTo(gf2.effectiveDate);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ public class GameRules {
|
||||
}
|
||||
|
||||
public void setAppliedVariants(final Set<GameType> appliedVariants) {
|
||||
this.appliedVariants.addAll(appliedVariants);
|
||||
if (appliedVariants != null && !appliedVariants.isEmpty())
|
||||
this.appliedVariants.addAll(appliedVariants);
|
||||
}
|
||||
|
||||
public boolean hasAppliedVariant(final GameType variant) {
|
||||
|
||||
@@ -76,7 +76,7 @@ public enum GameType {
|
||||
|
||||
private final DeckFormat deckFormat;
|
||||
private final boolean isCardPoolLimited, canSideboard, addWonCardsMidGame;
|
||||
private final String name, description;
|
||||
private final String name, englishName, description;
|
||||
private final Function<RegisteredPlayer, Deck> deckAutoGenerator;
|
||||
|
||||
GameType(DeckFormat deckFormat0, boolean isCardPoolLimited0, boolean canSideboard0, boolean addWonCardsMidgame0, String name0, String description0) {
|
||||
@@ -90,6 +90,7 @@ public enum GameType {
|
||||
canSideboard = canSideboard0;
|
||||
addWonCardsMidGame = addWonCardsMidgame0;
|
||||
name = localizer.getMessage(name0);
|
||||
englishName = localizer.getEnglishMessage(name0);
|
||||
if (description0.length()>0) {
|
||||
description0 = localizer.getMessage(description0);
|
||||
}
|
||||
@@ -148,6 +149,9 @@ public enum GameType {
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
public String getEnglishName() {
|
||||
return englishName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
|
||||
@@ -33,7 +33,6 @@ public enum GlobalRuleChange {
|
||||
onlyOneBlocker ("No more than one creature can block each combat."),
|
||||
onlyOneBlockerPerOpponent ("Each opponent can't block with more than one creature."),
|
||||
onlyTwoBlockers ("No more than two creatures can block each combat."),
|
||||
toughnessAssignsDamage ("Each creature assigns combat damage equal to its toughness rather than its power."),
|
||||
blankIsChaos("Each blank roll of the planar dice is a {CHAOS} roll.");
|
||||
|
||||
private final String ruleText;
|
||||
|
||||
@@ -288,6 +288,8 @@ public class StaticEffect {
|
||||
affectedCard.removeCanBlockAdditional(getTimestamp());
|
||||
}
|
||||
|
||||
affectedCard.removeChangedSVars(getTimestamp(), ability.getId());
|
||||
|
||||
affectedCard.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering
|
||||
}
|
||||
return affectedCards;
|
||||
|
||||
@@ -327,8 +327,8 @@ public final class AbilityFactory {
|
||||
}
|
||||
|
||||
private static final TargetRestrictions readTarget(Map<String, String> mapParams) {
|
||||
final String min = mapParams.containsKey("TargetMin") ? mapParams.get("TargetMin") : "1";
|
||||
final String max = mapParams.containsKey("TargetMax") ? mapParams.get("TargetMax") : "1";
|
||||
final String min = mapParams.getOrDefault("TargetMin", "1");
|
||||
final String max = mapParams.getOrDefault("TargetMax", "1");
|
||||
|
||||
// TgtPrompt should only be needed for more complicated ValidTgts
|
||||
String tgtWhat = mapParams.get("ValidTgts");
|
||||
|
||||
@@ -67,6 +67,7 @@ public enum AbilityKey {
|
||||
Explorer("Explorer"),
|
||||
ExtraTurn("ExtraTurn"),
|
||||
Event("Event"),
|
||||
ETB("ETB"),
|
||||
Fighter("Fighter"),
|
||||
Fighters("Fighters"),
|
||||
FirstTime("FirstTime"),
|
||||
@@ -112,12 +113,14 @@ public enum AbilityKey {
|
||||
Result("Result"),
|
||||
RoomName("RoomName"),
|
||||
Scheme("Scheme"),
|
||||
ScryNum("ScryNum"),
|
||||
Sides("Sides"),
|
||||
Source("Source"),
|
||||
Sources("Sources"),
|
||||
SourceSA("SourceSA"),
|
||||
SpellAbility("SpellAbility"),
|
||||
SpellAbilityStackInstance("SpellAbilityStackInstance"),
|
||||
SpellAbilityTarget("SpellAbilityTarget"),
|
||||
SpellAbilityTargetingCards("SpellAbilityTargetingCards"),
|
||||
StackInstance("StackInstance"),
|
||||
StackSa("StackSa"),
|
||||
|
||||
@@ -14,6 +14,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -146,7 +147,7 @@ public class AbilityUtils {
|
||||
} else if (defined.equals("TopOfGraveyard")) {
|
||||
final CardCollectionView grave = hostCard.getController().getCardsIn(ZoneType.Graveyard);
|
||||
|
||||
if (grave.size() > 0) { // TopOfLibrary or BottomOfLibrary
|
||||
if (grave.size() > 0) {
|
||||
c = grave.getLast();
|
||||
} else {
|
||||
// we don't want this to fall through and return the "Self"
|
||||
@@ -220,7 +221,7 @@ public class AbilityUtils {
|
||||
final Object crd = root.getReplacingObject(type);
|
||||
|
||||
if (crd instanceof Card) {
|
||||
c = game.getCardState((Card) crd);
|
||||
c = (Card) crd;
|
||||
} else if (crd instanceof Iterable<?>) {
|
||||
cards.addAll(Iterables.filter((Iterable<?>) crd, Card.class));
|
||||
}
|
||||
@@ -501,7 +502,7 @@ public class AbilityUtils {
|
||||
players.addAll(player.getOpponents());
|
||||
val = playerXCount(players, calcX[1], card, ability);
|
||||
} else if (hType.equals("RegisteredOpponents")) {
|
||||
players.addAll(Iterables.filter(game.getRegisteredPlayers(),PlayerPredicates.isOpponentOf(player)));
|
||||
players.addAll(Iterables.filter(game.getRegisteredPlayers(), PlayerPredicates.isOpponentOf(player)));
|
||||
val = playerXCount(players, calcX[1], card, ability);
|
||||
} else if (hType.equals("Other")) {
|
||||
players.addAll(player.getAllOtherPlayers());
|
||||
@@ -535,6 +536,9 @@ public class AbilityUtils {
|
||||
}
|
||||
}
|
||||
val = playerXCount(players, calcX[1], card, ability);
|
||||
} else if (hType.startsWith("Defined")) {
|
||||
String defined = hType.split("Defined")[1];
|
||||
val = playerXCount(getDefinedPlayers(card, defined, ability), calcX[1], card, ability);
|
||||
} else {
|
||||
val = 0;
|
||||
}
|
||||
@@ -1060,17 +1064,16 @@ public class AbilityUtils {
|
||||
final SpellAbility root = ((SpellAbility)sa).getRootAbility();
|
||||
Object o = null;
|
||||
if (defParsed.endsWith("Controller")) {
|
||||
String triggeringType = defParsed.substring(9);
|
||||
triggeringType = triggeringType.substring(0, triggeringType.length() - 10);
|
||||
final boolean orCont = defParsed.endsWith("OrController");
|
||||
String triggeringType = defParsed.substring(9, defParsed.length() - (orCont ? 12 : 10));
|
||||
final Object c = root.getTriggeringObject(AbilityKey.fromString(triggeringType));
|
||||
if (c instanceof Card) {
|
||||
if (orCont && c instanceof Player) {
|
||||
o = c;
|
||||
} else if (c instanceof Card) {
|
||||
o = ((Card) c).getController();
|
||||
}
|
||||
if (c instanceof SpellAbility) {
|
||||
} else if (c instanceof SpellAbility) {
|
||||
o = ((SpellAbility) c).getActivatingPlayer();
|
||||
}
|
||||
// For merged permanent
|
||||
if (c instanceof CardCollection) {
|
||||
} else if (c instanceof CardCollection) { // For merged permanent
|
||||
o = ((CardCollection) c).get(0).getController();
|
||||
}
|
||||
}
|
||||
@@ -1413,11 +1416,7 @@ public class AbilityUtils {
|
||||
|
||||
// Needed - Equip an untapped creature with Sword of the Paruns then cast Deadshot on it. Should deal 2 more damage.
|
||||
game.getAction().checkStaticAbilities(); // this will refresh continuous abilities for players and permanents.
|
||||
if (sa.isReplacementAbility() && abSub.getApi() == ApiType.InternalEtbReplacement) {
|
||||
game.getTriggerHandler().resetActiveTriggers(false);
|
||||
} else {
|
||||
game.getTriggerHandler().resetActiveTriggers();
|
||||
}
|
||||
game.getTriggerHandler().resetActiveTriggers(!sa.isReplacementAbility());
|
||||
AbilityUtils.resolveApiAbility(abSub, game);
|
||||
}
|
||||
|
||||
@@ -1448,7 +1447,7 @@ public class AbilityUtils {
|
||||
|
||||
// The player who has the chance to cancel the ability
|
||||
final String pays = sa.getParamOrDefault("UnlessPayer", "TargetedController");
|
||||
final FCollectionView<Player> allPayers = getDefinedPlayers(sa.getHostCard(), pays, sa);
|
||||
final FCollectionView<Player> allPayers = getDefinedPlayers(source, pays, sa);
|
||||
final String resolveSubs = sa.getParam("UnlessResolveSubs"); // no value means 'Always'
|
||||
final boolean execSubsWhenPaid = "WhenPaid".equals(resolveSubs) || StringUtils.isBlank(resolveSubs);
|
||||
final boolean execSubsWhenNotPaid = "WhenNotPaid".equals(resolveSubs) || StringUtils.isBlank(resolveSubs);
|
||||
@@ -1485,7 +1484,7 @@ public class AbilityUtils {
|
||||
cost = new Cost(new ManaCost(new ManaCostParser(String.valueOf(source.getChosenNumber()))), true);
|
||||
}
|
||||
else if (unlessCost.startsWith("DefinedCost")) {
|
||||
CardCollection definedCards = getDefinedCards(sa.getHostCard(), unlessCost.split("_")[1], sa);
|
||||
CardCollection definedCards = getDefinedCards(source, unlessCost.split("_")[1], sa);
|
||||
if (definedCards.isEmpty()) {
|
||||
sa.resolve();
|
||||
resolveSubAbilities(sa, game);
|
||||
@@ -1505,7 +1504,7 @@ public class AbilityUtils {
|
||||
cost = new Cost(newCost.toManaCost(), true);
|
||||
}
|
||||
else if (unlessCost.startsWith("DefinedSACost")) {
|
||||
FCollection<SpellAbility> definedSAs = getDefinedSpellAbilities(sa.getHostCard(), unlessCost.split("_")[1], sa);
|
||||
FCollection<SpellAbility> definedSAs = getDefinedSpellAbilities(source, unlessCost.split("_")[1], sa);
|
||||
if (definedSAs.isEmpty()) {
|
||||
sa.resolve();
|
||||
resolveSubAbilities(sa, game);
|
||||
@@ -2032,7 +2031,7 @@ public class AbilityUtils {
|
||||
return doXMath(c.getTotalDamageDoneBy(), expr, c, ctb);
|
||||
}
|
||||
if (sq[0].equals("TotalDamageReceivedThisTurn")) {
|
||||
return doXMath(c.getTotalDamageReceivedThisTurn(), expr, c, ctb);
|
||||
return doXMath(c.getAssignedDamage(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].contains("CardPower")) {
|
||||
@@ -2093,13 +2092,6 @@ public class AbilityUtils {
|
||||
return doXMath(c.getTimesMutated(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].startsWith("DamageDoneByPlayerThisTurn")) {
|
||||
int sum = 0;
|
||||
for (Player p : getDefinedPlayers(c, sq[1], ctb)) {
|
||||
sum += c.getReceivedDamageByPlayerThisTurn(p);
|
||||
}
|
||||
return doXMath(sum, expr, c, ctb);
|
||||
}
|
||||
if (sq[0].equals("RegeneratedThisTurn")) {
|
||||
return doXMath(c.getRegeneratedThisTurn(), expr, c, ctb);
|
||||
}
|
||||
@@ -2260,6 +2252,9 @@ public class AbilityUtils {
|
||||
if (sq[0].equals("Monarch")) {
|
||||
return doXMath(calculateAmount(c, sq[player.isMonarch() ? 1 : 2], ctb), expr, c, ctb);
|
||||
}
|
||||
if (sq[0].equals("Initiative")) {
|
||||
return doXMath(calculateAmount(c, sq[player.hasInitiative() ? 1: 2], ctb), expr, c, ctb);
|
||||
}
|
||||
if (sq[0].equals("StartingPlayer")) {
|
||||
return doXMath(calculateAmount(c, sq[player.isStartingPlayer() ? 1: 2], ctb), expr, c, ctb);
|
||||
}
|
||||
@@ -2358,20 +2353,28 @@ public class AbilityUtils {
|
||||
return doXMath(player.getOpponentsTotalPoisonCounters(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].equals("YourDamageThisTurn")) {
|
||||
return doXMath(player.getAssignedDamage(), expr, c, ctb);
|
||||
}
|
||||
if (sq[0].equals("TotalOppDamageThisTurn")) {
|
||||
return doXMath(player.getOpponentsAssignedDamage(), expr, c, ctb);
|
||||
}
|
||||
if (sq[0].equals("MaxOppDamageThisTurn")) {
|
||||
return doXMath(player.getMaxOpponentAssignedDamage(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].startsWith("YourDamageSourcesThisTurn")) {
|
||||
Iterable<Card> allSrc = player.getAssignedDamageSources();
|
||||
String restriction = sq[0].split(" ")[1];
|
||||
return doXMath(CardLists.getValidCardCount(allSrc, restriction, player, c, ctb), expr, c, ctb);
|
||||
if (sq[0].contains("TotalDamageThisTurn")) {
|
||||
String[] props = l[0].split(" ");
|
||||
int sum = 0;
|
||||
for (Pair<Integer, Boolean> p : c.getDamageReceivedThisTurn()) {
|
||||
if (game.getDamageLKI(p).getLeft().isValid(props[1], player, c, ctb)) {
|
||||
sum += p.getLeft();
|
||||
}
|
||||
}
|
||||
return doXMath(sum, expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].contains("DamageThisTurn")) {
|
||||
String[] props = l[0].split(" ");
|
||||
Boolean isCombat = null;
|
||||
if (sq[0].contains("CombatDamage")) {
|
||||
isCombat = true;
|
||||
}
|
||||
return doXMath(game.getDamageDoneThisTurn(isCombat, false, props[1], props[2], c, player, ctb).size(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].equals("YourTurns")) {
|
||||
@@ -3324,12 +3327,6 @@ public class AbilityUtils {
|
||||
totDmg += p.getAssignedDamage();
|
||||
}
|
||||
return doXMath(totDmg, m, source, ctb);
|
||||
} else if (sq[0].contains("LifeLostThisTurn")) {
|
||||
int totDmg = 0;
|
||||
for (Player p : players) {
|
||||
totDmg += p.getLifeLostThisTurn();
|
||||
}
|
||||
return doXMath(totDmg, m, source, ctb);
|
||||
}
|
||||
|
||||
if (players.size() > 0) {
|
||||
@@ -3386,7 +3383,7 @@ public class AbilityUtils {
|
||||
|
||||
if (value.contains("DomainPlayer")) {
|
||||
int n = 0;
|
||||
final CardCollectionView someCards = player.getCardsIn(ZoneType.Battlefield);
|
||||
final CardCollectionView someCards = player.getLandsInPlay();
|
||||
final List<String> basic = MagicColor.Constant.BASIC_LANDS;
|
||||
|
||||
for (int i = 0; i < basic.size(); i++) {
|
||||
@@ -3483,6 +3480,10 @@ public class AbilityUtils {
|
||||
return doXMath(opps == null ? 0 : opps.size(), m, source, ctb);
|
||||
}
|
||||
|
||||
if (value.equals("OpponentsAttackedThisCombat")) {
|
||||
return doXMath(game.getCombat().getAttackedOpponents(player).size(), m, source, ctb);
|
||||
}
|
||||
|
||||
if (value.equals("DungeonsCompleted")) {
|
||||
return doXMath(player.getCompletedDungeons().size(), m, source, ctb);
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ public enum ApiType {
|
||||
DigUntil (DigUntilEffect.class),
|
||||
Discard (DiscardEffect.class),
|
||||
DrainMana (DrainManaEffect.class),
|
||||
Draft (DraftEffect.class),
|
||||
Draw (DrawEffect.class),
|
||||
EachDamage (DamageEachEffect.class),
|
||||
Effect (EffectEffect.class),
|
||||
@@ -171,6 +172,7 @@ public enum ApiType {
|
||||
Subgame (SubgameEffect.class),
|
||||
Surveil (SurveilEffect.class),
|
||||
SwitchBlock (SwitchBlockEffect.class),
|
||||
TakeInitiative (TakeInitiativeEffect.class),
|
||||
Tap (TapEffect.class),
|
||||
TapAll (TapAllEffect.class),
|
||||
TapOrUntap (TapOrUntapEffect.class),
|
||||
@@ -188,7 +190,6 @@ public enum ApiType {
|
||||
|
||||
DamageResolve (DamageResolveEffect.class),
|
||||
ChangeZoneResolve (ChangeZoneResolveEffect.class),
|
||||
InternalEtbReplacement (ETBReplacementEffect.class),
|
||||
InternalLegendaryRule (CharmEffect.class),
|
||||
InternalIgnoreEffect (CharmEffect.class),
|
||||
UpdateRemember (UpdateRememberEffect.class);
|
||||
|
||||
@@ -300,13 +300,12 @@ public abstract class SpellAbilityEffect {
|
||||
delTrig.append("| TriggerDescription$ ").append(desc);
|
||||
|
||||
final Trigger trig = TriggerHandler.parseTrigger(delTrig.toString(), CardUtil.getLKICopy(sa.getHostCard()), intrinsic);
|
||||
long ts = sa.getHostCard().getGame().getNextTimestamp();
|
||||
for (final Card c : crds) {
|
||||
trig.addRemembered(c);
|
||||
|
||||
// Svar for AI
|
||||
if (!c.hasSVar("EndOfTurnLeavePlay")) {
|
||||
c.setSVar("EndOfTurnLeavePlay", "AtEOT");
|
||||
}
|
||||
c.addChangedSVars(Collections.singletonMap("EndOfTurnLeavePlay", "AtEOT"), ts, 0);
|
||||
}
|
||||
String trigSA = "";
|
||||
if (location.equals("Hand")) {
|
||||
@@ -346,9 +345,7 @@ public abstract class SpellAbilityEffect {
|
||||
card.addTrigger(trig);
|
||||
|
||||
// Svar for AI
|
||||
if (!card.hasSVar("EndOfTurnLeavePlay")) {
|
||||
card.setSVar("EndOfTurnLeavePlay", "AtEOT");
|
||||
}
|
||||
card.addChangedSVars(Collections.singletonMap("EndOfTurnLeavePlay", "AtEOT"), card.getGame().getNextTimestamp(), 0);
|
||||
}
|
||||
|
||||
protected static SpellAbility getForgetSpellAbility(final Card card) {
|
||||
|
||||
@@ -25,7 +25,7 @@ public class AbandonEffect extends SpellAbilityEffect {
|
||||
Player controller = source.getController();
|
||||
|
||||
boolean isOptional = sa.hasParam("Optional");
|
||||
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeAbandonSource", CardTranslation.getTranslatedName(source.getName())))) {
|
||||
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeAbandonSource", CardTranslation.getTranslatedName(source.getName())), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.GameCommand;
|
||||
import forge.card.CardType;
|
||||
@@ -64,17 +65,17 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
types.add(host.getChosenType2());
|
||||
}
|
||||
|
||||
final List<String> keywords = new ArrayList<>();
|
||||
final List<String> keywords = Lists.newArrayList();
|
||||
if (sa.hasParam("Keywords")) {
|
||||
keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & ")));
|
||||
}
|
||||
|
||||
final List<String> removeKeywords = new ArrayList<>();
|
||||
final List<String> removeKeywords = Lists.newArrayList();
|
||||
if (sa.hasParam("RemoveKeywords")) {
|
||||
removeKeywords.addAll(Arrays.asList(sa.getParam("RemoveKeywords").split(" & ")));
|
||||
}
|
||||
|
||||
final List<String> hiddenKeywords = new ArrayList<>();
|
||||
final List<String> hiddenKeywords = Lists.newArrayList();
|
||||
if (sa.hasParam("HiddenKeywords")) {
|
||||
hiddenKeywords.addAll(Arrays.asList(sa.getParam("HiddenKeywords").split(" & ")));
|
||||
}
|
||||
@@ -99,27 +100,38 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
}
|
||||
|
||||
// abilities to add to the animated being
|
||||
final List<String> abilities = new ArrayList<>();
|
||||
final List<String> abilities = Lists.newArrayList();
|
||||
if (sa.hasParam("Abilities")) {
|
||||
abilities.addAll(Arrays.asList(sa.getParam("Abilities").split(",")));
|
||||
}
|
||||
// replacement effects to add to the animated being
|
||||
final List<String> replacements = new ArrayList<>();
|
||||
final List<String> replacements = Lists.newArrayList();
|
||||
if (sa.hasParam("Replacements")) {
|
||||
replacements.addAll(Arrays.asList(sa.getParam("Replacements").split(",")));
|
||||
}
|
||||
// triggers to add to the animated being
|
||||
final List<String> triggers = new ArrayList<>();
|
||||
final List<String> triggers = Lists.newArrayList();
|
||||
if (sa.hasParam("Triggers")) {
|
||||
triggers.addAll(Arrays.asList(sa.getParam("Triggers").split(",")));
|
||||
}
|
||||
|
||||
// sVars to add to the animated being
|
||||
final List<String> sVars = new ArrayList<>();
|
||||
final List<String> sVars = Lists.newArrayList();
|
||||
if (sa.hasParam("sVars")) {
|
||||
sVars.addAll(Arrays.asList(sa.getParam("sVars").split(",")));
|
||||
}
|
||||
|
||||
// static abilities to add to the animated being
|
||||
final List<String> stAbs = Lists.newArrayList();
|
||||
if (sa.hasParam("staticAbilities")) {
|
||||
stAbs.addAll(Arrays.asList(sa.getParam("staticAbilities").split(",")));
|
||||
}
|
||||
|
||||
Map<String, String> sVarsMap = Maps.newHashMap();
|
||||
for (final String s : sVars) {
|
||||
sVarsMap.put(s, AbilityUtils.getSVar(sa, s));
|
||||
}
|
||||
|
||||
final String valid = sa.getParamOrDefault("ValidCards", "");
|
||||
|
||||
CardCollectionView list;
|
||||
@@ -133,15 +145,14 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
list = CardLists.getValidCards(list, valid, host.getController(), host, sa);
|
||||
|
||||
for (final Card c : list) {
|
||||
doAnimate(c, sa, power, toughness, types, removeTypes, finalColors,
|
||||
keywords, removeKeywords, hiddenKeywords,
|
||||
abilities, triggers, replacements, ImmutableList.of(),
|
||||
timestamp);
|
||||
doAnimate(c, sa, power, toughness, types, removeTypes, finalColors, keywords, removeKeywords,
|
||||
hiddenKeywords, abilities, triggers, replacements, stAbs, timestamp);
|
||||
|
||||
// give sVars
|
||||
for (final String s : sVars) {
|
||||
c.setSVar(s, AbilityUtils.getSVar(sa, s));
|
||||
if (!sVarsMap.isEmpty() ) {
|
||||
c.addChangedSVars(sVarsMap, timestamp, 0);
|
||||
}
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(c));
|
||||
|
||||
final GameCommand unanimate = new GameCommand() {
|
||||
|
||||
@@ -2,7 +2,9 @@ package forge.game.ability.effects;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.card.CardType;
|
||||
@@ -138,11 +140,22 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
}
|
||||
|
||||
// sVars to add to the animated being
|
||||
final List<String> sVars = Lists.newArrayList();
|
||||
Map<String, String> sVarsMap = Maps.newHashMap();
|
||||
if (sa.hasParam("sVars")) {
|
||||
sVars.addAll(Arrays.asList(sa.getParam("sVars").split(",")));
|
||||
for (final String s : sa.getParam("sVars").split(",")) {
|
||||
String actualsVar = AbilityUtils.getSVar(sa, s);
|
||||
String name = s;
|
||||
if (actualsVar.startsWith("SVar:")) {
|
||||
actualsVar = actualsVar.split("SVar:")[1];
|
||||
name = actualsVar.split(":")[0];
|
||||
actualsVar = actualsVar.split(":")[1];
|
||||
}
|
||||
sVarsMap.put(name, actualsVar);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
List<Card> tgts = getCardsfromTargets(sa);
|
||||
|
||||
if (sa.hasParam("Optional")) {
|
||||
@@ -151,7 +164,7 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
? TextUtil.fastReplace(sa.getParam("OptionQuestion"), "TARGETS", targets)
|
||||
: getStackDescription(sa);
|
||||
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message)) {
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message, null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -166,15 +179,8 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
}
|
||||
|
||||
// give sVars
|
||||
for (final String s : sVars) {
|
||||
String actualsVar = AbilityUtils.getSVar(sa, s);
|
||||
String name = s;
|
||||
if (actualsVar.startsWith("SVar:")) {
|
||||
actualsVar = actualsVar.split("SVar:")[1];
|
||||
name = actualsVar.split(":")[0];
|
||||
actualsVar = actualsVar.split(":")[1];
|
||||
}
|
||||
c.setSVar(name, actualsVar);
|
||||
if (!sVarsMap.isEmpty()) {
|
||||
c.addChangedSVars(sVarsMap, timestamp, 0);
|
||||
}
|
||||
|
||||
// give Remembered
|
||||
|
||||
@@ -137,6 +137,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
public void run() {
|
||||
doUnanimate(c, timestamp);
|
||||
|
||||
c.removeChangedSVars(timestamp, 0);
|
||||
c.removeChangedName(timestamp, 0);
|
||||
c.updateStateForView();
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ public class AttachEffect extends SpellAbilityEffect {
|
||||
// If Cast Targets will be checked on the Stack
|
||||
for (final Card attachment : attachments) {
|
||||
String message = Localizer.getInstance().getMessage("lblDoYouWantAttachSourceToTarget", CardTranslation.getTranslatedName(attachment.getName()), attachToName);
|
||||
if (sa.hasParam("Optional") && !p.getController().confirmAction(sa, null, message))
|
||||
if (sa.hasParam("Optional") && !p.getController().confirmAction(sa, null, message, null))
|
||||
// TODO add params for message
|
||||
continue;
|
||||
|
||||
|
||||
@@ -2,11 +2,10 @@ package forge.game.ability.effects;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Lang;
|
||||
|
||||
public class BecomeMonarchEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -16,8 +15,8 @@ public class BecomeMonarchEffect extends SpellAbilityEffect {
|
||||
|
||||
final List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
|
||||
sb.append(StringUtils.join(tgtPlayers, ", "));
|
||||
sb.append(" becomes the Monarch.");
|
||||
sb.append(Lang.joinHomogenous(tgtPlayers)).append(tgtPlayers.size() == 1 ? " becomes" : " become");
|
||||
sb.append(" the monarch.");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.CardTranslation;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
public class ChangeCombatantsEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -47,7 +47,8 @@ public class ChangeCombatantsEffect extends SpellAbilityEffect {
|
||||
if ((tgt == null) || c.canBeTargetedBy(sa)) {
|
||||
final Combat combat = game.getCombat();
|
||||
final GameEntity originalDefender = combat.getDefenderByAttacker(c);
|
||||
final FCollectionView<GameEntity> defs = combat.getDefenders();
|
||||
final FCollection<GameEntity> defs = new FCollection<>();
|
||||
defs.addAll(sa.hasParam("PlayerOnly") ? combat.getDefendingPlayers() : combat.getDefenders());
|
||||
|
||||
String title = Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(c.getName()));
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
|
||||
@@ -50,7 +50,7 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
||||
// Redirect rules read 'you MAY choose new targets' ... okay!
|
||||
// TODO: Don't even ask to change targets, if the SA and subs don't actually have targets
|
||||
boolean isOptional = sa.hasParam("Optional");
|
||||
if (isOptional && !chooser.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantChangeAbilityTargets", tgtSA.getHostCard().toString()))) {
|
||||
if (isOptional && !chooser.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantChangeAbilityTargets", tgtSA.getHostCard().toString()), null)) {
|
||||
continue;
|
||||
}
|
||||
if (sa.hasParam("ChangeSingleTarget")) {
|
||||
|
||||
@@ -120,7 +120,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
message = Localizer.getInstance().getMessage("lblMoveTargetFromOriginToDestination", targets, Lang.joinHomogenous(origin, ZoneType.Accessors.GET_TRANSLATED_NAME), destination.getTranslatedName());
|
||||
}
|
||||
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message)) {
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message, null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,8 +196,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
if (destination.equals("Battlefield")) {
|
||||
sb.append("onto the battlefield");
|
||||
if (tapped) {
|
||||
sb.append(" tapped");
|
||||
sb.append(" tapped").append(attacking ? " and" : "");
|
||||
}
|
||||
sb.append(attacking ? " attacking" : "");
|
||||
if (sa.hasParam("GainControl")) {
|
||||
sb.append(" under ").append(chooserNames).append("'s control");
|
||||
}
|
||||
@@ -259,14 +260,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
if (destination.equals("Battlefield")) {
|
||||
sb.append(" onto the battlefield");
|
||||
if (tapped) {
|
||||
sb.append(" tapped");
|
||||
if (attacking) {
|
||||
sb.append(" and");
|
||||
}
|
||||
}
|
||||
if (attacking) {
|
||||
sb.append(" attacking");
|
||||
sb.append(" tapped").append(attacking ? " and" : "");
|
||||
}
|
||||
sb.append(attacking ? " attacking" : "");
|
||||
if (sa.hasParam("GainControl")) {
|
||||
sb.append(" under ").append(chooserNames).append("'s control");
|
||||
}
|
||||
@@ -366,14 +362,16 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
final String fromGraveyard = " from the graveyard";
|
||||
|
||||
if (destination.equals(ZoneType.Battlefield)) {
|
||||
final boolean attacking = (sa.hasParam("Attacking"));
|
||||
if (ZoneType.Graveyard.equals(origin)) {
|
||||
sb.append("Return").append(targetname).append(fromGraveyard).append(" to the battlefield");
|
||||
} else {
|
||||
sb.append("Put").append(targetname).append(" onto the battlefield");
|
||||
}
|
||||
if (sa.hasParam("Tapped")) {
|
||||
sb.append(" tapped");
|
||||
sb.append(" tapped").append(attacking ? " and" : "");
|
||||
}
|
||||
sb.append(attacking ? " attacking" : "");
|
||||
if (sa.hasParam("GainControl")) {
|
||||
sb.append(" under your control");
|
||||
}
|
||||
@@ -494,7 +492,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
PlayerCollection deciders = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("AlternativeDecider"), sa);
|
||||
alterDecider = deciders.isEmpty() ? null : deciders.get(0);
|
||||
}
|
||||
if (alterDecider != null && !alterDecider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneToAltDestination, sb.toString())) {
|
||||
if (alterDecider != null && !alterDecider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneToAltDestination, sb.toString(), null)) {
|
||||
destination = ZoneType.smartValueOf(sa.getParam("DestinationAlternative"));
|
||||
altDest = true;
|
||||
}
|
||||
@@ -513,7 +511,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
removeFromStack(tgtSA, sa, si, game, triggerList);
|
||||
removeFromStack(tgtSA, sa, si, game, triggerList, counterTable);
|
||||
} // End of change from stack
|
||||
|
||||
final String remember = sa.getParam("RememberChanged");
|
||||
@@ -566,7 +564,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
final String prompt = TextUtil.concatWithSpace(Localizer.getInstance().getMessage("lblDoYouWantMoveTargetFromOriToDest", CardTranslation.getTranslatedName(gameCard.getName()), Lang.joinHomogenous(origin, ZoneType.Accessors.GET_TRANSLATED_NAME), destination.getTranslatedName()));
|
||||
if (optional && !chooser.getController().confirmAction(sa, null, prompt) )
|
||||
if (optional && !chooser.getController().confirmAction(sa, null, prompt, null) )
|
||||
continue;
|
||||
|
||||
final Zone originZone = game.getZoneOf(gameCard);
|
||||
@@ -958,7 +956,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
sb.append(sa.getParam("AlternativeMessage")).append(" ");
|
||||
sb.append(altFetchList.size()).append(" " + Localizer.getInstance().getMessage("lblCardMatchSearchingTypeInAlternateZones"));
|
||||
|
||||
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneFromAltSource, sb.toString())) {
|
||||
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneFromAltSource, sb.toString(), null)) {
|
||||
origin = alt;
|
||||
}
|
||||
}
|
||||
@@ -970,7 +968,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(sa.getParam("AlternativeDestinationMessage"));
|
||||
|
||||
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneToAltDestination, sb.toString())) {
|
||||
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneToAltDestination, sb.toString(), null)) {
|
||||
destination = ZoneType.smartValueOf(sa.getParam("DestinationAlternative"));
|
||||
libraryPos = sa.hasParam("LibraryPositionAlternative") ? Integer.parseInt(sa.getParam("LibraryPositionAlternative")) : 0;
|
||||
}
|
||||
@@ -987,7 +985,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
prompt = Localizer.getInstance().getMessage("lblSearchPlayerZoneConfirm", "{player's}", Lang.joinHomogenous(origin, ZoneType.Accessors.GET_TRANSLATED_NAME).toLowerCase());
|
||||
}
|
||||
String message = MessageUtil.formatMessage(prompt , decider, player);
|
||||
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message)) {
|
||||
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message, null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1073,7 +1071,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
SpellAbility tgtSA = decider.getController().getAbilityToPlay(tgtCard, sas);
|
||||
if (!decider.getController().confirmAction(tgtSA, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", CardTranslation.getTranslatedName(tgtCard.getName())))) {
|
||||
if (!decider.getController().confirmAction(tgtSA, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", CardTranslation.getTranslatedName(tgtCard.getName())), null)) {
|
||||
continue;
|
||||
}
|
||||
tgtSA.setSVar("IsCastFromPlayEffect", "True");
|
||||
@@ -1189,7 +1187,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
String message = Localizer.getInstance().getMessage("lblCancelSearchUpToSelectNumCards", String.valueOf(num));
|
||||
|
||||
if (fetchList.isEmpty() || sa.hasParam("SkipCancelPrompt") ||
|
||||
decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message)) {
|
||||
decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message, null)) {
|
||||
break;
|
||||
}
|
||||
i--;
|
||||
@@ -1545,7 +1543,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
* object.
|
||||
* @param game
|
||||
*/
|
||||
private static void removeFromStack(final SpellAbility tgtSA, final SpellAbility srcSA, final SpellAbilityStackInstance si, final Game game, CardZoneTable triggerList) {
|
||||
private static void removeFromStack(final SpellAbility tgtSA, final SpellAbility srcSA, final SpellAbilityStackInstance si, final Game game, CardZoneTable triggerList, GameEntityCounterTable counterTable) {
|
||||
final Card tgtHost = tgtSA.getHostCard();
|
||||
final Zone originZone = tgtHost.getZone();
|
||||
game.getStack().remove(si);
|
||||
@@ -1557,7 +1555,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
Card movedCard = null;
|
||||
if (srcSA.hasParam("Destination")) {
|
||||
final boolean remember = srcSA.hasParam("RememberChanged");
|
||||
final boolean rememberSpell = srcSA.hasParam("RememberSpell");
|
||||
final boolean imprint = srcSA.hasParam("Imprint");
|
||||
if (tgtSA.isAbility()) {
|
||||
// Shouldn't be able to target Abilities but leaving this in for now
|
||||
@@ -1589,13 +1586,20 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
+ srcSA.getHostCard().getName());
|
||||
}
|
||||
|
||||
if (srcSA.hasParam("WithCountersType")) {
|
||||
Player placer = srcSA.getActivatingPlayer();
|
||||
if (srcSA.hasParam("WithCountersPlacer")) {
|
||||
placer = AbilityUtils.getDefinedPlayers(srcSA.getHostCard(), srcSA.getParam("WithCountersPlacer"), srcSA).get(0);
|
||||
}
|
||||
CounterType cType = CounterType.getType(srcSA.getParam("WithCountersType"));
|
||||
int cAmount = AbilityUtils.calculateAmount(srcSA.getHostCard(), srcSA.getParamOrDefault("WithCountersAmount", "1"), srcSA);
|
||||
movedCard.addCounter(cType, cAmount, placer, counterTable);
|
||||
}
|
||||
|
||||
if (remember) {
|
||||
srcSA.getHostCard().addRemembered(tgtHost);
|
||||
// TODO or remember moved?
|
||||
}
|
||||
if (rememberSpell) {
|
||||
srcSA.getHostCard().addRemembered(tgtSA);
|
||||
}
|
||||
if (imprint) {
|
||||
srcSA.getHostCard().addImprintedCard(tgtHost);
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
num = Math.min(num, choices.size());
|
||||
|
||||
boolean isOptional = sa.hasParam("Optional");
|
||||
if (isOptional && !activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeCharm", CardTranslation.getTranslatedName(source.getName())))) {
|
||||
if (isOptional && !activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeCharm", CardTranslation.getTranslatedName(source.getName())), null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ public class ChooseCardEffect extends SpellAbilityEffect {
|
||||
Localizer.getInstance().getMessage("lblSelectCreatureWithTotalPowerLessOrEqualTo", (totP - chosenP - negativeNum))
|
||||
+ "\r\n(" + Localizer.getInstance().getMessage("lblSelected") + ":" + chosenPool + ")\r\n(" + Localizer.getInstance().getMessage("lblTotalPowerNum", chosenP) + ")", chosenP <= totP, null);
|
||||
if (c == null) {
|
||||
if (p.getController().confirmAction(sa, PlayerActionConfirmMode.OptionalChoose, Localizer.getInstance().getMessage("lblCancelChooseConfirm"))) {
|
||||
if (p.getController().confirmAction(sa, PlayerActionConfirmMode.OptionalChoose, Localizer.getInstance().getMessage("lblCancelChooseConfirm"), null)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -54,7 +54,6 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
boolean randomChoice = sa.hasParam("AtRandom");
|
||||
boolean draft = sa.hasParam("Draft"); //for digital "draft from spellbook" mechanic
|
||||
boolean chooseFromDefined = sa.hasParam("ChooseFromDefinedCards");
|
||||
boolean chooseFromList = sa.hasParam("ChooseFromList");
|
||||
boolean chooseFromOneTimeList = sa.hasParam("ChooseFromOneTimeList");
|
||||
@@ -62,8 +61,6 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
|
||||
if (!randomChoice) {
|
||||
if (sa.hasParam("SelectPrompt")) {
|
||||
message = sa.getParam("SelectPrompt");
|
||||
} else if (draft) {
|
||||
message = Localizer.getInstance().getMessage("lblChooseCardDraft");
|
||||
} else if (null == validDesc) {
|
||||
message = Localizer.getInstance().getMessage("lblChooseACardName");
|
||||
} else {
|
||||
@@ -108,12 +105,6 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
|
||||
chosen = p.getController().chooseCardName(sa, faces, message);
|
||||
} else if (chooseFromList) {
|
||||
String [] names = sa.getParam("ChooseFromList").split(",");
|
||||
if (sa.hasParam("Draft")) {
|
||||
List<String> options = Arrays.asList(names);
|
||||
Collections.shuffle(options);
|
||||
List<String> draftChoices = options.subList(0,3);
|
||||
names = draftChoices.toArray(new String[0]);
|
||||
}
|
||||
List<ICardFace> faces = new ArrayList<>();
|
||||
for (String name : names) {
|
||||
// Cardnames that include "," must use ";" instead in ChooseFromList$ (i.e. Tovolar; Dire Overlord)
|
||||
@@ -169,9 +160,6 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
|
||||
host.setNamedCard(chosen);
|
||||
if (!randomChoice) {
|
||||
p.setNamedCard(chosen);
|
||||
if (!draft) { //drafting is secret
|
||||
p.getGame().getAction().notifyOfValue(sa, host, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), chosen), p);
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("NoteFor")) {
|
||||
p.addNoteForName(sa.getParam("NoteFor"), "Name:" + chosen);
|
||||
|
||||
@@ -108,7 +108,7 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
final boolean optional = sa.hasParam("Optional");
|
||||
if (optional && !host.getController().getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantCopy", CardTranslation.getTranslatedName(cardToCopy.getName())))) {
|
||||
if (optional && !host.getController().getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantCopy", CardTranslation.getTranslatedName(cardToCopy.getName())), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,10 @@ import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Lang;
|
||||
import forge.util.Localizer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -24,13 +27,14 @@ public class ConniveEffect extends SpellAbilityEffect {
|
||||
*/
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
List<Card> tgt = getTargetCards(sa);
|
||||
|
||||
if (tgt.size() <= 0) {
|
||||
return "";
|
||||
} else {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(Lang.joinHomogenous(tgt)).append(tgt.size() > 1 ? " connive." : " connives.");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -39,55 +43,70 @@ public class ConniveEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player hostCon = host.getController();
|
||||
final Game game = host.getGame();
|
||||
final int num = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("ConniveNum", "1"), sa);
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
final CardZoneTable triggerList = new CardZoneTable();
|
||||
Map<Player, CardCollectionView> discardedMap = Maps.newHashMap();
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
|
||||
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
|
||||
CardCollection toConnive = getTargetCards(sa);
|
||||
if (toConnive.isEmpty()) { // if nothing is conniving, we're done
|
||||
return;
|
||||
}
|
||||
|
||||
for (final Card c : getTargetCards(sa)) {
|
||||
final Player p = c.getController();
|
||||
|
||||
p.drawCards(num, sa, moveParams);
|
||||
|
||||
CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand);
|
||||
dPHand = CardLists.filter(dPHand, CardPredicates.Presets.NON_TOKEN);
|
||||
if (dPHand.isEmpty()) { // seems unlikely, but just to be safe
|
||||
continue; // for loop over players
|
||||
}
|
||||
|
||||
CardCollection validCards = CardLists.getValidCards(dPHand, "Card", hostCon, host, sa);
|
||||
|
||||
if (!p.canDiscardBy(sa, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int amt = Math.min(validCards.size(), num);
|
||||
CardCollectionView toBeDiscarded = amt == 0 ? CardCollection.EMPTY :
|
||||
p.getController().chooseCardsToDiscardFrom(p, sa, validCards, amt, amt);
|
||||
|
||||
if (toBeDiscarded.size() > 1) {
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
}
|
||||
|
||||
discardedMap.put(p, toBeDiscarded);
|
||||
|
||||
int numCntrs = CardLists.getValidCardCount(toBeDiscarded, "Card.nonLand", hostCon, host, sa);
|
||||
|
||||
// need to get newest game state to check if it is still on the battlefield and the timestamp didn't change
|
||||
Card gamec = game.getCardState(c);
|
||||
// if the card is not in the game anymore, this might still return true, but it's no problem
|
||||
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(c)) {
|
||||
c.addCounter(CounterEnumType.P1P1, numCntrs, p, table);
|
||||
List<Player> controllers = new ArrayList<>();
|
||||
for (Card c : toConnive) {
|
||||
final Player controller = c.getController();
|
||||
if (!controllers.contains(controller)) {
|
||||
controllers.add(controller);
|
||||
}
|
||||
}
|
||||
//order controllers by APNAP
|
||||
int indexAP = controllers.indexOf(game.getPhaseHandler().getPlayerTurn());
|
||||
if (indexAP != -1) {
|
||||
Collections.rotate(controllers, - indexAP);
|
||||
}
|
||||
|
||||
for (final Player p : controllers) {
|
||||
CardCollection connivers = CardLists.filterControlledBy(toConnive, p);
|
||||
while (connivers.size() > 0) {
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
final CardZoneTable triggerList = new CardZoneTable();
|
||||
Map<Player, CardCollectionView> discardedMap = Maps.newHashMap();
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
|
||||
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
|
||||
|
||||
Card conniver = connivers.size() > 1 ? p.getController().chooseSingleEntityForEffect(connivers, sa,
|
||||
Localizer.getInstance().getMessage("lblChooseConniver"), null) : connivers.get(0);
|
||||
|
||||
p.drawCards(num, sa, moveParams);
|
||||
|
||||
CardCollection validDisards =
|
||||
CardLists.filter(p.getCardsIn(ZoneType.Hand), CardPredicates.Presets.NON_TOKEN);
|
||||
if (validDisards.isEmpty() || !p.canDiscardBy(sa, true)) { // hand being empty unlikely, just to be safe
|
||||
continue;
|
||||
}
|
||||
|
||||
int amt = Math.min(validDisards.size(), num);
|
||||
CardCollectionView toBeDiscarded = amt == 0 ? CardCollection.EMPTY :
|
||||
p.getController().chooseCardsToDiscardFrom(p, sa, validDisards, amt, amt);
|
||||
|
||||
if (toBeDiscarded.size() > 1) {
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
}
|
||||
|
||||
int numCntrs = CardLists.getValidCardCount(toBeDiscarded, "Card.nonLand", p, host, sa);
|
||||
|
||||
// need to get newest game state to check if it is still on the battlefield and the timestamp didn't change
|
||||
Card gamec = game.getCardState(conniver);
|
||||
// if the card is not in the game anymore, this might still return true, but it's no problem
|
||||
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(conniver)) {
|
||||
conniver.addCounter(CounterEnumType.P1P1, numCntrs, p, table);
|
||||
}
|
||||
connivers.remove(conniver);
|
||||
discardedMap.put(p, CardCollection.getView(toBeDiscarded));
|
||||
discard(sa, triggerList, true, discardedMap, moveParams);
|
||||
table.replaceCounterEffect(game, sa, true);
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
}
|
||||
}
|
||||
discard(sa, triggerList, true, discardedMap, moveParams);
|
||||
table.replaceCounterEffect(game, sa, true);
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public class ControlExchangeEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("Optional") && !sa.getActivatingPlayer().getController().confirmAction(sa, null,
|
||||
Localizer.getInstance().getMessage("lblExchangeControl",
|
||||
CardTranslation.getTranslatedName(object1.getName()),
|
||||
CardTranslation.getTranslatedName(object2.getName())))) {
|
||||
CardTranslation.getTranslatedName(object2.getName())), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.GameCommand;
|
||||
@@ -16,9 +20,6 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Localizer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ControlGainEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
@@ -118,22 +119,24 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
final Game game = newController.getGame();
|
||||
|
||||
CardCollectionView tgtCards = null;
|
||||
if (sa.hasParam("ControlledByTarget")) {
|
||||
tgtCards = CardLists.filterControlledBy(tgtCards, getTargetPlayers(sa));
|
||||
} else if (sa.hasParam("Choices")) {
|
||||
if (sa.hasParam("Choices")) {
|
||||
Player chooser = sa.hasParam("Chooser") ? AbilityUtils.getDefinedPlayers(source,
|
||||
sa.getParam("Chooser"), sa).get(0) : activator;
|
||||
CardCollectionView choices = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield),
|
||||
sa.getParam("Choices"), activator, source, sa);
|
||||
if (!choices.isEmpty()) {
|
||||
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") :
|
||||
Localizer.getInstance().getMessage("lblChooseaCard") +" ";
|
||||
tgtCards = activator.getController().chooseCardsForEffect(choices, sa, title, 1, 1, false, null);
|
||||
Localizer.getInstance().getMessage("lblChooseaCard") +" ";
|
||||
tgtCards = chooser.getController().chooseCardsForEffect(choices, sa, title, 1, 1, false, null);
|
||||
}
|
||||
} else {
|
||||
tgtCards = getDefinedCards(sa);
|
||||
}
|
||||
|
||||
if (tgtCards != null & sa.hasParam("ControlledByTarget")) {
|
||||
tgtCards = CardLists.filterControlledBy(tgtCards, getTargetPlayers(sa));
|
||||
}
|
||||
|
||||
// in case source was LKI or still resolving
|
||||
if (source.isLKI() || source.getZone().is(ZoneType.Stack)) {
|
||||
source = game.getCardState(source);
|
||||
@@ -208,11 +211,11 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
}
|
||||
if (lose.contains("EOT")) {
|
||||
game.getEndOfTurn().addUntil(loseControl);
|
||||
tgtC.setSVar("SacMe", "6");
|
||||
tgtC.addChangedSVars(Collections.singletonMap("SacMe", "6"), tStamp, 0);
|
||||
}
|
||||
if (lose.contains("EndOfCombat")) {
|
||||
game.getEndOfCombat().addUntil(loseControl);
|
||||
tgtC.setSVar("SacMe", "6");
|
||||
tgtC.addChangedSVars(Collections.singletonMap("SacMe", "6"), tStamp, 0);
|
||||
}
|
||||
if (lose.contains("StaticCommandCheck")) {
|
||||
String leftVar = sa.getSVar(sa.getParam("StaticCommandCheckSVar"));
|
||||
@@ -274,7 +277,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void run() {
|
||||
doLoseControl(c, hostCard, bTapOnLose, tStamp);
|
||||
c.removeSVar("SacMe");
|
||||
c.removeChangedSVars(tStamp, 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
TokenCreateTable tokenTable = new TokenCreateTable();
|
||||
|
||||
if (sa.hasParam("Optional") && !activator.getController().confirmAction(sa, null,
|
||||
Localizer.getInstance().getMessage("lblCopyPermanentConfirm"))) {
|
||||
Localizer.getInstance().getMessage("lblCopyPermanentConfirm"), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
if (choosen != null) {
|
||||
tgtCards.add(choosen);
|
||||
choices = CardLists.filter(choices, Predicates.not(CardPredicates.sharesNameWith(choosen)));
|
||||
} else if (chooser.getController().confirmAction(sa, PlayerActionConfirmMode.OptionalChoose, Localizer.getInstance().getMessage("lblCancelChooseConfirm"))) {
|
||||
} else if (chooser.getController().confirmAction(sa, PlayerActionConfirmMode.OptionalChoose, Localizer.getInstance().getMessage("lblCancelChooseConfirm"), null)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -224,6 +224,13 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
}
|
||||
|
||||
for (final Card c : tgtCards) {
|
||||
// 111.5. Similarly, if an effect would create a token that is a copy of an instant or sorcery card, no token is created.
|
||||
// instant and sorcery can't enter the battlefield
|
||||
// and it can't be replaced by other tokens
|
||||
if (c.isInstant() || c.isSorcery()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if it only targets player, it already got all needed cards from defined
|
||||
if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !c.canBeTargetedBy(sa)) {
|
||||
continue;
|
||||
|
||||
@@ -26,6 +26,7 @@ import forge.util.Aggregates;
|
||||
import forge.util.CardTranslation;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.Lang;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
|
||||
public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
@@ -88,7 +89,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
SpellAbility chosenSA = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa,
|
||||
Localizer.getInstance().getMessage("lblSelectASpellCopy"), ImmutableMap.of());
|
||||
|
||||
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoyouWantCopyTheSpell", CardTranslation.getTranslatedName(chosenSA.getHostCard().getName())))) {
|
||||
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoyouWantCopyTheSpell", CardTranslation.getTranslatedName(chosenSA.getHostCard().getName())), null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -128,32 +129,38 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
final Player p = (Player) o;
|
||||
if (p.equals(originalTargetPlayer))
|
||||
continue;
|
||||
if (p.isValid(type.split(","), chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa)) {
|
||||
if (p.isValid(type.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa)) {
|
||||
players.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
valid = CardLists.getValidCards(valid, type, chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa);
|
||||
valid = CardLists.getValidCards(valid, type, sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
Card originalTarget = Iterables.getFirst(getTargetCards(chosenSA), null);
|
||||
valid.remove(originalTarget);
|
||||
|
||||
if (sa.hasParam("ChooseOnlyOne")) {
|
||||
Card choice = controller.getController().chooseSingleEntityForEffect(valid, sa, Localizer.getInstance().getMessage("lblChooseOne"), null);
|
||||
FCollection<GameEntity> all = new FCollection<>(valid);
|
||||
all.addAll(players);
|
||||
GameEntity choice = controller.getController().chooseSingleEntityForEffect(all, sa,
|
||||
Localizer.getInstance().getMessage("lblChooseOne"), null);
|
||||
if (choice != null) {
|
||||
valid = new CardCollection(choice);
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||
resetFirstTargetOnCopy(copy, choice, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
} else {
|
||||
for (final Card c : valid) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||
resetFirstTargetOnCopy(copy, c, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
for (final Player p : players) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||
resetFirstTargetOnCopy(copy, p, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
}
|
||||
|
||||
for (final Card c : valid) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||
resetFirstTargetOnCopy(copy, c, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
for (final Player p : players) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||
resetFirstTargetOnCopy(copy, p, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < amount; i++) {
|
||||
|
||||
@@ -123,6 +123,7 @@ public class CounterEffect extends SpellAbilityEffect {
|
||||
for (final SpellAbility tgtSA : sas) {
|
||||
final Card tgtSACard = tgtSA.getHostCard();
|
||||
// should remember even that spell cannot be countered, e.g. Dovescape
|
||||
// TODO use LKI in case the spell gets countered before (else X amounts would be missing)
|
||||
if (sa.hasParam("RememberCounteredCMC")) {
|
||||
sa.getHostCard().addRemembered(Integer.valueOf(tgtSACard.getCMC()));
|
||||
}
|
||||
@@ -152,9 +153,7 @@ public class CounterEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberCountered")) {
|
||||
if (sa.getParam("RememberCountered").equals("True")) {
|
||||
sa.getHostCard().addRemembered(tgtSACard);
|
||||
}
|
||||
sa.getHostCard().addRemembered(tgtSACard);
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberSplicedOntoCounteredSpell")) {
|
||||
|
||||
@@ -20,7 +20,7 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
final CounterType cType = CounterType.getType(sa.getParam("CounterType"));
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParamOrDefault("CounterNum", "1"), sa);
|
||||
final String zone = sa.getParamOrDefault("ValidZone", "Battlefield");
|
||||
|
||||
sb.append("Put ");
|
||||
@@ -45,7 +45,7 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
final CounterType type = CounterType.getType(sa.getParam("CounterType"));
|
||||
final int counterAmount = AbilityUtils.calculateAmount(host, sa.getParam("CounterNum"), sa);
|
||||
final int counterAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("CounterNum", "1"), sa);
|
||||
final String valid = sa.getParam("ValidCards");
|
||||
final ZoneType zone = sa.hasParam("ValidZone") ? ZoneType.smartValueOf(sa.getParam("ValidZone")) : ZoneType.Battlefield;
|
||||
final Game game = activator.getGame();
|
||||
|
||||
@@ -174,7 +174,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
boolean putOnDefined = sa.hasParam("PutOnDefined");
|
||||
|
||||
if (sa.hasParam("Optional") && !pc.confirmAction
|
||||
(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPutCounter"))) {
|
||||
(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPutCounter"), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -231,7 +231,9 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("CounterType", counterType);
|
||||
if (counterType != null) {
|
||||
params.put("CounterType", counterType);
|
||||
}
|
||||
if (sa.hasParam("DividedRandomly")) {
|
||||
tgtObjects.addAll(choices);
|
||||
} else {
|
||||
@@ -509,7 +511,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
Player chooser = pc.chooseSingleEntityForEffect(activator.getOpponents(), sa,
|
||||
Localizer.getInstance().getMessage("lblChooseAnOpponent"), params);
|
||||
|
||||
if (chooser.getController().confirmAction(sa, PlayerActionConfirmMode.Tribute, message)) {
|
||||
if (chooser.getController().confirmAction(sa, PlayerActionConfirmMode.Tribute, message, null)) {
|
||||
gameCard.setTributed(true);
|
||||
} else {
|
||||
continue;
|
||||
|
||||
@@ -76,7 +76,7 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
if (!eachExisting && sa.hasParam("Optional") && !pl.getController().confirmAction(sa, null,
|
||||
Localizer.getInstance().getMessage("lblWouldYouLikePutRemoveCounters", ctype.getName(),
|
||||
CardTranslation.getTranslatedName(gameCard.getName())))) {
|
||||
CardTranslation.getTranslatedName(gameCard.getName())), null)) {
|
||||
continue;
|
||||
}
|
||||
if (!sa.usesTargeting() || gameCard.canBeTargetedBy(sa)) {
|
||||
|
||||
@@ -83,7 +83,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("Optional")) {
|
||||
String ctrs = cntToRemove > 1 ? Localizer.getInstance().getMessage("lblCounters") : num.equals("All") ? Localizer.getInstance().getMessage("lblAllCounters") : Localizer.getInstance().getMessage("lblACounters");
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblRemove") + " " + ctrs + "?")) {
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblRemove") + " " + ctrs + "?", null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
List<GameObject> tgts = getTargets(sa);
|
||||
if (sa.hasParam("OptionalDecider")) {
|
||||
Player decider = Iterables.getFirst(AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("OptionalDecider"), sa), null);
|
||||
if (decider != null && !decider.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoyouWantDealTargetDamageToTarget", String.valueOf(dmg), tgts.toString()))) {
|
||||
if (decider != null && !decider.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoyouWantDealTargetDamageToTarget", String.valueOf(dmg), tgts.toString()), null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -299,7 +299,7 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
} else {
|
||||
damageMap.put(sourceLKI, c, dmg);
|
||||
if (sa.hasParam("ExcessSVar")) {
|
||||
hostCard.setSVar(sa.getParam("ExcessSVar"), Integer.toString(excess));
|
||||
sa.setSVar(sa.getParam("ExcessSVar"), Integer.toString(excess));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberSVarAmount")) {
|
||||
delTrig.addRemembered(AbilityUtils.calculateAmount(host, host.getSVar(sa.getParam("RememberSVarAmount")), sa));
|
||||
delTrig.addRemembered(AbilityUtils.calculateAmount(host, sa.getSVar(sa.getParam("RememberSVarAmount")), sa));
|
||||
}
|
||||
|
||||
if (sa.hasAdditionalAbility("Execute")) {
|
||||
|
||||
@@ -34,73 +34,77 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
|
||||
final String toChange = sa.getParamOrDefault("ChangeNum", "1");
|
||||
final int numToChange = toChange.startsWith("All") ? numToDig : AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa);
|
||||
final List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
|
||||
String verb = " looks at ";
|
||||
if (sa.hasParam("Reveal") && sa.getParam("Reveal").equals("True")) {
|
||||
verb = " reveals ";
|
||||
} else if (sa.hasParam("DestinationZone") && sa.getParam("DestinationZone").equals("Exile") &&
|
||||
numToDig == numToChange) {
|
||||
verb = " exiles ";
|
||||
}
|
||||
sb.append(host.getController()).append(verb).append("the top ");
|
||||
sb.append(numToDig == 1 ? "card" : Lang.getNumeral(numToDig) + " cards").append(" of ");
|
||||
|
||||
if (tgtPlayers.contains(host.getController())) {
|
||||
sb.append("their ");
|
||||
final String spellDesc = sa.getParamOrDefault("SpellDescription", "");
|
||||
if (spellDesc.contains("X card")) { // X value can be changed after this goes to the stack, so use set desc
|
||||
sb.append("[").append(host.getController()).append("] ").append(spellDesc);
|
||||
} else {
|
||||
for (final Player p : tgtPlayers) {
|
||||
sb.append(Lang.getInstance().getPossesive(p.getName())).append(" ");
|
||||
}
|
||||
}
|
||||
sb.append("library.");
|
||||
final int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
|
||||
final String toChange = sa.getParamOrDefault("ChangeNum", "1");
|
||||
final int numToChange = toChange.startsWith("All") ? numToDig : AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa);
|
||||
|
||||
if (numToDig != numToChange) {
|
||||
String destZone1 = sa.hasParam("DestinationZone") ?
|
||||
sa.getParam("DestinationZone").toLowerCase() : "hand";
|
||||
String destZone2 = sa.hasParam("DestinationZone2") ?
|
||||
sa.getParam("DestinationZone2").toLowerCase() : "on the bottom of their library in any order.";
|
||||
if (sa.hasParam("RestRandomOrder")) {
|
||||
destZone2 = destZone2.replace("any", "a random");
|
||||
String verb = " looks at ";
|
||||
if (sa.hasParam("DestinationZone") && sa.getParam("DestinationZone").equals("Exile") &&
|
||||
numToDig == numToChange) {
|
||||
verb = " exiles ";
|
||||
} else if (sa.hasParam("Reveal") && sa.getParam("Reveal").equals("True")) {
|
||||
verb = " reveals ";
|
||||
}
|
||||
sb.append(host.getController()).append(verb).append("the top ");
|
||||
sb.append(numToDig == 1 ? "card" : Lang.getNumeral(numToDig) + " cards").append(" of ");
|
||||
|
||||
String verb2 = "put ";
|
||||
String where = " into their hand ";
|
||||
if (destZone1.equals("exile")) {
|
||||
verb2 = "exile ";
|
||||
where = " ";
|
||||
} else if (destZone1.equals("battlefield")) {
|
||||
verb2 = "put ";
|
||||
where = " onto the battlefield ";
|
||||
}
|
||||
|
||||
sb.append(" They ").append(sa.hasParam("Optional") ? "may " : "").append(verb2);
|
||||
if (sa.hasParam("ChangeValid")) {
|
||||
String what = sa.hasParam("ChangeValidDesc") ? sa.getParam("ChangeValidDesc") :
|
||||
sa.getParam("ChangeValid");
|
||||
if (!StringUtils.containsIgnoreCase(what, "card")) {
|
||||
what = what + " card";
|
||||
if (tgtPlayers.contains(host.getController())) {
|
||||
sb.append("their ");
|
||||
} else {
|
||||
for (final Player p : tgtPlayers) {
|
||||
sb.append(Lang.getInstance().getPossesive(p.getName())).append(" ");
|
||||
}
|
||||
sb.append(Lang.nounWithNumeralExceptOne(numToChange, what)).append(" from among them").append(where);
|
||||
} else {
|
||||
sb.append(Lang.getNumeral(numToChange)).append(" of them").append(where);
|
||||
}
|
||||
sb.append(sa.hasParam("ExileFaceDown") ? "face down " : "");
|
||||
if (sa.hasParam("WithCounter") || sa.hasParam("ExileWithCounter")) {
|
||||
String ctr = sa.hasParam("WithCounter") ? sa.getParam("WithCounter") :
|
||||
sa.getParam("ExileWithCounter");
|
||||
sb.append("with a ");
|
||||
sb.append(CounterType.getType(ctr).getName().toLowerCase());
|
||||
sb.append(" counter on it. They ");
|
||||
} else {
|
||||
sb.append("and ");
|
||||
}
|
||||
sb.append("put the rest ").append(destZone2);
|
||||
}
|
||||
sb.append("library.");
|
||||
|
||||
if (numToDig != numToChange) {
|
||||
String destZone1 = sa.hasParam("DestinationZone") ?
|
||||
sa.getParam("DestinationZone").toLowerCase() : "hand";
|
||||
String destZone2 = sa.hasParam("DestinationZone2") ?
|
||||
sa.getParam("DestinationZone2").toLowerCase() : "on the bottom of their library in any order.";
|
||||
if (sa.hasParam("RestRandomOrder")) {
|
||||
destZone2 = destZone2.replace("any", "a random");
|
||||
}
|
||||
|
||||
String verb2 = "put ";
|
||||
String where = " into their hand ";
|
||||
if (destZone1.equals("exile")) {
|
||||
verb2 = "exile ";
|
||||
where = " ";
|
||||
} else if (destZone1.equals("battlefield")) {
|
||||
verb2 = "put ";
|
||||
where = " onto the battlefield ";
|
||||
}
|
||||
|
||||
sb.append(" They ").append(sa.hasParam("Optional") ? "may " : "").append(verb2);
|
||||
if (sa.hasParam("ChangeValid")) {
|
||||
String what = sa.hasParam("ChangeValidDesc") ? sa.getParam("ChangeValidDesc") :
|
||||
sa.getParam("ChangeValid");
|
||||
if (!StringUtils.containsIgnoreCase(what, "card")) {
|
||||
what = what + " card";
|
||||
}
|
||||
sb.append(Lang.nounWithNumeralExceptOne(numToChange, what)).append(" from among them").append(where);
|
||||
} else {
|
||||
sb.append(Lang.getNumeral(numToChange)).append(" of them").append(where);
|
||||
}
|
||||
sb.append(sa.hasParam("ExileFaceDown") ? "face down " : "");
|
||||
if (sa.hasParam("WithCounter") || sa.hasParam("ExileWithCounter")) {
|
||||
String ctr = sa.hasParam("WithCounter") ? sa.getParam("WithCounter") :
|
||||
sa.getParam("ExileWithCounter");
|
||||
sb.append("with a ");
|
||||
sb.append(CounterType.getType(ctr).getName().toLowerCase());
|
||||
sb.append(" counter on it. They ");
|
||||
} else {
|
||||
sb.append("and ");
|
||||
}
|
||||
sb.append("put the rest ").append(destZone2);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -199,7 +203,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
else if (sa.hasParam("RevealOptional")) {
|
||||
String question = TextUtil.concatWithSpace(Localizer.getInstance().getMessage("lblReveal") + ":", TextUtil.addSuffix(Lang.joinHomogenous(top),"?"));
|
||||
|
||||
hasRevealed = p.getController().confirmAction(sa, null, question);
|
||||
hasRevealed = p.getController().confirmAction(sa, null, question, null);
|
||||
if (hasRevealed) {
|
||||
game.getAction().reveal(top, p);
|
||||
}
|
||||
@@ -272,7 +276,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
// Optional abilities that use a dialog box to prompt the user to skip the ability (e.g. Explorer's Scope, Quest for Ula's Temple)
|
||||
if (optional && mayBeSkipped && !valid.isEmpty()) {
|
||||
String prompt = !optionalAbilityPrompt.isEmpty() ? optionalAbilityPrompt : Localizer.getInstance().getMessage("lblWouldYouLikeProceedWithOptionalAbility") + " " + host + "?\n\n(" + sa.getDescription() + ")";
|
||||
if (!p.getController().confirmAction(sa, null, TextUtil.fastReplace(prompt, "CARDNAME", CardTranslation.getTranslatedName(host.getName())))) {
|
||||
if (!p.getController().confirmAction(sa, null, TextUtil.fastReplace(prompt, "CARDNAME", CardTranslation.getTranslatedName(host.getName())), null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -413,7 +417,9 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
c.setTapped(true);
|
||||
}
|
||||
if (destZone1.equals(ZoneType.Battlefield) && sa.hasParam("WithCounter")) {
|
||||
c.addEtbCounter(CounterType.getType(sa.getParam("WithCounter")), 1, player);
|
||||
final int numCtr = AbilityUtils.calculateAmount(host,
|
||||
sa.getParamOrDefault("WithCounterNum", "1"), sa);
|
||||
c.addEtbCounter(CounterType.getType(sa.getParam("WithCounter")), numCtr, player);
|
||||
}
|
||||
c = game.getAction().moveTo(zone, c, sa, moveParams);
|
||||
if (destZone1.equals(ZoneType.Battlefield)) {
|
||||
|
||||
@@ -120,7 +120,7 @@ public class DigUntilEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
|
||||
if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantDigYourLibrary"))) {
|
||||
if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantDigYourLibrary"), null)) {
|
||||
continue;
|
||||
}
|
||||
CardCollection found = new CardCollection();
|
||||
@@ -171,7 +171,7 @@ public class DigUntilEffect extends SpellAbilityEffect {
|
||||
final Card c = itr.next();
|
||||
final ZoneType origin = c.getZone().getZoneType();
|
||||
if (optionalFound && !p.getController().confirmAction(sa, null,
|
||||
Localizer.getInstance().getMessage("lblDoYouWantPutCardToZone", foundDest.getTranslatedName()))) {
|
||||
Localizer.getInstance().getMessage("lblDoYouWantPutCardToZone", foundDest.getTranslatedName()), null)) {
|
||||
continue;
|
||||
}
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
|
||||
@@ -153,7 +153,7 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
boolean runDiscard = !sa.hasParam("Optional")
|
||||
|| p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, sa.getParam("DiscardMessage"));
|
||||
|| p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, sa.getParam("DiscardMessage"), null);
|
||||
if (runDiscard) {
|
||||
toBeDiscarded = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedCards"), sa);
|
||||
|
||||
@@ -197,7 +197,7 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
String message = Localizer.getInstance().getMessage("lblWouldYouLikeRandomDiscardTargetCard", String.valueOf(numCards));
|
||||
boolean runDiscard = !sa.hasParam("Optional") || p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message);
|
||||
boolean runDiscard = !sa.hasParam("Optional") || p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message, null);
|
||||
|
||||
if (runDiscard) {
|
||||
final String valid = sa.getParamOrDefault("DiscardValid", "Card");
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.card.ICardFace;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Localizer;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class DraftEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Player player = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa).get(0);
|
||||
final ZoneType zone = ZoneType.smartValueOf(sa.getParamOrDefault("Zone", "Hand"));
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(player).append(" drafts a card from ").append(source.getName()).append("'s spellbook");
|
||||
if (zone.equals("Hand")) {
|
||||
sb.append(".");
|
||||
} else if (zone.equals("Battlefield")) {
|
||||
sb.append(" and puts it onto the battlefield.");
|
||||
} else if (zone.equals("Exile")) {
|
||||
sb.append(", then exiles it.");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
|
||||
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
|
||||
final Card source = sa.getHostCard();
|
||||
final Player player = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa).get(0);
|
||||
final Game game = player.getGame();
|
||||
final ZoneType zone = ZoneType.smartValueOf(sa.getParamOrDefault("Zone", "Hand"));
|
||||
List<String> spellbook = Arrays.asList(sa.getParam("Spellbook").split(","));
|
||||
final int numToDraft = AbilityUtils.calculateAmount(source,
|
||||
sa.getParamOrDefault("DraftNum", "1"), sa);
|
||||
CardCollection drafted = new CardCollection();
|
||||
|
||||
for (int i = 0; i < numToDraft; i++) {
|
||||
String chosen = "";
|
||||
Collections.shuffle(spellbook);
|
||||
List<ICardFace> faces = new ArrayList<>();
|
||||
for (String name : spellbook.subList(0, 3)) {
|
||||
// Cardnames that include "," must use ";" instead in Spellbook$ (i.e. Tovolar; Dire Overlord)
|
||||
name = name.replace(";", ",");
|
||||
faces.add(StaticData.instance().getCommonCards().getFaceByName(name));
|
||||
}
|
||||
chosen = player.getController().chooseCardName(sa, faces,
|
||||
Localizer.getInstance().getMessage("lblChooseCardDraft"));
|
||||
if (!chosen.equals("")) {
|
||||
Card card = Card.fromPaperCard(StaticData.instance().getCommonCards().getUniqueByName(chosen), player);
|
||||
card.setTokenCard(true);
|
||||
game.getAction().moveTo(ZoneType.None, card, sa, moveParams);
|
||||
drafted.add(card);
|
||||
}
|
||||
}
|
||||
|
||||
final CardZoneTable triggerList = new CardZoneTable();
|
||||
for (final Card c : drafted) {
|
||||
Card made = game.getAction().moveTo(zone, c, sa, moveParams);
|
||||
if (c != null) {
|
||||
triggerList.put(ZoneType.None, made.getZone().getZoneType(), made);
|
||||
}
|
||||
if (sa.hasParam("RememberDrafted")) {
|
||||
source.addRemembered(made);
|
||||
}
|
||||
}
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
if (zone.equals(ZoneType.Library)) {
|
||||
player.shuffle(sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ public class DrawEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantDrawCards", Lang.nounWithAmount(numCards, " card")))) {
|
||||
if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantDrawCards", Lang.nounWithAmount(numCards, " card")), null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class ETBReplacementEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Game game = sa.getActivatingPlayer().getGame();
|
||||
final Card card = (Card) sa.getReplacingObject(AbilityKey.Card);
|
||||
|
||||
Map<AbilityKey, Object> params = AbilityKey.newMap();
|
||||
params.put(AbilityKey.CardLKI, sa.getReplacingObject(AbilityKey.CardLKI));
|
||||
params.put(AbilityKey.ReplacementEffect, sa.getReplacementEffect());
|
||||
params.put(AbilityKey.LastStateBattlefield, sa.getReplacingObject(AbilityKey.LastStateBattlefield));
|
||||
params.put(AbilityKey.LastStateGraveyard, sa.getReplacingObject(AbilityKey.LastStateGraveyard));
|
||||
|
||||
final SpellAbility root = sa.getRootAbility();
|
||||
SpellAbility cause = (SpellAbility) root.getReplacingObject(AbilityKey.Cause);
|
||||
|
||||
game.getAction().moveToPlay(card, card.getController(), cause, params);
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
boolean imprintOnHost = false;
|
||||
final String duration = sa.getParam("Duration");
|
||||
|
||||
if (("UntilHostLeavesPlay".equals(duration) || "UntilLoseControlOfHost".equals(duration))
|
||||
if (((duration != null && duration.startsWith("UntilHostLeavesPlay")) || "UntilLoseControlOfHost".equals(duration))
|
||||
&& !(hostCard.isInPlay() || hostCard.isInZone(ZoneType.Stack))) {
|
||||
return;
|
||||
}
|
||||
@@ -88,6 +88,13 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
effectKeywords = sa.getParam("Keywords").split(",");
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberSpell")) {
|
||||
rememberList = new FCollection<>();
|
||||
for (final String rem : sa.getParam("RememberSpell").split(",")) {
|
||||
rememberList.addAll(AbilityUtils.getDefinedSpellAbilities(hostCard, rem, sa));
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberObjects")) {
|
||||
rememberList = new FCollection<>();
|
||||
for (final String rem : sa.getParam("RememberObjects").split(",")) {
|
||||
@@ -271,7 +278,7 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (sa.hasParam("CopySVar")) {
|
||||
eff.setSVar(sa.getParam("CopySVar"), sa.getHostCard().getSVar(sa.getParam("CopySVar")));
|
||||
eff.setSVar(sa.getParam("CopySVar"), hostCard.getSVar(sa.getParam("CopySVar")));
|
||||
}
|
||||
|
||||
// Copy text changes
|
||||
|
||||
@@ -48,7 +48,7 @@ public class EncodeEffect extends SpellAbilityEffect {
|
||||
}
|
||||
// Handle choice of whether or not to encoded
|
||||
|
||||
if (!player.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantExileCardAndEncodeOntoYouCreature", CardTranslation.getTranslatedName(host.getName())))) {
|
||||
if (!player.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantExileCardAndEncodeOntoYouCreature", CardTranslation.getTranslatedName(host.getName())), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ public class EndTurnEffect extends SpellAbilityEffect {
|
||||
public void resolve(SpellAbility sa) {
|
||||
final List<Player> enders = getDefinedPlayersOrTargeted(sa, "Defined");
|
||||
final Player ender = enders.isEmpty() ? sa.getActivatingPlayer() : enders.get(0);
|
||||
if (sa.hasParam("Optional") && !ender.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantEndTurn"))) {
|
||||
if (sa.hasParam("Optional") && !ender.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantEndTurn"), null)) {
|
||||
return;
|
||||
}
|
||||
Game game = ender.getGame();
|
||||
|
||||
@@ -59,7 +59,7 @@ public class FightEffect extends DamageBaseEffect {
|
||||
Player controller = host.getController();
|
||||
boolean isOptional = sa.hasParam("Optional");
|
||||
|
||||
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeFight", CardTranslation.getTranslatedName(fighters.get(0).getName()), CardTranslation.getTranslatedName(fighters.get(1).getName())))) {
|
||||
if (isOptional && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblWouldYouLikeFight", CardTranslation.getTranslatedName(fighters.get(0).getName()), CardTranslation.getTranslatedName(fighters.get(1).getName())), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,25 @@ import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Lang;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GoadEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
List<Card> tgt = getTargetCards(sa);
|
||||
if (tgt.size() <= 0) {
|
||||
return "";
|
||||
} else {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(player).append(" goads ").append(Lang.joinHomogenous(tgt)).append(".");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
|
||||
@@ -67,7 +67,7 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("RememberSVarAmount")) {
|
||||
immediateTrig.addRemembered(AbilityUtils.calculateAmount(host,
|
||||
host.getSVar(sa.getParam("RememberSVarAmount")), sa));
|
||||
sa.getSVar(sa.getParam("RememberSVarAmount")), sa));
|
||||
}
|
||||
|
||||
if (sa.hasAdditionalAbility("Execute")) {
|
||||
|
||||
@@ -40,7 +40,7 @@ public class InvestigateEffect extends TokenEffectBase {
|
||||
for (final Player p : getTargetPlayers(sa)) {
|
||||
for (int i = 0; i < amount; i++) {
|
||||
if (sa.hasParam("Optional") && !p.getController().confirmAction(sa, null,
|
||||
Localizer.getInstance().getMessage("lblWouldYouLikeInvestigate"))) {
|
||||
Localizer.getInstance().getMessage("lblWouldYouLikeInvestigate"), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package forge.game.ability.effects;
|
||||
|
||||
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -41,14 +40,7 @@ public class LifeLoseEffect extends SpellAbilityEffect {
|
||||
lifeLost += p.loseLife(lifeAmount, false, false);
|
||||
}
|
||||
}
|
||||
sa.getHostCard().setSVar("AFLifeLost", "Number$" + lifeLost);
|
||||
|
||||
// Exceptional case for Extort: must propagate the amount of life lost to subability,
|
||||
// otherwise the first Extort trigger per game won't work
|
||||
if (sa.getSubAbility() != null && ApiType.GainLife.equals(sa.getSubAbility().getApi())) {
|
||||
sa.getSubAbility().setSVar("AFLifeLost", "Number$" + lifeLost);
|
||||
}
|
||||
|
||||
sa.setSVar("AFLifeLost", "Number$" + lifeLost);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -60,15 +61,18 @@ public class MakeCardEffect extends SpellAbilityEffect {
|
||||
amount--;
|
||||
}
|
||||
|
||||
final CardZoneTable triggerList = new CardZoneTable();
|
||||
for (final Card c : cards) {
|
||||
game.getAction().moveTo(zone, c, sa, moveParams);
|
||||
Card made = game.getAction().moveTo(zone, c, sa, moveParams);
|
||||
triggerList.put(ZoneType.None, made.getZone().getZoneType(), made);
|
||||
if (sa.hasParam("RememberMade")) {
|
||||
sa.getHostCard().addRemembered(c);
|
||||
sa.getHostCard().addRemembered(made);
|
||||
}
|
||||
if (sa.hasParam("ImprintMade")) {
|
||||
sa.getHostCard().addImprintedCard(c);
|
||||
sa.getHostCard().addImprintedCard(made);
|
||||
}
|
||||
}
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
if (zone.equals(ZoneType.Library)) {
|
||||
player.shuffle(sa);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
final boolean optional = sa.hasParam("Optional");
|
||||
final Game game = sa.getActivatingPlayer().getGame();
|
||||
|
||||
if (optional && !sa.getActivatingPlayer().getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantAddMana"))) {
|
||||
if (optional && !sa.getActivatingPlayer().getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantAddMana"), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ public class MillEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("Optional")) {
|
||||
final String prompt = TextUtil.concatWithSpace(Localizer.getInstance().getMessage("lblDoYouWantPutLibraryCardsTo", destination.getTranslatedName()));
|
||||
// CR 701.13b
|
||||
if (numCards > p.getZone(ZoneType.Library).size() || !p.getController().confirmAction(sa, null, prompt)) {
|
||||
if (numCards > p.getZone(ZoneType.Library).size() || !p.getController().confirmAction(sa, null, prompt, null)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ public class PeekAndRevealEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (doReveal && sa.hasParam("RevealOptional"))
|
||||
doReveal = peekingPlayer.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblRevealCardToOtherPlayers"));
|
||||
doReveal = peekingPlayer.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblRevealCardToOtherPlayers"), null);
|
||||
|
||||
if (doReveal) {
|
||||
peekingPlayer.getGame().getAction().reveal(revealableCards, ZoneType.Library, libraryToPeek, !noPeek,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -42,12 +43,12 @@ public class PermanentEffect extends SpellAbilityEffect {
|
||||
|
||||
// some extra for Dashing
|
||||
if (sa.isDash() && c.isInPlay()) {
|
||||
c.setSVar("EndOfTurnLeavePlay", "Dash");
|
||||
c.addChangedSVars(Collections.singletonMap("EndOfTurnLeavePlay", "Dash"), c.getGame().getNextTimestamp(), 0);
|
||||
registerDelayedTrigger(sa, "Hand", Lists.newArrayList(c));
|
||||
}
|
||||
// similar for Blitz keyword
|
||||
if (sa.isBlitz() && c.isInPlay()) {
|
||||
c.setSVar("EndOfTurnLeavePlay", "Blitz");
|
||||
c.addChangedSVars(Collections.singletonMap("EndOfTurnLeavePlay", "Blitz"), c.getGame().getNextTimestamp(), 0);
|
||||
registerDelayedTrigger(sa, "Sacrifice", Lists.newArrayList(c));
|
||||
}
|
||||
|
||||
|
||||
@@ -259,7 +259,7 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
}
|
||||
if (singleOption && sa.getTargetCard() == null)
|
||||
sa.setPlayEffectCard(tgtCard);// show card to play rather than showing the source card
|
||||
if (singleOption && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", CardTranslation.getTranslatedName(tgtCard.getName())))) {
|
||||
if (singleOption && !controller.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", CardTranslation.getTranslatedName(tgtCard.getName())), null)) {
|
||||
if (wasFaceDown) {
|
||||
tgtCard.turnFaceDownNoUpdate();
|
||||
tgtCard.updateStateForView();
|
||||
@@ -285,6 +285,7 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO if cost isn't replaced should include alternative ones
|
||||
// get basic spells (no flashback, etc.)
|
||||
List<SpellAbility> sas = AbilityUtils.getBasicSpellsFromPlayEffect(tgtCard, controller);
|
||||
if (sa.hasParam("ValidSA")) {
|
||||
@@ -435,8 +436,9 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
final Zone currentZone = game.getCardState(tgtCard).getZone();
|
||||
if (!originZone.equals(currentZone)) {
|
||||
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), game.getCardState(tgtCard));
|
||||
if (!currentZone.equals(originZone)) {
|
||||
//fix Garth One-Eye activated ability and the likes..
|
||||
triggerList.put(originZone == null ? null : originZone.getZoneType(), currentZone.getZoneType(), game.getCardState(tgtCard));
|
||||
}
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
}
|
||||
|
||||
@@ -37,13 +37,15 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
final long timestamp) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
final String duration = sa.getParam("Duration");
|
||||
|
||||
//if host is not on the battlefield don't apply
|
||||
// Suspend should does Affect the Stack
|
||||
if (("UntilHostLeavesPlay".equals(sa.getParam("Duration")) || "UntilLoseControlOfHost".equals(sa.getParam("Duration")))
|
||||
if (((duration != null && duration.startsWith("UntilHostLeavesPlay")) || "UntilLoseControlOfHost".equals(duration))
|
||||
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
|
||||
return;
|
||||
}
|
||||
if ("UntilLoseControlOfHost".equals(sa.getParam("Duration")) && host.getController() != sa.getActivatingPlayer()) {
|
||||
if ("UntilLoseControlOfHost".equals(duration) && host.getController() != sa.getActivatingPlayer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -92,7 +94,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
addLeaveBattlefieldReplacement(gameCard, sa, sa.getParam("LeaveBattlefield"));
|
||||
}
|
||||
|
||||
if (!"Permanent".equals(sa.getParam("Duration"))) {
|
||||
if (!"Permanent".equals(duration)) {
|
||||
// If not Permanent, remove Pumped at EOT
|
||||
final GameCommand untilEOT = new GameCommand() {
|
||||
private static final long serialVersionUID = -42244224L;
|
||||
@@ -123,9 +125,11 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
private static void applyPump(final SpellAbility sa, final Player p,
|
||||
final List<String> keywords, final long timestamp) {
|
||||
final Card host = sa.getHostCard();
|
||||
final String duration = sa.getParam("Duration");
|
||||
|
||||
//if host is not on the battlefield don't apply
|
||||
// Suspend should does Affect the Stack
|
||||
if (("UntilHostLeavesPlay".equals(sa.getParam("Duration")) || "UntilLoseControlOfHost".equals(sa.getParam("Duration")))
|
||||
if (((duration != null && duration.startsWith("UntilHostLeavesPlay")) || "UntilLoseControlOfHost".equals(duration))
|
||||
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
|
||||
return;
|
||||
}
|
||||
@@ -134,7 +138,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
p.addChangedKeywords(keywords, ImmutableList.of(), timestamp, 0);
|
||||
}
|
||||
|
||||
if (!"Permanent".equals(sa.getParam("Duration"))) {
|
||||
if (!"Permanent".equals(duration)) {
|
||||
// If not Permanent, remove Pumped at EOT
|
||||
final GameCommand untilEOT = new GameCommand() {
|
||||
private static final long serialVersionUID = -32453460L;
|
||||
@@ -348,7 +352,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
? TextUtil.fastReplace(sa.getParam("OptionQuestion"), "TARGETS", targets)
|
||||
: Localizer.getInstance().getMessage("lblApplyPumpToTarget", targets);
|
||||
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message)) {
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message, null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ public class RearrangeTopOfLibraryEffect extends SpellAbilityEffect {
|
||||
Card next = orderedCards.get(i);
|
||||
player.getGame().getAction().moveToLibrary(next, 0, sa);
|
||||
}
|
||||
if (mayshuffle && activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoyouWantShuffleTheLibrary"))) {
|
||||
if (mayshuffle && activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoyouWantShuffleTheLibrary"), null)) {
|
||||
player.shuffle(sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class RepeatEachEffect extends SpellAbilityEffect {
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
final Game game = player.getGame();
|
||||
if (sa.hasParam("Optional") && sa.hasParam("OptionPrompt") && //for now, OptionPrompt is needed
|
||||
!player.getController().confirmAction(sa, null, sa.getParam("OptionPrompt"))) {
|
||||
!player.getController().confirmAction(sa, null, sa.getParam("OptionPrompt"), null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ public class RepeatEachEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
for (final Player p : repeatPlayers) {
|
||||
if (optional && !p.getController().confirmAction(repeat, null, sa.getParam("RepeatOptionalMessage"))) {
|
||||
if (optional && !p.getController().confirmAction(repeat, null, sa.getParam("RepeatOptionalMessage"), null)) {
|
||||
continue;
|
||||
}
|
||||
if (nextTurn) {
|
||||
|
||||
@@ -115,7 +115,7 @@ public class RepeatEffect extends SpellAbilityEffect {
|
||||
Player decider = sa.hasParam("RepeatOptionalDecider")
|
||||
? AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("RepeatOptionalDecider"), sa).get(0)
|
||||
: sa.getActivatingPlayer();
|
||||
return decider.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantRepeatProcessAgain"));
|
||||
return decider.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantRepeatProcessAgain"), null);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -55,6 +55,7 @@ public class RestartGameEffect extends SpellAbilityEffect {
|
||||
game.getStack().reset();
|
||||
game.clearCounterAddedThisTurn();
|
||||
game.setMonarch(null);
|
||||
game.setHasInitiative(null);
|
||||
game.setDayTime(null);
|
||||
GameAction action = game.getAction();
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ public class RevealHandEffect extends SpellAbilityEffect {
|
||||
|
||||
for (final Player p : getTargetPlayers(sa)) {
|
||||
if ((tgt == null) || p.canBeTargetedBy(sa)) {
|
||||
if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantRevealYourHand"))) {
|
||||
if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantRevealYourHand"), null)) {
|
||||
continue;
|
||||
}
|
||||
CardCollectionView hand = p.getCardsIn(ZoneType.Hand);
|
||||
|
||||
@@ -126,13 +126,13 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
|
||||
total += modifier;
|
||||
if (sa.hasParam("ResultSVar")) {
|
||||
host.setSVar(sa.getParam("ResultSVar"), Integer.toString(total));
|
||||
sa.setSVar(sa.getParam("ResultSVar"), Integer.toString(total));
|
||||
}
|
||||
if (sa.hasParam("ChosenSVar")) {
|
||||
int chosen = player.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblChooseAResult"), rolls, player);
|
||||
String message = Localizer.getInstance().getMessage("lblPlayerChooseValue", player, chosen);
|
||||
player.getGame().getAction().notifyOfValue(sa, player, message, player);
|
||||
host.setSVar(sa.getParam("ChosenSVar"), Integer.toString(chosen));
|
||||
sa.setSVar(sa.getParam("ChosenSVar"), Integer.toString(chosen));
|
||||
if (sa.hasParam("OtherSVar")) {
|
||||
int other = rolls.get(0);
|
||||
for (int i = 1; i < rolls.size(); ++i) {
|
||||
@@ -141,7 +141,7 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
break;
|
||||
}
|
||||
}
|
||||
host.setSVar(sa.getParam("OtherSVar"), Integer.toString(other));
|
||||
sa.setSVar(sa.getParam("OtherSVar"), Integer.toString(other));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ public class SacrificeEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("Echo")) {
|
||||
boolean isPaid;
|
||||
if (activator.hasKeyword("You may pay 0 rather than pay the echo cost for permanents you control.")
|
||||
&& activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPayEcho") + " {0}?")) {
|
||||
&& activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPayEcho") + " {0}?", null)) {
|
||||
isPaid = true;
|
||||
} else {
|
||||
isPaid = activator.getController().payManaOptional(card, new Cost(sa.getParam("Echo"), true),
|
||||
@@ -110,7 +110,7 @@ public class SacrificeEffect extends SpellAbilityEffect {
|
||||
if (valid.equals("Self") && game.getZoneOf(card) != null) {
|
||||
if (game.getZoneOf(card).is(ZoneType.Battlefield)) {
|
||||
if (!optional || activator.getController().confirmAction(sa, null,
|
||||
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", card.getName()))) {
|
||||
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", card.getName()), null)) {
|
||||
if (game.getAction().sacrifice(card, sa, true, table, params) != null) {
|
||||
if (remSacrificed) {
|
||||
card.addRemembered(card);
|
||||
@@ -153,7 +153,7 @@ public class SacrificeEffect extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("Random")) {
|
||||
choosenToSacrifice = Aggregates.random(validTargets, Math.min(amount, validTargets.size()), new CardCollection());
|
||||
} else if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantSacrifice"))) {
|
||||
} else if (optional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantSacrifice"), null)) {
|
||||
choosenToSacrifice = CardCollection.EMPTY;
|
||||
} else {
|
||||
boolean isStrict = sa.hasParam("StrictAmount");
|
||||
|
||||
@@ -44,7 +44,7 @@ public class ScryEffect extends SpellAbilityEffect {
|
||||
// Optional here for spells that have optional multi-player scrying
|
||||
for (final Player p : getTargetPlayers(sa)) {
|
||||
if ( (!sa.usesTargeting() || p.canBeTargetedBy(sa)) &&
|
||||
(!isOptional || p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWanttoScry"))) ) {
|
||||
(!isOptional || p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWanttoScry"), null)) ) {
|
||||
players.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ public class SetStateEffect extends SpellAbilityEffect {
|
||||
|
||||
if (optional) {
|
||||
String message = TextUtil.concatWithSpace("Transform", gameCard.getName(), "?");
|
||||
if (!p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message)) {
|
||||
if (!p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message, null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class ShuffleEffect extends SpellAbilityEffect {
|
||||
|
||||
for (final Player p : tgtPlayers) {
|
||||
if ((tgt == null) || p.canBeTargetedBy(sa)) {
|
||||
boolean mustShuffle = !optional || sa.getActivatingPlayer().getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblHaveTargetShuffle", p.getName()));
|
||||
boolean mustShuffle = !optional || sa.getActivatingPlayer().getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblHaveTargetShuffle", p.getName()), null);
|
||||
if (mustShuffle)
|
||||
p.shuffle(sa);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class SurveilEffect extends SpellAbilityEffect {
|
||||
|
||||
for (final Player p : getTargetPlayers(sa)) {
|
||||
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
|
||||
if (isOptional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantSurveil"))) {
|
||||
if (isOptional && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantSurveil"), null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Lang;
|
||||
|
||||
public class TakeInitiativeEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
final List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
|
||||
sb.append(Lang.joinHomogenous(tgtPlayers)).append(tgtPlayers.size() == 1 ? " takes" : " take");
|
||||
sb.append(" the initiative.");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
// TODO: improve ai and fix corner cases
|
||||
final String set = sa.getHostCard().getSetCode();
|
||||
|
||||
for (final Player p : getTargetPlayers(sa)) {
|
||||
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
|
||||
p.getGame().getAction().takeInitiative(p, set);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,8 @@ public class TokenEffect extends TokenEffectBase {
|
||||
if (numTokens != 0) { //0 probably means calculation isn't ready in time for stack
|
||||
if (numTokens != 1) { //if we are making more than one, substitute the numeral for a/an
|
||||
String numeral = " " + Lang.getNumeral(numTokens) + " ";
|
||||
String target = " " + words.get(words.indexOf(verb) + 1) + " ";
|
||||
List<String> words2 = Arrays.asList(desc.split(" "));
|
||||
String target = " " + words2.get(words2.indexOf(verb) + 1) + " ";
|
||||
desc = desc.replaceFirst(target, numeral);
|
||||
}
|
||||
//try to cut out unneeded description, which would now be confusing
|
||||
|
||||
@@ -24,6 +24,7 @@ import forge.game.trigger.WrappedAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.PaperCard;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.PredicateString.StringOp;
|
||||
|
||||
public class VentureEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -42,10 +43,19 @@ public class VentureEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new dungeon card chosen by player in command zone.
|
||||
List<PaperCard> dungeonCards = StaticData.instance().getVariantCards().getAllCards(
|
||||
Predicates.compose(CardRulesPredicates.Presets.IS_DUNGEON, PaperCard.FN_GET_RULES));
|
||||
|
||||
List<PaperCard> dungeonCards = null;
|
||||
if (sa.hasParam("Dungeon")) {
|
||||
dungeonCards = StaticData.instance().getVariantCards()
|
||||
.getAllCards(Predicates.compose(
|
||||
Predicates.and(CardRulesPredicates.Presets.IS_DUNGEON,
|
||||
CardRulesPredicates.subType(StringOp.EQUALS, sa.getParam("Dungeon"))),
|
||||
PaperCard.FN_GET_RULES));
|
||||
} else {
|
||||
// Create a new dungeon card chosen by player in command zone.
|
||||
dungeonCards = StaticData.instance().getVariantCards().getAllCards(
|
||||
Predicates.compose(CardRulesPredicates.Presets.IS_DUNGEON, PaperCard.FN_GET_RULES));
|
||||
dungeonCards.removeIf(c -> !c.getRules().isEnterableDungeon());
|
||||
}
|
||||
String message = Localizer.getInstance().getMessage("lblChooseDungeon");
|
||||
Card dungeon = player.getController().chooseDungeon(player, dungeonCards, message);
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ public class VoteEffect extends SpellAbilityEffect {
|
||||
}
|
||||
if (sa.hasParam("StoreVoteNum")) {
|
||||
for (final Object type : voteType) {
|
||||
host.setSVar("VoteNum" + type, "Number$" + votes.get(type).size());
|
||||
sa.setSVar("VoteNum" + type, "Number$" + votes.get(type).size());
|
||||
}
|
||||
} else {
|
||||
for (final String subAb : subAbs) {
|
||||
|
||||
@@ -54,13 +54,7 @@ import forge.game.replacement.ReplacementHandler;
|
||||
import forge.game.replacement.ReplacementResult;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
||||
import forge.game.staticability.StaticAbilityCantPutCounter;
|
||||
import forge.game.staticability.StaticAbilityCantSacrifice;
|
||||
import forge.game.staticability.StaticAbilityCantTarget;
|
||||
import forge.game.staticability.StaticAbilityCantTransform;
|
||||
import forge.game.staticability.StaticAbilityIgnoreLegendRule;
|
||||
import forge.game.staticability.*;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.trigger.TriggerType;
|
||||
@@ -165,6 +159,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
private final NavigableMap<Long, CardCloneStates> clonedStates = Maps.newTreeMap(); // Layer 1
|
||||
|
||||
private final Table<Long, Long, Map<String, String>> changedSVars = TreeBasedTable.create();
|
||||
|
||||
private final Map<Long, PlayerCollection> mayLook = Maps.newHashMap();
|
||||
private final PlayerCollection mayLookFaceDownExile = new PlayerCollection();
|
||||
private final PlayerCollection mayLookTemp = new PlayerCollection();
|
||||
@@ -181,15 +177,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
private final CardChangedWords changedTextColors = new CardChangedWords();
|
||||
private final CardChangedWords changedTextTypes = new CardChangedWords();
|
||||
|
||||
/** Original values of SVars changed by text changes. */
|
||||
private Map<String, String> originalSVars = Maps.newHashMap();
|
||||
|
||||
private final Set<Object> rememberedObjects = Sets.newLinkedHashSet();
|
||||
private Map<Player, String> flipResult;
|
||||
|
||||
private List<Pair<Card, Integer>> receivedDamageFromThisTurn = Lists.newArrayList();
|
||||
private Map<Player, Integer> receivedDamageFromPlayerThisTurn = Maps.newHashMap();
|
||||
|
||||
private final Map<Card, Integer> assignedDamageMap = Maps.newTreeMap();
|
||||
|
||||
private boolean isCommander = false;
|
||||
@@ -264,7 +254,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
private String oracleText = "";
|
||||
|
||||
private int damage;
|
||||
private Map<Integer, Integer> damage = Maps.newHashMap();
|
||||
private boolean hasBeenDealtDeathtouchDamage;
|
||||
|
||||
// regeneration
|
||||
@@ -510,7 +500,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
if (game != null) {
|
||||
// update Type, color and keywords again if they have changed
|
||||
if (!changedCardTypes.isEmpty()) {
|
||||
currentState.getView().updateType(currentState);
|
||||
updateTypesForView();
|
||||
}
|
||||
updateColorForView();
|
||||
|
||||
@@ -958,7 +948,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
devouredCards.add(c);
|
||||
}
|
||||
|
||||
public final void clearDevoured() {
|
||||
devouredCards = null;
|
||||
}
|
||||
@@ -989,7 +978,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
delvedCards = null;
|
||||
}
|
||||
|
||||
|
||||
public final CardCollectionView getConvoked() {
|
||||
return CardCollection.getView(convokedCards);
|
||||
}
|
||||
@@ -1592,10 +1580,20 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
public final String getSVar(final String var) {
|
||||
for (Map<String, String> map : changedSVars.values()) {
|
||||
if (map.containsKey(var)) {
|
||||
return map.get(var);
|
||||
}
|
||||
}
|
||||
return currentState.getSVar(var);
|
||||
}
|
||||
|
||||
public final boolean hasSVar(final String var) {
|
||||
for (Map<String, String> map : changedSVars.values()) {
|
||||
if (map.containsKey(var)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return currentState.hasSVar(var);
|
||||
}
|
||||
|
||||
@@ -1621,6 +1619,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
currentState.removeSVar(var);
|
||||
}
|
||||
|
||||
public final void addChangedSVars(Map<String, String> map, long timestamp, long staticId) {
|
||||
this.changedSVars.put(timestamp, staticId, map);
|
||||
}
|
||||
public final void removeChangedSVars(long timestamp, long staticId) {
|
||||
this.changedSVars.remove(timestamp, staticId);
|
||||
}
|
||||
|
||||
public final int getTurnInZone() {
|
||||
return turnInZone;
|
||||
}
|
||||
@@ -2091,7 +2096,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|| keyword.equals("Ascend") || keyword.equals("Totem armor")
|
||||
|| keyword.equals("Battle cry") || keyword.equals("Devoid") || keyword.equals("Riot")
|
||||
|| keyword.equals("Daybound") || keyword.equals("Nightbound")
|
||||
|| keyword.equals("Friends forever")) {
|
||||
|| keyword.equals("Friends forever") || keyword.equals("Choose a Background")) {
|
||||
sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")");
|
||||
} else if (keyword.startsWith("Partner:")) {
|
||||
final String[] k = keyword.split(":");
|
||||
@@ -2583,7 +2588,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|| keyword.equals("Devoid") || keyword.equals("Lifelink")
|
||||
|| keyword.equals("Split second")) {
|
||||
sbBefore.append(keyword).append(" (").append(inst.getReminderText()).append(")");
|
||||
sbBefore.append("\r\n");
|
||||
sbBefore.append("\r\n\r\n");
|
||||
} else if (keyword.equals("Conspire") || keyword.equals("Epic")
|
||||
|| keyword.equals("Suspend") || keyword.equals("Jump-start")
|
||||
|| keyword.equals("Fuse")) {
|
||||
@@ -3306,8 +3311,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
public final void removeTempController(final Player player) {
|
||||
boolean changed = false;
|
||||
// Remove each key that yields this player
|
||||
this.tempControllers.values().remove(player);
|
||||
while (tempControllers.values().remove(player)) {
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
view.updateController(this);
|
||||
}
|
||||
}
|
||||
|
||||
public final void clearTempControllers() {
|
||||
@@ -4124,8 +4135,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
public final boolean toughnessAssignsDamage() {
|
||||
return getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.toughnessAssignsDamage)
|
||||
|| hasKeyword("CARDNAME assigns combat damage equal to its toughness rather than its power");
|
||||
return StaticAbilityCombatDamageToughness.combatDamageToughness(this);
|
||||
}
|
||||
|
||||
// How much combat damage does the card deal
|
||||
@@ -4448,6 +4458,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
KeywordInterface result;
|
||||
if (staticId < 1 || !storedKeywords.contains(staticId, kw)) {
|
||||
result = Keyword.getInstance(kw);
|
||||
result.setStaticId(staticId);
|
||||
result.createTraits(this, false);
|
||||
if (staticId > 0) {
|
||||
storedKeywords.put(staticId, kw, result);
|
||||
@@ -4457,6 +4468,12 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public final void addKeywordForStaticAbility(KeywordInterface kw) {
|
||||
if (kw.getStaticId() > 0) {
|
||||
storedKeywords.put(kw.getStaticId(), kw.getOriginal(), kw);
|
||||
}
|
||||
}
|
||||
|
||||
public final void addChangedCardKeywordsByText(final List<KeywordInterface> keywords, final long timestamp, final long staticId, final boolean updateView) {
|
||||
// keywords should already created for Card, so no addKeywordsToCard
|
||||
@@ -4597,7 +4614,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
* Update the changed text of the intrinsic spell abilities and keywords.
|
||||
*/
|
||||
public void updateChangedText() {
|
||||
resetChangedSVars();
|
||||
|
||||
// update type
|
||||
List<String> toAdd = Lists.newArrayList();
|
||||
@@ -4665,26 +4681,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
changedTextTypes.copyFrom(other.changedTextTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a SVar due to a text change effect. Change is volatile and will be
|
||||
* reverted upon refreshing text changes (unless it is changed again at that
|
||||
* time).
|
||||
*
|
||||
* @param key the SVar name.
|
||||
* @param value the new SVar value.
|
||||
*/
|
||||
public final void changeSVar(final String key, final String value) {
|
||||
originalSVars.put(key, getSVar(key));
|
||||
setSVar(key, value);
|
||||
}
|
||||
|
||||
private void resetChangedSVars() {
|
||||
for (final Entry<String, String> svar : originalSVars.entrySet()) {
|
||||
setSVar(svar.getKey(), svar.getValue());
|
||||
}
|
||||
originalSVars.clear();
|
||||
}
|
||||
|
||||
public final KeywordInterface addIntrinsicKeyword(final String s) {
|
||||
KeywordInterface inst = currentState.addIntrinsicKeyword(s, true);
|
||||
if (inst != null) {
|
||||
@@ -5298,62 +5294,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
damageHistory = history;
|
||||
}
|
||||
|
||||
public final List<Pair<Card, Integer>> getReceivedDamageFromThisTurn() {
|
||||
return receivedDamageFromThisTurn;
|
||||
public List<Pair<Integer, Boolean>> getDamageReceivedThisTurn() {
|
||||
return damageReceivedThisTurn;
|
||||
}
|
||||
public final void setReceivedDamageFromThisTurn(final List<Pair<Card, Integer>> receivedDamageList) {
|
||||
receivedDamageFromThisTurn = Lists.newArrayList(receivedDamageList);
|
||||
}
|
||||
public final Map<Player, Integer> getReceivedDamageFromPlayerThisTurn() {
|
||||
return receivedDamageFromPlayerThisTurn;
|
||||
}
|
||||
public final void setReceivedDamageFromPlayerThisTurn(final Map<Player, Integer> receivedDamageMap) {
|
||||
receivedDamageFromPlayerThisTurn = Maps.newHashMap(receivedDamageMap);
|
||||
}
|
||||
|
||||
public int getReceivedDamageByPlayerThisTurn(final Player p) {
|
||||
if (receivedDamageFromPlayerThisTurn.containsKey(p)) {
|
||||
return receivedDamageFromPlayerThisTurn.get(p);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public final void addReceivedDamageFromThisTurn(Card c, final int damage) {
|
||||
int currentDamage = 0;
|
||||
// because Aegar cares about the past state we need to keep all LKI instances
|
||||
receivedDamageFromThisTurn.add(Pair.of(c.isLKI() ? c : CardUtil.getLKICopy(c), damage));
|
||||
|
||||
Player p = c.getController();
|
||||
if (p != null) {
|
||||
if (receivedDamageFromPlayerThisTurn.containsKey(p)) {
|
||||
currentDamage = receivedDamageFromPlayerThisTurn.get(p);
|
||||
}
|
||||
receivedDamageFromPlayerThisTurn.put(p, damage+currentDamage);
|
||||
}
|
||||
}
|
||||
public final void resetReceivedDamageFromThisTurn() {
|
||||
receivedDamageFromThisTurn.clear();
|
||||
receivedDamageFromPlayerThisTurn.clear();
|
||||
}
|
||||
|
||||
public final int getTotalDamageReceivedThisTurn() {
|
||||
int total = 0;
|
||||
for (Integer i : receivedDamageFromPlayerThisTurn.values()) {
|
||||
total += i;
|
||||
}
|
||||
return total;
|
||||
public void setDamageReceivedThisTurn(List<Pair<Integer, Boolean>> dmg) {
|
||||
damageReceivedThisTurn.addAll(dmg);
|
||||
}
|
||||
|
||||
public final boolean hasDealtDamageToOpponentThisTurn() {
|
||||
for (final GameEntity e : getDamageHistory().getThisTurnDamaged().keySet()) {
|
||||
if (e instanceof Player) {
|
||||
final Player p = (Player) e;
|
||||
if (getController().isOpponentOf(p)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return getDamageHistory().getDamageDoneThisTurn(null, true, null, "Player.Opponent", this, getController(), null) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5362,11 +5311,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
* @return the damage done to player p this turn
|
||||
*/
|
||||
public final int getTotalDamageDoneBy() {
|
||||
int sum = 0;
|
||||
for (final GameEntity e : getDamageHistory().getThisTurnDamaged().keySet()) {
|
||||
sum += getDamageHistory().getThisTurnDamaged().get(e);
|
||||
}
|
||||
return sum;
|
||||
return getDamageHistory().getDamageDoneThisTurn(null, false, null, null, this, getController(), null);
|
||||
}
|
||||
|
||||
// this is the amount of damage a creature needs to receive before it dies
|
||||
@@ -5389,15 +5334,26 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
public final int getDamage() {
|
||||
return damage;
|
||||
int sum = 0;
|
||||
for (int i : damage.values()) {
|
||||
sum += i;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
public final void setDamage(int damage0) {
|
||||
if (damage == damage0) { return; }
|
||||
damage = damage0;
|
||||
if (getDamage() == damage0) { return; }
|
||||
damage.clear();
|
||||
if (damage0 != 0) {
|
||||
damage.put(0, damage0);
|
||||
}
|
||||
view.updateDamage(this);
|
||||
getGame().fireEvent(new GameEventCardStatsChanged(this));
|
||||
}
|
||||
|
||||
public int getMaxDamageFromSource() {
|
||||
return damage.isEmpty() ? 0 : Collections.max(damage.values());
|
||||
}
|
||||
|
||||
public final boolean hasBeenDealtDeathtouchDamage() {
|
||||
return hasBeenDealtDeathtouchDamage;
|
||||
}
|
||||
@@ -5548,14 +5504,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
// Run replacement effects
|
||||
getGame().getReplacementHandler().run(ReplacementType.DealtDamage, AbilityKey.mapFromAffected(this));
|
||||
|
||||
addReceivedDamageFromThisTurn(source, damageIn);
|
||||
source.getDamageHistory().registerDamage(this, damageIn);
|
||||
if (isCombat) {
|
||||
source.getDamageHistory().registerCombatDamage(this, damageIn);
|
||||
} else {
|
||||
getDamageHistory().setHasBeenDealtNonCombatDamageThisTurn(true);
|
||||
}
|
||||
|
||||
// Run triggers
|
||||
Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.DamageSource, source);
|
||||
@@ -5582,7 +5530,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
damageType = DamageType.M1M1Counters;
|
||||
}
|
||||
else { // 120.3e
|
||||
damage += damageIn;
|
||||
int old = damage.getOrDefault(Objects.hash(source.getId(), source.getTimestamp()), 0);
|
||||
damage.put(Objects.hash(source.getId(), source.getTimestamp()), old + damageIn);
|
||||
view.updateDamage(this);
|
||||
}
|
||||
|
||||
@@ -5720,7 +5669,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public final void setForetold(final boolean foretold) {
|
||||
this.foretold = foretold;
|
||||
}
|
||||
@@ -5738,7 +5686,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
public final void setForetoldThisTurn(final boolean foretoldThisTurn) {
|
||||
this.foretoldThisTurn = foretoldThisTurn;
|
||||
}
|
||||
|
||||
public void resetForetoldThisTurn() {
|
||||
foretoldThisTurn = false;
|
||||
}
|
||||
@@ -5809,7 +5756,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
public final long getBestowTimestamp() {
|
||||
return bestowTimestamp;
|
||||
}
|
||||
|
||||
public final void setBestowTimestamp(final long t) {
|
||||
bestowTimestamp = t;
|
||||
}
|
||||
@@ -6212,7 +6158,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
setDamage(0);
|
||||
}
|
||||
setHasBeenDealtDeathtouchDamage(false);
|
||||
resetReceivedDamageFromThisTurn();
|
||||
setRegeneratedThisTurn(0);
|
||||
resetShield();
|
||||
setBecameTargetThisTurn(false);
|
||||
@@ -6220,6 +6165,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
clearMustBlockCards();
|
||||
getDamageHistory().setCreatureAttackedLastTurnOf(turn, getDamageHistory().getCreatureAttacksThisTurn() > 0);
|
||||
getDamageHistory().newTurn();
|
||||
damageReceivedThisTurn.clear();
|
||||
clearBlockedByThisTurn();
|
||||
clearBlockedThisTurn();
|
||||
resetMayPlayTurn();
|
||||
|
||||
@@ -4,11 +4,15 @@ package forge.game.card;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.game.CardTraitBase;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
@@ -19,7 +23,7 @@ public class CardDamageHistory {
|
||||
private boolean creatureAttackedThisCombat = false;
|
||||
private boolean creatureBlockedThisCombat = false;
|
||||
private boolean creatureGotBlockedThisCombat = false;
|
||||
private boolean receivedNonCombatDamageThisTurn = false;
|
||||
|
||||
private List<GameEntity> attackedThisTurn = Lists.newArrayList();
|
||||
|
||||
private final List<Player> creatureAttackedLastTurnOf = Lists.newArrayList();
|
||||
@@ -27,13 +31,16 @@ public class CardDamageHistory {
|
||||
private final List<Player> NotBlockedSinceLastUpkeepOf = Lists.newArrayList();
|
||||
private final List<Player> NotBeenBlockedSinceLastUpkeepOf = Lists.newArrayList();
|
||||
|
||||
private final Map<GameEntity, Integer> damagedThisCombat = Maps.newHashMap();
|
||||
private final Map<GameEntity, Integer> damagedThisTurn = Maps.newHashMap();
|
||||
private final Map<GameEntity, Integer> damagedThisTurnInCombat = Maps.newHashMap();
|
||||
private final Map<GameEntity, Integer> damagedThisGame = Maps.newHashMap();
|
||||
private List<Pair<Integer, Boolean>> damageDoneThisTurn = Lists.newArrayList();
|
||||
|
||||
// only needed for Glen Elendra (Plane)
|
||||
private final List<Player> damagedThisCombat = Lists.newArrayList();
|
||||
// only needed for The Fallen
|
||||
private final FCollection<GameEntity> damagedThisGame = new FCollection<>();
|
||||
boolean hasdealtDamagetoAny = false;
|
||||
|
||||
public final boolean getHasdealtDamagetoAny() {
|
||||
return !damagedThisGame.isEmpty();
|
||||
return hasdealtDamagetoAny;
|
||||
}
|
||||
|
||||
// used to see if an attacking creature with a triggering attack ability
|
||||
@@ -218,87 +225,68 @@ public class CardDamageHistory {
|
||||
return this.creatureGotBlockedThisCombat;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>receivedNonCombatDamageThisTurn</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean hasBeenDealtNonCombatDamageThisTurn() {
|
||||
return this.receivedNonCombatDamageThisTurn;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>receivedNonCombatDamageThisTurn</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param b
|
||||
* a boolean.
|
||||
*/
|
||||
public void setHasBeenDealtNonCombatDamageThisTurn(boolean b) {
|
||||
this.receivedNonCombatDamageThisTurn = b;
|
||||
}
|
||||
|
||||
public final Map<GameEntity, Integer> getThisCombatDamaged() {
|
||||
public final List<Player> getThisCombatDamaged() {
|
||||
return damagedThisCombat;
|
||||
}
|
||||
public final Map<GameEntity, Integer> getThisTurnDamaged() {
|
||||
return damagedThisTurn;
|
||||
}
|
||||
public final Map<GameEntity, Integer> getThisTurnCombatDamaged() {
|
||||
return damagedThisTurnInCombat;
|
||||
}
|
||||
public final Map<GameEntity, Integer> getThisGameDamaged() {
|
||||
public final FCollection<GameEntity> getThisGameDamaged() {
|
||||
return damagedThisGame;
|
||||
}
|
||||
/**
|
||||
* TODO: Write javadoc for this method.
|
||||
* @param player
|
||||
*/
|
||||
public void registerCombatDamage(GameEntity entity, int amount) {
|
||||
int old = 0;
|
||||
if (damagedThisCombat.containsKey(entity)) {
|
||||
old = damagedThisCombat.get(entity);
|
||||
public void registerDamage(int damage, boolean isCombat, Card sourceLKI, GameEntity target, Map<Integer, Card> lkiCache) {
|
||||
damagedThisGame.add(target);
|
||||
hasdealtDamagetoAny = true;
|
||||
if (isCombat && target instanceof Player) {
|
||||
damagedThisCombat.add((Player) target);
|
||||
}
|
||||
damagedThisCombat.put(entity, old + amount);
|
||||
old = 0;
|
||||
if (damagedThisTurnInCombat.containsKey(entity)) {
|
||||
old = damagedThisTurnInCombat.get(entity);
|
||||
}
|
||||
damagedThisTurnInCombat.put(entity, old + amount);
|
||||
Pair<Integer, Boolean> dmg = Pair.of(damage, isCombat);
|
||||
damageDoneThisTurn.add(dmg);
|
||||
target.receiveDamage(dmg);
|
||||
sourceLKI.getGame().addGlobalDamageHistory(this, dmg, sourceLKI.isLKI() ? sourceLKI : CardUtil.getLKICopy(sourceLKI, lkiCache), CardUtil.getLKICopy(target, lkiCache));
|
||||
}
|
||||
/**
|
||||
* TODO: Write javadoc for this method.
|
||||
*/
|
||||
|
||||
public int getDamageDoneThisTurn(Boolean isCombat, boolean anyIsEnough, String validSourceCard, String validTargetEntity, Card source, Player sourceController, CardTraitBase ctb) {
|
||||
int sum = 0;
|
||||
for (Pair<Integer, Boolean> damage : damageDoneThisTurn) {
|
||||
Pair<Card, GameEntity> sourceToTarget = sourceController.getGame().getDamageLKI(damage);
|
||||
|
||||
if (isCombat != null && damage.getRight() != isCombat) {
|
||||
continue;
|
||||
}
|
||||
if (validSourceCard != null && !sourceToTarget.getLeft().isValid(validSourceCard.split(","), sourceController, source == null ? sourceToTarget.getLeft() : source, ctb)) {
|
||||
continue;
|
||||
}
|
||||
if (validTargetEntity != null && !sourceToTarget.getRight().isValid(validTargetEntity.split(","), sourceController, source, ctb)) {
|
||||
continue;
|
||||
}
|
||||
sum += damage.getLeft();
|
||||
if (anyIsEnough) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
public void newTurn() {
|
||||
damagedThisCombat.clear();
|
||||
damagedThisTurnInCombat.clear();
|
||||
damagedThisTurn.clear();
|
||||
attackedThisTurn.clear();
|
||||
setHasBeenDealtNonCombatDamageThisTurn(false);
|
||||
damagedThisCombat.clear();
|
||||
damageDoneThisTurn.clear();
|
||||
|
||||
// if card already LTB we can safely dereference (allows quite a few objects to be cleaned up earlier for bigger boardstates)
|
||||
CardCollection toRemove = new CardCollection();
|
||||
for (GameEntity e : damagedThisGame) {
|
||||
if (e instanceof Card) {
|
||||
if (((Card) e).getZone().getZoneType() != ZoneType.Battlefield) {
|
||||
toRemove.add((Card)e);
|
||||
}
|
||||
}
|
||||
}
|
||||
damagedThisGame.removeAll(toRemove);
|
||||
}
|
||||
|
||||
public void endCombat() {
|
||||
damagedThisCombat.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this method.
|
||||
* @param player
|
||||
*/
|
||||
public void registerDamage(GameEntity entity, int amount) {
|
||||
int old = 0;
|
||||
if (damagedThisTurn.containsKey(entity)) {
|
||||
old = damagedThisTurn.get(entity);
|
||||
}
|
||||
damagedThisTurn.put(entity, old + amount);
|
||||
old = 0;
|
||||
if (damagedThisGame.containsKey(entity)) {
|
||||
old = damagedThisGame.get(entity);
|
||||
}
|
||||
damagedThisGame.put(entity, old + amount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameObjectPredicates;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.trigger.TriggerType;
|
||||
|
||||
public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
|
||||
@@ -66,11 +68,15 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
|
||||
game.getTriggerHandler().runTrigger(TriggerType.DamageDealtOnce, runParams, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Targets -> Source
|
||||
for (Map.Entry<GameEntity, Map<Card, Integer>> e : columnMap().entrySet()) {
|
||||
int sum = 0;
|
||||
for (final int i : e.getValue().values()) {
|
||||
sum += i;
|
||||
// controller list
|
||||
PlayerCollection controllers = new PlayerCollection();
|
||||
for (Entry<Card, Integer> ec : e.getValue().entrySet()) {
|
||||
sum += ec.getValue();
|
||||
controllers.add(ec.getKey().getController());
|
||||
}
|
||||
if (sum > 0) {
|
||||
final GameEntity ge = e.getKey();
|
||||
@@ -81,6 +87,15 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
|
||||
|
||||
game.getTriggerHandler().runTrigger(TriggerType.DamageDoneOnce, runParams, false);
|
||||
}
|
||||
for (Player p : controllers) {
|
||||
final GameEntity ge = e.getKey();
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.DamageTarget, ge);
|
||||
runParams.put(AbilityKey.DamageSource, p);
|
||||
runParams.put(AbilityKey.IsCombatDamage, isCombat);
|
||||
|
||||
game.getTriggerHandler().runTrigger(TriggerType.DamageDoneOnceByController, runParams, false);
|
||||
}
|
||||
}
|
||||
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
|
||||
@@ -29,6 +29,7 @@ import forge.game.Game;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementHandler;
|
||||
import forge.game.spellability.*;
|
||||
@@ -203,12 +204,25 @@ public class CardFactory {
|
||||
} else {
|
||||
copySA = targetSA.copy(c, controller, false);
|
||||
c.setCastSA(copySA);
|
||||
// need to copy keyword
|
||||
if (targetSA.getKeyword() != null) {
|
||||
KeywordInterface kw = targetSA.getKeyword().copy(c, false);
|
||||
copySA.setKeyword(kw);
|
||||
// need to add the keyword to so static doesn't make new keyword
|
||||
c.addKeywordForStaticAbility(kw);
|
||||
}
|
||||
}
|
||||
|
||||
copySA.setCopied(true);
|
||||
// 707.10b
|
||||
copySA.setOriginalAbility(targetSA);
|
||||
|
||||
// Copied spell is not cast face down
|
||||
if (copySA instanceof Spell) {
|
||||
Spell spell = (Spell) copySA;
|
||||
spell.setCastFaceDown(false);
|
||||
}
|
||||
|
||||
if (targetSA.usesTargeting()) {
|
||||
// do for SubAbilities too?
|
||||
copySA.setTargets(targetSA.getTargets().clone());
|
||||
|
||||
@@ -52,7 +52,6 @@ import forge.game.GameLogEntryType;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
@@ -651,14 +650,13 @@ public class CardFactoryUtil {
|
||||
final boolean intrinsic, final String valid, final String zone) {
|
||||
Card host = card.getCard();
|
||||
String desc = repAb.getDescription();
|
||||
setupETBReplacementAbility(repAb);
|
||||
if (!intrinsic) {
|
||||
repAb.setIntrinsic(false);
|
||||
}
|
||||
|
||||
StringBuilder repEffsb = new StringBuilder();
|
||||
repEffsb.append("Event$ Moved | ValidCard$ ").append(valid);
|
||||
repEffsb.append(" | Destination$ Battlefield | Description$ ").append(desc);
|
||||
repEffsb.append(" | Destination$ Battlefield | ReplacementResult$ Updated | Description$ ").append(desc);
|
||||
if (optional) {
|
||||
repEffsb.append(" | Optional$ True");
|
||||
}
|
||||
@@ -751,13 +749,12 @@ public class CardFactoryUtil {
|
||||
}
|
||||
|
||||
SpellAbility sa = AbilityFactory.getAbility(abStr, card);
|
||||
setupETBReplacementAbility(sa);
|
||||
if (!intrinsic) {
|
||||
sa.setIntrinsic(false);
|
||||
}
|
||||
|
||||
String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield "
|
||||
+ "| Secondary$ True | Description$ " + desc + (!extraparams.equals("") ? " | " + extraparams : "");
|
||||
+ "| Secondary$ True | ReplacementResult$ Updated | Description$ " + desc + (!extraparams.equals("") ? " | " + extraparams : "");
|
||||
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, card.getCard(), intrinsic, card);
|
||||
|
||||
@@ -866,7 +863,7 @@ public class CardFactoryUtil {
|
||||
inst.addTrigger(bushidoTrigger1);
|
||||
inst.addTrigger(bushidoTrigger2);
|
||||
} else if (keyword.equals("Cascade")) {
|
||||
final StringBuilder trigScript = new StringBuilder("Mode$ SpellCast | ValidCard$ Card.Self" +
|
||||
final StringBuilder trigScript = new StringBuilder("Mode$ SpellCast | ValidCard$ Card.Self | TriggerZones$ Stack" +
|
||||
" | Secondary$ True | TriggerDescription$ Cascade - CARDNAME");
|
||||
|
||||
final String abString = "DB$ DigUntil | Defined$ You | Amount$ 1 | Valid$ Card.nonLand+cmcLTCascadeX" +
|
||||
@@ -959,7 +956,7 @@ public class CardFactoryUtil {
|
||||
inst.addTrigger(parsedTrigger);
|
||||
inst.addTrigger(parsedTrigReturn);
|
||||
} else if (keyword.startsWith("Casualty")) {
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ Casualty | Secondary$ True";
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ Casualty | TriggerZones$ Stack | Secondary$ True";
|
||||
String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ 1 | MayChooseTarget$ True";
|
||||
String[] k = keyword.split(":");
|
||||
if (k.length > 2) {
|
||||
@@ -972,7 +969,7 @@ public class CardFactoryUtil {
|
||||
|
||||
inst.addTrigger(casualtyTrigger);
|
||||
} else if (keyword.equals("Conspire")) {
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ Conspire | Secondary$ True | TriggerDescription$ Copy CARDNAME if its conspire cost was paid";
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ Conspire | TriggerZones$ Stack | Secondary$ True | TriggerDescription$ Copy CARDNAME if its conspire cost was paid";
|
||||
final String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ 1 | MayChooseTarget$ True";
|
||||
|
||||
final Trigger conspireTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic);
|
||||
@@ -1037,7 +1034,7 @@ public class CardFactoryUtil {
|
||||
parsedTrigger.setOverridingAbility(delayTrigSA);
|
||||
inst.addTrigger(parsedTrigger);
|
||||
} else if (keyword.equals("Demonstrate")) {
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerDescription$ Demonstrate (" + inst.getReminderText() + ")";
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerZones$ Stack | TriggerDescription$ Demonstrate (" + inst.getReminderText() + ")";
|
||||
final String youCopyStr = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True | Optional$ True | RememberCopies$ True | IgnoreFreeze$ True";
|
||||
final String chooseOppStr = "DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | ConditionDefined$ Remembered | ConditionPresent$ Spell";
|
||||
final String oppCopyStr = "DB$ CopySpellAbility | Controller$ ChosenPlayer | Defined$ TriggeredSpellAbility | MayChooseTarget$ True | ConditionDefined$ Remembered | ConditionPresent$ Spell";
|
||||
@@ -1226,7 +1223,7 @@ public class CardFactoryUtil {
|
||||
|
||||
inst.addTrigger(trigger);
|
||||
} else if (keyword.startsWith("Gravestorm")) {
|
||||
String trigStr = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerDescription$ Gravestorm (" + inst.getReminderText() + ")";
|
||||
String trigStr = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerZones$ Stack | TriggerDescription$ Gravestorm (" + inst.getReminderText() + ")";
|
||||
String copyStr = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ GravestormCount | MayChooseTarget$ True";
|
||||
|
||||
SpellAbility copySa = AbilityFactory.getAbility(copyStr, card);
|
||||
@@ -1683,7 +1680,7 @@ public class CardFactoryUtil {
|
||||
|
||||
final String abString = "DB$ PeekAndReveal | PeekAmount$ " + num + " | RememberRevealed$ True";
|
||||
|
||||
final String dbCast = "DB$ Play | Valid$ Card.IsRemembered+sameName | " +
|
||||
final String dbCast = "DB$ Play | Valid$ Card.IsRemembered+sameName | ValidSA$ Spell | " +
|
||||
"ValidZone$ Library | WithoutManaCost$ True | Optional$ True | " +
|
||||
"Amount$ All";
|
||||
|
||||
@@ -1784,7 +1781,7 @@ public class CardFactoryUtil {
|
||||
|
||||
inst.addTrigger(parsedTrigger);
|
||||
} else if (keyword.equals("Storm")) {
|
||||
final String actualTrigger = "Mode$ SpellCast | ValidCard$ Card.Self | Secondary$ True"
|
||||
final String actualTrigger = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerZones$ Stack | Secondary$ True"
|
||||
+ "| TriggerDescription$ Storm (" + inst.getReminderText() + ")";
|
||||
|
||||
String effect = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ StormCount | MayChooseTarget$ True";
|
||||
@@ -1896,7 +1893,7 @@ public class CardFactoryUtil {
|
||||
String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | " +
|
||||
"TriggerDescription$ " + sb.toString();
|
||||
|
||||
String effect = "DB$ Sacrifice | SacValid$ Self | UnlessPayer$ You | UnlessCost$ " + k[1];
|
||||
String effect = "DB$ SacrificeAll | Defined$ Self | Controller$ You | UnlessPayer$ You | UnlessCost$ " + k[1];
|
||||
|
||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(upkeepTrig, card, intrinsic);
|
||||
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));
|
||||
@@ -2055,7 +2052,7 @@ public class CardFactoryUtil {
|
||||
|
||||
// Setup ETB replacement effects
|
||||
final String actualRep = "Event$ Moved | Destination$ Battlefield | ValidCard$ Card.Self |"
|
||||
+ " | Description$ Amplify " + amplifyMagnitude + " ("
|
||||
+ " | ReplacementResult$ Updated | Description$ Amplify " + amplifyMagnitude + " ("
|
||||
+ inst.getReminderText() + ")";
|
||||
|
||||
final String abString = "DB$ Reveal | AnyNumber$ True | RevealValid$ "
|
||||
@@ -2075,7 +2072,6 @@ public class CardFactoryUtil {
|
||||
AbilitySub saCleanup = (AbilitySub) AbilityFactory.getAbility(dbClean, card);
|
||||
|
||||
saPut.setSubAbility(saCleanup);
|
||||
setupETBReplacementAbility(saCleanup);
|
||||
|
||||
saReveal.setSubAbility(saPut);
|
||||
|
||||
@@ -2168,13 +2164,12 @@ public class CardFactoryUtil {
|
||||
|
||||
inst.addReplacement(re);
|
||||
} else if (keyword.equals("Daybound")) {
|
||||
final String actualRep = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Night | Secondary$ True | Layer$ Transform | Description$ If it is night, this permanent enters the battlefield transformed.";
|
||||
final String actualRep = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Night | Secondary$ True | Layer$ Transform | ReplacementResult$ Updated | Description$ If it is night, this permanent enters the battlefield transformed.";
|
||||
final String abTransform = "DB$ SetState | Defined$ ReplacedCard | Mode$ Transform | ETB$ True | Daybound$ True";
|
||||
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(actualRep, host, intrinsic, card);
|
||||
|
||||
SpellAbility saTransform = AbilityFactory.getAbility(abTransform, card);
|
||||
setupETBReplacementAbility(saTransform);
|
||||
re.setOverridingAbility(saTransform);
|
||||
|
||||
inst.addReplacement(re);
|
||||
@@ -2206,11 +2201,10 @@ public class CardFactoryUtil {
|
||||
AbilitySub cleanupSA = (AbilitySub) AbilityFactory.getAbility(cleanupStr, card);
|
||||
counterSA.setSubAbility(cleanupSA);
|
||||
|
||||
String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | Secondary$ True | Description$ Devour " + magnitude + " ("+ inst.getReminderText() + ")";
|
||||
String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | Secondary$ True | ReplacementResult$ Updated | Description$ Devour " + magnitude + " ("+ inst.getReminderText() + ")";
|
||||
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, host, intrinsic, card);
|
||||
|
||||
setupETBReplacementAbility(cleanupSA);
|
||||
re.setOverridingAbility(sacrificeSA);
|
||||
|
||||
inst.addReplacement(re);
|
||||
@@ -2329,12 +2323,11 @@ public class CardFactoryUtil {
|
||||
+ " | Origin$ Stack | Destination$ Graveyard | Fizzle$ False "
|
||||
+ " | Description$ Rebound (" + inst.getReminderText() + ")";
|
||||
|
||||
String abExile = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Stack | Destination$ Exile";
|
||||
String abExile = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Stack | Destination$ Exile | RememberChanged$ True";
|
||||
String delTrig = "DB$ DelayedTrigger | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You " +
|
||||
" | OptionalDecider$ You | RememberObjects$ ReplacedCard | TriggerDescription$"
|
||||
" | OptionalDecider$ You | RememberObjects$ Remembered | TriggerDescription$"
|
||||
+ " At the beginning of your next upkeep, you may cast " + card.toString() + " without paying its mana cost.";
|
||||
// TODO add check for still in exile
|
||||
String abPlay = "DB$ Play | Defined$ DelayTriggerRemembered | WithoutManaCost$ True | Optional$ True";
|
||||
String abPlay = "DB$ Play | Defined$ DelayTriggerRememberedLKI | WithoutManaCost$ True | Optional$ True";
|
||||
|
||||
SpellAbility saExile = AbilityFactory.getAbility(abExile, card);
|
||||
|
||||
@@ -2686,7 +2679,11 @@ public class CardFactoryUtil {
|
||||
final String[] k = keyword.split(":");
|
||||
final Cost blitzCost = new Cost(k[1], false);
|
||||
|
||||
final SpellAbility newSA = card.getFirstSpellAbility().copyWithDefinedCost(blitzCost);
|
||||
final SpellAbility newSA = card.getFirstSpellAbility().copyWithManaCostReplaced(host.getController(), blitzCost);
|
||||
|
||||
if (k.length > 2) {
|
||||
newSA.getMapParams().put("ValidAfterStack", k[2]);
|
||||
}
|
||||
|
||||
final StringBuilder desc = new StringBuilder();
|
||||
desc.append("Blitz ").append(blitzCost.toSimpleString()).append(" (");
|
||||
@@ -2797,7 +2794,18 @@ public class CardFactoryUtil {
|
||||
abilityStr.append(equipCost);
|
||||
abilityStr.append("| ValidTgts$ ").append(valid);
|
||||
abilityStr.append(" | TgtPrompt$ Select target ").append(vstr).append(" you control ");
|
||||
abilityStr.append("| SorcerySpeed$ True | Equip$ True | AILogic$ Pump | IsPresent$ Equipment.Self+nonCreature ");
|
||||
if (card.hasKeyword(Keyword.RECONFIGURE)) {
|
||||
/*
|
||||
* 301.5c An Equipment that’s also a creature can’t equip a creature unless that Equipment has reconfigure (see rule 702.151, “Reconfigure”).
|
||||
* An Equipment that loses the subtype “Equipment” can’t equip a creature. An Equipment can’t equip itself. An Equipment that equips an illegal
|
||||
* or nonexistent permanent becomes unattached from that permanent but remains on the battlefield. (This is a state-based action. See rule 704.)
|
||||
* An Equipment can’t equip more than one creature. If a spell or ability would cause an Equipment to equip more than one creature,
|
||||
* the Equipment’s controller chooses which creature it equips.
|
||||
*/
|
||||
abilityStr.append("| SorcerySpeed$ True | Equip$ True | AILogic$ Pump | IsPresent$ Equipment.Self ");
|
||||
} else {
|
||||
abilityStr.append("| SorcerySpeed$ True | Equip$ True | AILogic$ Pump | IsPresent$ Equipment.Self+nonCreature ");
|
||||
}
|
||||
// add AttachAi for some special cards
|
||||
if (card.hasSVar("AttachAi")) {
|
||||
abilityStr.append("| ").append(card.getSVar("AttachAi"));
|
||||
@@ -3440,9 +3448,21 @@ public class CardFactoryUtil {
|
||||
st.setSVar("AffinityX", "Count$Valid " + t + ".YouCtrl");
|
||||
inst.addStaticAbility(st);
|
||||
} else if (keyword.startsWith("Blitz")) {
|
||||
String effect = "Mode$ Continuous | Affected$ Card.Self+blitzed | AddKeyword$ Haste | AddTrigger$ Dies";
|
||||
String trig = "Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | " +
|
||||
"Execute$ TrigDraw | TriggerDescription$ When this creature dies, draw a card.";
|
||||
final String[] k = keyword.split(":");
|
||||
final String manacost = k[1];
|
||||
final Cost cost = new Cost(manacost, false);
|
||||
|
||||
StringBuilder sb = new StringBuilder("Blitz");
|
||||
if (!cost.isOnlyManaCost()) {
|
||||
sb.append("—");
|
||||
} else {
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(cost.toSimpleString());
|
||||
String effect = "Mode$ Continuous | Affected$ Card.Self+blitzed+castKeyword | AddKeyword$ Haste | AddTrigger$ Dies"
|
||||
+ " | Secondary$ True | Description$ " + sb.toString() + " (" + inst.getReminderText() + ")";
|
||||
String trig = "Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self" +
|
||||
" | Execute$ TrigDraw | Secondary$ True | TriggerDescription$ When this creature dies, draw a card.";
|
||||
String ab = "DB$ Draw | NumCards$ 1";
|
||||
|
||||
StaticAbility st = StaticAbility.create(effect, state.getCard(), state, intrinsic);
|
||||
@@ -3516,7 +3536,7 @@ public class CardFactoryUtil {
|
||||
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.startsWith("Dash")) {
|
||||
String effect = "Mode$ Continuous | Affected$ Card.Self+dashed | AddKeyword$ Haste";
|
||||
String effect = "Mode$ Continuous | Affected$ Card.Self+dashed+castKeyword | AddKeyword$ Haste";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Daybound")) {
|
||||
String effect = "Mode$ CantTransform | ValidCard$ Creature.Self | ExceptCause$ SpellAbility.Daybound | Secondary$ True | Description$ This permanent can't be transformed except by its daybound ability.";
|
||||
@@ -3693,14 +3713,6 @@ public class CardFactoryUtil {
|
||||
return altCostSA;
|
||||
}
|
||||
|
||||
private static final Map<String,String> emptyMap = Maps.newTreeMap();
|
||||
public static SpellAbility setupETBReplacementAbility(SpellAbility sa) {
|
||||
AbilitySub as = new AbilitySub(ApiType.InternalEtbReplacement, sa.getHostCard(), null, emptyMap);
|
||||
sa.appendSubAbility(as);
|
||||
return as;
|
||||
// ETBReplacementMove(sa.getHostCard(), null));
|
||||
}
|
||||
|
||||
public static void setupAdventureAbility(Card card) {
|
||||
if (card.getCurrentStateName() != CardStateName.Adventure) {
|
||||
return;
|
||||
|
||||
@@ -13,6 +13,7 @@ import forge.game.*;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.combat.AttackRequirement;
|
||||
import forge.game.combat.AttackingBand;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
@@ -34,6 +35,7 @@ import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public class CardProperty {
|
||||
@@ -109,8 +111,13 @@ public class CardProperty {
|
||||
if (card.getId() != Integer.parseInt(property.split("CardUID_")[1])) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("ChosenCard")) {
|
||||
if (!source.hasChosenCard(card)) {
|
||||
} else if (property.startsWith("ChosenCard")) {
|
||||
CardCollectionView chosen = source.getChosenCards();
|
||||
int i = chosen.indexOf(card);
|
||||
if (i == -1) {
|
||||
return false;
|
||||
}
|
||||
if (property.contains("Strict") && !chosen.get(i).equalsWithTimestamp(card)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("nonChosenCard")) {
|
||||
@@ -250,21 +257,6 @@ public class CardProperty {
|
||||
if (!AbilityUtils.getDefinedPlayers(source, "TargetedPlayer", spellAbility).contains(controller)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("TargetedControllerCtrl")) {
|
||||
final CardCollectionView cards = AbilityUtils.getDefinedCards(source, "Targeted", spellAbility);
|
||||
final List<SpellAbility> sas = AbilityUtils.getDefinedSpellAbilities(source, "Targeted", spellAbility);
|
||||
for (final Card c : cards) {
|
||||
final Player p = c.getController();
|
||||
if (!controller.equals(p)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (final SpellAbility s : sas) {
|
||||
final Player p = s.getHostCard().getController();
|
||||
if (!controller.equals(p)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (property.startsWith("ActivePlayerCtrl")) {
|
||||
if (!game.getPhaseHandler().isPlayerTurn(controller)) {
|
||||
return false;
|
||||
@@ -618,35 +610,6 @@ public class CardProperty {
|
||||
if ((card.getCloneOrigin() == null) || !card.getCloneOrigin().equals(source)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("DamagedBy")) {
|
||||
List<Card> damaged = Lists.newArrayList();
|
||||
for (Pair<Card, Integer> pair : card.getReceivedDamageFromThisTurn()) {
|
||||
damaged.add(pair.getLeft());
|
||||
}
|
||||
if (property.endsWith("Source") || property.equals("DamagedBy")) {
|
||||
if (!damaged.contains(source)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
String prop = property.substring("DamagedBy".length());
|
||||
boolean found = Iterables.any(damaged, CardPredicates.restriction(prop, sourceController, source, spellAbility));
|
||||
|
||||
if (!found) {
|
||||
for (Card d : AbilityUtils.getDefinedCards(source, prop, spellAbility)) {
|
||||
if (damaged.contains(d)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (property.startsWith("Damaged")) {
|
||||
if (!card.getDamageHistory().getThisTurnDamaged().containsKey(source)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("SharesCMCWith")) {
|
||||
if (property.equals("SharesCMCWith")) {
|
||||
if (!card.sharesCMCWith(source)) {
|
||||
@@ -1143,38 +1106,87 @@ public class CardProperty {
|
||||
&& !card.getDamageHistory().hasBlockedSinceLastUpkeepOf(sourceController)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("DamagedBy")) {
|
||||
String prop = property.substring("DamagedBy".length());
|
||||
CardCollection def = null;
|
||||
if (prop.startsWith(" ")) {
|
||||
def = AbilityUtils.getDefinedCards(source, prop.substring(1), spellAbility);
|
||||
}
|
||||
boolean found = false;
|
||||
for (Pair<Integer, Boolean> p : card.getDamageReceivedThisTurn()) {
|
||||
Card dmgSource = game.getDamageLKI(p).getLeft();
|
||||
if (def != null) {
|
||||
for (Card c : def) {
|
||||
if (dmgSource.equalsWithTimestamp(c)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (prop.isEmpty() && dmgSource.equalsWithTimestamp(source)) {
|
||||
found = true;
|
||||
} else if (dmgSource.isValid(prop.split(","), sourceController, source, spellAbility)) {
|
||||
found = true;
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("Damaged")) {
|
||||
for (Pair<Integer, Boolean> p : source.getDamageReceivedThisTurn()) {
|
||||
boolean found = false;
|
||||
if (game.getDamageLKI(p).getLeft().equalsWithTimestamp(card)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (property.startsWith("dealtCombatDamageThisCombat")) {
|
||||
if (card.getDamageHistory().getThisCombatDamaged().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("dealtDamageToYouThisTurn")) {
|
||||
if (!card.getDamageHistory().getThisTurnDamaged().containsKey(sourceController)) {
|
||||
if (card.getDamageHistory().getDamageDoneThisTurn(null, true, null, "You", card, sourceController, spellAbility) == 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("dealtDamageToOppThisTurn")) {
|
||||
if (!card.hasDealtDamageToOpponentThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
//dealtCombatDamageThisCombat <valid>, dealtCombatDamageThisTurn <valid>, and notDealt versions
|
||||
} else if (property.startsWith("dealtCombatDamage") || property.startsWith("notDealtCombatDamage")) {
|
||||
final String[] v = property.split(" ")[1].split(",");
|
||||
final Iterable<GameEntity> list = property.contains("ThisCombat") ?
|
||||
card.getDamageHistory().getThisCombatDamaged().keySet() :
|
||||
card.getDamageHistory().getThisTurnCombatDamaged().keySet();
|
||||
boolean found = Iterables.any(list, GameObjectPredicates.restriction(v, sourceController, source, spellAbility));
|
||||
final String v = property.split(" ")[1];
|
||||
boolean found = card.getDamageHistory().getDamageDoneThisTurn(true, true, null, v, card, sourceController, spellAbility) > 0;
|
||||
|
||||
if (found == property.startsWith("not")) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("controllerWasDealtCombatDamageByThisTurn")) {
|
||||
if (!source.getDamageHistory().getThisTurnCombatDamaged().containsKey(controller)) {
|
||||
if (source.getDamageHistory().getDamageDoneThisTurn(true, true, null, "You", card, controller, spellAbility) == 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("controllerWasDealtDamageByThisTurn")) {
|
||||
if (!source.getDamageHistory().getThisTurnDamaged().containsKey(controller)) {
|
||||
if (source.getDamageHistory().getDamageDoneThisTurn(null, true, null, "You", card, controller, spellAbility) == 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("wasDealtDamageThisTurn")) {
|
||||
if (card.getReceivedDamageFromPlayerThisTurn().isEmpty()) {
|
||||
if (card.getAssignedDamage() == 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("wasDealtNonCombatDamageThisTurn")) {
|
||||
if (!card.getDamageHistory().hasBeenDealtNonCombatDamageThisTurn()) {
|
||||
if (card.getAssignedDamage(false, null) == 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("wasDealtDamageByThisGame")) {
|
||||
int idx = source.getDamageHistory().getThisGameDamaged().indexOf(card);
|
||||
if (idx == -1) {
|
||||
return false;
|
||||
}
|
||||
Card c = (Card) source.getDamageHistory().getThisGameDamaged().get(idx);
|
||||
if (!c.equalsWithTimestamp(game.getCardState(card))) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("dealtDamageThisTurn")) {
|
||||
@@ -1343,6 +1355,10 @@ public class CardProperty {
|
||||
if (!card.isToken() && !card.isTokenCard()) {
|
||||
return false;
|
||||
}
|
||||
// copied spell don't count
|
||||
if (property.contains("Created") && card.getCastSA() != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("nonToken")) {
|
||||
if (card.isToken() || card.isTokenCard()) {
|
||||
return false;
|
||||
@@ -1674,6 +1690,11 @@ public class CardProperty {
|
||||
if (band == null || !band.getAttackers().contains(card)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("hadToAttackThisCombat")) {
|
||||
AttackRequirement e = game.getCombat().getAttackConstraints().getRequirements().get(card);
|
||||
if (e == null || !e.hasCreatureRequirement() || !e.getAttacker().equalsWithTimestamp(card)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("couldAttackButNotAttacking")) {
|
||||
if (!game.getPhaseHandler().isPlayerTurn(controller)) return false;
|
||||
return CombatUtil.couldAttackButNotAttacking(combat, card);
|
||||
@@ -1794,6 +1815,21 @@ public class CardProperty {
|
||||
if (card.hasCounters()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("castKeyword")) {
|
||||
SpellAbility castSA = card.getCastSA();
|
||||
if (castSA == null) {
|
||||
return false;
|
||||
}
|
||||
// intrinsic keyword might be a new one when the zone changes
|
||||
if (castSA.isIntrinsic()) {
|
||||
// so just check if the static is intrinsic too
|
||||
if (!spellAbility.isIntrinsic()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// otherwise check for keyword object
|
||||
return Objects.equals(castSA.getKeyword(), spellAbility.getKeyword());
|
||||
}
|
||||
} else if (property.startsWith("CastSa")) {
|
||||
SpellAbility castSA = card.getCastSA();
|
||||
if (castSA == null) {
|
||||
|
||||
@@ -250,9 +250,8 @@ public final class CardUtil {
|
||||
newCopy.setColor(in.getColor().getColor());
|
||||
newCopy.setPhasedOut(in.isPhasedOut());
|
||||
|
||||
newCopy.setReceivedDamageFromThisTurn(in.getReceivedDamageFromThisTurn());
|
||||
newCopy.setReceivedDamageFromPlayerThisTurn(in.getReceivedDamageFromPlayerThisTurn());
|
||||
newCopy.setDamageHistory(in.getDamageHistory());
|
||||
newCopy.setDamageReceivedThisTurn(in.getDamageReceivedThisTurn());
|
||||
for (Card c : in.getBlockedThisTurn()) {
|
||||
newCopy.addBlockedThisTurn(c);
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ public class AttackConstraints {
|
||||
}
|
||||
}
|
||||
}
|
||||
myPossibleAttackers.removeAll((Iterable<Card>) attackersToRemove);
|
||||
myPossibleAttackers.removeAll(attackersToRemove);
|
||||
for (final Card toRemove : attackersToRemove) {
|
||||
reqs.removeAll(findAll(reqs, toRemove));
|
||||
}
|
||||
@@ -186,8 +186,9 @@ public class AttackConstraints {
|
||||
// Now try all others (plus empty attack) and count their violations
|
||||
final FCollection<Map<Card, GameEntity>> legalAttackers = collectLegalAttackers(reqs, myMax);
|
||||
possible.putAll(Maps.asMap(legalAttackers.asSet(), FN_COUNT_VIOLATIONS));
|
||||
if (countViolations(Collections.emptyMap()) != -1) {
|
||||
possible.put(Collections.emptyMap(), countViolations(Collections.emptyMap()));
|
||||
int empty = countViolations(Collections.emptyMap());
|
||||
if (empty != -1) {
|
||||
possible.put(Collections.emptyMap(), empty);
|
||||
}
|
||||
|
||||
// take the case with the fewest violations
|
||||
@@ -392,7 +393,7 @@ public class AttackConstraints {
|
||||
* restriction is violated.
|
||||
*/
|
||||
public final int countViolations(final Map<Card, GameEntity> attackers) {
|
||||
if (!globalRestrictions.isLegal(attackers, possibleAttackers)) {
|
||||
if (!globalRestrictions.isLegal(attackers)) {
|
||||
return -1;
|
||||
}
|
||||
for (final Entry<Card, GameEntity> attacker : attackers.entrySet()) {
|
||||
|
||||
@@ -107,8 +107,17 @@ public class AttackRequirement {
|
||||
}
|
||||
}
|
||||
|
||||
public Card getAttacker() {
|
||||
return attacker;
|
||||
}
|
||||
|
||||
public boolean hasRequirement() {
|
||||
return !defenderSpecific.isEmpty() || !causesToAttack.isEmpty() || !defenderOrPWSpecific.isEmpty();
|
||||
return defenderSpecific.countAll() > 0 || causesToAttack.countAll() > 0 || defenderOrPWSpecific.countAll() > 0;
|
||||
}
|
||||
|
||||
// according to Firkraag ruling Trove of Temptation applies to players, not creatures
|
||||
public boolean hasCreatureRequirement() {
|
||||
return defenderSpecific.countAll() > 0 || causesToAttack.countAll() > 0;
|
||||
}
|
||||
|
||||
public final MapToAmount<Card> getCausesToAttack() {
|
||||
@@ -124,29 +133,24 @@ public class AttackRequirement {
|
||||
int violations = 0;
|
||||
|
||||
// first. check to see if "must attack X or Y with at least one creature" requirements are satisfied
|
||||
//List<GameEntity> toRemoveFromDefSpecific = Lists.newArrayList();
|
||||
if (!defenderOrPWSpecific.isEmpty()) {
|
||||
for (GameEntity def : defenderOrPWSpecific.keySet()) {
|
||||
if (defenderSpecificAlternatives.containsKey(def)) {
|
||||
boolean isAttackingDefender = false;
|
||||
outer: for (Card atk : attackers.keySet()) {
|
||||
// is anyone attacking this defender or any of the alternative defenders?
|
||||
if (attackers.get(atk).equals(def)) {
|
||||
isAttackingDefender = true;
|
||||
break;
|
||||
}
|
||||
for (GameEntity altDef : defenderSpecificAlternatives.get(def)) {
|
||||
if (attackers.get(atk).equals(altDef)) {
|
||||
isAttackingDefender = true;
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isAttackingDefender && CombatUtil.getAttackCost(attacker.getGame(), attacker, def) == null) {
|
||||
violations++; // no one is attacking that defender or any of his PWs
|
||||
for (GameEntity def : defenderOrPWSpecific.keySet()) {
|
||||
boolean isAttackingDefender = false;
|
||||
outer: for (Card atk : attackers.keySet()) {
|
||||
// is anyone attacking this defender or any of the alternative defenders?
|
||||
if (attackers.get(atk).equals(def)) {
|
||||
isAttackingDefender = true;
|
||||
break;
|
||||
}
|
||||
for (GameEntity altDef : defenderSpecificAlternatives.get(def)) {
|
||||
if (attackers.get(atk).equals(altDef)) {
|
||||
isAttackingDefender = true;
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isAttackingDefender) {
|
||||
violations++; // no one is attacking that defender or any of his PWs
|
||||
}
|
||||
}
|
||||
|
||||
// now, count everything else
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
@@ -200,6 +201,19 @@ public class Combat {
|
||||
return attackableEntries;
|
||||
}
|
||||
|
||||
//gets attacked player opponents (ignores planeswalkers)
|
||||
public final FCollection<Player> getAttackedOpponents(Player atk) {
|
||||
FCollection<Player> attackedOpps = new FCollection<Player>();
|
||||
if (atk == playerWhoAttacks) {
|
||||
for (Player defender : getDefendingPlayers()) {
|
||||
if (!getAttackersOf(defender).isEmpty()) {
|
||||
attackedOpps.add(defender);
|
||||
}
|
||||
}
|
||||
}
|
||||
return attackedOpps;
|
||||
}
|
||||
|
||||
public final FCollection<GameEntity> getDefendersControlledBy(Player who) {
|
||||
FCollection<GameEntity> res = new FCollection<>();
|
||||
for (GameEntity ge : attackableEntries) {
|
||||
@@ -711,7 +725,7 @@ public class Combat {
|
||||
"defending player and/or any number of creatures they control.")
|
||||
&& blocker.getController().getController().confirmAction(null, null,
|
||||
Localizer.getInstance().getMessage("lblAssignCombatDamageAsChoose",
|
||||
CardTranslation.getTranslatedName(blocker.getName())));
|
||||
CardTranslation.getTranslatedName(blocker.getName())), null);
|
||||
// choose defending player
|
||||
if (divideCombatDamageAsChoose) {
|
||||
defender = blocker.getController().getController().chooseSingleEntityForEffect(attackingPlayer.getOpponents(), null, Localizer.getInstance().getMessage("lblChoosePlayer"), null);
|
||||
@@ -784,13 +798,14 @@ public class Combat {
|
||||
final SpellAbility emptySA = new SpellAbility.EmptySa(attacker);
|
||||
|
||||
boolean assignToPlayer = false;
|
||||
if (attacker.hasKeyword("CARDNAME assigns its combat damage as though it weren't blocked.")) {
|
||||
if (StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker, false)) {
|
||||
assignToPlayer = true;
|
||||
}
|
||||
if (!assignToPlayer && attacker.getGame().getCombat().isBlocked(attacker) && attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
||||
if (!assignToPlayer && attacker.getGame().getCombat().isBlocked(attacker)
|
||||
&& StaticAbilityAssignCombatDamageAsUnblocked.assignCombatDamageAsUnblocked(attacker)) {
|
||||
assignToPlayer = assigningPlayer.getController().confirmAction(emptySA, PlayerActionConfirmMode.AlternativeDamageAssignment,
|
||||
Localizer.getInstance().getMessage("lblAssignCombatDamageWerentBlocked",
|
||||
CardTranslation.getTranslatedName(attacker.getName())));
|
||||
CardTranslation.getTranslatedName(attacker.getName())), null);
|
||||
}
|
||||
|
||||
boolean divideCombatDamageAsChoose = false;
|
||||
@@ -802,7 +817,7 @@ public class Combat {
|
||||
"defending player and/or any number of creatures they control.")
|
||||
&& assigningPlayer.getController().confirmAction(emptySA, PlayerActionConfirmMode.AlternativeDamageAssignment,
|
||||
Localizer.getInstance().getMessage("lblAssignCombatDamageAsChoose",
|
||||
CardTranslation.getTranslatedName(attacker.getName())));
|
||||
CardTranslation.getTranslatedName(attacker.getName())), null);
|
||||
if (defender instanceof Card && divideCombatDamageAsChoose) {
|
||||
defender = getDefenderPlayerByAttacker(attacker);
|
||||
}
|
||||
@@ -813,7 +828,7 @@ public class Combat {
|
||||
"a creature defending player controls.") &&
|
||||
assigningPlayer.getController().confirmAction(emptySA, PlayerActionConfirmMode.AlternativeDamageAssignment,
|
||||
Localizer.getInstance().getMessage("lblAssignCombatDamageToCreature",
|
||||
CardTranslation.getTranslatedName(attacker.getName())));
|
||||
CardTranslation.getTranslatedName(attacker.getName())), null);
|
||||
if (divideCombatDamageAsChoose) {
|
||||
if (orderedBlockers == null || orderedBlockers.isEmpty()) {
|
||||
orderedBlockers = getDefendersCreatures();
|
||||
|
||||
@@ -408,7 +408,7 @@ public class CombatUtil {
|
||||
c.getDamageHistory().setCreatureAttackedThisCombat(defender);
|
||||
c.getDamageHistory().clearNotAttackedSinceLastUpkeepOf();
|
||||
c.getController().addCreaturesAttackedThisTurn(CardUtil.getLKICopy(c));
|
||||
if (combat.getDefenderByAttacker(c) instanceof Player) {
|
||||
if (defender instanceof Player) {
|
||||
c.getController().addAttackedPlayersMyTurn(combat.getDefenderPlayerByAttacker(c));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,7 @@ import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import forge.util.maps.LinkedHashMapToAmount;
|
||||
@@ -22,11 +20,9 @@ public class GlobalAttackRestrictions {
|
||||
|
||||
private final int max;
|
||||
private final MapToAmount<GameEntity> defenderMax;
|
||||
private final PlayerCollection mustBeAttackedByEachOpp;
|
||||
private GlobalAttackRestrictions(final int max, final MapToAmount<GameEntity> defenderMax, PlayerCollection mustBeAttackedByEachOpp) {
|
||||
private GlobalAttackRestrictions(final int max, final MapToAmount<GameEntity> defenderMax) {
|
||||
this.max = max;
|
||||
this.defenderMax = defenderMax;
|
||||
this.mustBeAttackedByEachOpp = mustBeAttackedByEachOpp;
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
@@ -36,17 +32,17 @@ public class GlobalAttackRestrictions {
|
||||
return defenderMax;
|
||||
}
|
||||
|
||||
public boolean isLegal(final Map<Card, GameEntity> attackers, final CardCollection possibleAttackers) {
|
||||
return !getViolations(attackers, possibleAttackers, true).isViolated();
|
||||
public boolean isLegal(final Map<Card, GameEntity> attackers) {
|
||||
return !getViolations(attackers, true).isViolated();
|
||||
}
|
||||
|
||||
public GlobalAttackRestrictionViolations getViolations(final Map<Card, GameEntity> attackers, final CardCollection possibleAttackers) {
|
||||
return getViolations(attackers, possibleAttackers, false);
|
||||
public GlobalAttackRestrictionViolations getViolations(final Map<Card, GameEntity> attackers) {
|
||||
return getViolations(attackers, false);
|
||||
}
|
||||
private GlobalAttackRestrictionViolations getViolations(final Map<Card, GameEntity> attackers, final CardCollection possibleAttackers, final boolean returnQuickly) {
|
||||
private GlobalAttackRestrictionViolations getViolations(final Map<Card, GameEntity> attackers, final boolean returnQuickly) {
|
||||
final int nTooMany = max < 0 ? 0 : attackers.size() - max;
|
||||
if (returnQuickly && nTooMany > 0) {
|
||||
return new GlobalAttackRestrictionViolations(nTooMany, MapToAmountUtil.emptyMap(), MapToAmountUtil.emptyMap());
|
||||
return new GlobalAttackRestrictionViolations(nTooMany, MapToAmountUtil.emptyMap());
|
||||
}
|
||||
|
||||
final MapToAmount<GameEntity> defenderTooMany = new LinkedHashMapToAmount<>(defenderMax.size());
|
||||
@@ -77,49 +73,18 @@ public class GlobalAttackRestrictions {
|
||||
}
|
||||
}
|
||||
|
||||
final MapToAmount<GameEntity> defenderTooFew = new LinkedHashMapToAmount<>(defenderMax.size());
|
||||
for (final GameEntity mandatoryDef : mustBeAttackedByEachOpp) {
|
||||
// check to ensure that this defender can even legally be attacked in the first place
|
||||
boolean canAttackThisDef = false;
|
||||
for (Card c : possibleAttackers) {
|
||||
if (CombatUtil.canAttack(c, mandatoryDef) && null == CombatUtil.getAttackCost(c.getGame(), c, mandatoryDef)) {
|
||||
canAttackThisDef = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!canAttackThisDef) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isAttacked = false;
|
||||
for (final GameEntity defender : attackers.values()) {
|
||||
if (defender.equals(mandatoryDef)) {
|
||||
isAttacked = true;
|
||||
break;
|
||||
} else if (defender instanceof Card && ((Card)defender).getController().equals(mandatoryDef)) {
|
||||
isAttacked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isAttacked) {
|
||||
defenderTooFew.add(mandatoryDef);
|
||||
}
|
||||
}
|
||||
|
||||
return new GlobalAttackRestrictionViolations(nTooMany, defenderTooMany, defenderTooFew);
|
||||
return new GlobalAttackRestrictionViolations(nTooMany, defenderTooMany);
|
||||
}
|
||||
|
||||
final class GlobalAttackRestrictionViolations {
|
||||
private final boolean isViolated;
|
||||
private final int globalTooMany;
|
||||
private final MapToAmount<GameEntity> defenderTooMany;
|
||||
private final MapToAmount<GameEntity> defenderTooFew;
|
||||
|
||||
public GlobalAttackRestrictionViolations(final int globalTooMany, final MapToAmount<GameEntity> defenderTooMany, final MapToAmount<GameEntity> defenderTooFew) {
|
||||
this.isViolated = globalTooMany > 0 || !defenderTooMany.isEmpty() || !defenderTooFew.isEmpty();
|
||||
public GlobalAttackRestrictionViolations(final int globalTooMany, final MapToAmount<GameEntity> defenderTooMany) {
|
||||
this.isViolated = globalTooMany > 0 || !defenderTooMany.isEmpty();
|
||||
this.globalTooMany = globalTooMany;
|
||||
this.defenderTooMany = defenderTooMany;
|
||||
this.defenderTooFew = defenderTooFew;
|
||||
}
|
||||
public boolean isViolated() {
|
||||
return isViolated;
|
||||
@@ -127,9 +92,6 @@ public class GlobalAttackRestrictions {
|
||||
public int getGlobalTooMany() {
|
||||
return globalTooMany;
|
||||
}
|
||||
public MapToAmount<GameEntity> getDefenderTooFew() {
|
||||
return defenderTooFew;
|
||||
}
|
||||
public MapToAmount<GameEntity> getDefenderTooMany() {
|
||||
return defenderTooMany;
|
||||
}
|
||||
@@ -147,7 +109,6 @@ public class GlobalAttackRestrictions {
|
||||
public static GlobalAttackRestrictions getGlobalRestrictions(final Player attackingPlayer, final FCollectionView<GameEntity> possibleDefenders) {
|
||||
int max = -1;
|
||||
final MapToAmount<GameEntity> defenderMax = new LinkedHashMapToAmount<>(possibleDefenders.size());
|
||||
final PlayerCollection mustBeAttacked = new PlayerCollection();
|
||||
final Game game = attackingPlayer.getGame();
|
||||
|
||||
/* if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyOneAttackerATurn)) {
|
||||
@@ -171,14 +132,6 @@ public class GlobalAttackRestrictions {
|
||||
}
|
||||
}
|
||||
|
||||
for (final Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||
if (card.hasKeyword("Each opponent must attack you or a planeswalker you control with at least one creature each combat if able.")) {
|
||||
if (attackingPlayer.isOpponentOf(card.getController())) {
|
||||
mustBeAttacked.add(card.getController());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final GameEntity defender : possibleDefenders) {
|
||||
final int defMax = getMaxAttackTo(defender);
|
||||
if (defMax != -1) {
|
||||
@@ -190,7 +143,7 @@ public class GlobalAttackRestrictions {
|
||||
max = Ints.min(max, defenderMax.countAll());
|
||||
}
|
||||
|
||||
return new GlobalAttackRestrictions(max, defenderMax, mustBeAttacked);
|
||||
return new GlobalAttackRestrictions(max, defenderMax);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,7 @@ package forge.game.cost;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbilityActivateAbilityAsIfHaste;
|
||||
|
||||
/**
|
||||
* The Class CostTap.
|
||||
@@ -61,7 +62,7 @@ public class CostTap extends CostPart {
|
||||
@Override
|
||||
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
|
||||
final Card source = ability.getHostCard();
|
||||
return source.isUntapped() && (!source.isSick() || source.hasKeyword("CARDNAME may activate abilities as though it has haste."));
|
||||
return source.isUntapped() && !isAbilitySick(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,4 +75,10 @@ public class CostTap extends CostPart {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
public boolean isAbilitySick(final Card source) {
|
||||
if (!source.isSick()) {
|
||||
return false;
|
||||
}
|
||||
return !StaticAbilityActivateAbilityAsIfHaste.canActivate(source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package forge.game.cost;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbilityActivateAbilityAsIfHaste;
|
||||
|
||||
/**
|
||||
* The Class CostUntap.
|
||||
@@ -72,7 +73,7 @@ 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.isSick() || source.hasKeyword("CARDNAME may activate abilities as though it has haste."));
|
||||
return source.isTapped() && !isAbilitySick(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -85,4 +86,10 @@ public class CostUntap extends CostPart {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
public boolean isAbilitySick(final Card source) {
|
||||
if (!source.isSick()) {
|
||||
return false;
|
||||
}
|
||||
return !StaticAbilityActivateAbilityAsIfHaste.canActivate(source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ public enum Keyword {
|
||||
CASUALTY("Casualty", KeywordWithAmount.class, false, "As you cast this spell, you may sacrifice a creature with power %1$d or greater. When you do, copy this spell."),
|
||||
CHAMPION("Champion", KeywordWithType.class, false, "When this enters the battlefield, sacrifice it unless you exile another %s you control. When this leaves the battlefield, that card returns to the battlefield."),
|
||||
CHANGELING("Changeling", SimpleKeyword.class, true, "This card is every creature type."),
|
||||
CHOOSE_A_BACKGROUND("Choose a Background", Partner.class, true, "You can have a Background as a second commander."),
|
||||
CIPHER("Cipher", SimpleKeyword.class, true, "Then you may exile this spell card encoded on a creature you control. Whenever that creature deals combat damage to a player, its controller may cast a copy of the encoded card without paying its mana cost."),
|
||||
COMPANION("Companion", Companion.class, true, "Reveal your companion from outside the game if your deck meets the companion restriction."),
|
||||
COMPLEATED("Compleated", SimpleKeyword.class, true, "{$0} can be paid with {$1}, {$2}, or 2 life. If life was paid, this planeswalker enters with two fewer loyalty counters."),
|
||||
|
||||
@@ -22,6 +22,7 @@ import io.sentry.Sentry;
|
||||
public abstract class KeywordInstance<T extends KeywordInstance<?>> implements KeywordInterface {
|
||||
private Keyword keyword;
|
||||
private String original;
|
||||
private long staticId = 0;
|
||||
|
||||
private List<Trigger> triggers = Lists.newArrayList();
|
||||
private List<ReplacementEffect> replacements = Lists.newArrayList();
|
||||
@@ -181,6 +182,7 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
* @see forge.game.keyword.KeywordInterface#addTrigger(forge.game.trigger.Trigger)
|
||||
*/
|
||||
public final void addTrigger(final Trigger trg) {
|
||||
trg.setKeyword(this);
|
||||
triggers.add(trg);
|
||||
}
|
||||
|
||||
@@ -189,6 +191,7 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
* @see forge.game.keyword.KeywordInterface#addReplacement(forge.game.replacement.ReplacementEffect)
|
||||
*/
|
||||
public final void addReplacement(final ReplacementEffect trg) {
|
||||
trg.setKeyword(this);
|
||||
replacements.add(trg);
|
||||
}
|
||||
|
||||
@@ -197,6 +200,7 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
* @see forge.game.keyword.KeywordInterface#addSpellAbility(forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
public final void addSpellAbility(final SpellAbility s) {
|
||||
s.setKeyword(this);
|
||||
abilities.add(s);
|
||||
}
|
||||
|
||||
@@ -205,6 +209,7 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
* @see forge.game.keyword.KeywordInterface#addStaticAbility(forge.game.staticability.StaticAbility)
|
||||
*/
|
||||
public final void addStaticAbility(final StaticAbility st) {
|
||||
st.setKeyword(this);
|
||||
staticAbilities.add(st);
|
||||
}
|
||||
|
||||
@@ -247,22 +252,30 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
|
||||
result.abilities = Lists.newArrayList();
|
||||
for (SpellAbility sa : this.abilities) {
|
||||
result.abilities.add(sa.copy(host, lki));
|
||||
SpellAbility copy = sa.copy(host, lki);
|
||||
copy.setKeyword(result);
|
||||
result.abilities.add(copy);
|
||||
}
|
||||
|
||||
result.triggers = Lists.newArrayList();
|
||||
for (Trigger tr : this.triggers) {
|
||||
result.triggers.add(tr.copy(host, lki));
|
||||
Trigger copy = tr.copy(host, lki);
|
||||
copy.setKeyword(result);
|
||||
result.triggers.add(copy);
|
||||
}
|
||||
|
||||
result.replacements = Lists.newArrayList();
|
||||
for (ReplacementEffect re : this.replacements) {
|
||||
result.replacements.add(re.copy(host, lki));
|
||||
ReplacementEffect copy = re.copy(host, lki);
|
||||
copy.setKeyword(result);
|
||||
result.replacements.add(copy);
|
||||
}
|
||||
|
||||
result.staticAbilities = Lists.newArrayList();
|
||||
for (StaticAbility sa : this.staticAbilities) {
|
||||
result.staticAbilities.add(sa.copy(host, lki));
|
||||
StaticAbility copy = sa.copy(host, lki);
|
||||
copy.setKeyword(result);
|
||||
result.staticAbilities.add(copy);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -327,4 +340,11 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
sa.setIntrinsic(value);
|
||||
}
|
||||
}
|
||||
|
||||
public long getStaticId() {
|
||||
return this.staticId;
|
||||
}
|
||||
public void setStaticId(long v) {
|
||||
this.staticId = v;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ public interface KeywordInterface extends Cloneable {
|
||||
String getReminderText();
|
||||
|
||||
int getAmount();
|
||||
long getStaticId();
|
||||
void setStaticId(long v);
|
||||
|
||||
void createTraits(final Card host, final boolean intrinsic);
|
||||
void createTraits(final Card host, final boolean intrinsic, final boolean clear);
|
||||
|
||||
@@ -641,6 +641,7 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
game.getTriggerHandler().runTrigger(TriggerType.AttackersDeclared, runParams, false);
|
||||
}
|
||||
|
||||
playerTurn.clearAttackedPlayersMyCombat();
|
||||
for (final Card c : combat.getAttackers()) {
|
||||
CombatUtil.checkDeclaredAttacker(game, c, combat, true);
|
||||
}
|
||||
@@ -1063,7 +1064,7 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
// currently there can be only one Spell put on the Stack at once, or Land Abilities be played
|
||||
final CardZoneTable triggerList = new CardZoneTable();
|
||||
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), saHost);
|
||||
triggerList.triggerChangesZoneAll(game, null);
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ import forge.game.keyword.KeywordsChange;
|
||||
import forge.game.mana.ManaPool;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementHandler;
|
||||
import forge.game.replacement.ReplacementResult;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
@@ -146,8 +147,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
private int life = 20;
|
||||
private int startingLife = 20;
|
||||
private int lifeStartedThisTurnWith = startingLife;
|
||||
private final Map<Card, Integer> assignedDamage = Maps.newHashMap();
|
||||
private final Map<Card, Integer> assignedCombatDamage = Maps.newHashMap();
|
||||
private int spellsCastThisTurn;
|
||||
private int spellsCastThisGame;
|
||||
private int spellsCastLastTurn;
|
||||
@@ -206,6 +205,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
private List<Card> creatureAttackedThisTurn = new ArrayList<>();
|
||||
private List<Player> attackedPlayersThisTurn = new ArrayList<>();
|
||||
private List <Player> attackedPlayersLastTurn = new ArrayList<>();
|
||||
private List<Player> attackedPlayersThisCombat = new ArrayList<>();
|
||||
|
||||
private boolean activateLoyaltyAbilityThisTurn = false;
|
||||
private boolean tappedLandForManaThisTurn = false;
|
||||
private List<Card> completedDungeons = new ArrayList<>();
|
||||
@@ -216,7 +217,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
private Map<Card, Card> maingameCardsMap = Maps.newHashMap();
|
||||
|
||||
private CardCollection currentPlanes = new CardCollection();
|
||||
private Set<String> prowl = Sets.newHashSet();
|
||||
|
||||
private PlayerStatistics stats = new PlayerStatistics();
|
||||
private PlayerController controller;
|
||||
@@ -238,6 +238,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
private Deque<SpellAbility> paidForStack = new ArrayDeque<>();
|
||||
|
||||
private Card monarchEffect;
|
||||
private Card initiativeEffect;
|
||||
private Card blessingEffect;
|
||||
private Card keywordEffect;
|
||||
|
||||
@@ -700,19 +701,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
}
|
||||
|
||||
int old = assignedDamage.containsKey(source) ? assignedDamage.get(source) : 0;
|
||||
assignedDamage.put(source, old + amount);
|
||||
source.getDamageHistory().registerDamage(this, amount);
|
||||
|
||||
if (isCombat) {
|
||||
old = assignedCombatDamage.containsKey(source) ? assignedCombatDamage.get(source) : 0;
|
||||
assignedCombatDamage.put(source, old + amount);
|
||||
for (final String type : source.getType().getCreatureTypes()) {
|
||||
source.getController().addProwlType(type);
|
||||
}
|
||||
source.getDamageHistory().registerCombatDamage(this, amount);
|
||||
}
|
||||
|
||||
// Run triggers
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.DamageSource, source);
|
||||
@@ -826,49 +814,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
simultaneousDamage = 0;
|
||||
}
|
||||
|
||||
public final void clearAssignedDamage() {
|
||||
assignedDamage.clear();
|
||||
assignedCombatDamage.clear();
|
||||
}
|
||||
|
||||
public final int getAssignedDamage() {
|
||||
int num = 0;
|
||||
for (final Integer value : assignedDamage.values()) {
|
||||
num += value;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
public final int getAssignedCombatDamage() {
|
||||
int num = 0;
|
||||
for (final Integer value : assignedCombatDamage.values()) {
|
||||
num += value;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
public final Iterable<Card> getAssignedDamageSources() {
|
||||
return assignedDamage.keySet();
|
||||
}
|
||||
|
||||
public final int getAssignedDamage(final Card c) {
|
||||
return assignedDamage.get(c);
|
||||
}
|
||||
|
||||
public final int getAssignedDamage(final String type) {
|
||||
final Map<Card, Integer> valueMap = Maps.newHashMap();
|
||||
for (final Card c : assignedDamage.keySet()) {
|
||||
if (c.getType().hasStringType(type)) {
|
||||
valueMap.put(c, assignedDamage.get(c));
|
||||
}
|
||||
}
|
||||
int num = 0;
|
||||
for (final Integer value : valueMap.values()) {
|
||||
num += value;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total damage assigned to this player's opponents this turn.
|
||||
*/
|
||||
@@ -1886,6 +1831,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
|
||||
public final void addAttackedPlayersMyTurn(final Player p) {
|
||||
if (!attackedPlayersThisTurn.contains(p)) {
|
||||
attackedPlayersThisCombat.add(p);
|
||||
attackedPlayersThisTurn.add(p);
|
||||
}
|
||||
}
|
||||
@@ -1903,6 +1849,13 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
attackedPlayersLastTurn.addAll(players);
|
||||
}
|
||||
|
||||
public final List<Player> getAttackedPlayersMyCombat() {
|
||||
return attackedPlayersThisTurn;
|
||||
}
|
||||
public final void clearAttackedPlayersMyCombat() {
|
||||
attackedPlayersThisCombat.clear();
|
||||
}
|
||||
|
||||
public final int getVenturedThisTurn() {
|
||||
return venturedThisTurn;
|
||||
}
|
||||
@@ -2061,27 +2014,27 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return Iterables.any(getZone(ZoneType.Battlefield).getCardsAddedThisTurn(null), CardPredicates.Presets.LANDS);
|
||||
}
|
||||
|
||||
public boolean hasFerocious() {
|
||||
return !CardLists.filterPower(getCreaturesInPlay(), 4).isEmpty();
|
||||
}
|
||||
|
||||
public final boolean hasSurge() {
|
||||
return !CardLists.filterControlledBy(game.getStack().getSpellsCastThisTurn(), getYourTeam()).isEmpty();
|
||||
}
|
||||
|
||||
public final boolean hasBloodthirst() {
|
||||
for (Player p : game.getRegisteredPlayers()) {
|
||||
if (p.isOpponentOf(this) && p.getAssignedDamage() > 0) {
|
||||
for (Player p : getRegisteredOpponents()) {
|
||||
if (p.getAssignedDamage() > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasFerocious() {
|
||||
return !CardLists.filterPower(getCreaturesInPlay(), 4).isEmpty();
|
||||
}
|
||||
|
||||
public final int getBloodthirstAmount() {
|
||||
return Aggregates.sum(getRegisteredOpponents(), Accessors.FN_GET_ASSIGNED_DAMAGE);
|
||||
}
|
||||
|
||||
public final boolean hasSurge() {
|
||||
return !CardLists.filterControlledBy(game.getStack().getSpellsCastThisTurn(), getYourTeam()).isEmpty();
|
||||
}
|
||||
|
||||
public final int getOpponentLostLifeThisTurn() {
|
||||
int lost = 0;
|
||||
for (Player opp : getRegisteredOpponents()) {
|
||||
@@ -2090,14 +2043,12 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return lost;
|
||||
}
|
||||
|
||||
public final boolean hasProwl(final String type) {
|
||||
return prowl.contains(type);
|
||||
}
|
||||
public final void addProwlType(final String type) {
|
||||
prowl.add(type);
|
||||
}
|
||||
public final void resetProwl() {
|
||||
prowl.clear();
|
||||
public final boolean hasProwl(final Set<String> types) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String type : types) {
|
||||
sb.append("Card.YouCtrl+").append(type).append(",");
|
||||
}
|
||||
return !game.getDamageDoneThisTurn(true, true, sb.toString(), "Player", null, this, null).isEmpty();
|
||||
}
|
||||
|
||||
public final void setLibrarySearched(final int l) {
|
||||
@@ -2138,7 +2089,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
} else if (incR[0].equals("EnchantedController")) {
|
||||
final GameEntity enchanted = source.getEntityAttachedTo();
|
||||
if ((enchanted == null) || !(enchanted instanceof Card)) {
|
||||
if (enchanted == null || !(enchanted instanceof Card)) {
|
||||
return false;
|
||||
}
|
||||
final Card enchantedCard = (Card) enchanted;
|
||||
@@ -2227,10 +2178,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
investigatedThisTurn = 0;
|
||||
}
|
||||
|
||||
public final List<Card> getSacrificedThisTurn() {
|
||||
return sacrificedThisTurn;
|
||||
}
|
||||
|
||||
public final void addSacrificedThisTurn(final Card c, final SpellAbility source) {
|
||||
// Play the Sacrifice sound
|
||||
game.fireEvent(new GameEventCardSacrificed());
|
||||
@@ -2248,6 +2195,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Sacrificed, runParams, false);
|
||||
}
|
||||
|
||||
public final List<Card> getSacrificedThisTurn() {
|
||||
return sacrificedThisTurn;
|
||||
}
|
||||
public final void resetSacrificedThisTurn() {
|
||||
sacrificedThisTurn.clear();
|
||||
}
|
||||
@@ -2440,10 +2390,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
resetCycledThisTurn();
|
||||
resetEquippedThisTurn();
|
||||
resetSacrificedThisTurn();
|
||||
clearAssignedDamage();
|
||||
resetVenturedThisTurn();
|
||||
setRevolt(false);
|
||||
resetProwl();
|
||||
setSpellsCastLastTurn(getSpellsCastThisTurn());
|
||||
resetSpellsCastThisTurn();
|
||||
setLifeLostLastTurn(getLifeLostThisTurn());
|
||||
@@ -2455,6 +2403,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
setLibrarySearched(0);
|
||||
setNumManaConversion(0);
|
||||
|
||||
damageReceivedThisTurn.clear();
|
||||
|
||||
// set last turn nr
|
||||
if (game.getPhaseHandler().isPlayerTurn(this)) {
|
||||
setAttackedPlayersMyLastTurn(attackedPlayersThisTurn);
|
||||
@@ -3021,8 +2971,10 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
|
||||
String addToHandAbility = "Mode$ Continuous | EffectZone$ Command | Affected$ Card.YouOwn+EffectSource | AffectedZone$ Command | AddAbility$ MoveToHand";
|
||||
String moveToHand = "ST$ ChangeZone | Cost$ 3 | Defined$ Self | Origin$ Command | Destination$ Hand | SorcerySpeed$ True | ActivationZone$ Command | SpellDescription$ Companion - Put CARDNAME in to your hand";
|
||||
eff.setSVar("MoveToHand", moveToHand);
|
||||
eff.addStaticAbility(addToHandAbility);
|
||||
|
||||
StaticAbility stAb = StaticAbility.create(addToHandAbility, eff, eff.getCurrentState(), true);
|
||||
stAb.setSVar("MoveToHand", moveToHand);
|
||||
eff.addStaticAbility(stAb);
|
||||
|
||||
return eff;
|
||||
}
|
||||
@@ -3033,11 +2985,13 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
|
||||
if (game.getRules().hasAppliedVariant(GameType.Oathbreaker) && commander.getRules().canBeSignatureSpell()) {
|
||||
//signature spells can only reside on the stack or in the command zone
|
||||
eff.setSVar("SignatureSpellMoveReplacement", "DB$ ChangeZone | Origin$ Stack | Destination$ Command | Defined$ ReplacedCard");
|
||||
String effStr = "DB$ ChangeZone | Origin$ Stack | Destination$ Command | Defined$ ReplacedCard";
|
||||
|
||||
String moved = "Event$ Moved | ValidCard$ Card.EffectSource+YouOwn | Secondary$ True | ReplaceWith$ SignatureSpellMoveReplacement | Destination$ Graveyard,Exile,Hand,Library | " +
|
||||
String moved = "Event$ Moved | ValidCard$ Card.EffectSource+YouOwn | Secondary$ True | Destination$ Graveyard,Exile,Hand,Library | " +
|
||||
"Description$ If a signature spell would be put into another zone from the stack, put it into the command zone instead.";
|
||||
eff.addReplacementEffect(ReplacementHandler.parseReplacement(moved, eff, true));
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(moved, eff, true);
|
||||
re.setOverridingAbility(AbilityFactory.getAbility(effStr, eff));
|
||||
eff.addReplacementEffect(re);
|
||||
|
||||
//signature spells can only be cast if your oathbreaker is in on the battlefield under your control
|
||||
String castRestriction = "Mode$ CantBeCast | ValidCard$ Card.EffectSource+YouOwn | EffectZone$ Command | IsPresent$ Card.IsCommander+YouOwn+YouCtrl | PresentZone$ Battlefield | PresentCompare$ EQ0 | " +
|
||||
@@ -3045,9 +2999,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
eff.addStaticAbility(castRestriction);
|
||||
}
|
||||
else {
|
||||
eff.setSVar("CommanderMoveReplacement", "DB$ ChangeZone | Origin$ Battlefield,Graveyard,Exile,Library,Hand | Destination$ Command | Defined$ ReplacedCard");
|
||||
String effStr = "DB$ ChangeZone | Origin$ Battlefield,Graveyard,Exile,Library,Hand | Destination$ Command | Defined$ ReplacedCard";
|
||||
|
||||
String moved = "Event$ Moved | ValidCard$ Card.EffectSource+YouOwn | Secondary$ True | Optional$ True | OptionalDecider$ You | ReplaceWith$ CommanderMoveReplacement ";
|
||||
String moved = "Event$ Moved | ValidCard$ Card.EffectSource+YouOwn | Secondary$ True | Optional$ True | OptionalDecider$ You | CommanderMoveReplacement$ True ";
|
||||
if (game.getRules().hasAppliedVariant(GameType.TinyLeaders)) {
|
||||
moved += " | Destination$ Graveyard,Exile | Description$ If a commander would be put into its owner's graveyard or exile from anywhere, that player may put it into the command zone instead.";
|
||||
}
|
||||
@@ -3057,7 +3011,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
// rule 903.9b
|
||||
moved += " | Destination$ Hand,Library | Description$ If a commander would be put into its owner's hand or library from anywhere, its owner may put it into the command zone instead.";
|
||||
}
|
||||
eff.addReplacementEffect(ReplacementHandler.parseReplacement(moved, eff, true));
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(moved, eff, true);
|
||||
re.setOverridingAbility(AbilityFactory.getAbility(effStr, eff));
|
||||
eff.addReplacementEffect(re);
|
||||
}
|
||||
|
||||
String mayBePlayedAbility = "Mode$ Continuous | EffectZone$ Command | MayPlay$ True | Affected$ Card.YouOwn+EffectSource | AffectedZone$ Command";
|
||||
@@ -3177,6 +3133,77 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return !StaticAbilityCantBecomeMonarch.anyCantBecomeMonarch(this);
|
||||
}
|
||||
|
||||
public void createInitiativeEffect(final String set) {
|
||||
final PlayerZone com = getZone(ZoneType.Command);
|
||||
if (initiativeEffect == null) {
|
||||
initiativeEffect = new Card(game.nextCardId(), null, game);
|
||||
initiativeEffect.setOwner(this);
|
||||
initiativeEffect.setImmutable(true);
|
||||
if (set != null) {
|
||||
initiativeEffect.setImageKey("t:initiative_" + set.toLowerCase());
|
||||
initiativeEffect.setSetCode(set);
|
||||
} else {
|
||||
initiativeEffect.setImageKey("t:initiative");
|
||||
}
|
||||
initiativeEffect.setName("The Initiative");
|
||||
|
||||
//Set up damage trigger
|
||||
final String damageTrig = "Mode$ DamageDoneOnceByController | ValidSource$ Player | ValidTarget$ You | " +
|
||||
"CombatDamage$ True | TriggerZones$ Command | TriggerDescription$ Whenever one or more " +
|
||||
"creatures a player controls deal combat damage to you, that player takes the initiative.";
|
||||
final String damageEff = "DB$ TakeInitiative | Defined$ TriggeredSource";
|
||||
|
||||
final Trigger damageTrigger = TriggerHandler.parseTrigger(damageTrig, initiativeEffect, true);
|
||||
|
||||
damageTrigger.setOverridingAbility(AbilityFactory.getAbility(damageEff, initiativeEffect));
|
||||
initiativeEffect.addTrigger(damageTrigger);
|
||||
|
||||
//Set up triggers to venture into Undercity
|
||||
final String ventureTakeTrig = "Mode$ TakesInitiative | ValidPlayer$ You | TriggerZones$ Command | " +
|
||||
"TriggerDescription$ Whenever you take the initiative and at the beginning of your upkeep, " +
|
||||
"venture into Undercity. (If you're in a dungeon, advance to the next room. If not, enter " +
|
||||
"Undercity. You can take the initiative even if you already have it.)";
|
||||
|
||||
final String ventureUpkpTrig = "Mode$ Phase | Phase$ Upkeep | TriggerZones$ Command | ValidPlayer$ You " +
|
||||
"| TriggerDescription$ Whenever you take the initiative and at the beginning of your upkeep, " +
|
||||
"venture into Undercity. (If you're in a dungeon, advance to the next room. If not, enter " +
|
||||
"Undercity. You can take the initiative even if you already have it.) | Secondary$ True";
|
||||
|
||||
final String ventureEff = "DB$ Venture | Dungeon$ Undercity";
|
||||
|
||||
final Trigger ventureUTrigger = TriggerHandler.parseTrigger(ventureUpkpTrig, initiativeEffect, true);
|
||||
ventureUTrigger.setOverridingAbility(AbilityFactory.getAbility(ventureEff, initiativeEffect));
|
||||
initiativeEffect.addTrigger(ventureUTrigger);
|
||||
|
||||
final Trigger ventureTTrigger = TriggerHandler.parseTrigger(ventureTakeTrig, initiativeEffect, true);
|
||||
ventureTTrigger.setOverridingAbility(AbilityFactory.getAbility(ventureEff, initiativeEffect));
|
||||
initiativeEffect.addTrigger(ventureTTrigger);
|
||||
|
||||
initiativeEffect.updateStateForView();
|
||||
}
|
||||
|
||||
final TriggerHandler triggerHandler = game.getTriggerHandler();
|
||||
triggerHandler.suppressMode(TriggerType.ChangesZone);
|
||||
game.getAction().moveTo(ZoneType.Command, initiativeEffect, null, null);
|
||||
triggerHandler.clearSuppression(TriggerType.ChangesZone);
|
||||
triggerHandler.clearActiveTriggers(initiativeEffect, null);
|
||||
triggerHandler.registerActiveTrigger(initiativeEffect, false);
|
||||
|
||||
this.updateZoneForView(com);
|
||||
}
|
||||
|
||||
public boolean hasInitiative() {
|
||||
return equals(game.getHasInitiative());
|
||||
}
|
||||
|
||||
public void removeInitiativeEffect() {
|
||||
final PlayerZone com = getZone(ZoneType.Command);
|
||||
if (initiativeEffect != null) {
|
||||
com.remove(initiativeEffect);
|
||||
this.updateZoneForView(com);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateKeywordCardAbilityText() {
|
||||
if (getKeywordCard() == null)
|
||||
return;
|
||||
|
||||
@@ -134,7 +134,7 @@ public abstract class PlayerController {
|
||||
|
||||
public abstract <T extends GameEntity> List<T> chooseEntitiesForEffect(FCollectionView<T> optionList, int min, int max, DelayedReveal delayedReveal, SpellAbility sa, String title, Player relatedPlayer, Map<String, Object> params);
|
||||
|
||||
public abstract boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message);
|
||||
public abstract boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params);
|
||||
public abstract boolean confirmBidAction(SpellAbility sa, PlayerActionConfirmMode bidlife, String string, int bid, Player winner);
|
||||
public abstract boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, GameEntity affected, String question);
|
||||
public abstract boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user