mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
Global DamageHistory (#622)
* Global DamageHistory * Add new cards * Improve Ogre Enforcer * Clean up * Minor fixes
This commit is contained in:
@@ -206,7 +206,7 @@ public abstract class GameState {
|
||||
cardsReferencedByID.add(card.getExiledWith());
|
||||
}
|
||||
if (zone == ZoneType.Battlefield) {
|
||||
if (!card.getAttachedCards().isEmpty()) {
|
||||
if (card.hasCardAttachments()) {
|
||||
// Remember the ID of cards that have attachments
|
||||
cardsReferencedByID.add(card);
|
||||
}
|
||||
@@ -375,7 +375,7 @@ public abstract class GameState {
|
||||
newText.append("|Imprinting:").append(TextUtil.join(imprintedCardIds, ","));
|
||||
}
|
||||
|
||||
if (!c.getMergedCards().isEmpty()) {
|
||||
if (c.hasMergedCard()) {
|
||||
List<String> mergedCardNames = new ArrayList<>();
|
||||
for (Card merged : c.getMergedCards()) {
|
||||
if (c.getTopMergedCard() == merged) {
|
||||
|
||||
@@ -91,6 +91,7 @@ public class GameCopier {
|
||||
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
|
||||
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
|
||||
newPlayer.setLifeGainedThisTurn(origPlayer.getLifeGainedThisTurn());
|
||||
// TODO creatureAttackedThisTurn
|
||||
for (Mana m : origPlayer.getManaPool()) {
|
||||
newPlayer.getManaPool().addMana(m, false);
|
||||
}
|
||||
@@ -207,6 +208,13 @@ public class GameCopier {
|
||||
|
||||
private void copyGameState(Game newGame) {
|
||||
newGame.setAge(origGame.getAge());
|
||||
|
||||
// TODO countersAddedThisTurn
|
||||
|
||||
if (origGame.getMonarch() != null) {
|
||||
newGame.setMonarch(playerMap.get(origGame.getMonarch()));
|
||||
}
|
||||
|
||||
for (ZoneType zone : ZONES) {
|
||||
for (Card card : origGame.getCardsIn(zone)) {
|
||||
addCard(newGame, zone, card);
|
||||
@@ -300,6 +308,7 @@ public class GameCopier {
|
||||
newCard.setPTCharacterDefiningTable(c.getSetPTCharacterDefiningTable());
|
||||
|
||||
newCard.setPTBoost(c.getPTBoostTable());
|
||||
// TODO copy by map
|
||||
newCard.setDamage(c.getDamage());
|
||||
|
||||
newCard.setChangedCardColors(c.getChangedCardColorsTable());
|
||||
|
||||
@@ -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,6 +122,9 @@ 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();
|
||||
|
||||
@@ -251,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();
|
||||
@@ -1088,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();
|
||||
@@ -1129,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;
|
||||
@@ -1323,33 +1322,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
|
||||
@@ -2364,6 +2343,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()) {
|
||||
@@ -2390,6 +2370,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)) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
@@ -2030,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")) {
|
||||
@@ -2091,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);
|
||||
}
|
||||
@@ -2359,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")) {
|
||||
@@ -3325,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) {
|
||||
@@ -3387,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++) {
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -180,9 +180,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
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;
|
||||
@@ -257,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
|
||||
@@ -951,7 +948,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
devouredCards.add(c);
|
||||
}
|
||||
|
||||
public final void clearDevoured() {
|
||||
devouredCards = null;
|
||||
}
|
||||
@@ -982,7 +978,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
delvedCards = null;
|
||||
}
|
||||
|
||||
|
||||
public final CardCollectionView getConvoked() {
|
||||
return CardCollection.getView(convokedCards);
|
||||
}
|
||||
@@ -5292,62 +5287,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5356,11 +5304,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
|
||||
@@ -5383,15 +5327,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;
|
||||
}
|
||||
@@ -5542,14 +5497,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);
|
||||
@@ -5576,7 +5523,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);
|
||||
}
|
||||
|
||||
@@ -5714,7 +5662,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public final void setForetold(final boolean foretold) {
|
||||
this.foretold = foretold;
|
||||
}
|
||||
@@ -5732,7 +5679,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;
|
||||
}
|
||||
@@ -5803,7 +5749,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;
|
||||
}
|
||||
@@ -6206,7 +6151,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
setDamage(0);
|
||||
}
|
||||
setHasBeenDealtDeathtouchDamage(false);
|
||||
resetReceivedDamageFromThisTurn();
|
||||
setRegeneratedThisTurn(0);
|
||||
resetShield();
|
||||
setBecameTargetThisTurn(false);
|
||||
@@ -6214,6 +6158,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,9 +4,11 @@ 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;
|
||||
@@ -22,7 +24,6 @@ public class CardDamageHistory {
|
||||
private boolean creatureBlockedThisCombat = false;
|
||||
private boolean creatureGotBlockedThisCombat = false;
|
||||
|
||||
boolean hasdealtDamagetoAny = false;
|
||||
private List<GameEntity> attackedThisTurn = Lists.newArrayList();
|
||||
|
||||
private final List<Player> creatureAttackedLastTurnOf = Lists.newArrayList();
|
||||
@@ -30,14 +31,13 @@ public class CardDamageHistory {
|
||||
private final List<Player> NotBlockedSinceLastUpkeepOf = Lists.newArrayList();
|
||||
private final List<Player> NotBeenBlockedSinceLastUpkeepOf = Lists.newArrayList();
|
||||
|
||||
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<>();
|
||||
|
||||
private final Map<GameEntity, Integer> damagedThisTurn = Maps.newHashMap();
|
||||
private final Map<GameEntity, Integer> damagedThisTurnInCombat = Maps.newHashMap();
|
||||
private boolean receivedNonCombatDamageThisTurn = false;
|
||||
boolean hasdealtDamagetoAny = false;
|
||||
|
||||
public final boolean getHasdealtDamagetoAny() {
|
||||
return hasdealtDamagetoAny;
|
||||
@@ -225,38 +225,9 @@ 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 List<Player> getThisCombatDamaged() {
|
||||
return damagedThisCombat;
|
||||
}
|
||||
public final Map<GameEntity, Integer> getThisTurnDamaged() {
|
||||
return damagedThisTurn;
|
||||
}
|
||||
public final Map<GameEntity, Integer> getThisTurnCombatDamaged() {
|
||||
return damagedThisTurnInCombat;
|
||||
}
|
||||
public final FCollection<GameEntity> getThisGameDamaged() {
|
||||
return damagedThisGame;
|
||||
}
|
||||
@@ -264,38 +235,44 @@ public class CardDamageHistory {
|
||||
* TODO: Write javadoc for this method.
|
||||
* @param player
|
||||
*/
|
||||
public void registerCombatDamage(GameEntity entity, int amount) {
|
||||
int old = 0;
|
||||
if (entity instanceof Player) {
|
||||
damagedThisCombat.add((Player) entity);
|
||||
}
|
||||
old = 0;
|
||||
if (damagedThisTurnInCombat.containsKey(entity)) {
|
||||
old = damagedThisTurnInCombat.get(entity);
|
||||
}
|
||||
damagedThisTurnInCombat.put(entity, old + amount);
|
||||
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);
|
||||
}
|
||||
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.
|
||||
* @param player
|
||||
*/
|
||||
public void registerDamage(GameEntity entity, int amount) {
|
||||
int old = 0;
|
||||
if (damagedThisTurn.containsKey(entity)) {
|
||||
old = damagedThisTurn.get(entity);
|
||||
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;
|
||||
}
|
||||
}
|
||||
damagedThisTurn.put(entity, old + amount);
|
||||
damagedThisGame.add(entity);
|
||||
hasdealtDamagetoAny = true;
|
||||
return sum;
|
||||
}
|
||||
|
||||
public void newTurn() {
|
||||
damagedThisCombat.clear();
|
||||
damagedThisTurnInCombat.clear();
|
||||
damagedThisTurn.clear();
|
||||
attackedThisTurn.clear();
|
||||
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();
|
||||
@@ -307,7 +284,6 @@ public class CardDamageHistory {
|
||||
}
|
||||
}
|
||||
damagedThisGame.removeAll(toRemove);
|
||||
setHasBeenDealtNonCombatDamageThisTurn(false);
|
||||
}
|
||||
|
||||
public void endCombat() {
|
||||
|
||||
@@ -608,35 +608,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)) {
|
||||
@@ -1133,38 +1104,78 @@ 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") ?
|
||||
Lists.newArrayList(card.getDamageHistory().getThisCombatDamaged()) :
|
||||
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")) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -147,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;
|
||||
@@ -219,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;
|
||||
@@ -704,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);
|
||||
@@ -830,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.
|
||||
*/
|
||||
@@ -2082,8 +2023,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -2102,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) {
|
||||
@@ -2150,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;
|
||||
@@ -2239,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());
|
||||
@@ -2260,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();
|
||||
}
|
||||
@@ -2452,10 +2390,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
resetCycledThisTurn();
|
||||
resetEquippedThisTurn();
|
||||
resetSacrificedThisTurn();
|
||||
clearAssignedDamage();
|
||||
resetVenturedThisTurn();
|
||||
setRevolt(false);
|
||||
resetProwl();
|
||||
setSpellsCastLastTurn(getSpellsCastThisTurn());
|
||||
resetSpellsCastThisTurn();
|
||||
setLifeLostLastTurn(getLifeLostThisTurn());
|
||||
@@ -2467,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);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.game.player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import forge.game.CardTraitBase;
|
||||
@@ -86,81 +87,66 @@ public class PlayerProperty {
|
||||
if (!player.hasBlessing()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("damageDoneSingleSource")) {
|
||||
String props = property.split(" ")[1];
|
||||
List<Integer> sourceDmg = game.getDamageDoneThisTurn(null, false, "Card.YouCtrl", null, source, sourceController, spellAbility);
|
||||
int maxDmg = sourceDmg.isEmpty() ? 0 : Collections.max(sourceDmg);
|
||||
if (!Expressions.compare(maxDmg, props.substring(0, 2), AbilityUtils.calculateAmount(source, props.substring(2), spellAbility))) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("wasDealtCombatDamageThisCombatBy ")) {
|
||||
String v = property.split(" ")[1];
|
||||
|
||||
int count = 1;
|
||||
if (v.contains("_AtLeast")) {
|
||||
count = Integer.parseInt(v.substring(v.indexOf("_AtLeast") + 8));
|
||||
v = v.substring(0, v.indexOf("_AtLeast")).replace("Valid:", "Valid ");
|
||||
}
|
||||
boolean found = true;
|
||||
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(source, v, spellAbility);
|
||||
int found = 0;
|
||||
for (final Card card : cards) {
|
||||
if (card.getDamageHistory().getThisCombatDamaged().contains(player)) {
|
||||
found++;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found < count) {
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("wasDealtDamageThisGameBy ")) {
|
||||
String v = property.split(" ")[1];
|
||||
|
||||
int count = 1;
|
||||
if (v.contains("_AtLeast")) {
|
||||
count = Integer.parseInt(v.substring(v.indexOf("_AtLeast") + 8));
|
||||
v = TextUtil.fastReplace(v.substring(0, v.indexOf("_AtLeast")), "Valid:", "Valid ");
|
||||
}
|
||||
boolean found = true;
|
||||
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(source, v, spellAbility);
|
||||
int found = 0;
|
||||
for (final Card card : cards) {
|
||||
if (card.getDamageHistory().getThisGameDamaged().contains(player)) {
|
||||
found++;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found < count) {
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("wasDealtDamageThisTurnBy ")) {
|
||||
String v = property.split(" ")[1];
|
||||
int count = 1;
|
||||
|
||||
if (v.contains("_AtLeast")) {
|
||||
count = Integer.parseInt(v.substring(v.indexOf("_AtLeast") + 8));
|
||||
v = TextUtil.fastReplace(v.substring(0, v.indexOf("_AtLeast")), "Valid:", "Valid ");
|
||||
} else if (property.startsWith("wasDealt")) {
|
||||
boolean found = false;
|
||||
String validCard = null;
|
||||
Boolean combat = null;
|
||||
if (property.contains("CombatDamage")) {
|
||||
combat = true;
|
||||
}
|
||||
if (property.contains("ThisTurnBySource")) {
|
||||
found = source.getDamageHistory().getDamageDoneThisTurn(combat, validCard == null, validCard, "You", source, player, spellAbility) > 0;
|
||||
} else {
|
||||
String comp = "GE";
|
||||
int right = 1;
|
||||
int numValid = 0;
|
||||
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(source, v, spellAbility);
|
||||
int found = 0;
|
||||
for (final Card card : cards) {
|
||||
if (card.getDamageHistory().getThisTurnDamaged().containsKey(player)) {
|
||||
found++;
|
||||
if (property.contains("ThisTurnBy")) {
|
||||
String[] props = property.split(" ");
|
||||
validCard = props[1];
|
||||
if (props.length > 2) {
|
||||
comp = props[2].substring(0, 2);
|
||||
right = AbilityUtils.calculateAmount(source, props[2].substring(2), spellAbility);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found < count) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.startsWith("wasDealtCombatDamageThisTurnBy ")) {
|
||||
String v = property.split(" ")[1];
|
||||
|
||||
int count = 1;
|
||||
if (v.contains("_AtLeast")) {
|
||||
count = Integer.parseInt(v.substring(v.indexOf("_AtLeast") + 8));
|
||||
v = TextUtil.fastReplace(v.substring(0, v.indexOf("_AtLeast")), "Valid:", "Valid ");
|
||||
numValid = game.getDamageDoneThisTurn(combat, validCard == null, validCard, "You", source, player, spellAbility).size();
|
||||
found = Expressions.compare(numValid, comp, right);
|
||||
}
|
||||
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(source, v, spellAbility);
|
||||
|
||||
int found = 0;
|
||||
for (final Card card : cards) {
|
||||
if (card.getDamageHistory().getThisTurnCombatDamaged().containsKey(player)) {
|
||||
found++;
|
||||
}
|
||||
}
|
||||
if (found < count) {
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("attackedBySourceThisCombat")) {
|
||||
@@ -171,18 +157,10 @@ public class PlayerProperty {
|
||||
if (!source.getDamageHistory().hasAttackedThisTurn(player)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("wasDealtDamageThisTurn")) {
|
||||
if (player.getAssignedDamage() == 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("Defending")) {
|
||||
} else if (property.equals("Defending")) {
|
||||
if (!game.getCombat().getAttackersAndDefenders().values().contains(player)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("wasDealtCombatDamageThisTurn")) {
|
||||
if (player.getAssignedCombatDamage() == 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("LostLifeThisTurn")) {
|
||||
if (player.getLifeLostThisTurn() <= 0) {
|
||||
return false;
|
||||
|
||||
@@ -87,8 +87,8 @@ public class AbilityManaPart implements java.io.Serializable {
|
||||
public AbilityManaPart(final Card sourceCard, final Map<String, String> params) {
|
||||
this.sourceCard = sourceCard;
|
||||
|
||||
origProduced = params.containsKey("Produced") ? params.get("Produced") : "1";
|
||||
this.manaRestrictions = params.containsKey("RestrictValid") ? params.get("RestrictValid") : "";
|
||||
origProduced = params.getOrDefault("Produced", "1");
|
||||
this.manaRestrictions = params.getOrDefault("RestrictValid", "");
|
||||
this.cannotCounterSpell = params.get("AddsNoCounter");
|
||||
this.addsKeywords = params.get("AddsKeywords");
|
||||
this.addsKeywordsType = params.get("AddsKeywordsType");
|
||||
|
||||
@@ -434,13 +434,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
||||
}
|
||||
}
|
||||
if (sa.isProwl()) {
|
||||
boolean prowlFlag = false;
|
||||
for (final String type : c.getType().getCreatureTypes()) {
|
||||
if (activator.hasProwl(type)) {
|
||||
prowlFlag = true;
|
||||
}
|
||||
}
|
||||
if (!prowlFlag) {
|
||||
if (!activator.hasProwl(c.getType().getCreatureTypes())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ public class TriggerChangesZone extends Trigger {
|
||||
// need to check the ChangeZone LKI copy for damage, otherwise it'll return 0 for a new object in the new zone
|
||||
Card lkiCard = card.getGame().getChangeZoneLKIInfo(card);
|
||||
|
||||
final boolean expr = Expressions.compare(lkiCard.getTotalDamageReceivedThisTurn(), cond, rightSide);
|
||||
final boolean expr = Expressions.compare(lkiCard.getAssignedDamage(), cond, rightSide);
|
||||
if (!expr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public class TriggerDamageDone extends Trigger {
|
||||
|
||||
if (target instanceof Player) {
|
||||
final Player trigTgt = (Player) target;
|
||||
if (!Expressions.compare(trigTgt.getAssignedDamage(source), operator, operand)) {
|
||||
if (!Expressions.compare(trigTgt.getAssignedDamage(null, source), operator, operand)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Legendary Creature Human Pirate
|
||||
PT:3/3
|
||||
S:Mode$ Continuous | Affected$ Creature.Pirate+Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other Pirates you control get +1/+1.
|
||||
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigGainCtrl | TriggerDescription$ At the beginning of your end step, gain control of target nonland permanent controlled by a player who was dealt combat damage by three or more Pirates this turn.
|
||||
SVar:TrigGainCtrl:DB$ GainControl | ValidTgts$ Permanent.nonLand+ControlledBy Player.wasDealtCombatDamageThisTurnBy Valid:Creature.Pirate_AtLeast3 | TgtPrompt$ Select target creature controlled by a player who was dealt damage by three or more Pirates this turn | SpellDescription$ Gain control of target nonland permanent controlled by a player who was dealt combat damage by three or more Pirates this turn.
|
||||
SVar:TrigGainCtrl:DB$ GainControl | ValidTgts$ Permanent.nonLand+ControlledBy Player.wasDealtCombatDamageThisTurnBy Creature.Pirate GE3 | TgtPrompt$ Select target creature controlled by a player who was dealt damage by three or more Pirates this turn | SpellDescription$ Gain control of target nonland permanent controlled by a player who was dealt combat damage by three or more Pirates this turn.
|
||||
DeckHints:Type$Pirate
|
||||
Oracle:Other Pirates you control get +1/+1.\nAt the beginning of your end step, gain control of target nonland permanent controlled by a player who was dealt combat damage by three or more Pirates this turn.
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Artifact Equipment
|
||||
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ Y | Description$ Equipped creature gets +1/+0 for each opponent you have.
|
||||
SVar:Y:PlayerCountOpponents$Amount
|
||||
T:Mode$ DamageDoneOnce | Execute$ TrigDamage | ValidTarget$ Creature.EquippedBy | TriggerZones$ Battlefield | TriggerDescription$ Whenever equipped creature is dealt damage, it deals that much damage to any target.
|
||||
SVar:TrigDamage:DB$ DealDamage | NumDmg$ X | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target
|
||||
SVar:TrigDamage:DB$ DealDamage | NumDmg$ X | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | DamageSource$ TriggeredTargetLKICopy
|
||||
SVar:X:TriggerCount$DamageAmount
|
||||
K:Equip:4
|
||||
Oracle:Equipped creature gets +1/+0 for each opponent you have.\nWhenever equipped creature is dealt damage, it deals that much damage to any target.\nEquip {4}
|
||||
|
||||
@@ -5,7 +5,7 @@ K:Entwine:1
|
||||
A:SP$ Charm | Cost$ 2 W | Choices$ DBTap,DBEffect | CharmNum$ 1
|
||||
SVar:DBTap:DB$ Tap | ValidTgts$ Creature | TargetMin$ 2 | TargetMax$ 2 | TgtPrompt$ Select two target creatures | SpellDescription$ Tap two target creatures.
|
||||
SVar:DBEffect:DB$ Effect | ValidTgts$ Player | TgtPrompt$ Select target player | IsCurse$ True | StaticAbilities$ DontUntap | Triggers$ RestoreSight | RememberObjects$ Targeted | Duration$ Permanent | SpellDescription$ Creatures don't untap during target player's next untap step.
|
||||
SVar:DontUntap:Mode$ Continuous | ValidPlayer$ Player.IsRemembered | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature | AddHiddenKeyword$ This card doesn't untap.
|
||||
SVar:DontUntap:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature.RememberedPlayerCtrl | AddHiddenKeyword$ This card doesn't untap.
|
||||
SVar:RestoreSight:Mode$ Phase | Phase$ Untap | ValidPlayer$ Player.IsRemembered | TriggerZones$ Command | Execute$ ExileEffect | Static$ True
|
||||
SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
|
||||
AI:RemoveDeck:All
|
||||
|
||||
@@ -3,5 +3,5 @@ ManaCost:R
|
||||
Types:Creature Goblin Berserker
|
||||
PT:2/2
|
||||
S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ CARDNAME can't attack. | CheckSVar$ X | SVarCompare$ LT1 | Description$ CARDNAME can't attack unless an opponent has been dealt damage this turn.
|
||||
SVar:X:Count$TotalOppDamageThisTurn
|
||||
SVar:X:PlayerCountPropertyYou$DamageToOppsThisTurn
|
||||
Oracle:Bloodcrazed Goblin can't attack unless an opponent has been dealt damage this turn.
|
||||
|
||||
@@ -6,5 +6,5 @@ T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ Player.Opponent | TriggerZones
|
||||
SVar:GhostCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ X
|
||||
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ GhostClear | TriggerDescription$ At the beginning of your end step, remove all +1/+1 counters from CARDNAME.
|
||||
SVar:GhostClear:DB$ RemoveCounter | CounterType$ P1P1 | CounterNum$ All
|
||||
SVar:X:Count$YourDamageThisTurn
|
||||
SVar:X:PlayerCountPropertyYou$DamageThisTurn
|
||||
Oracle:At the beginning of each end step, if it's an opponent's turn, put a +1/+1 counter on Discordant Spirit for each 1 damage dealt to you this turn.\nAt the beginning of your end step, remove all +1/+1 counters from Discordant Spirit.
|
||||
|
||||
@@ -5,8 +5,8 @@ A:SP$ ChangeZone | ValidTgts$ Card | TgtZone$ Stack | Origin$ Stack | Fizzle$ Tr
|
||||
SVar:DBRoll:DB$ RollDice | Sides$ 20 | Modifier$ Y | ResultSubAbilities$ 1-14:DBMayPlay,Else:DBMayPlayWithoutCost | StackDescription$ SpellDescription | SpellDescription$ Roll a d20 and add that spell's mana value.
|
||||
SVar:DBMayPlay:DB$ Effect | StaticAbilities$ STPlay | RememberObjects$ Remembered | Duration$ Permanent | ExileOnMoved$ Exile | SubAbility$ DBCleanup | SpellDescription$ 1—14 VERT You may cast that card for as long as it remains exiled, and you may spend mana as though it were mana of any color to cast it.
|
||||
SVar:DBMayPlayWithoutCost:DB$ Effect | StaticAbilities$ STPlayWithoutCost | RememberObjects$ Remembered | Duration$ Permanent | ExileOnMoved$ Exile | SubAbility$ DBCleanup | SpellDescription$ 15+ VERT You may cast that card without paying its mana cost for as long as it remains exiled.
|
||||
SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreColor$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may cast that card for as long as it remains exiled, and you may spend mana as though it were mana of any color to cast it.
|
||||
SVar:STPlayWithoutCost:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may cast that card without paying its mana cost for as long as it remains exiled.
|
||||
SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreColor$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand | AffectedZone$ Exile | Description$ You may cast that card for as long as it remains exiled, and you may spend mana as though it were mana of any color to cast it.
|
||||
SVar:STPlayWithoutCost:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand | AffectedZone$ Exile | Description$ You may cast that card without paying its mana cost for as long as it remains exiled.
|
||||
SVar:Y:RememberedLKI$CardManaCost
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
AI:RemoveDeck:All
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Glen Elendra
|
||||
ManaCost:no cost
|
||||
Types:Plane Lorwyn
|
||||
T:Mode$ Phase | Phase$ EndCombat | ValidPlayer$ You | TriggerZones$ Command | OptionalDecider$ You | Execute$ TrigExchange | TriggerDescription$ At end of combat, you may exchange control of target creature you control that dealt combat damage to a player this combat and target creature that player controls.
|
||||
SVar:TrigExchange:DB$ Pump | ValidTgts$ Creature.YouCtrl+dealtCombatDamageThisCombat Player | TgtPrompt$ Select target creature you control that dealt combat damage to a player | SubAbility$ DBExchange
|
||||
SVar:TrigExchange:DB$ Pump | ValidTgts$ Creature.YouCtrl+dealtCombatDamageThisCombat | TgtPrompt$ Select target creature you control that dealt combat damage to a player | SubAbility$ DBExchange
|
||||
SVar:DBExchange:DB$ ExchangeControl | Defined$ ParentTarget | ValidTgts$ Creature.ControlledBy Player.wasDealtCombatDamageThisCombatBy ParentTarget | TgtPrompt$ Select target creature that player controls.
|
||||
T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, gain control of target creature you own.
|
||||
SVar:RolledChaos:DB$ GainControl | ValidTgts$ Creature.YouOwn | TgtPrompt$ Select target creature you own to gain control of
|
||||
|
||||
@@ -8,6 +8,6 @@ SVar:GrothamaFight:DB$ Fight | Defined$ TriggeredAttackerLKICopy | ExtraDefined$
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigRepeat | TriggerDescription$ When CARDNAME leaves the battlefield, each player draws cards equal to the amount of damage dealt to Grothama this turn by sources they controlled.
|
||||
SVar:TrigRepeat:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ TrigDraw
|
||||
SVar:TrigDraw:DB$ Draw | Defined$ Remembered | NumCards$ X
|
||||
SVar:X:TriggeredCard$DamageDoneByPlayerThisTurn.Remembered
|
||||
SVar:X:TriggeredCard$TotalDamageThisTurn Card.RememberedPlayerCtrl
|
||||
SVar:HasAttackEffect:TRUE
|
||||
Oracle:Other creatures have "Whenever this creature attacks, you may have it fight Grothama, All-Devouring."\nWhen Grothama leaves the battlefield, each player draws cards equal to the amount of damage dealt to Grothama this turn by sources they controlled.
|
||||
|
||||
@@ -3,6 +3,6 @@ ManaCost:1
|
||||
Types:Legendary Artifact Creature Thopter
|
||||
PT:1/1
|
||||
K:Flying
|
||||
A:AB$ Effect | Cost$ Sac<1/CARDNAME> | ValidTgts$ Player.wasDealtCombatDamageThisTurnBy Self | Name$ Hope of Ghirapur Effect | StaticAbilities$ STCantBeCast | RememberObjects$ Targeted | Duration$ UntilYourNextTurn | SpellDescription$ Until your next turn, target player who was dealt combat damage by Hope of Ghirapur can't cast noncreature spells.
|
||||
A:AB$ Effect | Cost$ Sac<1/CARDNAME> | ValidTgts$ Player.wasDealtCombatDamageThisTurnBySource | Name$ Hope of Ghirapur Effect | StaticAbilities$ STCantBeCast | RememberObjects$ Targeted | Duration$ UntilYourNextTurn | SpellDescription$ Until your next turn, target player who was dealt combat damage by Hope of Ghirapur can't cast noncreature spells.
|
||||
SVar:STCantBeCast:Mode$ CantBeCast | EffectZone$ Command | ValidCard$ Card.nonCreature | Caster$ Player.IsRemembered | Description$ Until your next turn, target player who was dealt combat damage by Hope of Ghirapur this turn can't cast noncreature spells.
|
||||
Oracle:Flying\nSacrifice Hope of Ghirapur: Until your next turn, target player who was dealt combat damage by Hope of Ghirapur this turn can't cast noncreature spells.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
Name:Inferno Trap
|
||||
ManaCost:3 R
|
||||
Types:Instant Trap
|
||||
SVar:AltCost:Cost$ R | CheckSVar$ CreaturesAttacked | SVarCompare$ GE2 | Description$ If you've been dealt damage by two or more creatures this turn, you may pay {R} rather than pay this spell's mana cost.
|
||||
SVar:AltCost:Cost$ R | CheckSVar$ CreaturesDmg | SVarCompare$ GE2 | Description$ If you've been dealt damage by two or more creatures this turn, you may pay {R} rather than pay this spell's mana cost.
|
||||
A:SP$ DealDamage | Cost$ 3 R | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to target creature.
|
||||
SVar:CreaturesAttacked:Count$YourDamageSourcesThisTurn Creature
|
||||
SVar:CreaturesDmg:Count$NumDamageThisTurn Creature You
|
||||
AI:RemoveDeck:All
|
||||
Oracle:If you've been dealt damage by two or more creatures this turn, you may pay {R} rather than pay this spell's mana cost.\nInferno Trap deals 4 damage to target creature.
|
||||
|
||||
@@ -25,6 +25,6 @@ Colors:red
|
||||
Types:Enchantment Creature Human Shaman
|
||||
PT:2/2
|
||||
K:Haste
|
||||
R:Event$ Moved | ValidLKI$ Creature.DamagedByCard.YouCtrl,Creature.DamagedByEmblem.YouCtrl | Destination$ Graveyard | ReplaceWith$ DBExile | ActiveZones$ Battlefield | Description$ If a creature dealt damage this turn by a source you controlled would die, exile it instead.
|
||||
R:Event$ Moved | ValidLKI$ Creature.DamagedByCard.YouCtrl,Emblem.YouCtrl | Destination$ Graveyard | ReplaceWith$ DBExile | ActiveZones$ Battlefield | Description$ If a creature dealt damage this turn by a source you controlled would die, exile it instead.
|
||||
SVar:DBExile:DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ Exile
|
||||
Oracle:Haste\nIf a creature dealt damage this turn by a source you controlled would die, exile it instead.
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Enchantment Aura
|
||||
K:Flash
|
||||
K:Enchant creature
|
||||
A:SP$ Attach | Cost$ 2 R | ValidTgts$ Creature | AILogic$ Pump
|
||||
R:Event$ Moved | ValidLKI$ Creature.DamagedByEnchanted | Destination$ Graveyard | ActiveZones$ Battlefield | ReplaceWith$ DBExile | Description$ If a creature dealt damage by enchanted creature this turn would die, exile it instead.
|
||||
R:Event$ Moved | ValidLKI$ Creature.DamagedBy Enchanted | Destination$ Graveyard | ActiveZones$ Battlefield | ReplaceWith$ DBExile | Description$ If a creature dealt damage by enchanted creature this turn would die, exile it instead.
|
||||
SVar:DBExile:DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ Exile
|
||||
AI:RemoveDeck:Random
|
||||
SVar:NonStackingAttachEffect:True
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:5 W
|
||||
Types:Creature Elephant Cleric
|
||||
PT:4/6
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigLife | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may have your life total become the total toughness of creatures you control.
|
||||
SVar:TrigLife:DB$ SetLife | Defined$ You | LifeAmount$ Y | SubAbility$ DBCleanup
|
||||
SVar:TrigLife:DB$ SetLife | Defined$ You | LifeAmount$ Y
|
||||
SVar:Y:Count$Valid Creature.YouCtrl$SumToughness
|
||||
A:AB$ Pump | Cost$ 5 W | NumAtt$ X | NumDef$ X | SpellDescription$ CARDNAME gets +X/+X until end of turn, where X is your life total.
|
||||
SVar:X:Count$YourLifeTotal
|
||||
|
||||
@@ -6,5 +6,5 @@ R:Event$ DamageDone | ActiveZones$ Command | ValidSource$ Card.OppCtrl,Emblem.Op
|
||||
SVar:DBReplace:DB$ ReplaceDamage | Amount$ 1
|
||||
T:Mode$ Phase | Phase$ End of Turn | CheckSVar$ X | SVarCompare$ GE5 | TriggerZones$ Command | Execute$ Abandon | TriggerDescription$ At the beginning of each end step, if you've been dealt 5 or more damage this turn, abandon this scheme.
|
||||
SVar:Abandon:AB$ Abandon | Cost$ 0
|
||||
SVar:X:Count$YourDamageThisTurn
|
||||
SVar:X:PlayerCountPropertyYou$DamageThisTurn
|
||||
Oracle:(An ongoing scheme remains face up until it's abandoned.)\nIf a source an opponent controls would deal damage to you, prevent 1 of that damage.\nAt the beginning of each end step, if you've been dealt 5 or more damage this turn, abandon this scheme.
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Tribal Sorcery Rogue
|
||||
K:Prowl:5 U
|
||||
A:SP$ Token | Cost$ 3 U | TokenAmount$ X | TokenScript$ b_1_1_faerie_rogue_flying | TokenOwner$ You | SubAbility$ DBTakeTurn | SpellDescription$ Create X 1/1 black Faerie Rogue creature tokens with flying, where X is the damage dealt to your opponents this turn. If CARDNAME's prowl cost was paid, take an extra turn after this one.
|
||||
SVar:DBTakeTurn:DB$ AddTurn | NumTurns$ 1 | ConditionDefined$ Self | ConditionPresent$ Card.prowled
|
||||
SVar:X:Count$TotalOppDamageThisTurn
|
||||
SVar:X:PlayerCountPropertyYou$DamageToOppsThisTurn
|
||||
DeckNeeds:Type$Rogue
|
||||
Oracle:Prowl {5}{U} (You may cast this for its prowl cost if you dealt combat damage to a player this turn with a Rogue.)\nCreate X 1/1 black Faerie Rogue creature tokens with flying, where X is the damage dealt to your opponents this turn. If this spell's prowl cost was paid, take an extra turn after this one.
|
||||
|
||||
@@ -8,6 +8,6 @@ SVar:RuneswordSac:DB$ SacrificeAll | Defined$ Imprinted | SubAbility$ ExileEffec
|
||||
SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
|
||||
SVar:TrigNoregen:Mode$ DamageDone | ValidSource$ Card.IsRemembered | ValidTarget$ Creature | Execute$ PumpNogen | Static$ True | TriggerDescription$ If the creature deals damage to a creature this turn, the creature dealt damage can't be regenerated this turn.
|
||||
SVar:PumpNogen:DB$ Pump | KW$ HIDDEN CARDNAME can't be regenerated. | Defined$ TriggeredTarget
|
||||
SVar:RuneswordRep:Event$ Moved | ValidLKI$ Creature.DamagedByRemembered | Destination$ Graveyard | ReplaceWith$ RuneswordExile | Description$ If a creature dealt damage by CARDNAME this turn would die, exile it instead.
|
||||
SVar:RuneswordRep:Event$ Moved | ValidLKI$ Creature.DamagedBy Remembered | Destination$ Graveyard | ReplaceWith$ RuneswordExile | Description$ If a creature dealt damage by CARDNAME this turn would die, exile it instead.
|
||||
SVar:RuneswordExile:DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ Exile
|
||||
Oracle:{3}, {T}: Target attacking creature gets +2/+0 until end of turn. When that creature leaves the battlefield this turn, sacrifice Runesword. If the creature deals damage to a creature this turn, the creature dealt damage can't be regenerated this turn. If a creature dealt damage by the targeted creature would die this turn, exile that creature instead.
|
||||
|
||||
@@ -3,6 +3,6 @@ ManaCost:1 B
|
||||
Types:Instant
|
||||
A:SP$ GainLife | Cost$ 1 B | Defined$ You | LifeAmount$ X | SubAbility$ Dmg | SpellDescription$ You gain life equal to the damage dealt to you this turn. CARDNAME deals damage to target creature you control equal to the damage dealt to you this turn.
|
||||
SVar:Dmg:DB$ DealDamage | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | NumDmg$ X
|
||||
SVar:X:Count$YourDamageThisTurn
|
||||
SVar:X:PlayerCountPropertyYou$DamageThisTurn
|
||||
AI:RemoveDeck:All
|
||||
Oracle:You gain life equal to the damage dealt to you this turn. Simulacrum deals damage to target creature you control equal to the damage dealt to you this turn.
|
||||
|
||||
@@ -5,5 +5,5 @@ PT:3/3
|
||||
K:Bloodthirst:3
|
||||
K:Flying
|
||||
A:AB$ ChangeZone | Cost$ R R R | Origin$ Graveyard | Destination$ Hand | ActivationZone$ Graveyard | CheckSVar$ OppDamaged | SVarCompare$ GE1 | SpellDescription$ Return CARDNAME from your graveyard to your hand. Activate only if an opponent was dealt damage this turn.
|
||||
SVar:OppDamaged:Count$TotalOppDamageThisTurn
|
||||
SVar:OppDamaged:PlayerCountPropertyYou$DamageToOppsThisTurn
|
||||
Oracle:Bloodthirst 3 (If an opponent was dealt damage this turn, this creature enters the battlefield with three +1/+1 counters on it.)\nFlying\n{R}{R}{R}: Return Skarrgan Firebird from your graveyard to your hand. Activate only if an opponent was dealt damage this turn.
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:1 R
|
||||
Types:Creature Human Wizard
|
||||
PT:1/2
|
||||
K:Haste
|
||||
A:AB$ SetState | Cost$ T | ValidTgts$ Creature.YouCtrl+faceDown | Mode$ TurnFace | SubAbility$ DBDelTrig | SpellDescription$ Turn target face-down creature you control face up. At the beginning of the next end step, sacrifice it.
|
||||
A:AB$ SetState | Cost$ T | ValidTgts$ Creature.YouCtrl+faceDown | Mode$ TurnFace | SubAbility$ DBPump | SpellDescription$ Turn target face-down creature you control face up. At the beginning of the next end step, sacrifice it.
|
||||
SVar:DBPump:DB$ Pump | Defined$ Targeted | AtEOT$ Sacrifice
|
||||
AI:RemoveDeck:All
|
||||
Oracle:Haste\n{T}: Turn target face-down creature you control face up. At the beginning of the next end step, sacrifice it.
|
||||
|
||||
@@ -3,6 +3,6 @@ ManaCost:B
|
||||
Types:Instant
|
||||
A:SP$ Pump | Cost$ B | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +1 | KW$ Deathtouch | RememberObjects$ Targeted | SubAbility$ DBMoonglove | SpellDescription$ Target creature you control gets +1/+0 and gains deathtouch until end of turn. Whenever a creature dealt damage by that creature dies this turn, its controller loses 2 life. (Any amount of damage a creature with deathtouch deals to a creature is enough to destroy it.)
|
||||
SVar:DBMoonglove:DB$ Effect | Triggers$ MoongloveTrigger | RememberObjects$ Remembered | StackDescription$ Whenever a creature dealt damage by that creature dies this turn, its controller loses 2 life.
|
||||
SVar:MoongloveTrigger:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.DamagedByRemembered | TriggerZones$ Command | Execute$ TrigLoseLife | TriggerDescription$ Whenever a creature dealt damage by that creature dies this turn, its controller loses 2 life.
|
||||
SVar:MoongloveTrigger:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.DamagedBy Remembered | TriggerZones$ Command | Execute$ TrigLoseLife | TriggerDescription$ Whenever a creature dealt damage by that creature dies this turn, its controller loses 2 life.
|
||||
SVar:TrigLoseLife:DB$ LoseLife | LifeAmount$ 2 | Defined$ TriggeredCardController
|
||||
Oracle:Target creature you control gets +1/+0 and gains deathtouch until end of turn. Whenever a creature dealt damage by that creature dies this turn, its controller loses 2 life. (Any amount of damage a creature with deathtouch deals to a creature is enough to destroy it.)
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:U B B R
|
||||
Types:Legendary Artifact Equipment
|
||||
K:Equip:2
|
||||
S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 3 | AddToughness$ 3 | AddKeyword$ First Strike | Description$ Equipped creature gets +3/+3 and has first strike.
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.DamagedByEquipped | Execute$ UnscytheTrigExile | TriggerZones$ Battlefield | OptionalDecider$ You | TriggerDescription$ Whenever a creature dealt damage by equipped creature this turn dies, you may exile that card. If you do, create a 2/2 black Zombie creature token.
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.DamagedBy Equipped | Execute$ UnscytheTrigExile | TriggerZones$ Battlefield | OptionalDecider$ You | TriggerDescription$ Whenever a creature dealt damage by equipped creature this turn dies, you may exile that card. If you do, create a 2/2 black Zombie creature token.
|
||||
SVar:UnscytheTrigExile:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Exile | RememberChanged$ True | SubAbility$ UnscytheDBToken
|
||||
SVar:UnscytheDBToken:DB$ Token | TokenOwner$ You | TokenAmount$ 1 | TokenScript$ b_2_2_zombie | ConditionDefined$ Remembered | ConditionPresent$ Card | SubAbility$ UnscytheDBCleanup
|
||||
SVar:UnscytheDBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
|
||||
9
forge-gui/res/cardsfolder/upcoming/dragon_cultist.txt
Normal file
9
forge-gui/res/cardsfolder/upcoming/dragon_cultist.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Name:Dragon Cultist
|
||||
ManaCost:4 R
|
||||
Types:Legendary Enchantment Background
|
||||
S:Mode$ Continuous | Affected$ Creature.IsCommander+YouOwn | AddTrigger$ TrigDragon | Description$ Commander creatures you own have "At the beginning of your end step, if a source you controlled dealt 5 or more damage this turn, create a 4/4 red Dragon creature token with flying."
|
||||
SVar:TrigDragon:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | CheckDefinedPlayer$ You.damageDoneSingleSource GE5 | Execute$ DBDragon | TriggerDescription$ At the beginning of your end step, if a source you controlled dealt 5 or more damage this turn, create a 4/4 red Dragon creature token with flying.
|
||||
SVar:DBDragon:DB$ Token | TokenScript$ r_4_4_dragon_flying
|
||||
DeckHas:Ability$Token & Type$Dragon
|
||||
AI:RemoveDeck:NonCommander
|
||||
Oracle:Commander creatures you own have "At the beginning of your end step, if a source you controlled dealt 5 or more damage this turn, create a 4/4 red Dragon creature token with flying."
|
||||
9
forge-gui/res/cardsfolder/upcoming/gnoll_war_band.txt
Normal file
9
forge-gui/res/cardsfolder/upcoming/gnoll_war_band.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Name:Gnoll War Band
|
||||
ManaCost:5 R
|
||||
Types:Creature Gnoll
|
||||
PT:5/5
|
||||
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {1} less to cast for each opponent who was dealt damage this turn.
|
||||
SVar:X:PlayerCountOpponents$HasPropertywasDealtDamageThisTurn
|
||||
K:Menace
|
||||
K:Myriad
|
||||
Oracle:This spell costs {1} less to cast for each opponent who was dealt damage this turn.\nMenace\nMyriad
|
||||
22
forge-gui/res/cardsfolder/upcoming/indulge_excess.txt
Normal file
22
forge-gui/res/cardsfolder/upcoming/indulge_excess.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
Name:Indulge
|
||||
ManaCost:2 R
|
||||
Types:Sorcery
|
||||
A:SP$ Effect | CounterType$ P1P1 | CounterNum$ 3 | Triggers$ TriggerAttacks | AddSVar$ AE | SpellDescription$ Whenever a creature you control attacks this turn, create a 1/1 green and white Citizen creature token that's tapped and attacking.
|
||||
SVar:TriggerAttacks:Mode$ Attacks | ValidCard$ Creature.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever a creature you control attacks this turn, create a 1/1 green and white Citizen creature token that's tapped and attacking.
|
||||
SVar:TrigToken:DB$ Token | TokenScript$ gw_1_1_citizen | TokenTapped$ True | TokenAttacking$ True
|
||||
SVar:AE:SVar:HasAttackEffect:TRUE
|
||||
DeckHas:Ability$Token & Type$Citizen
|
||||
AlternateMode:Split
|
||||
Oracle:Whenever a creature you control attacks this turn, create a 1/1 green and white Citizen creature token that's tapped and attacking.
|
||||
|
||||
ALTERNATE
|
||||
|
||||
Name:Excess
|
||||
ManaCost:1 R
|
||||
Types:Sorcery
|
||||
K:Aftermath
|
||||
A:SP$ Token | TokenAmount$ X | TokenScript$ c_a_treasure_sac | SpellDescription$ Create a Treasure token for each creature you controlled that dealt combat damage to a player this turn.
|
||||
SVar:X:Count$NumCombatDamageThisTurn Creature.YouCtrl Player
|
||||
DeckHas:Ability$Token|Sacrifice|Graveyard & Type$Artifact|Treasure
|
||||
SVar:NeedsToPlayVar:X GE3
|
||||
Oracle:Aftermath (Cast this spell only from your graveyard. Then exile it.)\nCreate a Treasure token for each creature you controlled that dealt combat damage to a player this turn.
|
||||
@@ -4,7 +4,7 @@ Types:Creature Elemental
|
||||
PT:1/1
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSac | TriggerDescription$ When CARDNAME enters the battlefield, sacrifice it unless an opponent was dealt damage this turn.
|
||||
SVar:TrigSac:DB$ Sacrifice | Defined$ Self | ConditionCheckSVar$ WarElementalX | ConditionSVarCompare$ EQ0
|
||||
SVar:WarElementalX:Count$TotalOppDamageThisTurn
|
||||
SVar:WarElementalX:PlayerCountPropertyYou$DamageToOppsThisTurn
|
||||
T:Mode$ DamageDoneOnce | ValidSource$ Card | ValidTarget$ Opponent | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever an opponent is dealt damage, put that many +1/+1 counters on CARDNAME.
|
||||
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ WarElementalY
|
||||
SVar:WarElementalY:TriggerCount$DamageAmount
|
||||
|
||||
@@ -2,5 +2,5 @@ Name:Wicked Akuba
|
||||
ManaCost:B B
|
||||
Types:Creature Spirit
|
||||
PT:2/2
|
||||
A:AB$ LoseLife | Cost$ B | ValidTgts$ Player.wasDealtDamageThisTurnBy Self | TgtPrompt$ Select target player that was dealt damage this turn | LifeAmount$ 1 | SpellDescription$ Target player dealt damage by CARDNAME this turn loses 1 life.
|
||||
A:AB$ LoseLife | Cost$ B | ValidTgts$ Player.wasDealtDamageThisTurnBySource | TgtPrompt$ Select target player that was dealt damage this turn | LifeAmount$ 1 | SpellDescription$ Target player dealt damage by CARDNAME this turn loses 1 life.
|
||||
Oracle:{B}: Target player dealt damage by Wicked Akuba this turn loses 1 life.
|
||||
|
||||
Reference in New Issue
Block a user