- Counters moved from being on Card to on GameEntity allowing Poison + Experience Counters to work the same as other counter types

- Improved some PlayerView updates which weren't re-writing player detail hoverbox often enough
This commit is contained in:
Sol
2015-11-18 20:19:32 +00:00
parent 83cd79e705
commit 5527f6fc6d
16 changed files with 323 additions and 123 deletions

View File

@@ -20,6 +20,7 @@ package forge.game;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CounterType;
import forge.game.event.GameEventCardAttachment;
import forge.game.event.GameEventCardAttachment.AttachMethod;
import forge.util.collect.FCollection;
@@ -34,6 +35,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
private int preventNextDamage = 0;
private CardCollection enchantedBy;
private Map<Card, Map<String, String>> preventionShieldsWithEffects = new TreeMap<Card, Map<String, String>>();
protected Map<CounterType, Integer> counters = new TreeMap<>();
protected GameEntity(int id0) {
id = id0;
@@ -198,6 +200,33 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
public abstract boolean hasProtectionFrom(final Card source);
// Counters!
public boolean hasCounters() {
return !counters.isEmpty();
}
// get all counters from a card
public final Map<CounterType, Integer> getCounters() {
return counters;
}
public final int getCounters(final CounterType counterName) {
Integer value = counters.get(counterName);
return value == null ? 0 : value;
}
public void setCounters(final CounterType counterType, final Integer num) {
counters.put(counterType, num);
}
abstract public void setCounters(final Map<CounterType, Integer> allCounters);
abstract public boolean canReceiveCounters(final CounterType type);
abstract protected void addCounter(final CounterType counterType, final int n, final boolean applyMultiplier, final boolean fireEvents);
abstract public void subtractCounter(final CounterType counterName, final int n);
abstract public void clearCounters();
@Override
public final boolean equals(Object o) {
if (o == null) { return false; }

View File

@@ -29,7 +29,7 @@ public class CountersProliferateEffect extends SpellAbilityEffect {
return;
for(Entry<GameEntity, CounterType> ge: proliferateChoice.entrySet()) {
if( ge.getKey() instanceof Player )
((Player) ge.getKey()).addPoisonCounters(1, sa.getHostCard());
((Player) ge.getKey()).addCounter(ge.getValue(), 1, true);
else if( ge.getKey() instanceof Card)
((Card) ge.getKey()).addCounter(ge.getValue(), 1, true);
}

View File

@@ -1,5 +1,7 @@
package forge.game.ability.effects;
import com.google.common.collect.Lists;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -95,63 +97,72 @@ public class CountersPutEffect extends SpellAbilityEffect {
}
CardCollection tgtCards = new CardCollection();
List<GameObject> tgtObjects = Lists.newArrayList();
if (sa.hasParam("Bolster")) {
CardCollection creatsYouCtrl = CardLists.filter(activator.getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
CardCollection leastToughness = new CardCollection(Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetDefense));
tgtCards.addAll(activator.getController().chooseCardsForEffect(leastToughness, sa, "Choose a creature with the least toughness", 1, 1, false));
tgtObjects.addAll(tgtCards);
} else {
tgtCards.addAll(getDefinedCardsOrTargeted(sa));
tgtObjects.addAll(getDefinedOrTargeted(sa, "Defined"));
}
for (final Card tgtCard : tgtCards) {
counterAmount = sa.usesTargeting() && sa.hasParam("DividedAsYouChoose") ? sa.getTargetRestrictions().getDividedValue(tgtCard) : counterAmount;
if (!sa.usesTargeting() || tgtCard.canBeTargetedBy(sa)) {
if (max != -1) {
counterAmount = max - tgtCard.getCounters(counterType);
}
if (sa.hasParam("Tribute")) {
String message = "Do you want to put " + tgtCard.getKeywordMagnitude("Tribute") + " +1/+1 counters on " + tgtCard + " ?";
Player chooser = activator.getController().chooseSingleEntityForEffect(activator.getOpponents(), sa, "Choose an opponent");
if (chooser.getController().confirmAction(sa, PlayerActionConfirmMode.Tribute, message)) {
tgtCard.setTributed(true);
} else {
continue;
for (final GameObject obj : tgtObjects) {
if (obj instanceof Card) {
Card tgtCard = (Card) obj;
counterAmount = sa.usesTargeting() && sa.hasParam("DividedAsYouChoose") ? sa.getTargetRestrictions().getDividedValue(tgtCard) : counterAmount;
if (!sa.usesTargeting() || tgtCard.canBeTargetedBy(sa)) {
if (max != -1) {
counterAmount = max - tgtCard.getCounters(counterType);
}
}
if (rememberCards) {
card.addRemembered(tgtCard);
}
final Zone zone = tgtCard.getGame().getZoneOf(tgtCard);
if (zone == null || zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Stack)) {
tgtCard.addCounter(counterType, counterAmount, true);
if (remember) {
final int value = tgtCard.getTotalCountersToAdd();
tgtCard.addCountersAddedBy(card, counterType, value);
if (sa.hasParam("Tribute")) {
String message = "Do you want to put " + tgtCard.getKeywordMagnitude("Tribute") + " +1/+1 counters on " + tgtCard + " ?";
Player chooser = activator.getController().chooseSingleEntityForEffect(activator.getOpponents(), sa, "Choose an opponent");
if (chooser.getController().confirmAction(sa, PlayerActionConfirmMode.Tribute, message)) {
tgtCard.setTributed(true);
} else {
continue;
}
}
if (rememberCards) {
card.addRemembered(tgtCard);
}
final Zone zone = tgtCard.getGame().getZoneOf(tgtCard);
if (zone == null || zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Stack)) {
tgtCard.addCounter(counterType, counterAmount, true);
if (remember) {
final int value = tgtCard.getTotalCountersToAdd();
tgtCard.addCountersAddedBy(card, counterType, value);
}
if (sa.hasParam("Evolve")) {
final HashMap<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Card", tgtCard);
tgtCard.getController().getGame().getTriggerHandler().runTrigger(TriggerType.Evolved, runParams, false);
if (sa.hasParam("Evolve")) {
final HashMap<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Card", tgtCard);
tgtCard.getController().getGame().getTriggerHandler().runTrigger(TriggerType.Evolved, runParams, false);
}
if (sa.hasParam("Monstrosity")) {
tgtCard.setMonstrous(true);
tgtCard.setMonstrosityNum(counterAmount);
final HashMap<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Card", tgtCard);
tgtCard.getController().getGame().getTriggerHandler().runTrigger(TriggerType.BecomeMonstrous, runParams, false);
}
if (sa.hasParam("Renown")) {
tgtCard.setRenowned(true);
final HashMap<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Card", tgtCard);
tgtCard.getController().getGame().getTriggerHandler().runTrigger(TriggerType.BecomeRenowned, runParams, false);
}
} else {
// adding counters to something like re-suspend cards
// etbcounter should apply multiplier
tgtCard.addCounter(counterType, counterAmount, etbcounter);
}
if (sa.hasParam("Monstrosity")) {
tgtCard.setMonstrous(true);
tgtCard.setMonstrosityNum(counterAmount);
final HashMap<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Card", tgtCard);
tgtCard.getController().getGame().getTriggerHandler().runTrigger(TriggerType.BecomeMonstrous, runParams, false);
}
if (sa.hasParam("Renown")) {
tgtCard.setRenowned(true);
final HashMap<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Card", tgtCard);
tgtCard.getController().getGame().getTriggerHandler().runTrigger(TriggerType.BecomeRenowned, runParams, false);
}
} else {
// adding counters to something like re-suspend cards
// etbcounter should apply multiplier
tgtCard.addCounter(counterType, counterAmount, etbcounter);
}
} else if (obj instanceof Player) {
// Add Counters to players!
Player pl = (Player) obj;
pl.addCounter(counterType, counterAmount, true);
}
}
}

View File

@@ -92,7 +92,6 @@ public class Card extends GameEntity implements Comparable<Card> {
private ZoneType castFrom = null;
private final CardDamageHistory damageHistory = new CardDamageHistory();
private Map<CounterType, Integer> counters = new TreeMap<>();
private Map<Card, Map<CounterType, Integer>> countersAddedBy = new TreeMap<>();
private List<String> extrinsicKeyword = new ArrayList<>();
// Hidden keywords won't be displayed on the card
@@ -911,7 +910,7 @@ public class Card extends GameEntity implements Comparable<Card> {
addCounter(counterType, n, applyMultiplier, false);
}
private void addCounter(final CounterType counterType, final int n, final boolean applyMultiplier, final boolean fireEvents) {
protected void addCounter(final CounterType counterType, final int n, final boolean applyMultiplier, final boolean fireEvents) {
int addAmount = n;
if(addAmount < 0) {
addAmount = 0; // As per rule 107.1b
@@ -1043,20 +1042,6 @@ public class Card extends GameEntity implements Comparable<Card> {
}
}
public final int getCounters(final CounterType counterName) {
Integer value = counters.get(counterName);
return value == null ? 0 : value;
}
// get all counters from a card
public final Map<CounterType, Integer> getCounters() {
return counters;
}
public final boolean hasCounters() {
return !counters.isEmpty();
}
public final void setCounters(final Map<CounterType, Integer> allCounters) {
counters = allCounters;
view.updateCounters(this);

View File

@@ -1158,6 +1158,12 @@ public class CardFactoryUtil {
return doXMath(c.getTotalDamageRecievedThisTurn(), m, c);
}
if (sq[0].startsWith("YourCounters")) {
// "YourCountersExperience" or "YourCountersPoison"
String counterType = sq[0].substring(12);
return doXMath(cc.getCounters(CounterType.getType(counterType)), m, c);
}
if (sq[0].contains("YourPoisonCounters")) {
return doXMath(cc.getPoisonCounters(), m, c);
}

View File

@@ -268,7 +268,13 @@ public enum CounterType {
WIND(),
WISH();
WISH(),
// Player Counters
EXPERIENCE(),
POISON();
private String name;

View File

@@ -0,0 +1,23 @@
package forge.game.event;
import forge.game.card.CounterType;
import forge.game.player.Player;
public class GameEventPlayerCounters extends GameEvent {
public final Player receiver;
public final CounterType type;
public final int oldValue;
public final int amount;
public GameEventPlayerCounters(Player recv, CounterType t, int old, int num) {
receiver = recv;
type = t;
oldValue = old;
amount = num;
}
@Override
public <T> T visit(IGameEventVisitor<T> visitor) {
return visitor.visit(this);
}
}

View File

@@ -33,6 +33,7 @@ public interface IGameEventVisitor<T> {
T visit(GameEventMulligan event);
T visit(GameEventPlayerControl event);
T visit(GameEventPlayerDamaged gameEventPlayerDamaged);
T visit(GameEventPlayerCounters event);
T visit(GameEventPlayerPoisoned event);
T visit(GameEventPlayerPriority event);
T visit(GameEventPlayerStatsChanged event);
@@ -77,6 +78,7 @@ public interface IGameEventVisitor<T> {
public T visit(GameEventManaBurn event) { return null; }
public T visit(GameEventMulligan event) { return null; }
public T visit(GameEventPlayerControl event) { return null; }
public T visit(GameEventPlayerCounters event) { return null; }
public T visit(GameEventPlayerPoisoned event) { return null; }
public T visit(GameEventPlayerPriority event) { return null; }
public T visit(GameEventPlayerStatsChanged event) { return null; }

View File

@@ -32,12 +32,7 @@ import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.DetachedCardEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.*;
import forge.game.card.CardPredicates.Presets;
import forge.game.event.*;
import forge.game.keyword.KeywordCollection;
@@ -88,7 +83,6 @@ public class Player extends GameEntity implements Comparable<Player> {
private final Map<Card, Integer> commanderDamage = new HashMap<Card, Integer>();
private int poisonCounters = 0;
private int life = 20;
private int startingLife = 20;
private final Map<Card, Integer> assignedDamage = new HashMap<Card, Integer>();
@@ -853,25 +847,144 @@ public class Player extends GameEntity implements Comparable<Player> {
return false;
}
public final boolean canReceiveCounters(final CounterType type) {
if (hasKeyword("PLAYER can't have counters placed on him or her.")) {
return false;
}
if (type == CounterType.POISON) {
if (hasKeyword("You can't get poison counters")) {
return false;
}
}
return true;
}
public final void addCounter(final CounterType counterType, final int n, final boolean applyMultiplier) {
addCounter(counterType, n, applyMultiplier, true);
}
@Override
protected void addCounter(CounterType counterType, int n, boolean applyMultiplier, boolean fireEvents) {
if (!canReceiveCounters(counterType)) {
return;
}
int addAmount = n;
if(addAmount <= 0) {
// Can't add negative or 0 counters, bail out now
return;
}
/* TODO Add Counter replacement if it ever effects Players
final HashMap<String, Object> repParams = new HashMap<>();
repParams.put("Event", "AddCounter");
repParams.put("Affected", this);
repParams.put("CounterType", counterType);
repParams.put("CounterNum", addAmount);
repParams.put("EffectOnly", applyMultiplier);
if (getGame().getReplacementHandler().run(repParams) != ReplacementResult.NotReplaced) {
return;
}
*/
final int oldValue = getCounters(counterType);
final int newValue = addAmount + oldValue;
counters.put(counterType, newValue);
view.updateCounters(this);
if (fireEvents) {
getGame().fireEvent(new GameEventPlayerCounters(this, counterType, oldValue, newValue));
}
/* TODO Run triggers when something cares
final Map<String, Object> runParams = new TreeMap<>();
runParams.put("Player", this);
runParams.put("CounterType", counterType);
for (int i = 0; i < addAmount; i++) {
getGame().getTriggerHandler().runTrigger(TriggerType.CounterAdded, runParams, false);
}
if (addAmount > 0) {
getGame().getTriggerHandler().runTrigger(TriggerType.CounterAddedOnce, runParams, false);
}
*/
}
@Override
public void subtractCounter(CounterType counterName, int num) {
int oldValue = getCounters(counterName);
int newValue = Math.max(oldValue - num, 0);
final int delta = oldValue - newValue;
if (delta == 0) { return; }
if (newValue > 0) {
counters.put(counterName, newValue);
}
else {
counters.remove(counterName);
}
view.updateCounters(this);
getGame().fireEvent(new GameEventPlayerCounters(this, counterName, oldValue, newValue));
/* TODO Run triggers when something cares
int curCounters = oldValue;
for (int i = 0; i < delta && curCounters != 0; i++) {
final Map<String, Object> runParams = new TreeMap<>();
runParams.put("Card", this);
runParams.put("CounterType", counterName);
runParams.put("NewCounterAmount", --curCounters);
getGame().getTriggerHandler().runTrigger(TriggerType.CounterRemoved, runParams, false);
}
*/
}
public final void clearCounters() {
if (counters.isEmpty()) { return; }
counters.clear();
view.updateCounters(this);
getGame().fireEvent(new GameEventPlayerCounters(this, null, 0, 0));
}
public void setCounters(final CounterType counterType, final Integer num) {
counters.put(counterType, num);
view.updateCounters(this);
getGame().fireEvent(new GameEventPlayerCounters(this, counterType, 0, 0));
}
@Override
public void setCounters(Map<CounterType, Integer> allCounters) {
counters = allCounters;
view.updateCounters(this);
getGame().fireEvent(new GameEventPlayerCounters(this, null, 0, 0));
}
// TODO Merge These calls into the primary counter calls
public final int getPoisonCounters() {
return poisonCounters;
return getCounters(CounterType.POISON);
}
public final void setPoisonCounters(final int num, Card source) {
if (poisonCounters == num) { return; }
int oldPoison = poisonCounters;
poisonCounters = num;
view.updatePoisonCounters(this);
int oldPoison = getCounters(CounterType.POISON);
setCounters(CounterType.POISON, num);
game.fireEvent(new GameEventPlayerPoisoned(this, source, oldPoison, num));
}
public final void addPoisonCounters(final int num, final Card source) {
if (!hasKeyword("You can't get poison counters")) {
setPoisonCounters(poisonCounters + num, source);
int oldPoison = getCounters(CounterType.POISON);
addCounter(CounterType.POISON, num, false, true);
if (oldPoison != getCounters(CounterType.POISON)) {
game.fireEvent(new GameEventPlayerPoisoned(this, source, oldPoison, num));
}
}
public final void removePoisonCounters(final int num, final Card source) {
setPoisonCounters(poisonCounters - num, source);
int oldPoison = getCounters(CounterType.POISON);
subtractCounter(CounterType.POISON, num);
if (oldPoison != getCounters(CounterType.POISON)) {
game.fireEvent(new GameEventPlayerPoisoned(this, source, oldPoison, num));
}
}
// ================ POISON Merged =================================
public final void addChangedKeywords(final String[] addKeywords, final String[] removeKeywords, final Long timestamp) {
addChangedKeywords(ImmutableList.copyOf(addKeywords), ImmutableList.copyOf(removeKeywords), timestamp);
@@ -1681,7 +1794,7 @@ public class Player extends GameEntity implements Comparable<Player> {
}
// Rule 704.5c - If a player has ten or more poison counters, he or she loses the game.
if (poisonCounters >= 10) {
if (getCounters(CounterType.POISON) >= 10) {
return loseConditionMet(GameLossReason.Poisoned, null);
}

View File

@@ -6,6 +6,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import forge.game.card.CounterType;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Objects;
@@ -159,6 +160,13 @@ public class PlayerView extends GameEntityView {
set(TrackableProperty.PoisonCounters, p.getPoisonCounters());
}
public Map<CounterType, Integer> getCounters() {
return get(TrackableProperty.Counters);
}
void updateCounters(Player p) {
set(TrackableProperty.Counters, p.getCounters());
}
public int getMaxHandSize() {
return get(TrackableProperty.MaxHandSize);
}
@@ -359,10 +367,16 @@ public class PlayerView extends GameEntityView {
private List<String> getDetailsList() {
final List<String> details = Lists.newArrayListWithCapacity(8);
details.add(String.format("Life: %d", getLife()));
final int poison = getPoisonCounters();
if (poison > 0) {
details.add(String.format("Poison counters: %d", poison));
Map<CounterType, Integer> counters = getCounters();
if (counters != null) {
for (Entry<CounterType, Integer> p : counters.entrySet()) {
if (p.getValue() > 0) {
details.add(String.format("%s counters: %d", p.getKey().getName(), p.getValue()));
}
}
}
details.add(String.format("Cards in hand: %d/%s", getHandSize(), getMaxHandString()));
details.add(String.format("Cards drawn this turn: %d", getNumDrawnThisTurn()));
details.add(String.format("Damage prevention: %d", getPreventNextDamage()));

View File

@@ -12,6 +12,7 @@ public enum TrackableProperty {
Text(TrackableTypes.StringType),
PreventNextDamage(TrackableTypes.IntegerType),
EnchantedBy(TrackableTypes.CardViewCollectionType),
Counters(TrackableTypes.CounterMapType),
//Card
Owner(TrackableTypes.PlayerViewType),
@@ -27,7 +28,6 @@ public enum TrackableProperty {
Tapped(TrackableTypes.BooleanType),
Token(TrackableTypes.BooleanType),
IsCommander(TrackableTypes.BooleanType),
Counters(TrackableTypes.CounterMapType),
Damage(TrackableTypes.IntegerType),
AssignedDamage(TrackableTypes.IntegerType),
ShieldCount(TrackableTypes.IntegerType),