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);
Set<CounterType> types = c.getCounters().keySet();
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.)
if (c.isTapped()) {

View File

@@ -1016,7 +1016,7 @@ public abstract class GameState {
String[] allCounterStrings = counterString.split(",");
for (final String counterPair : allCounterStrings) {
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.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
@@ -354,7 +355,25 @@ public class CountersRemoveAi extends SpellAbilityAi {
*/
@Override
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);
}
@@ -370,30 +389,49 @@ public class CountersRemoveAi extends SpellAbilityAi {
return super.chooseCounterType(options, sa, params);
}
Player ai = sa.getActivatingPlayer();
Card target = (Card) params.get("Target");
GameEntity target = (GameEntity) params.get("Target");
if (target.getController().isOpponentOf(ai)) {
// if its a Planeswalker try to remove Loyality first
if (target.isPlaneswalker()) {
return CounterType.LOYALTY;
}
for (CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, target)) {
return type;
if (target instanceof Card) {
Card targetCard = (Card) target;
if (targetCard.getController().isOpponentOf(ai)) {
// if its a Planeswalker try to remove Loyality first
if (targetCard.isPlaneswalker()) {
return CounterType.LOYALTY;
}
for (CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, targetCard)) {
return type;
}
}
} else {
if (options.contains(CounterType.M1M1) && targetCard.hasKeyword(Keyword.PERSIST)) {
return CounterType.M1M1;
} else if (options.contains(CounterType.P1P1) && targetCard.hasKeyword(Keyword.UNDYING)) {
return CounterType.P1P1;
}
for (CounterType type : options) {
if (ComputerUtil.isNegativeCounter(type, targetCard)) {
return type;
}
}
}
} else {
if (options.contains(CounterType.M1M1) && target.hasKeyword(Keyword.PERSIST)) {
return CounterType.M1M1;
} else if (options.contains(CounterType.P1P1) && target.hasKeyword(Keyword.UNDYING)) {
return CounterType.M1M1;
}
for (CounterType type : options) {
if (ComputerUtil.isNegativeCounter(type, target)) {
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);
}
}

View File

@@ -247,7 +247,7 @@ public class GameAction {
// fake etb counters thing, then if something changed,
// need to apply checkStaticAbilities again
if(!noLandLKI.isLand()) {
if (noLandLKI.putEtbCounters()) {
if (noLandLKI.putEtbCounters(null)) {
// counters are added need to check again
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
// 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)
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) {
c.setDamage(0); //clear damage after a card leaves the battlefield
c.setHasBeenDealtDeathtouchDamage(false);
@@ -400,16 +423,7 @@ public class GameAction {
game.getTriggerHandler().clearInstrinsicActiveTriggers(c, zoneFrom);
game.getTriggerHandler().registerActiveTrigger(lastKnownInfo, false);
// do ETB counters after StaticAbilities check
if (!suppress) {
if (toBattlefield) {
if (copied.putEtbCounters()) {
// if counter where put of card, call checkStaticAbilities again
checkStaticAbilities();
}
}
copied.clearEtbCounters();
}
table.triggerCountersPutAll(game);
// play the change zone sound
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,
final CardDamageMap damageMap, final CardDamageMap preventMap, final SpellAbility cause) {
final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
if (noPrevention) {
return addDamageWithoutPrevention(damage, source, damageMap, preventMap, cause);
return addDamageWithoutPrevention(damage, source, damageMap, preventMap, counterTable, cause);
} else if (isCombat) {
return addCombatDamage(damage, source, damageMap, preventMap);
return addCombatDamage(damage, source, damageMap, preventMap, counterTable);
} 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,
final CardDamageMap preventMap, final SpellAbility cause) {
final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
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);
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,
final CardDamageMap preventMap) {
final CardDamageMap preventMap, GameEntityCounterTable counterTable) {
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);
if (damageToDo > 0) {
source.getDamageHistory().registerCombatDamage(this);
}
// 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) {
return addDamageAfterPrevention(damage, source, true, damageMap);
protected int addCombatDamageBase(final int damage, final Card source, CardDamageMap damageMap, GameEntityCounterTable counterTable) {
return addDamageAfterPrevention(damage, source, true, damageMap, counterTable);
}
public int addDamageWithoutPrevention(final int damage, final Card source, final CardDamageMap damageMap,
final CardDamageMap preventMap, final SpellAbility cause) {
int damageToDo = replaceDamage(damage, source, false, false, damageMap, preventMap, cause);
return addDamageAfterPrevention(damageToDo, source, false, damageMap);
final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
int damageToDo = replaceDamage(damage, source, false, false, damageMap, preventMap, counterTable, cause);
return addDamageAfterPrevention(damageToDo, source, false, damageMap, counterTable);
}
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
final Map<String, Object> repParams = Maps.newHashMap();
repParams.put("Event", "DamageDone");
@@ -122,6 +122,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
repParams.put("NoPreventDamage", !prevention);
repParams.put("DamageMap", damageMap);
repParams.put("PreventMap", preventMap);
repParams.put("CounterTable", counterTable);
if (cause != null) {
repParams.put("Cause", cause);
}
@@ -139,7 +140,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
if (prevention) {
newDamage = newTarget.preventDamage(newDamage, source, isCombat, preventMap, cause);
}
newTarget.addDamageAfterPrevention(newDamage, source, isCombat, damageMap);
newTarget.addDamageAfterPrevention(newDamage, source, isCombat, damageMap, counterTable);
}
default:
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
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
// 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 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 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;
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -62,14 +63,16 @@ public class AmassEffect extends SpellAbilityEffect {
CardCollectionView tgtCards = CardLists.getType(activator.getCardsIn(ZoneType.Battlefield), "Army");
tgtCards = pc.chooseCardsForEffect(tgtCards, sa, "Choose an army to put counters on", 1, 1, false);
GameEntityCounterTable table = new GameEntityCounterTable();
for(final Card tgtCard : tgtCards) {
tgtCard.addCounter(CounterType.P1P1, amount, activator, true);
tgtCard.addCounter(CounterType.P1P1, amount, activator, true, table);
game.updateLastStateForCard(tgtCard);
if (remember) {
card.addRemembered(tgtCard);
}
}
table.triggerCountersPutAll(game);
}
}

View File

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

View File

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

View File

@@ -2,9 +2,10 @@ package forge.game.ability.effects;
import java.util.Map.Entry;
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -20,16 +21,19 @@ public class CountersNoteEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
Card source = sa.getHostCard();
CardCollection tgtCards = new CardCollection();
tgtCards.addAll(getDefinedCardsOrTargeted(sa));
final Game game = source.getGame();
Player p = sa.getActivatingPlayer();
String mode = sa.getParamOrDefault("Mode", "Load");
for (Card c : tgtCards) {
GameEntityCounterTable table = new GameEntityCounterTable();
for (Card c : getDefinedCardsOrTargeted(sa)) {
if (mode.equals(MODE_STORE)) {
noteCounters(c, source);
} 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) {
@@ -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()) {
String key = svar.getKey();
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
}

View File

@@ -2,6 +2,7 @@ package forge.game.ability.effects;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
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,
"Choose any number of permanents and/or players for proliferate", p);
GameEntityCounterTable table = new GameEntityCounterTable();
for (final GameEntity ge : result) {
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) {
Card c = (Card) ge;
game.updateLastStateForCard(c);
}
}
table.triggerCountersPutAll(game);
}
}

View File

@@ -1,6 +1,7 @@
package forge.game.ability.effects;
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -63,15 +64,13 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
placer = AbilityUtils.getDefinedPlayers(host, pstr, sa).get(0);
}
GameEntityCounterTable table = new GameEntityCounterTable();
for (final Card tgtCard : cards) {
if (game.getZoneOf(tgtCard).is(ZoneType.Battlefield)) {
tgtCard.addCounter(CounterType.valueOf(type), counterAmount, placer, true);
} else {
// adding counters to something like re-suspend cards
tgtCard.addCounter(CounterType.valueOf(type), counterAmount, placer, false);
}
boolean inBattlefield = game.getZoneOf(tgtCard).is(ZoneType.Battlefield);
tgtCard.addCounter(CounterType.valueOf(type), counterAmount, placer, inBattlefield, table);
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.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
@@ -133,6 +134,8 @@ public class CountersPutEffect extends SpellAbilityEffect {
tgtObjects.addAll(getDefinedOrTargeted(sa, "Defined"));
}
GameEntityCounterTable table = new GameEntityCounterTable();
for (final GameObject obj : tgtObjects) {
// check if the object is still in game or if it was moved
Card gameCard = null;
@@ -162,10 +165,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
if (eachExistingCounter) {
for(CounterType ct : choices) {
if (obj instanceof Player) {
((Player) obj).addCounter(ct, counterAmount, placer, true);
((Player) obj).addCounter(ct, counterAmount, placer, true, table);
}
if (obj instanceof Card) {
gameCard.addCounter(ct, counterAmount, placer, true);
gameCard.addCounter(ct, counterAmount, placer, true, table);
}
}
continue;
@@ -249,7 +252,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
if (etbcounter) {
tgtCard.addEtbCounter(counterType, counterAmount, placer);
} else {
tgtCard.addCounter(counterType, counterAmount, placer, true);
tgtCard.addCounter(counterType, counterAmount, placer, true, table);
}
if (remember) {
final int value = tgtCard.getTotalCountersToAdd();
@@ -287,7 +290,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
if (etbcounter) {
tgtCard.addEtbCounter(counterType, counterAmount, placer);
} else {
tgtCard.addCounter(counterType, counterAmount, placer, false);
tgtCard.addCounter(counterType, counterAmount, placer, false, table);
}
}
game.updateLastStateForCard(tgtCard);
@@ -295,9 +298,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
} else if (obj instanceof Player) {
// Add Counters to players!
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;
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -57,6 +58,8 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
ctype = CounterType.valueOf(sa.getParam("CounterType"));
}
GameEntityCounterTable table = new GameEntityCounterTable();
for (final Card tgtCard : getDefinedCardsOrTargeted(sa)) {
Card gameCard = game.getCardState(tgtCard, null);
// 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 (sa.hasParam("EachExistingCounter")) {
for (CounterType listType : Lists.newArrayList(gameCard.getCounters().keySet())) {
addOrRemoveCounter(sa, gameCard, listType, counterAmount);
addOrRemoveCounter(sa, gameCard, listType, counterAmount, table);
}
} else {
addOrRemoveCounter(sa, gameCard, ctype, counterAmount);
addOrRemoveCounter(sa, gameCard, ctype, counterAmount, table);
}
game.updateLastStateForCard(gameCard);
}
}
}
table.triggerCountersPutAll(game);
}
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 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);
tgtCard.addCounter(chosenType, counterAmount, pl, apply);
tgtCard.addCounter(chosenType, counterAmount, pl, apply, table);
} else {
tgtCard.subtractCounter(chosenType, counterAmount);
}

View File

@@ -1,6 +1,7 @@
package forge.game.ability.effects;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -106,10 +107,20 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
for (final Player tgtPlayer : getTargetPlayers(sa)) {
// Removing energy
if (!sa.usesTargeting() || tgtPlayer.canBeTargetedBy(sa)) {
if (num.equals("All")) {
cntToRemove = tgtPlayer.getCounters(counterType);
if (type.equals("All")) {
for (Map.Entry<CounterType, Integer> e : tgtPlayer.getCounters().entrySet()) {
tgtPlayer.subtractCounter(e.getKey(), e.getValue());
}
} else {
if (num.equals("All")) {
cntToRemove = tgtPlayer.getCounters(counterType);
}
if (type.equals("Any")) {
removeAnyType(tgtPlayer, cntToRemove, sa);
} else {
tgtPlayer.subtractCounter(counterType, cntToRemove);
}
}
tgtPlayer.subtractCounter(counterType, cntToRemove);
}
}
@@ -152,32 +163,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
}
if (type.equals("Any")) {
while (cntToRemove > 0 && gameCard.hasCounters()) {
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;
}
}
removeAnyType(gameCard, cntToRemove, sa);
} else {
cntToRemove = Math.min(cntToRemove, gameCard.getCounters(counterType));
@@ -189,7 +175,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
String title = "Select the number of " + type + " counters to remove";
cntToRemove = pc.chooseNumber(sa, title, 0, cntToRemove, params);
}
}
if (cntToRemove > 0) {
gameCard.subtractCounter(counterType, cntToRemove);
@@ -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.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -88,6 +89,7 @@ public class DamageAllEffect extends DamageBaseEffect {
boolean usedDamageMap = true;
CardDamageMap damageMap = sa.getDamageMap();
CardDamageMap preventMap = sa.getPreventMap();
GameEntityCounterTable counterTable = new GameEntityCounterTable();
if (damageMap == null) {
// make a new damage map
@@ -97,13 +99,13 @@ public class DamageAllEffect extends DamageBaseEffect {
}
for (final Card c : list) {
c.addDamage(dmg, sourceLKI, damageMap, preventMap, sa);
c.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
}
if (!players.equals("")) {
final List<Player> playerList = AbilityUtils.getDefinedPlayers(card, players, sa);
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();
}
counterTable.triggerCountersPutAll(game);
replaceDying(sa);
}
}

View File

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

View File

@@ -1,5 +1,7 @@
package forge.game.ability.effects;
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -60,8 +62,9 @@ public class DamageEachEffect extends DamageBaseEffect {
@Override
public void resolve(SpellAbility sa) {
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")) {
sources = CardLists.getValidCards(sources, sa.getParam("ValidCards"), card.getController(), card);
}
@@ -73,6 +76,7 @@ public class DamageEachEffect extends DamageBaseEffect {
boolean usedDamageMap = true;
CardDamageMap damageMap = sa.getDamageMap();
CardDamageMap preventMap = sa.getPreventMap();
GameEntityCounterTable counterTable = new GameEntityCounterTable();
if (damageMap == null) {
// make a new damage map
@@ -83,21 +87,22 @@ public class DamageEachEffect extends DamageBaseEffect {
for (final Object o : tgts) {
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"));
// System.out.println(source+" deals "+dmg+" damage to "+o.toString());
if (o instanceof Card) {
final Card c = (Card) o;
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) {
final Player p = (Player) o;
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.getParam("DefinedCards").equals("Self")) {
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"));
// 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")) {
@@ -122,7 +127,7 @@ public class DamageEachEffect extends DamageBaseEffect {
if (o instanceof Card) {
Card rememberedcard = (Card) o;
// 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();
}
counterTable.triggerCountersPutAll(game);
replaceDying(sa);
}
}

View File

@@ -2,6 +2,7 @@ package forge.game.ability.effects;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -110,6 +111,7 @@ public class DigEffect extends SpellAbilityEffect {
}
CardZoneTable table = new CardZoneTable();
GameEntityCounterTable counterTable = new GameEntityCounterTable();
for (final Player p : tgtPlayers) {
if (tgt != null && !p.canBeTargetedBy(sa)) {
continue;
@@ -378,7 +380,8 @@ public class DigEffect extends SpellAbilityEffect {
}
} else if (destZone2 == ZoneType.Exile) {
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);
}
@@ -389,6 +392,7 @@ public class DigEffect extends SpellAbilityEffect {
}
//table trigger there
table.triggerChangesZoneAll(game);
counterTable.triggerCountersPutAll(game);
}
// 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.Maps;
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -47,9 +48,9 @@ public class ExploreEffect extends SpellAbilityEffect {
final Player pl = sa.getActivatingPlayer();
final PlayerController pc = pl.getController();
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
boolean revealedLand = false;
CardCollection top = pl.getTopXCardsFromLibrary(1);
@@ -76,8 +77,8 @@ public class ExploreEffect extends SpellAbilityEffect {
Card gamec = game.getCardState(c);
// if the card is not more in the game anymore
// this might still return true but its no problem
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.getTimestamp() == c.getTimestamp()) {
c.addCounter(CounterType.P1P1, 1, pl, true);
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(c)) {
c.addCounter(CounterType.P1P1, 1, pl, true, table);
}
}
@@ -86,6 +87,7 @@ public class ExploreEffect extends SpellAbilityEffect {
runParams.put("Card", c);
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 forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardDamageMap;
@@ -130,6 +131,7 @@ public class FightEffect extends DamageBaseEffect {
boolean usedDamageMap = true;
CardDamageMap damageMap = sa.getDamageMap();
CardDamageMap preventMap = sa.getPreventMap();
GameEntityCounterTable counterTable = new GameEntityCounterTable();
if (damageMap == null) {
// make a new damage map
@@ -142,12 +144,12 @@ public class FightEffect extends DamageBaseEffect {
final int dmg1 = fightToughness ? fighterA.getNetToughness() : fighterA.getNetPower();
if (fighterA.equals(fighterB)) {
fighterA.addDamage(dmg1 * 2, fighterA, damageMap, preventMap, sa);
fighterA.addDamage(dmg1 * 2, fighterA, damageMap, preventMap, counterTable, sa);
} else {
final int dmg2 = fightToughness ? fighterB.getNetToughness() : fighterB.getNetPower();
fighterB.addDamage(dmg1, fighterA, damageMap, preventMap, sa);
fighterA.addDamage(dmg2, fighterB, damageMap, preventMap, sa);
fighterB.addDamage(dmg1, fighterA, damageMap, preventMap, counterTable, sa);
fighterA.addDamage(dmg2, fighterB, damageMap, preventMap, counterTable, sa);
}
if (!usedDamageMap) {
@@ -157,6 +159,7 @@ public class FightEffect extends DamageBaseEffect {
preventMap.clear();
damageMap.clear();
}
counterTable.triggerCountersPutAll(sa.getHostCard().getGame());
replaceDying(sa);
}

View File

@@ -1,7 +1,10 @@
package forge.game.ability.effects;
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -21,17 +24,21 @@ import java.util.List;
*/
@Override
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)) {
if ((!sa.usesTargeting()) || p.canBeTargetedBy(sa)) {
if (amount >= 0) {
p.addPoisonCounters(amount, sa.getHostCard());
p.addPoisonCounters(amount, host, table);
} else {
p.removePoisonCounters(-amount, sa.getHostCard());
p.removePoisonCounters(-amount, host);
}
}
}
table.triggerCountersPutAll(game);
}
/* (non-Javadoc)

View File

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

View File

@@ -9,6 +9,7 @@ import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
@@ -57,6 +58,7 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect {
CardDamageMap damageMap = (CardDamageMap) originalParams.get("DamageMap");
CardDamageMap preventMap = (CardDamageMap) originalParams.get("PreventMap");
GameEntityCounterTable counterTable = (GameEntityCounterTable) originalParams.get("CounterTable");
SpellAbility cause = (SpellAbility) originalParams.get("Cause");
boolean isCombat = (Boolean) originalParams.get("IsCombat");
@@ -64,7 +66,7 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect {
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
@@ -74,7 +76,6 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect {
}
params.put("DamageAmount", dmg);
//try to call replacementHandler with new Params
ReplacementResult result = game.getReplacementHandler().run(params);
switch (result) {

View File

@@ -4,6 +4,7 @@ import com.google.common.collect.Maps;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.*;
@@ -43,7 +44,11 @@ public class SacrificeEffect extends SpellAbilityEffect {
return;
}
} 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 payCost = new Cost(ManaCost.ZERO, true);
int n = card.getCounters(CounterType.AGE);

View File

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

View File

@@ -1131,16 +1131,18 @@ public class Card extends GameEntity implements Comparable<Card> {
@Override
public final boolean canReceiveCounters(final CounterType type) {
if (hasKeyword("CARDNAME can't have counters put on it.")) {
return false;
}
if (isCreature() && type == CounterType.M1M1) {
for (final Card c : getController().getCreaturesInPlay()) { // look for Melira, Sylvok Outcast
if (c.hasKeyword("Creatures you control can't have -1/-1 counters put on them.")) {
// 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;
}
}
} 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) {
return false;
}
@@ -1156,17 +1158,17 @@ public class Card extends GameEntity implements Comparable<Card> {
countersAdded = value;
}
public final int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier) {
return addCounter(counterType, n, source, applyMultiplier, true);
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, table);
}
public final int addCounterFireNoEvents(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier) {
return addCounter(counterType, n, source, applyMultiplier, false);
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, table);
}
@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;
if(addAmount < 0) {
if(addAmount <= 0) {
addAmount = 0; // As per rule 107.1b
return 0;
}
@@ -1245,6 +1247,9 @@ public class Card extends GameEntity implements Comparable<Card> {
getController().addCounterToPermThisTurn(counterType, addAmount);
view.updateCounters(this);
}
if (table != null) {
table.put(this, counterType, addAmount);
}
return addAmount;
}
@@ -4622,15 +4627,20 @@ public class Card extends GameEntity implements Comparable<Card> {
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()) {
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()) {
return super.addCombatDamageBase(damage, source, damageMap);
return super.addCombatDamageBase(damage, source, damageMap, counterTable);
}
return 0;
}
@@ -4857,10 +4867,10 @@ public class Card extends GameEntity implements Comparable<Card> {
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()) {
// 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.
*/
@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) {
return 0; // Rule 119.8
@@ -4903,7 +4913,7 @@ public class Card extends GameEntity implements Comparable<Card> {
if (isInPlay()) {
if (wither) {
addCounter(CounterType.M1M1, damageIn, source.getController(), true);
addCounter(CounterType.M1M1, damageIn, source.getController(), true, counterTable);
damageType = DamageType.M1M1Counters;
}
else {
@@ -6052,7 +6062,7 @@ public class Card extends GameEntity implements Comparable<Card> {
return etbCounters.cellSet();
}
public final boolean putEtbCounters() {
public final boolean putEtbCounters(GameEntityCounterTable table) {
boolean changed = false;
for (Table.Cell<Player, CounterType, Integer> e : etbCounters.cellSet()) {
CounterType ct = e.getColumnKey();
@@ -6062,7 +6072,7 @@ public class Card extends GameEntity implements Comparable<Card> {
changed = true;
}
} else {
changed |= addCounter(ct, e.getValue(), e.getRowKey(), true) > 0;
changed |= addCounter(ct, e.getValue(), e.getRowKey(), true, table) > 0;
}
}
return changed;

View File

@@ -32,6 +32,7 @@ import forge.card.mana.ManaCostParser;
import forge.card.mana.ManaCostShard;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
@@ -4277,7 +4278,9 @@ public class CardFactoryUtil {
final Card c = game.getAction().exile(this.getHostCard(), 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.");
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 forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameEntityCounterTable;
import forge.game.GameLogEntryType;
import forge.game.GameObjectMap;
import forge.game.card.Card;
@@ -783,18 +784,20 @@ public class Combat {
}
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
for (final Entry<Card, Integer> entry : defendingDamageMap.entrySet()) {
GameEntity defender = getDefenderByAttacker(entry.getKey());
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
((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;
}
c.addCombatDamage(c.getAssignedDamageMap(), dealtDamageTo, preventMap);
c.addCombatDamage(c.getAssignedDamageMap(), dealtDamageTo, preventMap, counterTable);
c.clearAssignedDamage();
}
@@ -823,6 +826,9 @@ public class Combat {
// LifeLink for Combat Damage at this place
dealtDamageTo.triggerDamageDoneOnce(true, null);
dealtDamageTo.clear();
counterTable.triggerCountersPutAll(game);
counterTable.clear();
}
public final boolean isUnblocked(final Card att) {

View File

@@ -17,6 +17,7 @@
*/
package forge.game.cost;
import forge.game.GameEntityCounterTable;
import forge.game.card.Card;
import forge.game.card.CardDamageMap;
import forge.game.player.Player;
@@ -47,7 +48,7 @@ public class CostDamage extends CostPart {
@Override
public final String toString() {
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();
}
@@ -68,14 +69,17 @@ public class CostDamage extends CostPart {
final Card source = sa.getHostCard();
CardDamageMap damageMap = 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);
damageMap.triggerDamageDoneOnce(false, sa);
table.triggerCountersPutAll(payer.getGame());
preventMap.clear();
damageMap.clear();
table.clear();
return decision.c > 0;
}

View File

@@ -53,7 +53,7 @@ public abstract class CostPartWithList extends CostPart {
/**
* Reset list.
*/
public final void resetLists() {
public void resetLists() {
lkiList.clear();
cardList.clear();
table.clear();
@@ -121,7 +121,7 @@ public abstract class CostPartWithList extends CostPart {
}
// 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.
lkiList.addAll(targetCards);
cardList.addAll(doListPayment(ability, targetCards));

View File

@@ -6,18 +6,18 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.game.cost;
import forge.game.ability.AbilityUtils;
import forge.game.GameEntityCounterTable;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
@@ -40,13 +40,15 @@ public class CostPutCounter extends CostPartWithList {
private final CounterType counter;
private int lastPaidAmount = 0;
private final GameEntityCounterTable counterTable = new GameEntityCounterTable();
public final CounterType getCounter() {
return this.counter;
}
/**
* Sets the last paid amount.
*
*
* @param paidAmount
* the new last paid amount
*/
@@ -56,7 +58,7 @@ public class CostPutCounter extends CostPartWithList {
/**
* Instantiates a new cost put counter.
*
*
* @param amount
* the amount
* @param cntr
@@ -73,7 +75,7 @@ public class CostPutCounter extends CostPartWithList {
@Override
public int paymentOrder() { return 8; }
@Override
public boolean isReusable() {
return counter != CounterType.M1M1;
@@ -81,7 +83,7 @@ public class CostPutCounter extends CostPartWithList {
/*
* (non-Javadoc)
*
*
* @see forge.card.cost.CostPart#toString()
*/
@Override
@@ -112,22 +114,24 @@ public class CostPutCounter extends CostPartWithList {
/*
* (non-Javadoc)
*
*
* @see forge.card.cost.CostPart#refund(forge.Card)
*/
@Override
public final void refund(final Card source) {
if(this.payCostFromSource())
source.subtractCounter(this.counter, this.lastPaidAmount);
else
else {
final Integer i = this.convertAmount();
for (final Card c : this.getCardList()) {
c.subtractCounter(this.counter, 1);
c.subtractCounter(this.counter, i);
}
}
}
/*
* (non-Javadoc)
*
*
* @see
* forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility,
* forge.Card, forge.Player, forge.card.cost.Cost)
@@ -143,7 +147,7 @@ public class CostPutCounter extends CostPartWithList {
// 3 Cards have Put a -1/-1 Counter on a Creature you control.
List<Card> typeList = CardLists.getValidCards(source.getGame().getCardsIn(ZoneType.Battlefield),
this.getType().split(";"), payer, source, ability);
typeList = CardLists.filter(typeList, CardPredicates.canReceiveCounters(this.counter));
if (typeList.isEmpty()) {
@@ -156,55 +160,30 @@ public class CostPutCounter extends CostPartWithList {
/*
* (non-Javadoc)
*
*
* @see forge.card.cost.CostPart#payAI(forge.card.spellability.SpellAbility,
* forge.Card, forge.card.cost.Cost_Payment)
*/
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
Integer c = getNumberOfCounters(ability);
if (this.payCostFromSource()) {
executePayment(ability, ability.getHostCard(), c);
executePayment(ability, ability.getHostCard());
} else {
// Put counter on chosen card
for (int i = 0; i < c; i++)
executePayment(ability, decision.cards);
ability.getHostCard().setSVar("CostCountersAdded", Integer.toString(Math.min(decision.cards.size(), c)));
executePayment(ability, decision.cards);
}
triggerCounterPutAll(ability);
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)
* @see forge.card.cost.CostPartWithList#executePayment(forge.card.spellability.SpellAbility, forge.Card)
*/
@Override
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;
}
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
public String getHashForLKIList() {
@@ -219,4 +198,24 @@ public class CostPutCounter extends CostPartWithList {
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) {
int refund = this.getCardList().size() == 1 ? this.cntRemoved : 1; // is wrong for Ooze Flux and Novijen Sages
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()) {
playerTurn.setSchemeInMotion();
}
GameEntityCounterTable table = new GameEntityCounterTable();
// all Saga get Lore counter at the begin of pre combat
for (Card c : playerTurn.getCardsIn(ZoneType.Battlefield)) {
if (c.getType().hasSubtype("Saga")) {
c.addCounter(CounterType.LORE, 1, null, false);
c.addCounter(CounterType.LORE, 1, null, false, table);
}
}
table.triggerCountersPutAll(game);
}
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
@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) {
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.");
if (infect) {
addPoisonCounters(amount, source);
addPoisonCounters(amount, source, counterTable);
}
else {
// 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) {
if (hasKeyword("PLAYER can't have counters put on him or her.")) {
return false;
}
if (type == CounterType.POISON) {
if (hasKeyword("You can't get poison counters")) {
return false;
// 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 true;
}
public final int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier) {
return addCounter(counterType, n, source, applyMultiplier, true);
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, table);
}
@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)) {
return 0;
}
@@ -942,6 +942,9 @@ public class Player extends GameEntity implements Comparable<Player> {
if (addAmount > 0) {
getGame().getTriggerHandler().runTrigger(TriggerType.CounterAddedOnce, runParams, false);
}
if (table != null) {
table.put(this, counterType, addAmount);
}
return addAmount;
}
@@ -999,9 +1002,9 @@ public class Player extends GameEntity implements Comparable<Player> {
setCounters(CounterType.POISON, num, true);
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);
addCounter(CounterType.POISON, num, source.getController(), false, true);
addCounter(CounterType.POISON, num, source.getController(), false, true, table);
if (oldPoison != getCounters(CounterType.POISON)) {
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.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
@@ -386,6 +387,43 @@ public class StaticAbility extends CardTraitBase implements Comparable<StaticAbi
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.

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),
CounterAdded(TriggerCounterAdded.class),
CounterAddedOnce(TriggerCounterAddedOnce.class),
CounterAddedAll(TriggerCounterAddedAll.class),
Countered(TriggerCountered.class),
CounterRemoved(TriggerCounterRemoved.class),
CounterRemovedOnce(TriggerCounterRemovedOnce.class),

View File

@@ -217,7 +217,7 @@ public class GameSimulatorTest extends SimulationTestCase {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
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.getAction().checkStateEffects(true);
@@ -261,7 +261,7 @@ public class GameSimulatorTest extends SimulationTestCase {
String bearCardName = "Runeclaw Bear";
addCard(bearCardName, 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.getAction().checkStateEffects(true);
@@ -380,7 +380,7 @@ public class GameSimulatorTest extends SimulationTestCase {
Game game = initAndCreateGame();
Player p = game.getPlayers().get(1);
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.getAction().checkStateEffects(true);
@@ -414,7 +414,7 @@ public class GameSimulatorTest extends SimulationTestCase {
addCard(ornithoperCardName, p);
addCard(bearCardName, 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.getAction().checkStateEffects(true);
@@ -445,7 +445,7 @@ public class GameSimulatorTest extends SimulationTestCase {
SpellAbility boltSA = boltCard.getFirstSpellAbility();
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.getAction().checkStateEffects(true);
@@ -494,7 +494,7 @@ public class GameSimulatorTest extends SimulationTestCase {
addCard("Swamp", p);
addCard("Swamp", 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);
game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p);
game.getAction().checkStateEffects(true);

View File

@@ -1,12 +1,12 @@
Name:Hungering Hydra
ManaCost:X G
Types:Creature Hydra
PT:0/0
K:CantBeBlockedByAmount GT1
K:etbCounter:P1P1:X
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.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ Y | References$ Y
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.)
PT:0/0

View File

@@ -2,8 +2,8 @@ Name:Melira, Sylvok Outcast
ManaCost:1 G
Types:Legendary Creature Human Scout
PT:2/2
K:Creatures you control can't have -1/-1 counters put on them.
S:Mode$ Continuous | Affected$ You | AddKeyword$ You can't get poison counters | Description$ You can't get poison counters.
S:Mode$ CantPutCounter | ValidPlayer$ You | CounterType$ POISON | 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.YouCtrl+withPersist | AddSVar$ Sac
SVar:Sac:SVar:SacMe:3

View File

@@ -2,6 +2,6 @@ Name:Melira's Keepers
ManaCost:4 G
Types:Creature Human Warrior
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
Oracle:Melira's Keepers can't have counters put on it.

View File

@@ -1,7 +1,7 @@
Name:Solemnity
ManaCost:2 W
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$ 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 | ValidPlayer$ Player | Description$ Players can't get counters.
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
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
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: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: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:DBPumpCreature:DB$ Pump | Defined$ Targeted | KW$ CARDNAME can't have counters put on it. | UntilLoseControlOfHost$ True
SVar:DBPumpOpponent:DB$ Pump | Defined$ Targeted | KW$ PLAYER can't have counters put on him or her. | UntilLoseControlOfHost$ True
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.
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 | 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$ Effect | StaticAbilities$ DBCantPutCounterCreature | RememberObjects$ Targeted | Duration$ UntilHostLeavesPlay | References$ DBCantPutCounterCreature
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.

View File

@@ -3,6 +3,6 @@ ManaCost:3
Types:Artifact Creature Scarecrow
PT:2/1
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
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 null;
@@ -738,7 +738,7 @@ public class HumanCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(final CostPutCounter cost) {
final Integer c = cost.getNumberOfCounters(ability);
final Integer c = cost.convertAmount();
if (cost.payCostFromSource()) {
cost.setLastPaidAmount(c);

View File

@@ -6,7 +6,6 @@ import forge.FThreads;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.CharmEffect;
@@ -394,58 +393,21 @@ public class HumanPlay {
}
}
else if (part instanceof CostDamage) {
int amount = getAmountFromPartX(part, source, sourceAbility);
if (!p.canPayLife(amount)) {
// not a pay life but damage!
PaymentDecision pd = part.accept(hcd);
if (pd == null)
return false;
}
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
part.payAsDecided(p, pd, sourceAbility);
}
else if (part instanceof CostPutCounter) {
CounterType counterType = ((CostPutCounter) part).getCounter();
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;
}
PaymentDecision pd = part.accept(hcd);
if (!p.getController().confirmPayment(part, "Do you want to put " + Lang.nounWithAmount(amount, counterType.getName() + " counter") + " on " + source + "?", sourceAbility)) {
return false;
}
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--;
}
}
if (pd == null)
return false;
else
part.payAsDecided(p, pd, sourceAbility);
}
else if (part instanceof CostRemoveCounter) {
CounterType counterType = ((CostRemoveCounter) part).counter;

View File

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