Optimize card rendering behind other cards

Fix issue with achievement tooltip not appearing at times
This commit is contained in:
drdev
2014-09-26 10:32:09 +00:00
parent 3f1f199537
commit 1cc9056b7b
11 changed files with 95 additions and 19 deletions

View File

@@ -16,6 +16,7 @@ import forge.assets.FSkinImage;
import forge.assets.FSkinTexture; import forge.assets.FSkinTexture;
import forge.assets.TextRenderer; import forge.assets.TextRenderer;
import forge.card.CardDetailUtil.DetailColors; import forge.card.CardDetailUtil.DetailColors;
import forge.card.CardRenderer.CardStackPosition;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.screens.FScreen; import forge.screens.FScreen;
import forge.view.CardView; import forge.view.CardView;
@@ -56,7 +57,7 @@ public class CardImageRenderer {
prevImageHeight = h; prevImageHeight = h;
} }
public static void drawCardImage(Graphics g, CardView card, float x, float y, float w, float h) { public static void drawCardImage(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos) {
updateStaticFields(w, h); updateStaticFields(w, h);
final CardStateView state = card.getOriginal(); final CardStateView state = card.getOriginal();
@@ -94,6 +95,10 @@ public class CardImageRenderer {
Color headerColor1 = FSkinColor.tintColor(Color.WHITE, color1, CardRenderer.NAME_BOX_TINT); Color headerColor1 = FSkinColor.tintColor(Color.WHITE, color1, CardRenderer.NAME_BOX_TINT);
Color headerColor2 = color2 == null ? null : FSkinColor.tintColor(Color.WHITE, color2, CardRenderer.NAME_BOX_TINT); Color headerColor2 = color2 == null ? null : FSkinColor.tintColor(Color.WHITE, color2, CardRenderer.NAME_BOX_TINT);
drawHeader(g, card, headerColor1, headerColor2, x, y, w, headerHeight); drawHeader(g, card, headerColor1, headerColor2, x, y, w, headerHeight);
if (pos == CardStackPosition.BehindVert) { return; } //remaining rendering not needed if card is behind another card in a vertical stack
boolean onTop = (pos == CardStackPosition.Top);
y += headerHeight; y += headerHeight;
float artWidth = w - 2 * artInset; float artWidth = w - 2 * artInset;
@@ -138,7 +143,8 @@ public class CardImageRenderer {
y += textBoxHeight; y += textBoxHeight;
//draw P/T box //draw P/T box
if (ptBoxHeight > 0) { if (onTop && ptBoxHeight > 0) {
//only needed if on top since otherwise P/T will be hidden
Color ptColor1 = FSkinColor.tintColor(Color.WHITE, color1, CardRenderer.PT_BOX_TINT); Color ptColor1 = FSkinColor.tintColor(Color.WHITE, color1, CardRenderer.PT_BOX_TINT);
Color ptColor2 = color2 == null ? null : FSkinColor.tintColor(Color.WHITE, color2, CardRenderer.PT_BOX_TINT); Color ptColor2 = color2 == null ? null : FSkinColor.tintColor(Color.WHITE, color2, CardRenderer.PT_BOX_TINT);
drawPtBox(g, card, ptColor1, ptColor2, x, y - 2 * artInset, w, ptBoxHeight); drawPtBox(g, card, ptColor1, ptColor2, x, y - 2 * artInset, w, ptBoxHeight);

View File

@@ -40,6 +40,12 @@ import forge.view.CardView.CardStateView;
import forge.view.ViewUtil; import forge.view.ViewUtil;
public class CardRenderer { public class CardRenderer {
public enum CardStackPosition {
Top,
BehindHorz,
BehindVert
}
private static final FSkinFont NAME_FONT = FSkinFont.get(16); private static final FSkinFont NAME_FONT = FSkinFont.get(16);
private static final FSkinFont TYPE_FONT = FSkinFont.get(14); private static final FSkinFont TYPE_FONT = FSkinFont.get(14);
private static final FSkinFont SET_FONT = TYPE_FONT; private static final FSkinFont SET_FONT = TYPE_FONT;
@@ -94,7 +100,7 @@ public class CardRenderer {
w = h / FCardPanel.ASPECT_RATIO; w = h / FCardPanel.ASPECT_RATIO;
x += (oldWidth - w) / 2; x += (oldWidth - w) / 2;
} }
CardImageRenderer.drawCardImage(g, card, x, y, w, h); CardImageRenderer.drawCardImage(g, card, x, y, w, h, CardStackPosition.Top);
drawFoilEffect(g, card, x, y, w, h); drawFoilEffect(g, card, x, y, w, h);
return true; return true;
} }
@@ -495,11 +501,11 @@ public class CardRenderer {
g.drawText(ptText, PT_FONT, Color.BLACK, x, y, w, h, false, HAlignment.CENTER, true); g.drawText(ptText, PT_FONT, Color.BLACK, x, y, w, h, false, HAlignment.CENTER, true);
} }
public static void drawCard(Graphics g, IPaperCard pc, float x, float y, float w, float h) { public static void drawCard(Graphics g, IPaperCard pc, float x, float y, float w, float h, CardStackPosition pos) {
Texture image = ImageCache.getImage(pc); Texture image = ImageCache.getImage(pc);
if (image != null) { if (image != null) {
if (image == ImageCache.defaultImage) { if (image == ImageCache.defaultImage) {
CardImageRenderer.drawCardImage(g, ViewUtil.getCardForUi(pc), x, y, w, h); CardImageRenderer.drawCardImage(g, ViewUtil.getCardForUi(pc), x, y, w, h, pos);
} }
else { else {
g.drawImage(image, x, y, w, h); g.drawImage(image, x, y, w, h);
@@ -516,11 +522,11 @@ public class CardRenderer {
g.fillRect(Color.BLACK, x, y, w, h); g.fillRect(Color.BLACK, x, y, w, h);
} }
} }
public static void drawCard(Graphics g, CardView card, float x, float y, float w, float h) { public static void drawCard(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos) {
Texture image = ImageCache.getImage(card); Texture image = ImageCache.getImage(card);
if (image != null) { if (image != null) {
if (image == ImageCache.defaultImage) { if (image == ImageCache.defaultImage) {
CardImageRenderer.drawCardImage(g, card, x, y, w, h); CardImageRenderer.drawCardImage(g, card, x, y, w, h, pos);
} }
else { else {
g.drawImage(image, x, y, w, h); g.drawImage(image, x, y, w, h);
@@ -532,8 +538,8 @@ public class CardRenderer {
} }
} }
public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h) { public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos) {
drawCard(g, card, x, y, w, h); drawCard(g, card, x, y, w, h, pos);
float padding = w * 0.021f; //adjust for card border float padding = w * 0.021f; //adjust for card border
x += padding; x += padding;
@@ -564,6 +570,9 @@ public class CardRenderer {
} }
} }
if (pos == CardStackPosition.BehindVert) { return; } //remaining rendering not needed if card is behind another card in a vertical stack
boolean onTop = (pos == CardStackPosition.Top);
if (showCardIdOverlay(card)) { if (showCardIdOverlay(card)) {
FSkinFont idFont = FSkinFont.forHeight(h * 0.12f); FSkinFont idFont = FSkinFont.forHeight(h * 0.12f);
float idHeight = idFont.getCapHeight(); float idHeight = idFont.getCapHeight();
@@ -612,7 +621,8 @@ public class CardRenderer {
} }
} }
if (details.isCreature() && card.isSick() && card.getZone() == ZoneType.Battlefield) { if (onTop && details.isCreature() && card.isSick() && card.getZone() == ZoneType.Battlefield) {
//only needed if on top since otherwise symbol will be hidden
CardFaceSymbols.drawSymbol("summonsick", g, stateXSymbols, ySymbols, otherSymbolsSize, otherSymbolsSize); CardFaceSymbols.drawSymbol("summonsick", g, stateXSymbols, ySymbols, otherSymbolsSize, otherSymbolsSize);
} }
@@ -625,7 +635,8 @@ public class CardRenderer {
CardFaceSymbols.drawSymbol("sacrifice", g, (x + (w / 2)) - sacSymbolSize / 2, (y + (h / 2)) - sacSymbolSize / 2, otherSymbolsSize, otherSymbolsSize); CardFaceSymbols.drawSymbol("sacrifice", g, (x + (w / 2)) - sacSymbolSize / 2, (y + (h / 2)) - sacSymbolSize / 2, otherSymbolsSize, otherSymbolsSize);
} }
if (showCardPowerOverlay(card)) { //make sure card p/t box appears on top if (onTop && showCardPowerOverlay(card)) { //make sure card p/t box appears on top
//only needed if on top since otherwise P/T will be hidden
drawPtBox(g, card, details, color, x, y, w, h); drawPtBox(g, card, details, color, x, y, w, h);
} }
} }

View File

@@ -26,6 +26,7 @@ import forge.assets.FSkinFont;
import forge.assets.FSkinImage; import forge.assets.FSkinImage;
import forge.card.CardEdition; import forge.card.CardEdition;
import forge.card.CardRenderer; import forge.card.CardRenderer;
import forge.card.CardRenderer.CardStackPosition;
import forge.card.CardRules; import forge.card.CardRules;
import forge.card.CardZoom; import forge.card.CardZoom;
import forge.card.mana.ManaCostShard; import forge.card.mana.ManaCostShard;
@@ -401,7 +402,7 @@ public class AddBasicLandsDialog extends FDialog {
@Override @Override
public void draw(Graphics g) { public void draw(Graphics g) {
if (card == null) { return; } if (card == null) { return; }
CardRenderer.drawCard(g, card, 0, 0, getWidth(), getHeight()); CardRenderer.drawCard(g, card, 0, 0, getWidth(), getHeight(), CardStackPosition.Top);
} }
} }
} }

View File

@@ -9,6 +9,7 @@ import forge.assets.FSkinColor.Colors;
import forge.assets.FSkinFont; import forge.assets.FSkinFont;
import forge.assets.ImageCache; import forge.assets.ImageCache;
import forge.card.CardRenderer; import forge.card.CardRenderer;
import forge.card.CardRenderer.CardStackPosition;
import forge.card.CardZoom; import forge.card.CardZoom;
import forge.deck.DeckProxy; import forge.deck.DeckProxy;
import forge.item.IPaperCard; import forge.item.IPaperCard;
@@ -474,6 +475,8 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
x = 0; x = 0;
for (ItemInfo itemInfo : group.items) { for (ItemInfo itemInfo : group.items) {
itemInfo.pos = CardStackPosition.Top;
if (pile.items.size() == columnCount) { if (pile.items.size() == columnCount) {
pile = new Pile(); pile = new Pile();
x = 0; x = 0;
@@ -500,9 +503,11 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
Pile pile = group.piles.get(j); Pile pile = group.piles.get(j);
y = pileY; y = pileY;
for (ItemInfo itemInfo : pile.items) { for (ItemInfo itemInfo : pile.items) {
itemInfo.pos = CardStackPosition.BehindVert;
itemInfo.setBounds(x, y, itemWidth, itemHeight); itemInfo.setBounds(x, y, itemWidth, itemHeight);
y += dy; y += dy;
} }
pile.items.get(pile.items.size() - 1).pos = CardStackPosition.Top;
pileHeight = y + itemHeight - dy - pileY; pileHeight = y + itemHeight - dy - pileY;
if (pileHeight > maxPileHeight) { if (pileHeight > maxPileHeight) {
maxPileHeight = pileHeight; maxPileHeight = pileHeight;
@@ -906,7 +911,10 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
} }
} }
if (skippedItem != null) { if (skippedItem != null) {
CardStackPosition backupPos = skippedItem.pos;
skippedItem.pos = CardStackPosition.Top; //ensure skipped item rendered as if it was on top
skippedItem.draw(g); skippedItem.draw(g);
skippedItem.pos = backupPos;
} }
} }
} }
@@ -914,6 +922,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
private final T item; private final T item;
private final Group group; private final Group group;
private int index; private int index;
private CardStackPosition pos;
private boolean selected; private boolean selected;
private ItemInfo(T item0, Group group0) { private ItemInfo(T item0, Group group0) {
@@ -954,7 +963,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
} }
if (item instanceof PaperCard) { if (item instanceof PaperCard) {
CardRenderer.drawCard(g, (PaperCard)item, x, y, w, h); CardRenderer.drawCard(g, (PaperCard)item, x, y, w, h, pos);
} }
else { else {
Texture img = ImageCache.getImage(item); Texture img = ImageCache.getImage(item);

View File

@@ -12,6 +12,7 @@ import forge.FThreads;
import forge.Graphics; import forge.Graphics;
import forge.GuiBase; import forge.GuiBase;
import forge.card.CardZoom; import forge.card.CardZoom;
import forge.card.CardRenderer.CardStackPosition;
import forge.match.MatchUtil; import forge.match.MatchUtil;
import forge.toolbox.FCardPanel; import forge.toolbox.FCardPanel;
import forge.view.CardView; import forge.view.CardView;
@@ -213,6 +214,14 @@ public abstract class VCardDisplayArea extends VDisplayArea {
prevPanelInStack = prevPanelInStack0; prevPanelInStack = prevPanelInStack0;
} }
@Override
protected CardStackPosition getStackPosition() {
if (nextPanelInStack == null && attachedToPanel == null) {
return CardStackPosition.Top;
}
return CardStackPosition.BehindHorz;
}
//clear and reset all pointers from this panel //clear and reset all pointers from this panel
public void reset() { public void reset() {
if (!attachedPanels.isEmpty()) { if (!attachedPanels.isEmpty()) {
@@ -230,16 +239,22 @@ public abstract class VCardDisplayArea extends VDisplayArea {
@Override @Override
public boolean tap(float x, float y, int count) { public boolean tap(float x, float y, int count) {
if (renderedCardContains(x, y)) { if (renderedCardContains(x, y)) {
forge.FTrace.get("tap").start();
forge.FTrace.get("tap2").start();
FThreads.invokeInBackgroundThread(new Runnable() { //must invoke in game thread in case a dialog needs to be shown FThreads.invokeInBackgroundThread(new Runnable() { //must invoke in game thread in case a dialog needs to be shown
@Override @Override
public void run() { public void run() {
forge.FTrace.get("selectCard").start();
if (!selectCard()) { if (!selectCard()) {
//if no cards in stack can be selected, just show zoom/details for card //if no cards in stack can be selected, just show zoom/details for card
CardZoom.show(getCard()); CardZoom.show(getCard());
} }
forge.FTrace.get("selectCard").end();
Gdx.graphics.requestRendering(); Gdx.graphics.requestRendering();
forge.FTrace.get("tap2").end();
} }
}); });
forge.FTrace.get("tap").end();
return true; return true;
} }
return false; return false;

View File

@@ -18,6 +18,7 @@ import forge.card.CardDetailUtil;
import forge.card.CardRenderer; import forge.card.CardRenderer;
import forge.card.CardZoom; import forge.card.CardZoom;
import forge.card.CardDetailUtil.DetailColors; import forge.card.CardDetailUtil.DetailColors;
import forge.card.CardRenderer.CardStackPosition;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.match.MatchUtil; import forge.match.MatchUtil;
@@ -329,7 +330,7 @@ public class VStack extends FDropDown {
x += PADDING; x += PADDING;
y += PADDING; y += PADDING;
CardRenderer.drawCardWithOverlays(g, stackInstance.getSource(), x, y, CARD_WIDTH, CARD_HEIGHT); CardRenderer.drawCardWithOverlays(g, stackInstance.getSource(), x, y, CARD_WIDTH, CARD_HEIGHT, CardStackPosition.Top);
x += CARD_WIDTH + PADDING; x += CARD_WIDTH + PADDING;
w -= x + PADDING - BORDER_THICKNESS; w -= x + PADDING - BORDER_THICKNESS;

View File

@@ -146,7 +146,8 @@ public class AchievementsPage extends TabPage<SettingsScreen> {
@Override @Override
public boolean longPress(float x, float y) { public boolean longPress(float x, float y) {
return showCard(getAchievementAt(x, y)); selectedAchievement = getAchievementAt(x, y);
return showCard(selectedAchievement);
} }
private boolean showCard(Achievement achievement) { private boolean showCard(Achievement achievement) {
@@ -245,7 +246,7 @@ public class AchievementsPage extends TabPage<SettingsScreen> {
x = startX; x = startX;
if (y >= getHeight()) { if (y >= getHeight()) {
return; break;
} }
} }

View File

@@ -2,6 +2,7 @@ package forge.toolbox;
import forge.Graphics; import forge.Graphics;
import forge.card.CardRenderer; import forge.card.CardRenderer;
import forge.card.CardRenderer.CardStackPosition;
import forge.util.Utils; import forge.util.Utils;
import forge.view.CardView; import forge.view.CardView;
@@ -75,6 +76,11 @@ public class FCardPanel extends FDisplayObject {
return PADDING; return PADDING;
} }
//allow overriding stack position
protected CardStackPosition getStackPosition() {
return CardStackPosition.Top;
}
@Override @Override
public void draw(Graphics g) { public void draw(Graphics g) {
if (card == null) { return; } if (card == null) { return; }
@@ -93,7 +99,7 @@ public class FCardPanel extends FDisplayObject {
g.startRotateTransform(x + edgeOffset, y + h - edgeOffset, tappedAngle); g.startRotateTransform(x + edgeOffset, y + h - edgeOffset, tappedAngle);
} }
CardRenderer.drawCardWithOverlays(g, card, x, y, w, h); CardRenderer.drawCardWithOverlays(g, card, x, y, w, h, getStackPosition());
if (tapped) { if (tapped) {
g.endTransform(); g.endTransform();

View File

@@ -14,6 +14,7 @@ import forge.assets.TextRenderer;
import forge.assets.FSkinColor.Colors; import forge.assets.FSkinColor.Colors;
import forge.card.CardRenderer; import forge.card.CardRenderer;
import forge.card.CardZoom; import forge.card.CardZoom;
import forge.card.CardRenderer.CardStackPosition;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.screens.match.MatchController; import forge.screens.match.MatchController;
import forge.screens.match.views.VAvatar; import forge.screens.match.views.VAvatar;
@@ -393,7 +394,7 @@ public class FChoiceList<T> extends FList<T> {
@Override @Override
public void drawValue(Graphics g, T value, FSkinFont font, FSkinColor foreColor, boolean pressed, float x, float y, float w, float h) { public void drawValue(Graphics g, T value, FSkinFont font, FSkinColor foreColor, boolean pressed, float x, float y, float w, float h) {
SpellAbilityView spellAbility = (SpellAbilityView)value; SpellAbilityView spellAbility = (SpellAbilityView)value;
CardRenderer.drawCardWithOverlays(g, spellAbility.getHostCard(), x, y, VStack.CARD_WIDTH, VStack.CARD_HEIGHT); CardRenderer.drawCardWithOverlays(g, spellAbility.getHostCard(), x, y, VStack.CARD_WIDTH, VStack.CARD_HEIGHT, CardStackPosition.Top);
float dx = VStack.CARD_WIDTH + FList.PADDING; float dx = VStack.CARD_WIDTH + FList.PADDING;
x += dx; x += dx;

View File

@@ -11,6 +11,7 @@ import forge.assets.FImage;
import forge.assets.FSkinImage; import forge.assets.FSkinImage;
import forge.card.CardRenderer; import forge.card.CardRenderer;
import forge.card.CardZoom; import forge.card.CardZoom;
import forge.card.CardRenderer.CardStackPosition;
import forge.screens.match.views.VPrompt; import forge.screens.match.views.VPrompt;
import forge.toolbox.FEvent.*; import forge.toolbox.FEvent.*;
import forge.util.Callback; import forge.util.Callback;
@@ -110,7 +111,7 @@ public class FOptionPane extends FDialog {
float x = (getWidth() - w) / 2; float x = (getWidth() - w) / 2;
float y = 0; float y = 0;
CardRenderer.drawCard(g, card, x, y, w, h); CardRenderer.drawCard(g, card, x, y, w, h, CardStackPosition.Top);
} }
}; };
cardDisplay.setHeight(Utils.SCREEN_HEIGHT / 2); cardDisplay.setHeight(Utils.SCREEN_HEIGHT / 2);

View File

@@ -169,17 +169,21 @@ public class InputAttack extends InputSyncronizedBase {
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
protected final boolean onCardSelected(final Card card, final ITriggerEvent triggerEvent) { protected final boolean onCardSelected(final Card card, final ITriggerEvent triggerEvent) {
forge.FTrace.get("onCardSelected").start();
final List<Card> att = combat.getAttackers(); final List<Card> att = combat.getAttackers();
if (triggerEvent != null && triggerEvent.getButton() == 3 && att.contains(card)) { if (triggerEvent != null && triggerEvent.getButton() == 3 && att.contains(card)) {
if (undeclareAttacker(card)) { if (undeclareAttacker(card)) {
updateMessage(); updateMessage();
forge.FTrace.get("onCardSelected").end();
return true; return true;
} }
} }
forge.FTrace.get("isAttackingDefender").start();
if (combat.isAttacking(card, currentDefender)) { if (combat.isAttacking(card, currentDefender)) {
boolean validAction = true; boolean validAction = true;
if (isBandingPossible()) { if (isBandingPossible()) {
forge.FTrace.get("activateBand").start();
// Activate band by selecting/deselecting a band member // Activate band by selecting/deselecting a band member
if (activeBand == null) { if (activeBand == null) {
activateBand(combat.getBandOfAttacker(card)); activateBand(combat.getBandOfAttacker(card));
@@ -197,41 +201,61 @@ public class InputAttack extends InputSyncronizedBase {
validAction = false; validAction = false;
} }
} }
forge.FTrace.get("activateBand").end();
} }
else { else {
//if banding not possible, just undeclare attacker //if banding not possible, just undeclare attacker
forge.FTrace.get("undeclareAttacker").start();
undeclareAttacker(card); undeclareAttacker(card);
forge.FTrace.get("undeclareAttacker").end();
} }
updateMessage(); updateMessage();
forge.FTrace.get("isAttackingDefender").end();
forge.FTrace.get("onCardSelected").end();
return validAction; return validAction;
} }
forge.FTrace.get("isAttackingDefender").end();
forge.FTrace.get("isOpponentOf").start();
if (card.getController().isOpponentOf(playerAttacks)) { if (card.getController().isOpponentOf(playerAttacks)) {
if (defenders.contains(card)) { // planeswalker? if (defenders.contains(card)) { // planeswalker?
setCurrentDefender(card); setCurrentDefender(card);
forge.FTrace.get("isOpponentOf").end();
forge.FTrace.get("onCardSelected").end();
return true; return true;
} }
} }
forge.FTrace.get("isOpponentOf").end();
forge.FTrace.get("canAttack").start();
if (playerAttacks.getZone(ZoneType.Battlefield).contains(card) && CombatUtil.canAttack(card, currentDefender, combat)) { if (playerAttacks.getZone(ZoneType.Battlefield).contains(card) && CombatUtil.canAttack(card, currentDefender, combat)) {
if (activeBand != null && !activeBand.canJoinBand(card)) { if (activeBand != null && !activeBand.canJoinBand(card)) {
activateBand(null); activateBand(null);
updateMessage(); updateMessage();
flashIncorrectAction(); flashIncorrectAction();
forge.FTrace.get("onCardSelected").end();
return false; return false;
} }
forge.FTrace.get("isAttacking").start();
if (combat.isAttacking(card)) { if (combat.isAttacking(card)) {
combat.removeFromCombat(card); combat.removeFromCombat(card);
} }
forge.FTrace.get("isAttacking").end();
forge.FTrace.get("declareAttacker").start();
declareAttacker(card); declareAttacker(card);
forge.FTrace.get("declareAttacker").end();
updateMessage(); updateMessage();
forge.FTrace.get("canAttack").end();
forge.FTrace.get("onCardSelected").end();
return true; return true;
} }
forge.FTrace.get("canAttack").end();
flashIncorrectAction(); flashIncorrectAction();
forge.FTrace.get("onCardSelected").end();
return false; return false;
} }