Multiple fixes.

- Make determination of card viewing permissions much faster;
- A general fix related to choosing a fixed amount of options from a list;
- Improve display of card's names;
- Reduce card flickering when changing phases.
This commit is contained in:
elcnesh
2014-09-28 15:43:34 +00:00
parent 6969aaad91
commit e44b4b3cbc
20 changed files with 111 additions and 120 deletions

1
.gitattributes vendored
View File

@@ -594,7 +594,6 @@ forge-game/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java s
forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java -text
forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java svneol=native#text/plain
forge-game/src/main/java/forge/game/staticability/StaticAbilityETBTapped.java -text
forge-game/src/main/java/forge/game/staticability/StaticAbilityMayLookAt.java -text
forge-game/src/main/java/forge/game/staticability/StaticAbilityPreventDamage.java svneol=native#text/plain
forge-game/src/main/java/forge/game/staticability/package-info.java svneol=native#text/plain
forge-game/src/main/java/forge/game/trigger/Trigger.java svneol=native#text/plain

View File

@@ -809,12 +809,17 @@ public class GameAction {
* <p>
* checkStateEffects.
* </p>
*
* @param runEvents
* {@code true} to have this method run
* {@link GameEventCardStatsChanged} events.
* @return a set of affected cards.
*/
public final void checkStateEffects() {
public final Set<Card> checkStateEffects(final boolean runEvents) {
// sol(10/29) added for Phase updates, state effects shouldn't be
// checked during Spell Resolution (except when persist-returning
if (game.getStack().isResolving()) {
return;
return Collections.emptySet();
}
// final JFrame frame = Singletons.getView().getFrame();
@@ -823,7 +828,7 @@ public class GameAction {
// }
if (game.isGameOver()) {
return;
return Collections.emptySet();
}
// Max: I don't know where to put this! - but since it's a state based action, it must be in check state effects
@@ -835,8 +840,8 @@ public class GameAction {
final boolean refreeze = game.getStack().isFrozen();
game.getStack().setFrozen(true);
// do this twice, sometimes creatures/permanents will survive when they
// shouldn't
// do this multiple times, sometimes creatures/permanents will survive
// when they shouldn't
final Set<Card> allAffectedCards = Sets.newHashSet();
for (int q = 0; q < 9; q++) {
final Set<Card> affectedCards = this.checkStaticAbilities(false);
@@ -923,18 +928,22 @@ public class GameAction {
if (!checkAgain) {
break; // do not continue the loop
}
} // for q=0;q<2
} // for q=0;q<9
if (runEvents) {
game.fireEvent(new GameEventCardStatsChanged(allAffectedCards));
}
checkGameOverCondition();
if (game.getAge() != GameStage.Play)
return;
return Collections.emptySet();
game.getTriggerHandler().resetActiveTriggers();
if (!refreeze) {
game.getStack().unfreezeStack();
}
return allAffectedCards;
} // checkStateEffects()
/**
@@ -1561,7 +1570,7 @@ public class GameAction {
}
runOpeningHandActions(first);
checkStateEffects(); // why?
checkStateEffects(true); // why?
// Run Trigger beginning of the game
final HashMap<String, Object> runParams = new HashMap<String, Object>();

View File

@@ -86,6 +86,7 @@ public class StaticEffects implements IGameStateObject {
final List<Card> affectedCards = se.getAffectedCards();
final ArrayList<Player> affectedPlayers = se.getAffectedPlayers();
final Map<String, String> params = se.getParams();
final Player controller = se.getSource().getController();
String changeColorWordsTo = null;
@@ -97,6 +98,7 @@ public class StaticEffects implements IGameStateObject {
boolean setPT = false;
String[] addHiddenKeywords = null;
String addColors = null;
boolean removeMayLookAt = false;
if (params.containsKey("ChangeColorWordsTo")) {
changeColorWordsTo = params.get("ChangeColorWordsTo");
@@ -159,6 +161,10 @@ public class StaticEffects implements IGameStateObject {
}
}
if (params.containsKey("MayLookAt")) {
removeMayLookAt = true;
}
if (params.containsKey("IgnoreEffectCost")) {
for (final SpellAbility s : se.getSource().getSpellAbilities()) {
if (s instanceof AbilityStatic && s.isTemporary()) {
@@ -249,6 +255,11 @@ public class StaticEffects implements IGameStateObject {
affectedCard.removeColor(addColors, affectedCard, !se.isOverwriteColors(),
se.getTimestamp(affectedCard));
}
// remove may look at
if (removeMayLookAt) {
affectedCard.setMayLookAt(controller, false);
}
}
se.clearTimestamps();
return affectedCards;

View File

@@ -33,7 +33,7 @@ public class EndTurnEffect extends SpellAbilityEffect {
// 3) State-based actions are checked. No player gets priority, and no
// triggered abilities are put onto the stack.
game.getAction().checkStateEffects();
game.getAction().checkStateEffects(true);
// 4) The current phase and/or step ends. The game skips straight to the
// cleanup step. The cleanup step happens in its entirety.

View File

@@ -210,6 +210,7 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
private Player controller = null;
private long controllerTimestamp = 0;
private TreeMap<Long, Player> tempControllers = new TreeMap<Long, Player>();
private final Set<Player> mayLookAt = Sets.newHashSet();
private String originalText = "", text = "";
private String echoCost = "";
@@ -3256,6 +3257,14 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
this.owner = player;
}
public final void setMayLookAt(final Player player, final boolean mayLookAt) {
if (mayLookAt) {
this.mayLookAt.add(player);
} else {
this.mayLookAt.remove(player);
}
}
/**
* <p>
* Getter for the field <code>equippedBy</code>.
@@ -4013,7 +4022,7 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
* left and right values of a {@link Pair}, respectively. A value of -1
* means that particular property has not been set.
*/
private final Pair<Integer, Integer> getLatestPT() {
private final synchronized Pair<Integer, Integer> getLatestPT() {
// Find latest set power
long maxPowerTimestamp = -2;
int latestPower = -1;
@@ -6336,9 +6345,13 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
final SpellAbility root = sa.getRootAbility();
if (root != null && (root.getPaidList("MovedToGrave") != null)
&& !root.getPaidList("MovedToGrave").isEmpty()) {
List<Card> list = root.getPaidList("MovedToGrave");
for (Card card : list) {
if (this.getName().equals(card.getName())) {
final List<Card> list = root.getPaidList("MovedToGrave");
for (final Card card : list) {
String name = card.getName();
if (StringUtils.isEmpty(name)) {
name = card.getPaperCard().getName();
}
if (this.getName().equals(name)) {
return true;
}
}
@@ -8933,16 +8946,10 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
return true;
}
//one last check to see if card can be shown
final Game game = this.getGame();
for (Card host : game.getCardsIn(ZoneType.Battlefield)) {
final ArrayList<StaticAbility> staticAbilities = host.getStaticAbilities();
for (final StaticAbility stAb : staticAbilities) {
if (stAb.applyAbility("MayLookAt", this, viewer)) {
// special viewing permissions for viewer
if (this.mayLookAt.contains(viewer)) {
return true;
}
}
}
//if viewer is controlled by another player, also check if card can be shown to that player
if (controller.isMindSlaved() && viewer == controller.getMindSlaveMaster()) {
@@ -8959,6 +8966,12 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
if (viewer.hasKeyword("CanSeeOpponentsFaceDownCards")) {
return true;
}
// special viewing permissions for viewer
if (this.mayLookAt.contains(viewer)) {
return true;
}
//if viewer is controlled by another player, also check if face can be shown to that player
if (viewer.isMindSlaved() && canCardFaceBeShownTo(viewer.getMindSlaveMaster())) {
return true;

View File

@@ -19,6 +19,7 @@ package forge.game.phase;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import forge.card.mana.ManaCost;
import forge.game.*;
@@ -384,7 +385,7 @@ public class PhaseHandler implements java.io.Serializable, IGameStateObject {
givePriorityToPlayer = false;
// Rule 514.3a - state-based actions
game.getAction().checkStateEffects();
game.getAction().checkStateEffects(true);
break;
default:
@@ -1001,17 +1002,19 @@ public class PhaseHandler implements java.io.Serializable, IGameStateObject {
int loopCount = 0;
do {
final Set<Card> allAffectedCards = Sets.newHashSet();
boolean addedAnythingToStack = false;
do {
// Rule 704.3 Whenever a player would get priority, the game checks ... for state-based actions,
game.getAction().checkStateEffects();
allAffectedCards.addAll(game.getAction().checkStateEffects(false));
if (game.isGameOver())
return; // state-based effects check could lead to game over
addedAnythingToStack = game.getStack().addAllTriggeredAbilitiesToStack();
} while(addedAnythingToStack);
game.fireEvent(new GameEventCardStatsChanged(allAffectedCards));
if (playerTurn.hasLost() && pPlayerPriority.equals(playerTurn) && pFirstPriority.equals(playerTurn)) {
// If the active player has lost, and they have priority, set the next player to have priority
System.out.println("Active player is no longer in the game...");

View File

@@ -288,10 +288,6 @@ public class StaticAbility extends CardTraitBase {
return StaticAbilityCantBeCast.applyCantPlayLandAbility(this, card, player);
}
if (mode.equals("MayLookAt")) {
return StaticAbilityMayLookAt.applyMayLookAtAbility(this, card, player);
}
return false;
}

View File

@@ -69,6 +69,7 @@ public class StaticAbilityContinuous {
public static List<Card> applyContinuousAbility(final StaticAbility stAb, List<Card> affectedCards) {
final Map<String, String> params = stAb.getMapParams();
final Card hostCard = stAb.getHostCard();
final Player controller = hostCard.getController();
final StaticEffect se = new StaticEffect(hostCard);
final ArrayList<Player> affectedPlayers = StaticAbilityContinuous.getAffectedPlayers(stAb);
@@ -109,6 +110,7 @@ public class StaticAbilityContinuous {
boolean removeCardTypes = false;
boolean removeSubTypes = false;
boolean removeCreatureTypes = false;
boolean controllerMayLookAt = false;
//Global rules changes
if (params.containsKey("GlobalRule")) {
@@ -326,6 +328,10 @@ public class StaticAbilityContinuous {
}
}
if (params.containsKey("MayLookAt")) {
controllerMayLookAt = true;
}
if (params.containsKey("IgnoreEffectCost")) {
String cost = params.get("IgnoreEffectCost");
buildIgnorEffectAbility(stAb, cost, affectedPlayers, affectedCards);
@@ -551,6 +557,10 @@ public class StaticAbilityContinuous {
rE.setTemporarilySuppressed(true);
}
}
if (controllerMayLookAt) {
affectedCard.setMayLookAt(controller, true);
}
}
return affectedCards;

View File

@@ -1,65 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* 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.staticability;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import java.util.Map;
/**
* The Class StaticAbility_CantBeCast.
*/
public class StaticAbilityMayLookAt {
/**
* TODO Write javadoc for this method.
*
* @param stAb
* a StaticAbility
* @param card
* the card
* @param activator
* the player
* @return true, if successful
*/
public static boolean applyMayLookAtAbility(final StaticAbility stAb, final Card card, final Player player) {
final Map<String, String> params = stAb.getMapParams();
final Card hostCard = stAb.getHostCard();
if (params.containsKey("Affected")
&& !card.isValid(params.get("Affected").split(","), hostCard.getController(), hostCard)) {
return false;
}
if (params.containsKey("Player") && player != null
&& !player.isValid(params.get("Player"), hostCard.getController(), hostCard)) {
return false;
}
if (params.containsKey("AffectedZone")) {
ZoneType zone = card.getGame().getZoneOf(card).getZoneType();
if (!ZoneType.listValueOf(params.get("AffectedZone")).contains(zone)) {
return false;
}
}
return true;
}
}

View File

@@ -10,11 +10,13 @@ import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Function;
import forge.assets.FSkinProp;
@@ -145,11 +147,7 @@ public class GuiDesktop implements IGuiBase {
}
@Override
public <T> T showInputDialog(String message, String title, FSkinProp icon, T initialInput, T[] inputOptions) {
if (initialInput instanceof GameObject || (inputOptions != null && inputOptions.length > 0 && inputOptions[0] instanceof GameObject)) {
System.err.println("Warning: GameObject passed to GUI! Printing stack trace.");
Thread.dumpStack();
}
public String showInputDialog(String message, String title, FSkinProp icon, String initialInput, String[] inputOptions) {
return FOptionPane.showInputDialog(message, title, icon == null ? null : FSkin.getImage(icon), initialInput, inputOptions);
}

View File

@@ -3,6 +3,7 @@ package forge;
import java.io.File;
import java.util.Collection;
import java.util.List;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Application.ApplicationType;
import com.badlogic.gdx.graphics.Texture;
@@ -153,8 +154,8 @@ public class GuiMobile implements IGuiBase {
}
@Override
public <T> T showInputDialog(final String message, final String title, final FSkinProp icon, final T initialInput, final T[] inputOptions) {
return new WaitCallback<T>() {
public String showInputDialog(final String message, final String title, final FSkinProp icon, final String initialInput, final String[] inputOptions) {
return new WaitCallback<String>() {
@Override
public void run() {
FOptionPane.showInputDialog(message, title, icon == null ? null : FSkin.getImages().get(icon), initialInput, inputOptions, this);

View File

@@ -4,7 +4,7 @@ Types:Creature Human Rogue
PT:0/3
A:AB$ Draw | Cost$ T | NumCards$ 1 | SubAbility$ DBExile | SpellDescription$ Draw a card, then exile a card from your hand face down.
SVar:DBExile:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | ExileFaceDown$ True | Mandatory$ True | RememberChanged$ True
S:Mode$ MayLookAt | Affected$ Card.IsRemembered | Player$ You | AffectedZone$ Exile | Description$ You may look at cards exiled with CARDNAME.
S:Mode$ Continuous | Affected$ Card.IsRemembered | AffectedZone$ Exile | MayLookAt$ True | Description$ You may look at cards exiled with CARDNAME.
A:AB$ ChooseCard | Cost$ U B T | Defined$ You | Amount$ 1 | Mandatory$ True | AILogic$ AtLeast1 | ChoiceTitle$ Choose a card to put into your hand | Choices$ Card.IsRemembered | ChoiceZone$ Exile | SubAbility$ MoveChosen | SpellDescription$ Return a card exiled with CARDNAME to its owner's hand.
SVar:MoveChosen:DB$ ChangeZone | Origin$ Exile | Destination$ Hand | Defined$ ChosenCard
T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered | Execute$ DBForget

View File

@@ -4,7 +4,7 @@ Types:Creature Merfolk Rogue
PT:2/2
T:Mode$ Taps | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ Whenever CARDNAME becomes tapped, exile the top three cards of target opponent's library face down.
SVar:TrigExile:AB$ Mill | Cost$ 0 | ValidTgts$ Opponent | NumCards$ 3 | Destination$ Exile | ExileFaceDown$ True | RememberMilled$ True
S:Mode$ MayLookAt | Affected$ Card.IsRemembered | Player$ You | AffectedZone$ Exile | Description$ You may look at cards exiled with CARDNAME.
S:Mode$ Continuous | Affected$ Card.IsRemembered | AffectedZone$ Exile | MayLookAt$ True | Description$ You may look at cards exiled with CARDNAME.
A:AB$ SetState | Cost$ U Sac<1/CARDNAME> | Defined$ Remembered | Mode$ TurnFace | SubAbility$ DBCounter | SpellDescription$ Turn all cards exiled with CARDNAME face up. Counter all spells with those names.
SVar:DBCounter:DB$ Counter | AllType$ Spell | AllValid$ Card.sharesNameWith Remembered | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True

View File

@@ -1,8 +1,8 @@
Name:Gustha's Scepter
ManaCost:0
Types:Artifact
A:AB$ ChangeZone | Cost$ T | ChangeType$ Card | ChangeNum$ 1 | Origin$ Hand | Destination$ Exile | ExileFaceDown$ True | RememberChanged$ True | Mandatory$ True | SpellDescription$ Exile a card from your hand face down. You may look at it for as long as it remains exiled.
S:Mode$ MayLookAt | Affected$ Card.IsRemembered | Player$ You | AffectedZone$ Exile
A:AB$ ChangeZone | Cost$ T | ChangeType$ Card | ChangeNum$ 1 | Origin$ Hand | Destination$ Exile | ExileFaceDown$ True | RememberChanged$ True | Mandatory$ True | SubAbility$ DBMayLookAt | SpellDescription$ Exile a card from your hand face down. You may look at it for as long as it remains exiled.
SVar:DBMayLookAt:DB$ Pump | Defined$ Remembered | PumpZone$ Exile | KW$ You may look at this card. | Permanent$ True
A:AB$ ChooseCard | Cost$ T | Defined$ You | Amount$ 1 | Mandatory$ True | AILogic$ AtLeast1 | ChoiceTitle$ Choose a card you own to put into your hand | Choices$ Card.IsRemembered+YouOwn | ChoiceZone$ Exile | SubAbility$ MoveChosen | SpellDescription$ Return a card you own exiled with CARDNAME to your hand.
SVar:MoveChosen:DB$ ChangeZone | Origin$ Exile | Destination$ Hand | Defined$ ChosenCard
T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered | Execute$ DBForget

View File

@@ -1,9 +1,11 @@
Name:Jester's Scepter
ManaCost:3
Types:Artifact
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters the battlefield, exile the top five cards of target player's library face down.
SVar:TrigExile:AB$ Mill | Cost$ 0 | ValidTgts$ Player | NumCards$ 5 | Destination$ Exile | ExileFaceDown$ True | RememberMilled$ True
S:Mode$ MayLookAt | Affected$ Card.IsRemembered | Player$ You | AffectedZone$ Exile | Description$ You may look at cards exiled with CARDNAME.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters the battlefield, exile the top five cards of target player's library face down. You may look at those cards for as long as they remain exiled.
SVar:TrigExile:AB$ Mill | Cost$ 0 | ValidTgts$ Player | NumCards$ 5 | Destination$ Exile | ExileFaceDown$ True | RememberMilled$ True | SubAbility$ DBMayLookAt
SVar:DBMayLookAt:DB$ Effect | Name$ Jester's Scepter Effect | StaticAbilities$ SMayLookAt | Triggers$ TForget | SVars$ DBForget | RememberObjects$ Remembered | Duration$ Permanent
SVar:SMayLookAt:Mode$ Continuous | Affected$ Card.IsRemembered | AffectedZone$ Exile | EffectZone$ Command | MayLookAt$ True | Duration$ Permanent
SVar:TForget:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered | Execute$ DBForget
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ DBCleanup | Static$ True
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered | Execute$ DBForget

View File

@@ -102,7 +102,7 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
return null;
}
public void updateCombat() {
private void updateCombat() {
if (!isMainHandler || combatUpdPlanned.getAndSet(true)) { return; }
FThreads.invokeInEdtNowOrLater(gameView.getGui(), new Runnable() {

View File

@@ -3,6 +3,7 @@ package forge.interfaces;
import java.io.File;
import java.util.Collection;
import java.util.List;
import com.google.common.base.Function;
import forge.LobbyPlayer;
@@ -29,7 +30,7 @@ public interface IGuiBase {
void showImageDialog(ISkinImage image, String message, String title);
int showOptionDialog(String message, String title, FSkinProp icon, String[] options, int defaultOption);
int showCardOptionDialog(CardView card, String message, String title, FSkinProp icon, String[] options, int defaultOption);
<T> T showInputDialog(String message, String title, FSkinProp icon, T initialInput, T[] inputOptions);
String showInputDialog(String message, String title, FSkinProp icon, String initialInput, String[] inputOptions);
<T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final T selected, final Function<T, String> display);
<T> List<T> order(final String title, final String top, final int remainingObjectsMin, final int remainingObjectsMax,
final List<T> sourceChoices, final List<T> destChoices, final CardView referenceCard, final boolean sideboardingMode);

View File

@@ -192,7 +192,7 @@ public class SGuiChoose {
}
public static <T> List<T> many(final IGuiBase gui, final String title, final String topCaption, int cnt, final List<T> sourceChoices, final CardView referenceCard) {
return order(gui, title, topCaption, cnt, cnt, sourceChoices, null, referenceCard, false);
return many(gui, title, topCaption, cnt, cnt, sourceChoices, referenceCard);
}
public static <T> List<T> many(final IGuiBase gui, final String title, final String topCaption, int min, int max, final List<T> sourceChoices, final CardView referenceCard) {

View File

@@ -76,7 +76,7 @@ public class SOptionPane {
return showInputDialog(gui, message, title, icon, initialInput, null);
}
public static <T> T showInputDialog(IGuiBase gui, String message, String title, FSkinProp icon, T initialInput, T[] inputOptions) {
public static String showInputDialog(IGuiBase gui, String message, String title, FSkinProp icon, String initialInput, String[] inputOptions) {
return gui.showInputDialog(message, title, icon, initialInput, inputOptions);
}

View File

@@ -726,7 +726,23 @@ public class CardView extends GameEntityView {
@Override
public String toString() {
return this.getOriginal().toString();
if (StringUtils.isEmpty(this.getOriginal().getName())) {
if (this.getId() <= 0) {
return "(Unknown card)";
} else if (this.hasAltState()) {
return "Face-down card (" + this.getAlternate().getName() + ")";
}
}
return this.getOriginal().getName() + " (" + this.getId() + ")";
}
public String determineName(final CardStateView state) {
if (state == original) {
return this.toString();
}
return this.getAlternate().getName() + " (" + this.getId() + ")";
}
public class CardStateView {
@@ -767,10 +783,7 @@ public class CardView extends GameEntityView {
@Override
public String toString() {
if (StringUtils.isEmpty(this.getName()) && this.getCard().getId() <= 0) {
return "(Unknown card)";
}
return this.getName() + " (" + this.getCard().getId() + ")";
return this.getCard().determineName(this);
}
public CardView getCard() {