Bioessence Hydra: add new Trigger for counters on many game entities at once

This commit is contained in:
Hans Mackowiak
2019-04-25 01:48:29 +00:00
committed by swordshine
parent f7546eca32
commit f408950aad
51 changed files with 671 additions and 305 deletions

View File

@@ -1601,7 +1601,7 @@ public class ComputerUtilCard {
pumped.addChangedCardKeywords(kws, null, false, false, timestamp); pumped.addChangedCardKeywords(kws, null, false, false, timestamp);
Set<CounterType> types = c.getCounters().keySet(); Set<CounterType> types = c.getCounters().keySet();
for(CounterType ct : types) { for(CounterType ct : types) {
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, true); pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, true, null);
} }
//Copies tap-state and extra keywords (auras, equipment, etc.) //Copies tap-state and extra keywords (auras, equipment, etc.)
if (c.isTapped()) { if (c.isTapped()) {

View File

@@ -1016,7 +1016,7 @@ public abstract class GameState {
String[] allCounterStrings = counterString.split(","); String[] allCounterStrings = counterString.split(",");
for (final String counterPair : allCounterStrings) { for (final String counterPair : allCounterStrings) {
String[] pair = counterPair.split("=", 2); String[] pair = counterPair.split("=", 2);
entity.addCounter(CounterType.valueOf(pair[0]), Integer.parseInt(pair[1]), null, false, false); entity.addCounter(CounterType.valueOf(pair[0]), Integer.parseInt(pair[1]), null, false, false, null);
} }
} }

View File

@@ -6,6 +6,7 @@ import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana; import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GlobalRuleChange; import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.*; import forge.game.card.*;
@@ -354,7 +355,25 @@ public class CountersRemoveAi extends SpellAbilityAi {
*/ */
@Override @Override
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) { public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
// TODO Auto-generated method stub GameEntity target = (GameEntity) params.get("Target");
CounterType type = (CounterType) params.get("CounterType");
if (target instanceof Card) {
Card targetCard = (Card) target;
if (targetCard.getController().isOpponentOf(player)) {
return !ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
} else {
return ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
}
} else if (target instanceof Player) {
Player targetPlayer = (Player) target;
if (targetPlayer.isOpponentOf(player)) {
return !type.equals(CounterType.POISON) ? max : min;
} else {
return type.equals(CounterType.POISON) ? max : min;
}
}
return super.chooseNumber(player, sa, min, max, params); return super.chooseNumber(player, sa, min, max, params);
} }
@@ -370,30 +389,49 @@ public class CountersRemoveAi extends SpellAbilityAi {
return super.chooseCounterType(options, sa, params); return super.chooseCounterType(options, sa, params);
} }
Player ai = sa.getActivatingPlayer(); Player ai = sa.getActivatingPlayer();
Card target = (Card) params.get("Target"); GameEntity target = (GameEntity) params.get("Target");
if (target.getController().isOpponentOf(ai)) { if (target instanceof Card) {
Card targetCard = (Card) target;
if (targetCard.getController().isOpponentOf(ai)) {
// if its a Planeswalker try to remove Loyality first // if its a Planeswalker try to remove Loyality first
if (target.isPlaneswalker()) { if (targetCard.isPlaneswalker()) {
return CounterType.LOYALTY; return CounterType.LOYALTY;
} }
for (CounterType type : options) { for (CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, target)) { if (!ComputerUtil.isNegativeCounter(type, targetCard)) {
return type; return type;
} }
} }
} else { } else {
if (options.contains(CounterType.M1M1) && target.hasKeyword(Keyword.PERSIST)) { if (options.contains(CounterType.M1M1) && targetCard.hasKeyword(Keyword.PERSIST)) {
return CounterType.M1M1;
} else if (options.contains(CounterType.P1P1) && target.hasKeyword(Keyword.UNDYING)) {
return CounterType.M1M1; return CounterType.M1M1;
} else if (options.contains(CounterType.P1P1) && targetCard.hasKeyword(Keyword.UNDYING)) {
return CounterType.P1P1;
} }
for (CounterType type : options) { for (CounterType type : options) {
if (ComputerUtil.isNegativeCounter(type, target)) { if (ComputerUtil.isNegativeCounter(type, targetCard)) {
return type; return type;
} }
} }
} }
} else if (target instanceof Player) {
Player targetPlayer = (Player) target;
if (targetPlayer.isOpponentOf(ai)) {
for (CounterType type : options) {
if (!type.equals(CounterType.POISON)) {
return type;
}
}
} else {
for (CounterType type : options) {
if (type.equals(CounterType.POISON)) {
return type;
}
}
}
}
return super.chooseCounterType(options, sa, params); return super.chooseCounterType(options, sa, params);
} }
} }

View File

@@ -247,7 +247,7 @@ public class GameAction {
// fake etb counters thing, then if something changed, // fake etb counters thing, then if something changed,
// need to apply checkStaticAbilities again // need to apply checkStaticAbilities again
if(!noLandLKI.isLand()) { if(!noLandLKI.isLand()) {
if (noLandLKI.putEtbCounters()) { if (noLandLKI.putEtbCounters(null)) {
// counters are added need to check again // counters are added need to check again
checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList); checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList);
} }
@@ -380,11 +380,34 @@ 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);
}
}
}
// "enter the battlefield as a copy" - apply code here // "enter the battlefield as a copy" - apply code here
// but how to query for input here and continue later while the callers assume synchronous result? // but how to query for input here and continue later while the callers assume synchronous result?
zoneTo.add(copied, position, c); // the modified state of the card is also reported here (e.g. for Morbid + Awaken) zoneTo.add(copied, position, c); // the modified state of the card is also reported here (e.g. for Morbid + Awaken)
c.setZone(zoneTo); c.setZone(zoneTo);
// 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);
}
}
copied.clearEtbCounters();
}
if (fromBattlefield) { if (fromBattlefield) {
c.setDamage(0); //clear damage after a card leaves the battlefield c.setDamage(0); //clear damage after a card leaves the battlefield
c.setHasBeenDealtDeathtouchDamage(false); c.setHasBeenDealtDeathtouchDamage(false);
@@ -400,16 +423,7 @@ public class GameAction {
game.getTriggerHandler().clearInstrinsicActiveTriggers(c, zoneFrom); game.getTriggerHandler().clearInstrinsicActiveTriggers(c, zoneFrom);
game.getTriggerHandler().registerActiveTrigger(lastKnownInfo, false); game.getTriggerHandler().registerActiveTrigger(lastKnownInfo, false);
// do ETB counters after StaticAbilities check table.triggerCountersPutAll(game);
if (!suppress) {
if (toBattlefield) {
if (copied.putEtbCounters()) {
// if counter where put of card, call checkStaticAbilities again
checkStaticAbilities();
}
}
copied.clearEtbCounters();
}
// play the change zone sound // play the change zone sound
game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo)); game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo));

View File

@@ -66,52 +66,52 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
} }
public final int addDamage(final int damage, final Card source, boolean isCombat, boolean noPrevention, public final int addDamage(final int damage, final Card source, boolean isCombat, boolean noPrevention,
final CardDamageMap damageMap, final CardDamageMap preventMap, final SpellAbility cause) { final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
if (noPrevention) { if (noPrevention) {
return addDamageWithoutPrevention(damage, source, damageMap, preventMap, cause); return addDamageWithoutPrevention(damage, source, damageMap, preventMap, counterTable, cause);
} else if (isCombat) { } else if (isCombat) {
return addCombatDamage(damage, source, damageMap, preventMap); return addCombatDamage(damage, source, damageMap, preventMap, counterTable);
} else { } else {
return addDamage(damage, source, damageMap, preventMap, cause); return addDamage(damage, source, damageMap, preventMap, counterTable, cause);
} }
} }
public int addDamage(final int damage, final Card source, final CardDamageMap damageMap, public int addDamage(final int damage, final Card source, final CardDamageMap damageMap,
final CardDamageMap preventMap, final SpellAbility cause) { final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
int damageToDo = damage; int damageToDo = damage;
damageToDo = replaceDamage(damageToDo, source, false, true, damageMap, preventMap, cause); damageToDo = replaceDamage(damageToDo, source, false, true, damageMap, preventMap, counterTable, cause);
damageToDo = preventDamage(damageToDo, source, false, preventMap, cause); damageToDo = preventDamage(damageToDo, source, false, preventMap, cause);
return addDamageAfterPrevention(damageToDo, source, false, damageMap); return addDamageAfterPrevention(damageToDo, source, false, damageMap, counterTable);
} }
public final int addCombatDamage(final int damage, final Card source, final CardDamageMap damageMap, public final int addCombatDamage(final int damage, final Card source, final CardDamageMap damageMap,
final CardDamageMap preventMap) { final CardDamageMap preventMap, GameEntityCounterTable counterTable) {
int damageToDo = damage; int damageToDo = damage;
damageToDo = replaceDamage(damageToDo, source, true, true, damageMap, preventMap, null); damageToDo = replaceDamage(damageToDo, source, true, true, damageMap, preventMap, counterTable, null);
damageToDo = preventDamage(damageToDo, source, true, preventMap, null); damageToDo = preventDamage(damageToDo, source, true, preventMap, null);
if (damageToDo > 0) { if (damageToDo > 0) {
source.getDamageHistory().registerCombatDamage(this); source.getDamageHistory().registerCombatDamage(this);
} }
// damage prevention is already checked // damage prevention is already checked
return addCombatDamageBase(damageToDo, source, damageMap); return addCombatDamageBase(damageToDo, source, damageMap, counterTable);
} }
protected int addCombatDamageBase(final int damage, final Card source, CardDamageMap damageMap) { protected int addCombatDamageBase(final int damage, final Card source, CardDamageMap damageMap, GameEntityCounterTable counterTable) {
return addDamageAfterPrevention(damage, source, true, damageMap); return addDamageAfterPrevention(damage, source, true, damageMap, counterTable);
} }
public int addDamageWithoutPrevention(final int damage, final Card source, final CardDamageMap damageMap, public int addDamageWithoutPrevention(final int damage, final Card source, final CardDamageMap damageMap,
final CardDamageMap preventMap, final SpellAbility cause) { final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
int damageToDo = replaceDamage(damage, source, false, false, damageMap, preventMap, cause); int damageToDo = replaceDamage(damage, source, false, false, damageMap, preventMap, counterTable, cause);
return addDamageAfterPrevention(damageToDo, source, false, damageMap); return addDamageAfterPrevention(damageToDo, source, false, damageMap, counterTable);
} }
public int replaceDamage(final int damage, final Card source, final boolean isCombat, final boolean prevention, public int replaceDamage(final int damage, final Card source, final boolean isCombat, final boolean prevention,
final CardDamageMap damageMap, final CardDamageMap preventMap, final SpellAbility cause) { final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
// Replacement effects // Replacement effects
final Map<String, Object> repParams = Maps.newHashMap(); final Map<String, Object> repParams = Maps.newHashMap();
repParams.put("Event", "DamageDone"); repParams.put("Event", "DamageDone");
@@ -122,6 +122,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
repParams.put("NoPreventDamage", !prevention); repParams.put("NoPreventDamage", !prevention);
repParams.put("DamageMap", damageMap); repParams.put("DamageMap", damageMap);
repParams.put("PreventMap", preventMap); repParams.put("PreventMap", preventMap);
repParams.put("CounterTable", counterTable);
if (cause != null) { if (cause != null) {
repParams.put("Cause", cause); repParams.put("Cause", cause);
} }
@@ -139,7 +140,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
if (prevention) { if (prevention) {
newDamage = newTarget.preventDamage(newDamage, source, isCombat, preventMap, cause); newDamage = newTarget.preventDamage(newDamage, source, isCombat, preventMap, cause);
} }
newTarget.addDamageAfterPrevention(newDamage, source, isCombat, damageMap); newTarget.addDamageAfterPrevention(newDamage, source, isCombat, damageMap, counterTable);
} }
default: default:
return 0; return 0;
@@ -147,7 +148,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
} }
// This function handles damage after replacement and prevention effects are applied // This function handles damage after replacement and prevention effects are applied
public abstract int addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat, CardDamageMap damageMap); public abstract int addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat, CardDamageMap damageMap, GameEntityCounterTable counterTable);
// This should be also usable by the AI to forecast an effect (so it must // This should be also usable by the AI to forecast an effect (so it must
// not change the game state) // not change the game state)
@@ -470,7 +471,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
abstract public void setCounters(final Map<CounterType, Integer> allCounters); abstract public void setCounters(final Map<CounterType, Integer> allCounters);
abstract public boolean canReceiveCounters(final CounterType type); abstract public boolean canReceiveCounters(final CounterType type);
abstract public int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier, final boolean fireEvents); abstract public int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier, final boolean fireEvents, GameEntityCounterTable table);
abstract public void subtractCounter(final CounterType counterName, final int n); abstract public void subtractCounter(final CounterType counterName, final int n);
abstract public void clearCounters(); abstract public void clearCounters();

View File

@@ -0,0 +1,55 @@
package forge.game;
import java.util.Map;
import com.google.common.collect.ForwardingTable;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
public class GameEntityCounterTable extends ForwardingTable<GameEntity, CounterType, Integer> {
private Table<GameEntity, CounterType, Integer> dataMap = HashBasedTable.create();
/*
* (non-Javadoc)
* @see com.google.common.collect.ForwardingTable#delegate()
*/
@Override
protected Table<GameEntity, CounterType, Integer> delegate() {
return dataMap;
}
/* (non-Javadoc)
* @see com.google.common.collect.ForwardingTable#put(java.lang.Object, java.lang.Object, java.lang.Object)
*/
@Override
public Integer put(GameEntity rowKey, CounterType columnKey, Integer value) {
Integer old = contains(rowKey, columnKey) ? get(rowKey, columnKey) : 0;
return super.put(rowKey, columnKey, old + value);
}
public Map<GameEntity, Integer> filterTable(CounterType type, String valid, Card host, SpellAbility sa) {
Map<GameEntity, Integer> result = Maps.newHashMap();
for (Map.Entry<GameEntity, Integer> e : column(type).entrySet()) {
if (e.getKey().isValid(valid, host.getController(), host, sa)) {
result.put(e.getKey(), e.getValue());
}
}
return result;
}
public void triggerCountersPutAll(final Game game) {
if (!isEmpty()) {
final Map<String, Object> runParams = Maps.newHashMap();
runParams.put("Objects", this);
game.getTriggerHandler().runTrigger(TriggerType.CounterAddedAll, runParams, false);
}
}
}

View File

@@ -1,6 +1,7 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -62,14 +63,16 @@ public class AmassEffect extends SpellAbilityEffect {
CardCollectionView tgtCards = CardLists.getType(activator.getCardsIn(ZoneType.Battlefield), "Army"); CardCollectionView tgtCards = CardLists.getType(activator.getCardsIn(ZoneType.Battlefield), "Army");
tgtCards = pc.chooseCardsForEffect(tgtCards, sa, "Choose an army to put counters on", 1, 1, false); tgtCards = pc.chooseCardsForEffect(tgtCards, sa, "Choose an army to put counters on", 1, 1, false);
GameEntityCounterTable table = new GameEntityCounterTable();
for(final Card tgtCard : tgtCards) { for(final Card tgtCard : tgtCards) {
tgtCard.addCounter(CounterType.P1P1, amount, activator, true); tgtCard.addCounter(CounterType.P1P1, amount, activator, true, table);
game.updateLastStateForCard(tgtCard); game.updateLastStateForCard(tgtCard);
if (remember) { if (remember) {
card.addRemembered(tgtCard); card.addRemembered(tgtCard);
} }
} }
table.triggerCountersPutAll(game);
} }
} }

View File

@@ -1,6 +1,7 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -78,6 +79,8 @@ public class CountersMoveEffect extends SpellAbilityEffect {
} }
} }
GameEntityCounterTable table = new GameEntityCounterTable();
// uses for multi sources -> one defined/target // uses for multi sources -> one defined/target
// this needs given counter type // this needs given counter type
if (sa.hasParam("ValidSource")) { if (sa.hasParam("ValidSource")) {
@@ -146,8 +149,9 @@ public class CountersMoveEffect extends SpellAbilityEffect {
} }
if (csum > 0) { if (csum > 0) {
dest.addCounter(cType, csum, player, true); dest.addCounter(cType, csum, player, true, table);
game.updateLastStateForCard(dest); game.updateLastStateForCard(dest);
table.triggerCountersPutAll(game);
} }
return; return;
} else if (sa.hasParam("ValidDefined")) { } else if (sa.hasParam("ValidDefined")) {
@@ -202,7 +206,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
if (cnum > 0) { if (cnum > 0) {
source.subtractCounter(cType, cnum); source.subtractCounter(cType, cnum);
cur.addCounter(cType, cnum, player, true); cur.addCounter(cType, cnum, player, true, table);
game.updateLastStateForCard(cur); game.updateLastStateForCard(cur);
updateSource = true; updateSource = true;
} }
@@ -210,6 +214,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
if (updateSource) { if (updateSource) {
// update source // update source
game.updateLastStateForCard(source); game.updateLastStateForCard(source);
table.triggerCountersPutAll(game);
} }
return; return;
} }
@@ -263,7 +268,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
if (source.getCounters(cType) >= cntToMove) { if (source.getCounters(cType) >= cntToMove) {
source.subtractCounter(cType, cntToMove); source.subtractCounter(cType, cntToMove);
cur.addCounter(cType, cntToMove, player, true); cur.addCounter(cType, cntToMove, player, true, table);
game.updateLastStateForCard(cur); game.updateLastStateForCard(cur);
} }
} else { } else {
@@ -297,7 +302,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
sa, sb.toString(), 0, Math.min(tgtCounters.get(chosenType), cntToMove), params); sa, sb.toString(), 0, Math.min(tgtCounters.get(chosenType), cntToMove), params);
if (chosenAmount > 0) { if (chosenAmount > 0) {
dest.addCounter(chosenType, chosenAmount, player, true); dest.addCounter(chosenType, chosenAmount, player, true, table);
source.subtractCounter(chosenType, chosenAmount); source.subtractCounter(chosenType, chosenAmount);
game.updateLastStateForCard(dest); game.updateLastStateForCard(dest);
cntToMove -= chosenAmount; cntToMove -= chosenAmount;
@@ -307,5 +312,6 @@ public class CountersMoveEffect extends SpellAbilityEffect {
} }
// update source // update source
game.updateLastStateForCard(source); game.updateLastStateForCard(source);
table.triggerCountersPutAll(game);
} // moveCounterResolve } // moveCounterResolve
} }

View File

@@ -3,6 +3,7 @@ package forge.game.ability.effects;
import java.util.Map; import java.util.Map;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -43,6 +44,7 @@ public class CountersMultiplyEffect extends SpellAbilityEffect {
final CounterType counterType = getCounterType(sa); final CounterType counterType = getCounterType(sa);
final int n = Integer.valueOf(sa.getParamOrDefault("Multiplier", "2")) - 1; final int n = Integer.valueOf(sa.getParamOrDefault("Multiplier", "2")) - 1;
GameEntityCounterTable table = new GameEntityCounterTable();
for (final Card tgtCard : getTargetCards(sa)) { for (final Card tgtCard : getTargetCards(sa)) {
Card gameCard = game.getCardState(tgtCard, null); Card gameCard = game.getCardState(tgtCard, null);
// gameCard is LKI in that case, the card is not in game anymore // gameCard is LKI in that case, the card is not in game anymore
@@ -52,14 +54,15 @@ public class CountersMultiplyEffect extends SpellAbilityEffect {
continue; continue;
} }
if (counterType != null) { if (counterType != null) {
gameCard.addCounter(counterType, gameCard.getCounters(counterType) * n, player, true); gameCard.addCounter(counterType, gameCard.getCounters(counterType) * n, player, true, table);
} else { } else {
for (Map.Entry<CounterType, Integer> e : gameCard.getCounters().entrySet()) { for (Map.Entry<CounterType, Integer> e : gameCard.getCounters().entrySet()) {
gameCard.addCounter(e.getKey(), e.getValue() * n, player, true); gameCard.addCounter(e.getKey(), e.getValue() * n, player, true, table);
} }
} }
game.updateLastStateForCard(gameCard); game.updateLastStateForCard(gameCard);
} }
table.triggerCountersPutAll(game);
} }

View File

@@ -2,9 +2,10 @@ package forge.game.ability.effects;
import java.util.Map.Entry; import java.util.Map.Entry;
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -20,16 +21,19 @@ public class CountersNoteEffect extends SpellAbilityEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
Card source = sa.getHostCard(); Card source = sa.getHostCard();
CardCollection tgtCards = new CardCollection(); final Game game = source.getGame();
tgtCards.addAll(getDefinedCardsOrTargeted(sa)); Player p = sa.getActivatingPlayer();
String mode = sa.getParamOrDefault("Mode", "Load"); String mode = sa.getParamOrDefault("Mode", "Load");
for (Card c : tgtCards) {
GameEntityCounterTable table = new GameEntityCounterTable();
for (Card c : getDefinedCardsOrTargeted(sa)) {
if (mode.equals(MODE_STORE)) { if (mode.equals(MODE_STORE)) {
noteCounters(c, source); noteCounters(c, source);
} else if (mode.equals(MODE_LOAD)) { } else if (mode.equals(MODE_LOAD)) {
loadCounters(c, source, sa.getActivatingPlayer()); loadCounters(c, source, p, table);
} }
} }
table.triggerCountersPutAll(game);
} }
private void noteCounters(Card notee, Card source) { private void noteCounters(Card notee, Card source) {
@@ -40,11 +44,13 @@ public class CountersNoteEffect extends SpellAbilityEffect {
} }
} }
private void loadCounters(Card notee, Card source, final Player p) { private void loadCounters(Card notee, Card source, final Player p, GameEntityCounterTable table) {
for(Entry<String, String> svar : source.getSVars().entrySet()) { for(Entry<String, String> svar : source.getSVars().entrySet()) {
String key = svar.getKey(); String key = svar.getKey();
if (key.startsWith(NOTE_COUNTERS)) { if (key.startsWith(NOTE_COUNTERS)) {
notee.addCounter(CounterType.getType(key.substring(NOTE_COUNTERS.length())), Integer.parseInt(svar.getValue()), p, false); notee.addCounter(
CounterType.getType(key.substring(NOTE_COUNTERS.length())),
Integer.parseInt(svar.getValue()), p, false, table);
} }
// TODO Probably should "remove" the svars that were temporarily used // TODO Probably should "remove" the svars that were temporarily used
} }

View File

@@ -2,6 +2,7 @@ package forge.game.ability.effects;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardLists; import forge.game.card.CardLists;
@@ -43,14 +44,16 @@ public class CountersProliferateEffect extends SpellAbilityEffect {
List<GameEntity> result = pc.chooseEntitiesForEffect(list, 0, list.size(), null, sa, List<GameEntity> result = pc.chooseEntitiesForEffect(list, 0, list.size(), null, sa,
"Choose any number of permanents and/or players for proliferate", p); "Choose any number of permanents and/or players for proliferate", p);
GameEntityCounterTable table = new GameEntityCounterTable();
for (final GameEntity ge : result) { for (final GameEntity ge : result) {
for (final CounterType ct : ge.getCounters().keySet()) { for (final CounterType ct : ge.getCounters().keySet()) {
ge.addCounter(ct, 1, p, true, true); ge.addCounter(ct, 1, p, true, true, table);
} }
if (ge instanceof Card) { if (ge instanceof Card) {
Card c = (Card) ge; Card c = (Card) ge;
game.updateLastStateForCard(c); game.updateLastStateForCard(c);
} }
} }
table.triggerCountersPutAll(game);
} }
} }

View File

@@ -1,6 +1,7 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -63,15 +64,13 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
placer = AbilityUtils.getDefinedPlayers(host, pstr, sa).get(0); placer = AbilityUtils.getDefinedPlayers(host, pstr, sa).get(0);
} }
GameEntityCounterTable table = new GameEntityCounterTable();
for (final Card tgtCard : cards) { for (final Card tgtCard : cards) {
if (game.getZoneOf(tgtCard).is(ZoneType.Battlefield)) { boolean inBattlefield = game.getZoneOf(tgtCard).is(ZoneType.Battlefield);
tgtCard.addCounter(CounterType.valueOf(type), counterAmount, placer, true); tgtCard.addCounter(CounterType.valueOf(type), counterAmount, placer, inBattlefield, table);
} else {
// adding counters to something like re-suspend cards
tgtCard.addCounter(CounterType.valueOf(type), counterAmount, placer, false);
}
game.updateLastStateForCard(tgtCard); game.updateLastStateForCard(tgtCard);
} }
table.triggerCountersPutAll(game);
} }
} }

View File

@@ -6,6 +6,7 @@ import com.google.common.collect.Sets;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
@@ -133,6 +134,8 @@ public class CountersPutEffect extends SpellAbilityEffect {
tgtObjects.addAll(getDefinedOrTargeted(sa, "Defined")); tgtObjects.addAll(getDefinedOrTargeted(sa, "Defined"));
} }
GameEntityCounterTable table = new GameEntityCounterTable();
for (final GameObject obj : tgtObjects) { for (final GameObject obj : tgtObjects) {
// check if the object is still in game or if it was moved // check if the object is still in game or if it was moved
Card gameCard = null; Card gameCard = null;
@@ -162,10 +165,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
if (eachExistingCounter) { if (eachExistingCounter) {
for(CounterType ct : choices) { for(CounterType ct : choices) {
if (obj instanceof Player) { if (obj instanceof Player) {
((Player) obj).addCounter(ct, counterAmount, placer, true); ((Player) obj).addCounter(ct, counterAmount, placer, true, table);
} }
if (obj instanceof Card) { if (obj instanceof Card) {
gameCard.addCounter(ct, counterAmount, placer, true); gameCard.addCounter(ct, counterAmount, placer, true, table);
} }
} }
continue; continue;
@@ -249,7 +252,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
if (etbcounter) { if (etbcounter) {
tgtCard.addEtbCounter(counterType, counterAmount, placer); tgtCard.addEtbCounter(counterType, counterAmount, placer);
} else { } else {
tgtCard.addCounter(counterType, counterAmount, placer, true); tgtCard.addCounter(counterType, counterAmount, placer, true, table);
} }
if (remember) { if (remember) {
final int value = tgtCard.getTotalCountersToAdd(); final int value = tgtCard.getTotalCountersToAdd();
@@ -287,7 +290,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
if (etbcounter) { if (etbcounter) {
tgtCard.addEtbCounter(counterType, counterAmount, placer); tgtCard.addEtbCounter(counterType, counterAmount, placer);
} else { } else {
tgtCard.addCounter(counterType, counterAmount, placer, false); tgtCard.addCounter(counterType, counterAmount, placer, false, table);
} }
} }
game.updateLastStateForCard(tgtCard); game.updateLastStateForCard(tgtCard);
@@ -295,9 +298,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
} else if (obj instanceof Player) { } else if (obj instanceof Player) {
// Add Counters to players! // Add Counters to players!
Player pl = (Player) obj; Player pl = (Player) obj;
pl.addCounter(counterType, counterAmount, placer, true); pl.addCounter(counterType, counterAmount, placer, true, table);
} }
} }
table.triggerCountersPutAll(game);
} }
} }

View File

@@ -1,6 +1,7 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -57,6 +58,8 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
ctype = CounterType.valueOf(sa.getParam("CounterType")); ctype = CounterType.valueOf(sa.getParam("CounterType"));
} }
GameEntityCounterTable table = new GameEntityCounterTable();
for (final Card tgtCard : getDefinedCardsOrTargeted(sa)) { for (final Card tgtCard : getDefinedCardsOrTargeted(sa)) {
Card gameCard = game.getCardState(tgtCard, null); Card gameCard = game.getCardState(tgtCard, null);
// gameCard is LKI in that case, the card is not in game anymore // gameCard is LKI in that case, the card is not in game anymore
@@ -69,19 +72,20 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
if (gameCard.hasCounters()) { if (gameCard.hasCounters()) {
if (sa.hasParam("EachExistingCounter")) { if (sa.hasParam("EachExistingCounter")) {
for (CounterType listType : Lists.newArrayList(gameCard.getCounters().keySet())) { for (CounterType listType : Lists.newArrayList(gameCard.getCounters().keySet())) {
addOrRemoveCounter(sa, gameCard, listType, counterAmount); addOrRemoveCounter(sa, gameCard, listType, counterAmount, table);
} }
} else { } else {
addOrRemoveCounter(sa, gameCard, ctype, counterAmount); addOrRemoveCounter(sa, gameCard, ctype, counterAmount, table);
} }
game.updateLastStateForCard(gameCard); game.updateLastStateForCard(gameCard);
} }
} }
} }
table.triggerCountersPutAll(game);
} }
private void addOrRemoveCounter(final SpellAbility sa, final Card tgtCard, CounterType ctype, private void addOrRemoveCounter(final SpellAbility sa, final Card tgtCard, CounterType ctype,
final int counterAmount) { final int counterAmount, GameEntityCounterTable table) {
final Player pl = sa.getActivatingPlayer(); final Player pl = sa.getActivatingPlayer();
final PlayerController pc = pl.getController(); final PlayerController pc = pl.getController();
@@ -106,7 +110,7 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
boolean apply = zone == null || zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Stack); boolean apply = zone == null || zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Stack);
tgtCard.addCounter(chosenType, counterAmount, pl, apply); tgtCard.addCounter(chosenType, counterAmount, pl, apply, table);
} else { } else {
tgtCard.subtractCounter(chosenType, counterAmount); tgtCard.subtractCounter(chosenType, counterAmount);
} }

View File

@@ -1,6 +1,7 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -106,12 +107,22 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
for (final Player tgtPlayer : getTargetPlayers(sa)) { for (final Player tgtPlayer : getTargetPlayers(sa)) {
// Removing energy // Removing energy
if (!sa.usesTargeting() || tgtPlayer.canBeTargetedBy(sa)) { if (!sa.usesTargeting() || tgtPlayer.canBeTargetedBy(sa)) {
if (type.equals("All")) {
for (Map.Entry<CounterType, Integer> e : tgtPlayer.getCounters().entrySet()) {
tgtPlayer.subtractCounter(e.getKey(), e.getValue());
}
} else {
if (num.equals("All")) { if (num.equals("All")) {
cntToRemove = tgtPlayer.getCounters(counterType); cntToRemove = tgtPlayer.getCounters(counterType);
} }
if (type.equals("Any")) {
removeAnyType(tgtPlayer, cntToRemove, sa);
} else {
tgtPlayer.subtractCounter(counterType, cntToRemove); tgtPlayer.subtractCounter(counterType, cntToRemove);
} }
} }
}
}
CardCollectionView srcCards = null; CardCollectionView srcCards = null;
if (sa.hasParam("ValidSource")) { if (sa.hasParam("ValidSource")) {
@@ -152,32 +163,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
} }
if (type.equals("Any")) { if (type.equals("Any")) {
while (cntToRemove > 0 && gameCard.hasCounters()) { removeAnyType(gameCard, cntToRemove, sa);
final Map<CounterType, Integer> tgtCounters = gameCard.getCounters();
Map<String, Object> params = Maps.newHashMap();
params.put("Target", gameCard);
String prompt = "Select type of counters to remove";
CounterType chosenType = pc.chooseCounterType(
ImmutableList.copyOf(tgtCounters.keySet()), sa, prompt, params);
prompt = "Select the number of " + chosenType.getName() + " counters to remove";
int max = Math.min(cntToRemove, tgtCounters.get(chosenType));
params = Maps.newHashMap();
params.put("Target", gameCard);
params.put("CounterType", chosenType);
int chosenAmount = pc.chooseNumber(sa, prompt, 1, max, params);
if (chosenAmount > 0) {
gameCard.subtractCounter(chosenType, chosenAmount);
game.updateLastStateForCard(gameCard);
if (rememberRemoved) {
for (int i = 0; i < chosenAmount; i++) {
card.addRemembered(Pair.of(chosenType, i));
}
}
cntToRemove -= chosenAmount;
}
}
} else { } else {
cntToRemove = Math.min(cntToRemove, gameCard.getCounters(counterType)); cntToRemove = Math.min(cntToRemove, gameCard.getCounters(counterType));
@@ -212,4 +198,47 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
} }
} }
protected void removeAnyType(GameEntity entity, int cntToRemove, SpellAbility sa) {
boolean rememberRemoved = sa.hasParam("RememberRemoved");
final Card card = sa.getHostCard();
final Game game = card.getGame();
final Player player = sa.getActivatingPlayer();
PlayerController pc = player.getController();
while (cntToRemove > 0 && entity.hasCounters()) {
final Map<CounterType, Integer> tgtCounters = entity.getCounters();
Map<String, Object> params = Maps.newHashMap();
params.put("Target", entity);
String prompt = "Select type of counters to remove";
CounterType chosenType = pc.chooseCounterType(
ImmutableList.copyOf(tgtCounters.keySet()), sa, prompt, params);
prompt = "Select the number of " + chosenType.getName() + " counters to remove";
int max = Math.min(cntToRemove, tgtCounters.get(chosenType));
params = Maps.newHashMap();
params.put("Target", entity);
params.put("CounterType", chosenType);
int chosenAmount = pc.chooseNumber(sa, prompt, sa.hasParam("UpTo") ? 0 : 1, max, params);
if (chosenAmount > 0) {
entity.subtractCounter(chosenType, chosenAmount);
if (entity instanceof Card) {
Card gameCard = (Card) entity;
game.updateLastStateForCard(gameCard);
}
if (rememberRemoved) {
for (int i = 0; i < chosenAmount; i++) {
card.addRemembered(Pair.of(chosenType, i));
}
}
cntToRemove -= chosenAmount;
} else if (sa.hasParam("UpTo")) {
break;
}
}
}
} }

View File

@@ -2,6 +2,7 @@ package forge.game.ability.effects;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
@@ -88,6 +89,7 @@ public class DamageAllEffect extends DamageBaseEffect {
boolean usedDamageMap = true; boolean usedDamageMap = true;
CardDamageMap damageMap = sa.getDamageMap(); CardDamageMap damageMap = sa.getDamageMap();
CardDamageMap preventMap = sa.getPreventMap(); CardDamageMap preventMap = sa.getPreventMap();
GameEntityCounterTable counterTable = new GameEntityCounterTable();
if (damageMap == null) { if (damageMap == null) {
// make a new damage map // make a new damage map
@@ -97,13 +99,13 @@ public class DamageAllEffect extends DamageBaseEffect {
} }
for (final Card c : list) { for (final Card c : list) {
c.addDamage(dmg, sourceLKI, damageMap, preventMap, sa); c.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
} }
if (!players.equals("")) { if (!players.equals("")) {
final List<Player> playerList = AbilityUtils.getDefinedPlayers(card, players, sa); final List<Player> playerList = AbilityUtils.getDefinedPlayers(card, players, sa);
for (final Player p : playerList) { for (final Player p : playerList) {
p.addDamage(dmg, sourceLKI, damageMap, preventMap, sa); p.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
} }
} }
@@ -126,6 +128,8 @@ public class DamageAllEffect extends DamageBaseEffect {
damageMap.clear(); damageMap.clear();
} }
counterTable.triggerCountersPutAll(game);
replaceDying(sa); replaceDying(sa);
} }
} }

View File

@@ -3,6 +3,7 @@ package forge.game.ability.effects;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
@@ -119,6 +120,7 @@ public class DamageDealEffect extends DamageBaseEffect {
boolean usedDamageMap = true; boolean usedDamageMap = true;
CardDamageMap damageMap = sa.getDamageMap(); CardDamageMap damageMap = sa.getDamageMap();
CardDamageMap preventMap = sa.getPreventMap(); CardDamageMap preventMap = sa.getPreventMap();
GameEntityCounterTable counterTable = new GameEntityCounterTable();
if (damageMap == null) { if (damageMap == null) {
// make a new damage map // make a new damage map
@@ -159,7 +161,7 @@ public class DamageDealEffect extends DamageBaseEffect {
Player assigningPlayer = players.get(0); Player assigningPlayer = players.get(0);
Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(sourceLKI, assigneeCards, dmg, null, true); Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(sourceLKI, assigneeCards, dmg, null, true);
for (Entry<Card, Integer> dt : map.entrySet()) { for (Entry<Card, Integer> dt : map.entrySet()) {
dt.getKey().addDamage(dt.getValue(), sourceLKI, damageMap, preventMap, sa); dt.getKey().addDamage(dt.getValue(), sourceLKI, damageMap, preventMap, counterTable, sa);
} }
if (!usedDamageMap) { if (!usedDamageMap) {
@@ -170,6 +172,8 @@ public class DamageDealEffect extends DamageBaseEffect {
preventMap.clear(); preventMap.clear();
damageMap.clear(); damageMap.clear();
} }
counterTable.triggerCountersPutAll(game);
replaceDying(sa); replaceDying(sa);
return; return;
} }
@@ -194,13 +198,13 @@ public class DamageDealEffect extends DamageBaseEffect {
c.clearAssignedDamage(); c.clearAssignedDamage();
} }
else { else {
c.addDamage(dmg, sourceLKI, false, noPrevention, damageMap, preventMap, sa); c.addDamage(dmg, sourceLKI, false, noPrevention, damageMap, preventMap, counterTable, sa);
} }
} }
} else if (o instanceof Player) { } else if (o instanceof Player) {
final Player p = (Player) o; final Player p = (Player) o;
if (!targeted || p.canBeTargetedBy(sa)) { if (!targeted || p.canBeTargetedBy(sa)) {
p.addDamage(dmg, sourceLKI, false, noPrevention, damageMap, preventMap, sa); p.addDamage(dmg, sourceLKI, false, noPrevention, damageMap, preventMap, counterTable, sa);
} }
} }
} }
@@ -217,6 +221,7 @@ public class DamageDealEffect extends DamageBaseEffect {
preventMap.clear(); preventMap.clear();
damageMap.clear(); damageMap.clear();
} }
counterTable.triggerCountersPutAll(game);
replaceDying(sa); replaceDying(sa);
} }
} }

View File

@@ -1,5 +1,7 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
@@ -60,8 +62,9 @@ public class DamageEachEffect extends DamageBaseEffect {
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
final Game game = card.getGame();
FCollectionView<Card> sources = card.getGame().getCardsIn(ZoneType.Battlefield); FCollectionView<Card> sources = game.getCardsIn(ZoneType.Battlefield);
if (sa.hasParam("ValidCards")) { if (sa.hasParam("ValidCards")) {
sources = CardLists.getValidCards(sources, sa.getParam("ValidCards"), card.getController(), card); sources = CardLists.getValidCards(sources, sa.getParam("ValidCards"), card.getController(), card);
} }
@@ -73,6 +76,7 @@ public class DamageEachEffect extends DamageBaseEffect {
boolean usedDamageMap = true; boolean usedDamageMap = true;
CardDamageMap damageMap = sa.getDamageMap(); CardDamageMap damageMap = sa.getDamageMap();
CardDamageMap preventMap = sa.getPreventMap(); CardDamageMap preventMap = sa.getPreventMap();
GameEntityCounterTable counterTable = new GameEntityCounterTable();
if (damageMap == null) { if (damageMap == null) {
// make a new damage map // make a new damage map
@@ -83,21 +87,22 @@ public class DamageEachEffect extends DamageBaseEffect {
for (final Object o : tgts) { for (final Object o : tgts) {
for (final Card source : sources) { for (final Card source : sources) {
final Card sourceLKI = source.getGame().getChangeZoneLKIInfo(source); final Card sourceLKI = game.getChangeZoneLKIInfo(source);
// TODO shouldn't that be using Num or something first?
final int dmg = CardFactoryUtil.xCount(source, sa.getSVar("X")); final int dmg = CardFactoryUtil.xCount(source, sa.getSVar("X"));
// System.out.println(source+" deals "+dmg+" damage to "+o.toString()); // System.out.println(source+" deals "+dmg+" damage to "+o.toString());
if (o instanceof Card) { if (o instanceof Card) {
final Card c = (Card) o; final Card c = (Card) o;
if (c.isInPlay() && (!targeted || c.canBeTargetedBy(sa))) { if (c.isInPlay() && (!targeted || c.canBeTargetedBy(sa))) {
c.addDamage(dmg, sourceLKI, damageMap, preventMap, sa); c.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
} }
} else if (o instanceof Player) { } else if (o instanceof Player) {
final Player p = (Player) o; final Player p = (Player) o;
if (!targeted || p.canBeTargetedBy(sa)) { if (!targeted || p.canBeTargetedBy(sa)) {
p.addDamage(dmg, sourceLKI, damageMap, preventMap, sa); p.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
} }
} }
} }
@@ -106,11 +111,11 @@ public class DamageEachEffect extends DamageBaseEffect {
if (sa.hasParam("DefinedCards")) { if (sa.hasParam("DefinedCards")) {
if (sa.getParam("DefinedCards").equals("Self")) { if (sa.getParam("DefinedCards").equals("Self")) {
for (final Card source : sources) { for (final Card source : sources) {
final Card sourceLKI = source.getGame().getChangeZoneLKIInfo(source); final Card sourceLKI = game.getChangeZoneLKIInfo(source);
final int dmg = CardFactoryUtil.xCount(source, card.getSVar("X")); final int dmg = CardFactoryUtil.xCount(source, card.getSVar("X"));
// System.out.println(source+" deals "+dmg+" damage to "+source); // System.out.println(source+" deals "+dmg+" damage to "+source);
source.addDamage(dmg, sourceLKI, damageMap, preventMap, sa); source.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
} }
} }
if (sa.getParam("DefinedCards").equals("Remembered")) { if (sa.getParam("DefinedCards").equals("Remembered")) {
@@ -122,7 +127,7 @@ public class DamageEachEffect extends DamageBaseEffect {
if (o instanceof Card) { if (o instanceof Card) {
Card rememberedcard = (Card) o; Card rememberedcard = (Card) o;
// System.out.println(source + " deals " + dmg + " damage to " + rememberedcard); // System.out.println(source + " deals " + dmg + " damage to " + rememberedcard);
rememberedcard.addDamage(dmg, sourceLKI, damageMap, preventMap, sa); rememberedcard.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
} }
} }
} }
@@ -137,6 +142,8 @@ public class DamageEachEffect extends DamageBaseEffect {
damageMap.clear(); damageMap.clear();
} }
counterTable.triggerCountersPutAll(game);
replaceDying(sa); replaceDying(sa);
} }
} }

View File

@@ -2,6 +2,7 @@ package forge.game.ability.effects;
import forge.game.Game; import forge.game.Game;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -110,6 +111,7 @@ public class DigEffect extends SpellAbilityEffect {
} }
CardZoneTable table = new CardZoneTable(); CardZoneTable table = new CardZoneTable();
GameEntityCounterTable counterTable = new GameEntityCounterTable();
for (final Player p : tgtPlayers) { for (final Player p : tgtPlayers) {
if (tgt != null && !p.canBeTargetedBy(sa)) { if (tgt != null && !p.canBeTargetedBy(sa)) {
continue; continue;
@@ -378,7 +380,8 @@ public class DigEffect extends SpellAbilityEffect {
} }
} else if (destZone2 == ZoneType.Exile) { } else if (destZone2 == ZoneType.Exile) {
if (sa.hasParam("ExileWithCounter")) { if (sa.hasParam("ExileWithCounter")) {
c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), 1, player, true); c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")),
1, player, true, counterTable);
} }
c.setExiledWith(effectHost); c.setExiledWith(effectHost);
} }
@@ -389,6 +392,7 @@ public class DigEffect extends SpellAbilityEffect {
} }
//table trigger there //table trigger there
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game);
counterTable.triggerCountersPutAll(game);
} }
// TODO This should be somewhere else, maybe like CardUtil or something like that // TODO This should be somewhere else, maybe like CardUtil or something like that

View File

@@ -3,6 +3,7 @@ package forge.game.ability.effects;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
@@ -47,9 +48,9 @@ public class ExploreEffect extends SpellAbilityEffect {
final Player pl = sa.getActivatingPlayer(); final Player pl = sa.getActivatingPlayer();
final PlayerController pc = pl.getController(); final PlayerController pc = pl.getController();
final Game game = pl.getGame(); final Game game = pl.getGame();
List<Card> tgt = getTargetCards(sa);
for (final Card c : tgt) { GameEntityCounterTable table = new GameEntityCounterTable();
for (final Card c : getTargetCards(sa)) {
// revealed land card // revealed land card
boolean revealedLand = false; boolean revealedLand = false;
CardCollection top = pl.getTopXCardsFromLibrary(1); CardCollection top = pl.getTopXCardsFromLibrary(1);
@@ -76,8 +77,8 @@ public class ExploreEffect extends SpellAbilityEffect {
Card gamec = game.getCardState(c); Card gamec = game.getCardState(c);
// if the card is not more in the game anymore // if the card is not more in the game anymore
// this might still return true but its no problem // this might still return true but its no problem
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.getTimestamp() == c.getTimestamp()) { if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(c)) {
c.addCounter(CounterType.P1P1, 1, pl, true); c.addCounter(CounterType.P1P1, 1, pl, true, table);
} }
} }
@@ -86,6 +87,7 @@ public class ExploreEffect extends SpellAbilityEffect {
runParams.put("Card", c); runParams.put("Card", c);
game.getTriggerHandler().runTrigger(TriggerType.Explores, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.Explores, runParams, false);
} }
table.triggerCountersPutAll(game);
} }
} }

View File

@@ -4,6 +4,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardDamageMap; import forge.game.card.CardDamageMap;
@@ -130,6 +131,7 @@ public class FightEffect extends DamageBaseEffect {
boolean usedDamageMap = true; boolean usedDamageMap = true;
CardDamageMap damageMap = sa.getDamageMap(); CardDamageMap damageMap = sa.getDamageMap();
CardDamageMap preventMap = sa.getPreventMap(); CardDamageMap preventMap = sa.getPreventMap();
GameEntityCounterTable counterTable = new GameEntityCounterTable();
if (damageMap == null) { if (damageMap == null) {
// make a new damage map // make a new damage map
@@ -142,12 +144,12 @@ public class FightEffect extends DamageBaseEffect {
final int dmg1 = fightToughness ? fighterA.getNetToughness() : fighterA.getNetPower(); final int dmg1 = fightToughness ? fighterA.getNetToughness() : fighterA.getNetPower();
if (fighterA.equals(fighterB)) { if (fighterA.equals(fighterB)) {
fighterA.addDamage(dmg1 * 2, fighterA, damageMap, preventMap, sa); fighterA.addDamage(dmg1 * 2, fighterA, damageMap, preventMap, counterTable, sa);
} else { } else {
final int dmg2 = fightToughness ? fighterB.getNetToughness() : fighterB.getNetPower(); final int dmg2 = fightToughness ? fighterB.getNetToughness() : fighterB.getNetPower();
fighterB.addDamage(dmg1, fighterA, damageMap, preventMap, sa); fighterB.addDamage(dmg1, fighterA, damageMap, preventMap, counterTable, sa);
fighterA.addDamage(dmg2, fighterB, damageMap, preventMap, sa); fighterA.addDamage(dmg2, fighterB, damageMap, preventMap, counterTable, sa);
} }
if (!usedDamageMap) { if (!usedDamageMap) {
@@ -157,6 +159,7 @@ public class FightEffect extends DamageBaseEffect {
preventMap.clear(); preventMap.clear();
damageMap.clear(); damageMap.clear();
} }
counterTable.triggerCountersPutAll(sa.getHostCard().getGame());
replaceDying(sa); replaceDying(sa);
} }

View File

@@ -1,7 +1,10 @@
package forge.game.ability.effects; package forge.game.ability.effects;
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -21,17 +24,21 @@ import java.util.List;
*/ */
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Num"), sa); final Card host = sa.getHostCard();
final Game game = host.getGame();
final int amount = AbilityUtils.calculateAmount(host, sa.getParam("Num"), sa);
GameEntityCounterTable table = new GameEntityCounterTable();
for (final Player p : getTargetPlayers(sa)) { for (final Player p : getTargetPlayers(sa)) {
if ((!sa.usesTargeting()) || p.canBeTargetedBy(sa)) { if ((!sa.usesTargeting()) || p.canBeTargetedBy(sa)) {
if (amount >= 0) { if (amount >= 0) {
p.addPoisonCounters(amount, sa.getHostCard()); p.addPoisonCounters(amount, host, table);
} else { } else {
p.removePoisonCounters(-amount, sa.getHostCard()); p.removePoisonCounters(-amount, host);
} }
} }
} }
table.triggerCountersPutAll(game);
} }
/* (non-Javadoc) /* (non-Javadoc)

View File

@@ -33,7 +33,7 @@ public class PumpEffect extends SpellAbilityEffect {
final Game game = host.getGame(); final Game game = host.getGame();
//if host is not on the battlefield don't apply //if host is not on the battlefield don't apply
// Suspend should does Affect the Stack // Suspend should does Affect the Stack
if (sa.hasParam("UntilLoseControlOfHost") if ((sa.hasParam("UntilLoseControlOfHost") || sa.hasParam("UntilHostLeavesPlay"))
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) { && !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
return; return;
} }
@@ -104,7 +104,7 @@ public class PumpEffect extends SpellAbilityEffect {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
//if host is not on the battlefield don't apply //if host is not on the battlefield don't apply
// Suspend should does Affect the Stack // Suspend should does Affect the Stack
if (sa.hasParam("UntilLoseControlOfHost") if ((sa.hasParam("UntilLoseControlOfHost") || sa.hasParam("UntilHostLeavesPlay"))
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) { && !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
return; return;
} }

View File

@@ -9,6 +9,7 @@ import com.google.common.collect.Maps;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
@@ -57,6 +58,7 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect {
CardDamageMap damageMap = (CardDamageMap) originalParams.get("DamageMap"); CardDamageMap damageMap = (CardDamageMap) originalParams.get("DamageMap");
CardDamageMap preventMap = (CardDamageMap) originalParams.get("PreventMap"); CardDamageMap preventMap = (CardDamageMap) originalParams.get("PreventMap");
GameEntityCounterTable counterTable = (GameEntityCounterTable) originalParams.get("CounterTable");
SpellAbility cause = (SpellAbility) originalParams.get("Cause"); SpellAbility cause = (SpellAbility) originalParams.get("Cause");
boolean isCombat = (Boolean) originalParams.get("IsCombat"); boolean isCombat = (Boolean) originalParams.get("IsCombat");
@@ -64,7 +66,7 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect {
GameEntity obj = (GameEntity) list.get(0); GameEntity obj = (GameEntity) list.get(0);
obj.addDamage(n, sourceLKI, isCombat, noPrevention, damageMap, preventMap, cause); obj.addDamage(n, sourceLKI, isCombat, noPrevention, damageMap, preventMap, counterTable, cause);
} }
// no damage for original target anymore // no damage for original target anymore
@@ -74,7 +76,6 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect {
} }
params.put("DamageAmount", dmg); params.put("DamageAmount", dmg);
//try to call replacementHandler with new Params //try to call replacementHandler with new Params
ReplacementResult result = game.getReplacementHandler().run(params); ReplacementResult result = game.getReplacementHandler().run(params);
switch (result) { switch (result) {

View File

@@ -4,6 +4,7 @@ import com.google.common.collect.Maps;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.Game; import forge.game.Game;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.*; import forge.game.card.*;
@@ -43,7 +44,11 @@ public class SacrificeEffect extends SpellAbilityEffect {
return; return;
} }
} else if (sa.hasParam("CumulativeUpkeep")) { } else if (sa.hasParam("CumulativeUpkeep")) {
card.addCounter(CounterType.AGE, 1, activator, true); GameEntityCounterTable table = new GameEntityCounterTable();
card.addCounter(CounterType.AGE, 1, activator, true, table);
table.triggerCountersPutAll(game);
Cost cumCost = new Cost(sa.getParam("CumulativeUpkeep"), true); Cost cumCost = new Cost(sa.getParam("CumulativeUpkeep"), true);
Cost payCost = new Cost(ManaCost.ZERO, true); Cost payCost = new Cost(ManaCost.ZERO, true);
int n = card.getCounters(CounterType.AGE); int n = card.getCounters(CounterType.AGE);

View File

@@ -2,6 +2,7 @@ package forge.game.ability.effects;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.GameLogEntryType; import forge.game.GameLogEntryType;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
@@ -49,7 +50,6 @@ public class SetStateEffect extends SpellAbilityEffect {
final String mode = sa.getParam("Mode"); final String mode = sa.getParam("Mode");
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final Game game = host.getGame(); final Game game = host.getGame();
final List<Card> tgtCards = getTargetCards(sa);
final boolean remChanged = sa.hasParam("RememberChanged"); final boolean remChanged = sa.hasParam("RememberChanged");
final boolean morphUp = sa.hasParam("MorphUp"); final boolean morphUp = sa.hasParam("MorphUp");
@@ -57,7 +57,9 @@ public class SetStateEffect extends SpellAbilityEffect {
final boolean hiddenAgenda = sa.hasParam("HiddenAgenda"); final boolean hiddenAgenda = sa.hasParam("HiddenAgenda");
final boolean optional = sa.hasParam("Optional"); final boolean optional = sa.hasParam("Optional");
for (final Card tgt : tgtCards) { GameEntityCounterTable table = new GameEntityCounterTable();
for (final Card tgt : getTargetCards(sa)) {
if (sa.usesTargeting() && !tgt.canBeTargetedBy(sa)) { if (sa.usesTargeting() && !tgt.canBeTargetedBy(sa)) {
continue; continue;
} }
@@ -125,12 +127,13 @@ public class SetStateEffect extends SpellAbilityEffect {
} }
game.fireEvent(new GameEventCardStatsChanged(tgt)); game.fireEvent(new GameEventCardStatsChanged(tgt));
if (sa.hasParam("Mega")) { if (sa.hasParam("Mega")) {
tgt.addCounter(CounterType.P1P1, 1, p, true); tgt.addCounter(CounterType.P1P1, 1, p, true, table);
} }
if (remChanged) { if (remChanged) {
host.addRemembered(tgt); host.addRemembered(tgt);
} }
} }
} }
table.triggerCountersPutAll(game);
} }
} }

View File

@@ -1131,16 +1131,18 @@ public class Card extends GameEntity implements Comparable<Card> {
@Override @Override
public final boolean canReceiveCounters(final CounterType type) { public final boolean canReceiveCounters(final CounterType type) {
if (hasKeyword("CARDNAME can't have counters put on it.")) {
return false; // CantPutCounter static abilities
} for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
if (isCreature() && type == CounterType.M1M1) { for (final StaticAbility stAb : ca.getStaticAbilities()) {
for (final Card c : getController().getCreaturesInPlay()) { // look for Melira, Sylvok Outcast if (stAb.applyAbility("CantPutCounter", this, type)) {
if (c.hasKeyword("Creatures you control can't have -1/-1 counters put on them.")) {
return false; return false;
} }
} }
} else if (type == CounterType.DREAM) { }
if (type == CounterType.DREAM) {
// need to be done extra because it is also a state based action
if (hasKeyword("CARDNAME can't have more than seven dream counters on it.") && getCounters(CounterType.DREAM) > 6) { if (hasKeyword("CARDNAME can't have more than seven dream counters on it.") && getCounters(CounterType.DREAM) > 6) {
return false; return false;
} }
@@ -1156,17 +1158,17 @@ public class Card extends GameEntity implements Comparable<Card> {
countersAdded = value; countersAdded = value;
} }
public final int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier) { public final int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier, GameEntityCounterTable table) {
return addCounter(counterType, n, source, applyMultiplier, true); return addCounter(counterType, n, source, applyMultiplier, true, table);
} }
public final int addCounterFireNoEvents(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier) { public final int addCounterFireNoEvents(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier, GameEntityCounterTable table) {
return addCounter(counterType, n, source, applyMultiplier, false); return addCounter(counterType, n, source, applyMultiplier, false, table);
} }
@Override @Override
public int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier, final boolean fireEvents) { public int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier, final boolean fireEvents, GameEntityCounterTable table) {
int addAmount = n; int addAmount = n;
if(addAmount < 0) { if(addAmount <= 0) {
addAmount = 0; // As per rule 107.1b addAmount = 0; // As per rule 107.1b
return 0; return 0;
} }
@@ -1245,6 +1247,9 @@ public class Card extends GameEntity implements Comparable<Card> {
getController().addCounterToPermThisTurn(counterType, addAmount); getController().addCounterToPermThisTurn(counterType, addAmount);
view.updateCounters(this); view.updateCounters(this);
} }
if (table != null) {
table.put(this, counterType, addAmount);
}
return addAmount; return addAmount;
} }
@@ -4622,15 +4627,20 @@ public class Card extends GameEntity implements Comparable<Card> {
return total; return total;
} }
public final void addCombatDamage(final Map<Card, Integer> map, final CardDamageMap damageMap, final CardDamageMap preventMap) { public final void addCombatDamage(final Map<Card, Integer> map, final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable) {
for (final Entry<Card, Integer> entry : map.entrySet()) { for (final Entry<Card, Integer> entry : map.entrySet()) {
addCombatDamage(entry.getValue(), entry.getKey(), damageMap, preventMap); addCombatDamage(entry.getValue(), entry.getKey(), damageMap, preventMap, counterTable);
} }
} }
protected int addCombatDamageBase(final int damage, final Card source, CardDamageMap damageMap) { /*
* (non-Javadoc)
* @see forge.game.GameEntity#addCombatDamageBase(int, forge.game.card.Card, forge.game.card.CardDamageMap, forge.game.GameEntityCounterTable)
*/
@Override
protected int addCombatDamageBase(final int damage, final Card source, CardDamageMap damageMap, GameEntityCounterTable counterTable) {
if (isInPlay()) { if (isInPlay()) {
return super.addCombatDamageBase(damage, source, damageMap); return super.addCombatDamageBase(damage, source, damageMap, counterTable);
} }
return 0; return 0;
} }
@@ -4857,10 +4867,10 @@ public class Card extends GameEntity implements Comparable<Card> {
return restDamage; return restDamage;
} }
public final void addDamage(final Map<Card, Integer> sourcesMap, CardDamageMap damageMap) { public final void addDamage(final Map<Card, Integer> sourcesMap, CardDamageMap damageMap, GameEntityCounterTable counterTable) {
for (final Entry<Card, Integer> entry : sourcesMap.entrySet()) { for (final Entry<Card, Integer> entry : sourcesMap.entrySet()) {
// damage prevention is already checked! // damage prevention is already checked!
addDamageAfterPrevention(entry.getValue(), entry.getKey(), true, damageMap); addDamageAfterPrevention(entry.getValue(), entry.getKey(), true, damageMap, counterTable);
} }
} }
@@ -4869,7 +4879,7 @@ public class Card extends GameEntity implements Comparable<Card> {
* applied. * applied.
*/ */
@Override @Override
public final int addDamageAfterPrevention(final int damageIn, final Card source, final boolean isCombat, CardDamageMap damageMap) { public final int addDamageAfterPrevention(final int damageIn, final Card source, final boolean isCombat, CardDamageMap damageMap, GameEntityCounterTable counterTable) {
if (damageIn == 0) { if (damageIn == 0) {
return 0; // Rule 119.8 return 0; // Rule 119.8
@@ -4903,7 +4913,7 @@ public class Card extends GameEntity implements Comparable<Card> {
if (isInPlay()) { if (isInPlay()) {
if (wither) { if (wither) {
addCounter(CounterType.M1M1, damageIn, source.getController(), true); addCounter(CounterType.M1M1, damageIn, source.getController(), true, counterTable);
damageType = DamageType.M1M1Counters; damageType = DamageType.M1M1Counters;
} }
else { else {
@@ -6052,7 +6062,7 @@ public class Card extends GameEntity implements Comparable<Card> {
return etbCounters.cellSet(); return etbCounters.cellSet();
} }
public final boolean putEtbCounters() { public final boolean putEtbCounters(GameEntityCounterTable table) {
boolean changed = false; boolean changed = false;
for (Table.Cell<Player, CounterType, Integer> e : etbCounters.cellSet()) { for (Table.Cell<Player, CounterType, Integer> e : etbCounters.cellSet()) {
CounterType ct = e.getColumnKey(); CounterType ct = e.getColumnKey();
@@ -6062,7 +6072,7 @@ public class Card extends GameEntity implements Comparable<Card> {
changed = true; changed = true;
} }
} else { } else {
changed |= addCounter(ct, e.getValue(), e.getRowKey(), true) > 0; changed |= addCounter(ct, e.getValue(), e.getRowKey(), true, table) > 0;
} }
} }
return changed; return changed;

View File

@@ -32,6 +32,7 @@ import forge.card.mana.ManaCostParser;
import forge.card.mana.ManaCostShard; import forge.card.mana.ManaCostShard;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.GameLogEntryType; import forge.game.GameLogEntryType;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
@@ -4277,7 +4278,9 @@ public class CardFactoryUtil {
final Card c = game.getAction().exile(this.getHostCard(), this); final Card c = game.getAction().exile(this.getHostCard(), this);
int counters = AbilityUtils.calculateAmount(c, k[1], this); int counters = AbilityUtils.calculateAmount(c, k[1], this);
c.addCounter(CounterType.TIME, counters, getActivatingPlayer(), true); GameEntityCounterTable table = new GameEntityCounterTable();
c.addCounter(CounterType.TIME, counters, getActivatingPlayer(), true, table);
table.triggerCountersPutAll(game);
String sb = TextUtil.concatWithSpace(getActivatingPlayer().toString(),"has suspended", c.getName(), "with", String.valueOf(counters),"time counters on it."); String sb = TextUtil.concatWithSpace(getActivatingPlayer().toString(),"has suspended", c.getName(), "with", String.valueOf(counters),"time counters on it.");
game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb); game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);

View File

@@ -21,6 +21,7 @@ import com.google.common.base.Function;
import com.google.common.collect.*; import com.google.common.collect.*;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.GameLogEntryType; import forge.game.GameLogEntryType;
import forge.game.GameObjectMap; import forge.game.GameObjectMap;
import forge.game.card.Card; import forge.game.card.Card;
@@ -783,18 +784,20 @@ public class Combat {
} }
public void dealAssignedDamage() { public void dealAssignedDamage() {
playerWhoAttacks.getGame().copyLastState(); final Game game = playerWhoAttacks.getGame();
game.copyLastState();
CardDamageMap preventMap = new CardDamageMap(); CardDamageMap preventMap = new CardDamageMap();
GameEntityCounterTable counterTable = new GameEntityCounterTable();
// This function handles both Regular and First Strike combat assignment // This function handles both Regular and First Strike combat assignment
for (final Entry<Card, Integer> entry : defendingDamageMap.entrySet()) { for (final Entry<Card, Integer> entry : defendingDamageMap.entrySet()) {
GameEntity defender = getDefenderByAttacker(entry.getKey()); GameEntity defender = getDefenderByAttacker(entry.getKey());
if (defender instanceof Player) { // player if (defender instanceof Player) { // player
((Player) defender).addCombatDamage(entry.getValue(), entry.getKey(), dealtDamageTo, preventMap); ((Player) defender).addCombatDamage(entry.getValue(), entry.getKey(), dealtDamageTo, preventMap, counterTable);
} }
else if (defender instanceof Card) { // planeswalker else if (defender instanceof Card) { // planeswalker
((Card) defender).getController().addCombatDamage(entry.getValue(), entry.getKey(), dealtDamageTo, preventMap); ((Card) defender).getController().addCombatDamage(entry.getValue(), entry.getKey(), dealtDamageTo, preventMap, counterTable);
} }
} }
@@ -811,7 +814,7 @@ public class Combat {
continue; continue;
} }
c.addCombatDamage(c.getAssignedDamageMap(), dealtDamageTo, preventMap); c.addCombatDamage(c.getAssignedDamageMap(), dealtDamageTo, preventMap, counterTable);
c.clearAssignedDamage(); c.clearAssignedDamage();
} }
@@ -823,6 +826,9 @@ public class Combat {
// LifeLink for Combat Damage at this place // LifeLink for Combat Damage at this place
dealtDamageTo.triggerDamageDoneOnce(true, null); dealtDamageTo.triggerDamageDoneOnce(true, null);
dealtDamageTo.clear(); dealtDamageTo.clear();
counterTable.triggerCountersPutAll(game);
counterTable.clear();
} }
public final boolean isUnblocked(final Card att) { public final boolean isUnblocked(final Card att) {

View File

@@ -17,6 +17,7 @@
*/ */
package forge.game.cost; package forge.game.cost;
import forge.game.GameEntityCounterTable;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardDamageMap; import forge.game.card.CardDamageMap;
import forge.game.player.Player; import forge.game.player.Player;
@@ -47,7 +48,7 @@ public class CostDamage extends CostPart {
@Override @Override
public final String toString() { public final String toString() {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
sb.append("Pay ").append(this.getAmount()).append(" Life"); sb.append("Deal ").append(this.getAmount()).append(" damage to you");
return sb.toString(); return sb.toString();
} }
@@ -68,14 +69,17 @@ public class CostDamage extends CostPart {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
CardDamageMap damageMap = new CardDamageMap(); CardDamageMap damageMap = new CardDamageMap();
CardDamageMap preventMap = new CardDamageMap(); CardDamageMap preventMap = new CardDamageMap();
GameEntityCounterTable table = new GameEntityCounterTable();
payer.addDamage(decision.c, source, damageMap, preventMap, sa); payer.addDamage(decision.c, source, damageMap, preventMap, table, sa);
preventMap.triggerPreventDamage(false); preventMap.triggerPreventDamage(false);
damageMap.triggerDamageDoneOnce(false, sa); damageMap.triggerDamageDoneOnce(false, sa);
table.triggerCountersPutAll(payer.getGame());
preventMap.clear(); preventMap.clear();
damageMap.clear(); damageMap.clear();
table.clear();
return decision.c > 0; return decision.c > 0;
} }

View File

@@ -53,7 +53,7 @@ public abstract class CostPartWithList extends CostPart {
/** /**
* Reset list. * Reset list.
*/ */
public final void resetLists() { public void resetLists() {
lkiList.clear(); lkiList.clear();
cardList.clear(); cardList.clear();
table.clear(); table.clear();
@@ -121,7 +121,7 @@ public abstract class CostPartWithList extends CostPart {
} }
// always returns true, made this to inline with return // always returns true, made this to inline with return
public final boolean executePayment(SpellAbility ability, CardCollectionView targetCards) { public boolean executePayment(SpellAbility ability, CardCollectionView targetCards) {
if (canPayListAtOnce()) { // This is used by reveal. Without it when opponent would reveal hand, you'll get N message boxes. if (canPayListAtOnce()) { // This is used by reveal. Without it when opponent would reveal hand, you'll get N message boxes.
lkiList.addAll(targetCards); lkiList.addAll(targetCards);
cardList.addAll(doListPayment(ability, targetCards)); cardList.addAll(doListPayment(ability, targetCards));

View File

@@ -17,7 +17,7 @@
*/ */
package forge.game.cost; package forge.game.cost;
import forge.game.ability.AbilityUtils; import forge.game.GameEntityCounterTable;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
@@ -40,6 +40,8 @@ public class CostPutCounter extends CostPartWithList {
private final CounterType counter; private final CounterType counter;
private int lastPaidAmount = 0; private int lastPaidAmount = 0;
private final GameEntityCounterTable counterTable = new GameEntityCounterTable();
public final CounterType getCounter() { public final CounterType getCounter() {
return this.counter; return this.counter;
} }
@@ -119,9 +121,11 @@ public class CostPutCounter extends CostPartWithList {
public final void refund(final Card source) { public final void refund(final Card source) {
if(this.payCostFromSource()) if(this.payCostFromSource())
source.subtractCounter(this.counter, this.lastPaidAmount); source.subtractCounter(this.counter, this.lastPaidAmount);
else else {
final Integer i = this.convertAmount();
for (final Card c : this.getCardList()) { for (final Card c : this.getCardList()) {
c.subtractCounter(this.counter, 1); c.subtractCounter(this.counter, i);
}
} }
} }
@@ -162,50 +166,25 @@ public class CostPutCounter extends CostPartWithList {
*/ */
@Override @Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) { public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
Integer c = getNumberOfCounters(ability);
if (this.payCostFromSource()) { if (this.payCostFromSource()) {
executePayment(ability, ability.getHostCard(), c); executePayment(ability, ability.getHostCard());
} else { } else {
// Put counter on chosen card
for (int i = 0; i < c; i++)
executePayment(ability, decision.cards); executePayment(ability, decision.cards);
ability.getHostCard().setSVar("CostCountersAdded", Integer.toString(Math.min(decision.cards.size(), c)));
} }
triggerCounterPutAll(ability);
return true; return true;
} }
public Integer getNumberOfCounters(final SpellAbility ability) {
Integer c = this.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(ability.getHostCard(), this.getAmount(), ability);
}
return c;
}
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.cost.CostPartWithList#executePayment(forge.card.spellability.SpellAbility, forge.Card) * @see forge.card.cost.CostPartWithList#executePayment(forge.card.spellability.SpellAbility, forge.Card)
*/ */
@Override @Override
protected Card doPayment(SpellAbility ability, Card targetCard){ protected Card doPayment(SpellAbility ability, Card targetCard){
targetCard.addCounter(this.getCounter(), 1, ability.getActivatingPlayer(), false); final Integer i = this.convertAmount();
targetCard.addCounter(this.getCounter(), i, ability.getActivatingPlayer(), false, counterTable);
return targetCard; return targetCard;
} }
protected void executePayment(SpellAbility ability, Card targetCard, int c) {
CounterType counterType = this.getCounter();
if( c > 1 ) {
Integer oldValue = targetCard.getCounters().get(counterType);
int newValue = c + (oldValue == null ? 0 : oldValue.intValue()) - 1;
targetCard.getCounters().put(counterType, Integer.valueOf(newValue));
}
// added c - 1 without firing triggers, the last counter added should fire trigger.
if (c > 0) {
executePayment(ability, targetCard);
}
}
@Override @Override
public String getHashForLKIList() { public String getHashForLKIList() {
return "CounterPut"; return "CounterPut";
@@ -219,4 +198,24 @@ public class CostPutCounter extends CostPartWithList {
return visitor.visit(this); return visitor.visit(this);
} }
protected void triggerCounterPutAll(final SpellAbility ability) {
if (counterTable.isEmpty()) {
return;
}
GameEntityCounterTable tempTable = new GameEntityCounterTable();
tempTable.putAll(counterTable);
tempTable.triggerCountersPutAll(ability.getHostCard().getGame());
}
/* (non-Javadoc)
* @see forge.game.cost.CostPartWithList#resetLists()
*/
@Override
public void resetLists() {
super.resetLists();
counterTable.clear();
}
} }

View File

@@ -114,7 +114,7 @@ public class CostRemoveCounter extends CostPartWithList {
public final void refund(final Card source) { public final void refund(final Card source) {
int refund = this.getCardList().size() == 1 ? this.cntRemoved : 1; // is wrong for Ooze Flux and Novijen Sages int refund = this.getCardList().size() == 1 ? this.cntRemoved : 1; // is wrong for Ooze Flux and Novijen Sages
for (final Card c : this.getCardList()) { for (final Card c : this.getCardList()) {
c.addCounter(this.counter, refund, source.getController(), false); c.addCounter(this.counter, refund, source.getController(), false, false, null);
} }
} }

View File

@@ -261,12 +261,14 @@ public class PhaseHandler implements java.io.Serializable {
if (playerTurn.isArchenemy()) { if (playerTurn.isArchenemy()) {
playerTurn.setSchemeInMotion(); playerTurn.setSchemeInMotion();
} }
GameEntityCounterTable table = new GameEntityCounterTable();
// all Saga get Lore counter at the begin of pre combat // all Saga get Lore counter at the begin of pre combat
for (Card c : playerTurn.getCardsIn(ZoneType.Battlefield)) { for (Card c : playerTurn.getCardsIn(ZoneType.Battlefield)) {
if (c.getType().hasSubtype("Saga")) { if (c.getType().hasSubtype("Saga")) {
c.addCounter(CounterType.LORE, 1, null, false); c.addCounter(CounterType.LORE, 1, null, false, table);
} }
} }
table.triggerCountersPutAll(game);
} }
break; break;

View File

@@ -559,7 +559,7 @@ public class Player extends GameEntity implements Comparable<Player> {
// This function handles damage after replacement and prevention effects are applied // This function handles damage after replacement and prevention effects are applied
@Override @Override
public final int addDamageAfterPrevention(final int amount, final Card source, final boolean isCombat, CardDamageMap damageMap) { public final int addDamageAfterPrevention(final int amount, final Card source, final boolean isCombat, CardDamageMap damageMap, GameEntityCounterTable counterTable) {
if (amount <= 0) { if (amount <= 0) {
return 0; return 0;
} }
@@ -570,7 +570,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|| hasKeyword("All damage is dealt to you as though its source had infect."); || hasKeyword("All damage is dealt to you as though its source had infect.");
if (infect) { if (infect) {
addPoisonCounters(amount, source); addPoisonCounters(amount, source, counterTable);
} }
else { else {
// Worship does not reduce the damage dealt but changes the effect // Worship does not reduce the damage dealt but changes the effect
@@ -882,23 +882,23 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
public final boolean canReceiveCounters(final CounterType type) { public final boolean canReceiveCounters(final CounterType type) {
if (hasKeyword("PLAYER can't have counters put on him or her.")) { // CantPutCounter static abilities
for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (stAb.applyAbility("CantPutCounter", this, type)) {
return false; return false;
} }
if (type == CounterType.POISON) {
if (hasKeyword("You can't get poison counters")) {
return false;
} }
} }
return true; return true;
} }
public final int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier) { public final int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier, GameEntityCounterTable table) {
return addCounter(counterType, n, source, applyMultiplier, true); return addCounter(counterType, n, source, applyMultiplier, true, table);
} }
@Override @Override
public int addCounter(CounterType counterType, int n, final Player source, boolean applyMultiplier, boolean fireEvents) { public int addCounter(CounterType counterType, int n, final Player source, boolean applyMultiplier, boolean fireEvents, GameEntityCounterTable table) {
if (!canReceiveCounters(counterType)) { if (!canReceiveCounters(counterType)) {
return 0; return 0;
} }
@@ -942,6 +942,9 @@ public class Player extends GameEntity implements Comparable<Player> {
if (addAmount > 0) { if (addAmount > 0) {
getGame().getTriggerHandler().runTrigger(TriggerType.CounterAddedOnce, runParams, false); getGame().getTriggerHandler().runTrigger(TriggerType.CounterAddedOnce, runParams, false);
} }
if (table != null) {
table.put(this, counterType, addAmount);
}
return addAmount; return addAmount;
} }
@@ -999,9 +1002,9 @@ public class Player extends GameEntity implements Comparable<Player> {
setCounters(CounterType.POISON, num, true); setCounters(CounterType.POISON, num, true);
game.fireEvent(new GameEventPlayerPoisoned(this, source, oldPoison, num)); game.fireEvent(new GameEventPlayerPoisoned(this, source, oldPoison, num));
} }
public final void addPoisonCounters(final int num, final Card source) { public final void addPoisonCounters(final int num, final Card source, GameEntityCounterTable table) {
int oldPoison = getCounters(CounterType.POISON); int oldPoison = getCounters(CounterType.POISON);
addCounter(CounterType.POISON, num, source.getController(), false, true); addCounter(CounterType.POISON, num, source.getController(), false, true, table);
if (oldPoison != getCounters(CounterType.POISON)) { if (oldPoison != getCounters(CounterType.POISON)) {
game.fireEvent(new GameEventPlayerPoisoned(this, source, oldPoison, num)); game.fireEvent(new GameEventPlayerPoisoned(this, source, oldPoison, num));

View File

@@ -29,6 +29,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
@@ -386,6 +387,43 @@ public class StaticAbility extends CardTraitBase implements Comparable<StaticAbi
return false; return false;
} }
public final boolean applyAbility(String mode, Card card, CounterType type) {
// don't apply the ability if it hasn't got the right mode
if (!getParam("Mode").equals(mode)) {
return false;
}
if (this.isSuppressed() || !this.checkConditions()) {
return false;
}
if (mode.equals("CantPutCounter")) {
return StaticAbilityCantPutCounter.applyCantPutCounter(this, card, type);
}
return false;
}
public final boolean applyAbility(String mode, Player player, CounterType type) {
// don't apply the ability if it hasn't got the right mode
if (!getParam("Mode").equals(mode)) {
return false;
}
if (this.isSuppressed() || !this.checkConditions()) {
return false;
}
if (mode.equals("CantPutCounter")) {
return StaticAbilityCantPutCounter.applyCantPutCounter(this, player, type);
}
return false;
}
/** /**
* Apply ability. * Apply ability.

View File

@@ -0,0 +1,54 @@
package forge.game.staticability;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.player.Player;
public class StaticAbilityCantPutCounter {
public static boolean applyCantPutCounter(final StaticAbility staticAbility, final Card card,
final CounterType type) {
final Card hostCard = staticAbility.getHostCard();
if (staticAbility.hasParam("CounterType")) {
CounterType t = CounterType.valueOf(staticAbility.getParam("CounterType"));
if (t != null && !type.equals(t)) {
return false;
}
}
if (staticAbility.hasParam("ValidCard")) {
if (!card.isValid(staticAbility.getParam("ValidCard").split(","), hostCard.getController(), hostCard, null)) {
return false;
}
} else if (staticAbility.hasParam("ValidPlayer")) {
// for the other part
return false;
}
return true;
}
public static boolean applyCantPutCounter(final StaticAbility staticAbility, final Player player,
final CounterType type) {
final Card hostCard = staticAbility.getHostCard();
if (staticAbility.hasParam("CounterType")) {
CounterType t = CounterType.valueOf(staticAbility.getParam("CounterType"));
if (t != null && !type.equals(t)) {
return false;
}
}
if (staticAbility.hasParam("ValidPlayer")) {
if (!player.isValid(staticAbility.getParam("ValidPlayer").split(","), hostCard.getController(), hostCard, null)) {
return false;
}
} else if (staticAbility.hasParam("ValidCard")) {
// for the other part
return false;
}
return true;
}
}

View File

@@ -0,0 +1,57 @@
package forge.game.trigger;
import java.util.Map;
import com.google.common.collect.Lists;
import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.card.*;
import forge.game.spellability.SpellAbility;
public class TriggerCounterAddedAll extends Trigger {
public TriggerCounterAddedAll(Map<String, String> params, Card host, boolean intrinsic) {
super(params, host, intrinsic);
}
@Override
public boolean performTest(Map<String, Object> runParams2) {
final GameEntityCounterTable table = (GameEntityCounterTable) runParams2.get("Objects");
if (filterTable(table).isEmpty()) {
return false;
}
return true;
}
@Override
public void setTriggeringObjects(SpellAbility sa) {
final GameEntityCounterTable table = (GameEntityCounterTable) getRunParams().get("Objects");
Map<GameEntity, Integer> all = this.filterTable(table);
int amount = 0;
for (final Integer v : all.values()) {
amount += v;
}
sa.setTriggeringObject("Objects", Lists.newArrayList(all.keySet()));
sa.setTriggeringObject("Amount", amount);
}
@Override
public String getImportantStackObjects(SpellAbility sa) {
StringBuilder sb = new StringBuilder();
sb.append("Amount: ").append(sa.getTriggeringObject("Amount"));
return sb.toString();
}
private Map<GameEntity, Integer> filterTable(GameEntityCounterTable table) {
CounterType counterType = CounterType.getType(getParam("CounterType"));
String valid = getParam("Valid");
return table.filterTable(counterType, valid, getHostCard(), null);
}
}

View File

@@ -36,6 +36,7 @@ public enum TriggerType {
Clashed(TriggerClashed.class), Clashed(TriggerClashed.class),
CounterAdded(TriggerCounterAdded.class), CounterAdded(TriggerCounterAdded.class),
CounterAddedOnce(TriggerCounterAddedOnce.class), CounterAddedOnce(TriggerCounterAddedOnce.class),
CounterAddedAll(TriggerCounterAddedAll.class),
Countered(TriggerCountered.class), Countered(TriggerCountered.class),
CounterRemoved(TriggerCounterRemoved.class), CounterRemoved(TriggerCounterRemoved.class),
CounterRemovedOnce(TriggerCounterRemovedOnce.class), CounterRemovedOnce(TriggerCounterRemovedOnce.class),

View File

@@ -217,7 +217,7 @@ public class GameSimulatorTest extends SimulationTestCase {
Game game = initAndCreateGame(); Game game = initAndCreateGame();
Player p = game.getPlayers().get(1); Player p = game.getPlayers().get(1);
Card sorin = addCard("Sorin, Solemn Visitor", p); Card sorin = addCard("Sorin, Solemn Visitor", p);
sorin.addCounter(CounterType.LOYALTY, 5, p, false); sorin.addCounter(CounterType.LOYALTY, 5, p, false, null);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true); game.getAction().checkStateEffects(true);
@@ -261,7 +261,7 @@ public class GameSimulatorTest extends SimulationTestCase {
String bearCardName = "Runeclaw Bear"; String bearCardName = "Runeclaw Bear";
addCard(bearCardName, p); addCard(bearCardName, p);
Card gideon = addCard("Gideon, Ally of Zendikar", p); Card gideon = addCard("Gideon, Ally of Zendikar", p);
gideon.addCounter(CounterType.LOYALTY, 4, p, false); gideon.addCounter(CounterType.LOYALTY, 4, p, false, null);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true); game.getAction().checkStateEffects(true);
@@ -380,7 +380,7 @@ public class GameSimulatorTest extends SimulationTestCase {
Game game = initAndCreateGame(); Game game = initAndCreateGame();
Player p = game.getPlayers().get(1); Player p = game.getPlayers().get(1);
Card sarkhan = addCard(sarkhanCardName, p); Card sarkhan = addCard(sarkhanCardName, p);
sarkhan.addCounter(CounterType.LOYALTY, 4, p, false); sarkhan.addCounter(CounterType.LOYALTY, 4, p, false, null);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true); game.getAction().checkStateEffects(true);
@@ -414,7 +414,7 @@ public class GameSimulatorTest extends SimulationTestCase {
addCard(ornithoperCardName, p); addCard(ornithoperCardName, p);
addCard(bearCardName, p); addCard(bearCardName, p);
Card ajani = addCard(ajaniCardName, p); Card ajani = addCard(ajaniCardName, p);
ajani.addCounter(CounterType.LOYALTY, 4, p, false); ajani.addCounter(CounterType.LOYALTY, 4, p, false, null);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true); game.getAction().checkStateEffects(true);
@@ -445,7 +445,7 @@ public class GameSimulatorTest extends SimulationTestCase {
SpellAbility boltSA = boltCard.getFirstSpellAbility(); SpellAbility boltSA = boltCard.getFirstSpellAbility();
Card ajani = addCard(ajaniCardName, p); Card ajani = addCard(ajaniCardName, p);
ajani.addCounter(CounterType.LOYALTY, 8, p, false); ajani.addCounter(CounterType.LOYALTY, 8, p, false, null);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true); game.getAction().checkStateEffects(true);
@@ -494,7 +494,7 @@ public class GameSimulatorTest extends SimulationTestCase {
addCard("Swamp", p); addCard("Swamp", p);
addCard("Swamp", p); addCard("Swamp", p);
Card depths = addCard("Dark Depths", p); Card depths = addCard("Dark Depths", p);
depths.addCounter(CounterType.ICE, 10, p, false); depths.addCounter(CounterType.ICE, 10, p, false, null);
Card thespian = addCard("Thespian's Stage", p); Card thespian = addCard("Thespian's Stage", p);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true); game.getAction().checkStateEffects(true);

View File

@@ -1,12 +1,12 @@
Name:Hungering Hydra Name:Hungering Hydra
ManaCost:X G ManaCost:X G
Types:Creature Hydra Types:Creature Hydra
PT:0/0
K:CantBeBlockedByAmount GT1 K:CantBeBlockedByAmount GT1
K:etbCounter:P1P1:X K:etbCounter:P1P1:X
SVar:X:Count$xPaid SVar:X:Count$xPaid
T:Mode$ DamageDoneOnce | ValidTarget$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME is dealt damage, put that many +1/+1 counters on CARDNAME. T:Mode$ DamageDoneOnce | ValidTarget$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME is dealt damage, put that many +1/+1 counters on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ Y | References$ Y SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ Y | References$ Y
SVar:Y:TriggerCount$DamageAmount SVar:Y:TriggerCount$DamageAmount
DeckHas:Ability$Token DeckHas:Ability$Counter
Oracle:Hungering Hydra enters the battlefield with X +1/+1 counters on it.\nHungering Hydra can't be blocked by more than one creature.\nWhenever Hungering Hydra is dealt damage, put that many +1/+1 counters on it. (It must survive the damage to get the counters.) Oracle:Hungering Hydra enters the battlefield with X +1/+1 counters on it.\nHungering Hydra can't be blocked by more than one creature.\nWhenever Hungering Hydra is dealt damage, put that many +1/+1 counters on it. (It must survive the damage to get the counters.)
PT:0/0

View File

@@ -2,8 +2,8 @@ Name:Melira, Sylvok Outcast
ManaCost:1 G ManaCost:1 G
Types:Legendary Creature Human Scout Types:Legendary Creature Human Scout
PT:2/2 PT:2/2
K:Creatures you control can't have -1/-1 counters put on them. S:Mode$ CantPutCounter | ValidPlayer$ You | CounterType$ POISON | Description$ You can't get poison counters.
S:Mode$ Continuous | Affected$ You | AddKeyword$ You can't get poison counters | Description$ You can't get poison counters. S:Mode$ CantPutCounter | ValidCard$ Creature.YouCtrl | CounterType$ M1M1 | Description$ Creatures you control can't have -1/-1 counters put on them.
S:Mode$ Continuous | Affected$ Creature.OppCtrl | RemoveKeyword$ Infect | Description$ Creatures your opponents control lose infect. S:Mode$ Continuous | Affected$ Creature.OppCtrl | RemoveKeyword$ Infect | Description$ Creatures your opponents control lose infect.
S:Mode$ Continuous | Affected$ Creature.YouCtrl+withPersist | AddSVar$ Sac S:Mode$ Continuous | Affected$ Creature.YouCtrl+withPersist | AddSVar$ Sac
SVar:Sac:SVar:SacMe:3 SVar:Sac:SVar:SacMe:3

View File

@@ -2,6 +2,6 @@ Name:Melira's Keepers
ManaCost:4 G ManaCost:4 G
Types:Creature Human Warrior Types:Creature Human Warrior
PT:4/4 PT:4/4
K:CARDNAME can't have counters put on it. S:Mode$ CantPutCounter | ValidCard$ Card.Self | Description$ CARDNAME can't have counters put on it.
SVar:Picture:http://www.wizards.com/global/images/magic/general/meliras_keepers.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/meliras_keepers.jpg
Oracle:Melira's Keepers can't have counters put on it. Oracle:Melira's Keepers can't have counters put on it.

View File

@@ -1,7 +1,7 @@
Name:Solemnity Name:Solemnity
ManaCost:2 W ManaCost:2 W
Types:Enchantment Types:Enchantment
S:Mode$ Continuous | Affected$ Player | AddKeyword$ PLAYER can't have counters put on him or her. | Description$ Players can't get counters. S:Mode$ CantPutCounter | ValidPlayer$ Player | Description$ Players can't get counters.
S:Mode$ Continuous | Affected$ Creature,Artifact,Enchantment,Land | AddKeyword$ CARDNAME can't have counters put on it. | Description$ Counters can't be put on artifacts, creatures, enchantments or lands. S:Mode$ CantPutCounter | ValidCard$ Creature,Artifact,Enchantment,Land | Description$ Counters can't be put on artifacts, creatures, enchantments or lands.
SVar:Picture:http://www.wizards.com/global/images/magic/general/solemnity.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/solemnity.jpg
Oracle:Players can't get counters.\nCounters can't be put on artifacts, creatures, enchantments, or lands. Oracle:Players can't get counters.\nCounters can't be put on artifacts, creatures, enchantments, or lands.

View File

@@ -4,8 +4,10 @@ Types:Creature Human Cleric
PT:1/4 PT:1/4
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigCharm | TriggerDescription$ When CARDNAME enters the battlefield, ABILITY T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigCharm | TriggerDescription$ When CARDNAME enters the battlefield, ABILITY
SVar:TrigCharm:DB$ Charm | Choices$ CreatureDBRemoveCounter,OpponentDBRemoveCounter | CharmNum$ 1 SVar:TrigCharm:DB$ Charm | Choices$ CreatureDBRemoveCounter,OpponentDBRemoveCounter | CharmNum$ 1
SVar:CreatureDBRemoveCounter:DB$ RemoveCounter | ValidTgts$ Creature | RememberObjects$ Targeted | TgtPrompt$ Select target creature | CounterType$ All | CounterNum$ All | SubAbility$ DBPumpCreature | SpellDescription$ Remove all counters from target creature. It can't have counters put on it for as long as CARDNAME remains on the battlefield. SVar:CreatureDBRemoveCounter:DB$ RemoveCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ All | CounterNum$ All | SubAbility$ DBPumpCreature | SpellDescription$ Remove all counters from target creature. It can't have counters put on it for as long as CARDNAME remains on the battlefield.
SVar:OpponentDBRemoveCounter:DB$ RemoveCounter | ValidTgts$ Opponent | RememberObjects$ Targeted | TgtPrompt$ Select target opponent | CounterType$ All | CounterNum$ All | SubAbility$ DBPumpOpponent | SpellDescription$ Target opponent loses all counters. That player can't get counters for as long as CARDNAME remains on the battlefield. SVar:OpponentDBRemoveCounter:DB$ RemoveCounter | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | CounterType$ All | CounterNum$ All | SubAbility$ DBPumpOpponent | SpellDescription$ Target opponent loses all counters. That player can't get counters for as long as CARDNAME remains on the battlefield.
SVar:DBPumpCreature:DB$ Pump | Defined$ Targeted | KW$ CARDNAME can't have counters put on it. | UntilLoseControlOfHost$ True SVar:DBPumpCreature:DB$ Effect | StaticAbilities$ DBCantPutCounterCreature | RememberObjects$ Targeted | Duration$ UntilHostLeavesPlay | References$ DBCantPutCounterCreature
SVar:DBPumpOpponent:DB$ Pump | Defined$ Targeted | KW$ PLAYER can't have counters put on him or her. | UntilLoseControlOfHost$ True SVar:DBCantPutCounterCreature:Mode$ CantPutCounter | ValidCard$ Card.IsRemembered | EffectZone$ Command | Description$ It can't have counters put on it for as long as EFFECTSOURCE remains on the battlefield.
SVar:DBPumpOpponent:DB$ Effect | StaticAbilities$ DBCantPutCounterPlayer | RememberObjects$ Targeted | Duration$ UntilHostLeavesPlay | References$ DBCantPutCounterPlayer
SVar:DBCantPutCounterPlayer:Mode$ CantPutCounter | ValidPlayer$ Player.IsRemembered | EffectZone$ Command | Description$ That player can't get counters for as long as Suncleanser remains on the battlefield.
Oracle:When Suncleanser enters the battlefield, choose one —\n• Remove all counters from target creature. It can't have counters put on it for as long as Suncleanser remains on the battlefield.\n• Target opponent loses all counters. That player can't get counters for as long as Suncleanser remains on the battlefield. Oracle:When Suncleanser enters the battlefield, choose one —\n• Remove all counters from target creature. It can't have counters put on it for as long as Suncleanser remains on the battlefield.\n• Target opponent loses all counters. That player can't get counters for as long as Suncleanser remains on the battlefield.

View File

@@ -3,6 +3,6 @@ ManaCost:3
Types:Artifact Creature Scarecrow Types:Artifact Creature Scarecrow
PT:2/1 PT:2/1
K:Flying K:Flying
K:CARDNAME can't have counters put on it. S:Mode$ CantPutCounter | ValidCard$ Card.Self | Description$ CARDNAME can't have counters put on it.
SVar:Picture:http://www.wizards.com/global/images/magic/general/tatterkite.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/tatterkite.jpg
Oracle:Flying\nTatterkite can't have counters put on it. Oracle:Flying\nTatterkite can't have counters put on it.

View File

@@ -0,0 +1,14 @@
Name:Bioessence Hydra
ManaCost:3 G U
Types:Creature Hydra Mutant
PT:4/4
K:Trample
K:etbCounter:P1P1:Y:no Condition:CARDNAME enters the battlefield with a +1/+1 counter on it for each loyalty counter on planeswalkers you control.
SVar:Y:Count$TotalCounters_LOYALTY_Planeswalker.YouCtrl
T:Mode$ CounterAddedAll | CounterType$ LOYALTY | Valid$ Planeswalker.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever one or more loyalty counters are put on planeswalkers you control, put that many +1/+1 counters on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ Z | References$ Z
SVar:Z:TriggerCount$Amount
DeckHints:Type$Planeswalker
DeckHas:Ability$Token
Oracle:Trample\nBioessence Hydra enters the battlefield with a +1/+1 counter on it for each loyalty counter on planeswalkers you control.\nWhenever one or more loyalty counters are put on planeswalkers you control, put that many +1/+1 counters on Bioessence Hydra.

View File

@@ -0,0 +1,5 @@
Name:Price of Betrayal
ManaCost:B
Types:Sorcery
A:SP$ RemoveCounter | Cost$ B | ValidTgts$ Artifact,Creature,Planeswalker,Opponent | TgtPrompt$ Select target artifact, creature, planeswalker, or opponent. | CounterType$ Any | CounterNum$ 5 | UpTo$ True | StackDescription$ SpellDescription | SpellDescription$ Remove up to five counters from target artifact, creature, planeswalker, or opponent.
Oracle:Remove up to five counters from target artifact, creature, planeswalker, or opponent.

View File

@@ -201,7 +201,7 @@ public class HumanCostDecision extends CostDecisionMakerBase {
} }
} }
if (player.canPayLife(c) && player.getController().confirmPayment(cost, "Pay " + c + " Life?", ability)) { if (controller.confirmPayment(cost, "Do you want " + source + " to deal " + c + " damage to you?", ability)) {
return PaymentDecision.number(c); return PaymentDecision.number(c);
} }
return null; return null;
@@ -738,7 +738,7 @@ public class HumanCostDecision extends CostDecisionMakerBase {
@Override @Override
public PaymentDecision visit(final CostPutCounter cost) { public PaymentDecision visit(final CostPutCounter cost) {
final Integer c = cost.getNumberOfCounters(ability); final Integer c = cost.convertAmount();
if (cost.payCostFromSource()) { if (cost.payCostFromSource()) {
cost.setLastPaidAmount(c); cost.setLastPaidAmount(c);

View File

@@ -6,7 +6,6 @@ import forge.FThreads;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.Game; import forge.game.Game;
import forge.game.GameActionUtil; import forge.game.GameActionUtil;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.ability.effects.CharmEffect; import forge.game.ability.effects.CharmEffect;
@@ -394,58 +393,21 @@ public class HumanPlay {
} }
} }
else if (part instanceof CostDamage) { else if (part instanceof CostDamage) {
int amount = getAmountFromPartX(part, source, sourceAbility); // not a pay life but damage!
if (!p.canPayLife(amount)) { PaymentDecision pd = part.accept(hcd);
if (pd == null)
return false; return false;
} else
part.payAsDecided(p, pd, sourceAbility);
if (!p.getController().confirmPayment(part, "Do you want " + source + " to deal " + amount + " damage to you?", sourceAbility)) {
return false;
}
CardDamageMap damageMap = new CardDamageMap();
CardDamageMap preventMap = new CardDamageMap();
p.addDamage(amount, source, damageMap, preventMap, sourceAbility);
preventMap.triggerPreventDamage(false);
damageMap.triggerDamageDoneOnce(false, sourceAbility);
} }
else if (part instanceof CostPutCounter) { else if (part instanceof CostPutCounter) {
CounterType counterType = ((CostPutCounter) part).getCounter(); PaymentDecision pd = part.accept(hcd);
int amount = getAmountFromPartX(part, source, sourceAbility);
if (part.payCostFromSource()) {
if (!source.canReceiveCounters(counterType)) {
String message = TextUtil.concatNoSpace("Won't be able to pay upkeep for ", source.toString(), " but it can't have ", counterType.getName(), " counters put on it.");
p.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, message);
return false;
}
if (!p.getController().confirmPayment(part, "Do you want to put " + Lang.nounWithAmount(amount, counterType.getName() + " counter") + " on " + source + "?", sourceAbility)) { if (pd == null)
return false; return false;
} else
part.payAsDecided(p, pd, sourceAbility);
source.addCounter(counterType, amount, p, false);
}
else {
CardCollectionView list = p.getGame().getCardsIn(ZoneType.Battlefield);
list = CardLists.getValidCards(list, part.getType().split(";"), p, source, sourceAbility);
if (list.isEmpty()) { return false; }
if (!p.getController().confirmPayment(part, "Do you want to put " + Lang.nounWithAmount(amount, counterType.getName() + " counter") + " on " + part.getTypeDescription() + "?", sourceAbility)) {
return false;
}
while (amount > 0) {
InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 1, 1, list, sourceAbility);
inp.setMessage("Select a card to add a counter to");
inp.setCancelAllowed(true);
inp.showAndWait();
if (inp.hasCancelled()) {
continue;
}
Card selected = inp.getFirstSelected();
selected.addCounter(counterType, 1, p, false);
amount--;
}
}
} }
else if (part instanceof CostRemoveCounter) { else if (part instanceof CostRemoveCounter) {
CounterType counterType = ((CostRemoveCounter) part).counter; CounterType counterType = ((CostRemoveCounter) part).counter;

View File

@@ -2190,7 +2190,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
if (subtract) { if (subtract) {
card.subtractCounter(counter, count); card.subtractCounter(counter, count);
} else { } else {
card.addCounter(counter, count, card.getController(), false); card.addCounter(counter, count, card.getController(), false, null);
} }
} }