...
 
Commits (52)
......@@ -59,6 +59,7 @@ import forge.item.PaperCard;
import forge.util.Aggregates;
import forge.util.Expressions;
import forge.util.MyRandom;
import forge.util.ComparatorUtil;
import forge.util.collect.FCollectionView;
import io.sentry.Sentry;
import io.sentry.event.BreadcrumbBuilder;
......@@ -609,6 +610,7 @@ public class AiController {
ComputerUtilAbility.getAvailableCards(game, player);
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(cards, player);
ComparatorUtil.verifyTransitivity(saComparator, all);
Collections.sort(all, saComparator); // put best spells first
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
......@@ -1572,8 +1574,9 @@ public class AiController {
if (all == null || all.isEmpty())
return null;
ComparatorUtil.verifyTransitivity(saComparator, all);
Collections.sort(all, saComparator); // put best spells first
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
// Don't add Counterspells to the "normal" playcard lookups
if (skipCounter && sa.getApi() == ApiType.Counter) {
......
package forge.util;
import java.util.*;
/**
* @author Gili Tzabari
*/
public final class ComparatorUtil
{
/**
* Verify that a comparator is transitive.
*
* @param <T> the type being compared
* @param comparator the comparator to test
* @param elements the elements to test against
* @throws AssertionError if the comparator is not transitive
*/
public static <T> void verifyTransitivity(Comparator<T> comparator, Collection<T> elements)
{
for (T first: elements)
{
for (T second: elements)
{
int result1 = comparator.compare(first, second);
int result2 = comparator.compare(second, first);
if (result1 != -result2)
{
// Uncomment the following line to step through the failed case
//comparator.compare(first, second);
throw new AssertionError("compare(" + first + ", " + second + ") == " + result1 +
" but swapping the parameters returns " + result2);
}
}
}
for (T first: elements)
{
for (T second: elements)
{
int firstGreaterThanSecond = comparator.compare(first, second);
if (firstGreaterThanSecond <= 0)
continue;
for (T third: elements)
{
int secondGreaterThanThird = comparator.compare(second, third);
if (secondGreaterThanThird <= 0)
continue;
int firstGreaterThanThird = comparator.compare(first, third);
if (firstGreaterThanThird <= 0)
{
// Uncomment the following line to step through the failed case
//comparator.compare(first, third);
throw new AssertionError("compare(" + first + ", " + second + ") > 0, " +
"compare(" + second + ", " + third + ") > 0, but compare(" + first + ", " + third + ") == " +
firstGreaterThanThird);
}
}
}
}
}
/**
* Prevent construction.
*/
private ComparatorUtil()
{
}
}
\ No newline at end of file
......@@ -12,6 +12,8 @@ import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.Lang;
import java.util.*;
public class BlockEffect extends SpellAbilityEffect {
......@@ -106,7 +108,7 @@ public class BlockEffect extends SpellAbilityEffect {
}
}
sb.append(String.join(", ", blockers)).append(" block ").append(String.join(", ", attackers));
sb.append(Lang.joinHomogenous(blockers)).append(" block ").append(Lang.joinHomogenous(attackers));
return sb.toString();
}
......
......@@ -225,8 +225,11 @@ public class TokenEffect extends SpellAbilityEffect {
// Cause of the Token Effect, in general it should be this
// but if its a Replacement Effect, it might be something else or null
SpellAbility cause = sa;
if (root.isReplacementAbility() && root.hasReplacingObject(AbilityKey.Cause)) {
cause = (SpellAbility) root.getReplacingObject(AbilityKey.Cause);
if (root.isReplacementAbility()) {
SpellAbility replacingObject = (SpellAbility) root.getReplacingObject(AbilityKey.Cause);
if (replacingObject != null) {
cause = replacingObject;
}
}
final boolean remember = sa.hasParam("RememberTokens");
......
......@@ -532,6 +532,9 @@ public class CardState extends GameObject {
staticAbilities.add(sa.copy(card, lki));
}
}
if (lki && source.loyaltyRep != null) {
this.loyaltyRep = source.loyaltyRep.copy(card, lki);
}
}
public CardState copy(final Card host, CardStateName name, final boolean lki) {
......
......@@ -25,7 +25,10 @@ import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardTraitChanges;
import forge.game.card.CardUtil;
import forge.game.keyword.KeywordInterface;
import forge.game.keyword.KeywordsChange;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.Zone;
......@@ -43,6 +46,8 @@ import java.util.*;
public class ReplacementHandler {
private final Game game;
private Set<ReplacementEffect> hasRun = Sets.newHashSet();
/**
* ReplacementHandler.
* @param gameState
......@@ -68,7 +73,8 @@ public class ReplacementHandler {
final ReplacementEffect re = cause.getReplacementEffect();
// only return for same layer
if (ReplacementType.Moved.equals(re.getMode()) && layer.equals(re.getLayer())) {
return re.getOtherChoices();
if (!re.getOtherChoices().isEmpty())
return re.getOtherChoices();
}
}
......@@ -79,6 +85,54 @@ public class ReplacementHandler {
preList.add(affectedLKI);
game.getAction().checkStaticAbilities(false, Sets.newHashSet(affectedLKI), preList);
checkAgain = true;
// need to check if Intrinsic has run
for (ReplacementEffect re : affectedLKI.getReplacementEffects()) {
if (re.isIntrinsic() && this.hasRun.contains(re)) {
re.setHasRun(true);
}
}
// need to check non Intrinsic
for (Map.Entry<Long, CardTraitChanges> e : affectedLKI.getChangedCardTraits().entrySet()) {
boolean hasRunRE = false;
String skey = String.valueOf(e.getKey());
for (ReplacementEffect re : this.hasRun) {
if (!re.isIntrinsic() && skey.equals(re.getSVar("_ReplacedTimestamp"))) {
hasRunRE = true;
break;
}
}
for (ReplacementEffect re : e.getValue().getReplacements()) {
re.setSVar("_ReplacedTimestamp", skey);
if (hasRunRE) {
re.setHasRun(true);
}
}
}
for (Map.Entry<Long, KeywordsChange> e : affectedLKI.getChangedCardKeywords().entrySet()) {
boolean hasRunRE = false;
String skey = String.valueOf(e.getKey());
for (ReplacementEffect re : this.hasRun) {
if (!re.isIntrinsic() && skey.equals(re.getSVar("_ReplacedTimestamp"))) {
hasRunRE = true;
break;
}
}
for (KeywordInterface k : e.getValue().getKeywords()) {
for (ReplacementEffect re : k.getReplacements()) {
re.setSVar("_ReplacedTimestamp", skey);
if (hasRunRE) {
re.setHasRun(true);
}
}
}
}
runParams.put(AbilityKey.Affected, affectedLKI);
}
......@@ -186,6 +240,7 @@ public class ReplacementHandler {
possibleReplacers.remove(chosenRE);
chosenRE.setHasRun(true);
hasRun.add(chosenRE);
chosenRE.setOtherChoices(possibleReplacers);
ReplacementResult res = executeReplacement(runParams, chosenRE, decider, game);
if (res == ReplacementResult.NotReplaced) {
......@@ -193,10 +248,12 @@ public class ReplacementHandler {
res = run(event, runParams);
}
chosenRE.setHasRun(false);
hasRun.remove(chosenRE);
chosenRE.setOtherChoices(null);
return res;
}
chosenRE.setHasRun(false);
hasRun.remove(chosenRE);
chosenRE.setOtherChoices(null);
String message = chosenRE.toString();
if ( !StringUtils.isEmpty(message))
......
......@@ -596,9 +596,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public Map<AbilityKey, Object> getReplacingObjects() {
return replacingObjects;
}
public boolean hasReplacingObject(final AbilityKey type) {
return replacingObjects.containsKey(type);
}
public Object getReplacingObject(final AbilityKey type) {
final Object res = replacingObjects.get(type);
return res;
......
......@@ -104,6 +104,24 @@
<artifactId>gdx-backend-android</artifactId>
<version>1.9.10</version>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-base-bom</artifactId>
<version>1.2.4.Final</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-core</artifactId>
<version>1.2.4.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-api</artifactId>
<version>1.2.4.Final</version>
<scope>compile</scope>
</dependency>
</dependencies>
<profiles>
......
......@@ -28,6 +28,20 @@
-dontwarn org.slf4j.**
-dontwarn javax.**
# mandatory proguard rules for cache2k to keep the core implementation
-dontwarn org.cache2k.impl.xmlConfiguration.**
-dontwarn org.cache2k.impl.serverSide.**
-keep interface org.cache2k.spi.Cache2kCoreProvider
-keep public class * extends org.cache2k.spi.Cache2kCoreProvider
# optional proguard rules for cache2k, to keep XML configuration code
# if only programmatic configuration is used, these rules may be ommitted
-keep interface org.cache2k.core.spi.CacheConfigurationProvider
-keep public class * extends org.cache2k.core.spi.CacheConfigurationProvider
-keepclassmembers public class * extends org.cache2k.configuration.ConfigurationBean {
public void set*(...);
public ** get*();
}
-keep class forge.** { *; }
-keep class com.thoughtworks.xstream.** { *; }
-keep class org.apache.commons.lang3.** { *; }
......
......@@ -28,7 +28,7 @@ public class FScreen {
public static final FScreen HOME_SCREEN = new FScreen(
VHomeUI.SINGLETON_INSTANCE,
CHomeUI.SINGLETON_INSTANCE,
"Home",
"Home ",
FSkin.getIcon(FSkinProp.ICO_FAVICON),
false,
"Exit Forge",
......@@ -46,7 +46,7 @@ public class FScreen {
public static final FScreen DECK_EDITOR_CONSTRUCTED = new FScreen(
VDeckEditorUI.SINGLETON_INSTANCE,
CDeckEditorUI.SINGLETON_INSTANCE,
"Deck Editor",
"Deck Editor ",
FSkin.getImage(FSkinProp.IMG_PACK),
false,
"Back to Home",
......
......@@ -77,6 +77,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
private Point hoverScrollPos;
private ItemInfo hoveredItem;
private ItemInfo focalItem;
private boolean panelOptionsCreated = false;
private final List<ItemInfo> orderedItems = new ArrayList<>();
private final List<Group> groups = new ArrayList<>();
......@@ -337,7 +338,12 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
@Override
public void setup(ItemManagerConfig config, Map<ColumnDef, ItemTableColumn> colOverrides) {
setPanelOptions(config.getShowUniqueCardsOption());
// if this is the first setup call, panel options will be added to UI components
if (!this.panelOptionsCreated){
setPanelOptions(config.getShowUniqueCardsOption());
this.panelOptionsCreated = true;
}
// set status of components in the panel
setGroupBy(config.getGroupBy(), true);
setPileBy(config.getPileBy(), true);
setColumnCount(config.getImageColumnCount(), true);
......
......@@ -75,5 +75,23 @@
<artifactId>gdx-backend-robovm</artifactId>
<version>1.9.10</version>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-base-bom</artifactId>
<version>1.2.4.Final</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-core</artifactId>
<version>1.2.4.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-api</artifactId>
<version>1.2.4.Final</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
......@@ -80,5 +80,23 @@
<artifactId>commons-cli</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-base-bom</artifactId>
<version>1.2.4.Final</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-core</artifactId>
<version>1.2.4.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-api</artifactId>
<version>1.2.4.Final</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
......@@ -70,6 +70,24 @@
<artifactId>gdx-freetype</artifactId>
<version>1.9.10</version>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-base-bom</artifactId>
<version>1.2.4.Final</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-core</artifactId>
<version>1.2.4.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-api</artifactId>
<version>1.2.4.Final</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
......@@ -35,6 +35,8 @@ import forge.util.FileUtil;
import forge.util.Localizer;
import forge.util.Utils;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
......@@ -48,6 +50,7 @@ public class Forge implements ApplicationListener {
private static int screenWidth;
private static int screenHeight;
private static Graphics graphics;
private static FrameRate frameRate;
private static FScreen currentScreen;
private static SplashScreen splashScreen;
private static KeyInputAdapter keyInputAdapter;
......@@ -59,6 +62,7 @@ public class Forge implements ApplicationListener {
public static String extrawide = "default";
public static float heigtModifier = 0.0f;
private static boolean isloadingaMatch = false;
public static boolean showFPS = false;
public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0) {
if (GuiBase.getInterface() == null) {
......@@ -79,6 +83,7 @@ public class Forge implements ApplicationListener {
graphics = new Graphics();
splashScreen = new SplashScreen();
frameRate = new FrameRate();
Gdx.input.setInputProcessor(new MainInputProcessor());
/*
Set CatchBackKey here and exit the app when you hit the
......@@ -101,6 +106,8 @@ public class Forge implements ApplicationListener {
textureFiltering = prefs.getPrefBoolean(FPref.UI_LIBGDX_TEXTURE_FILTERING);
showFPS = prefs.getPrefBoolean(FPref.UI_SHOW_FPS);
final Localizer localizer = Localizer.getInstance();
//load model on background thread (using progress bar to report progress)
......@@ -114,23 +121,53 @@ public class Forge implements ApplicationListener {
FModel.initialize(splashScreen.getProgressBar(), null);
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblLoadingFonts"));
FSkinFont.preloadAll();
FSkinFont.preloadAll(prefs.getPref(FPref.UI_LANGUAGE));
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblLoadingCardTranslations"));
CardTranslation.preloadTranslation(prefs.getPref(FPref.UI_LANGUAGE));
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup"));
//add reminder to preload
if (prefs.getPrefBoolean(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART))
splashScreen.getProgressBar().setDescription("Preload Extended Art...");
Gdx.app.postRunnable(new Runnable() {
@Override
public void run() {
afterDbLoaded();
/* call preloadExtendedArt here, if we put it above we will *
* get error: No OpenGL context found in the current thread. */
preloadExtendedArt();
}
});
}
});
}
private void preloadExtendedArt() {
if (!FModel.getPreferences().getPrefBoolean(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART))
return;
List<String> keys = new ArrayList<>();
File[] directories = new File(ForgeConstants.CACHE_CARD_PICS_DIR).listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
if (!file.getName().startsWith("MPS_"))
return false;
return file.isDirectory();
}
});
for (File folder : directories) {
File[] files = new File(folder.toString()).listFiles();
for (File file : files) {
if (file.isFile()) {
keys.add(folder.getName() + "/" +file.getName().replace(".jpg","").replace(".png",""));
}
}
}
if (!keys.isEmpty())
ImageCache.preloadCache((Iterable<String>)keys);
}
private void afterDbLoaded() {
stopContinuousRendering(); //save power consumption by disabling continuous rendering once assets loaded
......@@ -366,6 +403,9 @@ public class Forge implements ApplicationListener {
@Override
public void render() {
if (showFPS)
frameRate.update();
try {
ImageCache.allowSingleLoad();
ForgeAnimation.advanceAll();
......@@ -408,6 +448,8 @@ public class Forge implements ApplicationListener {
graphics.end();
BugReporter.reportException(ex);
}
if (showFPS)
frameRate.render();
}
@Override
......
package forge;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.TimeUtils;
/**
* A nicer class for showing framerate that doesn't spam the console
* like Logger.log()
*
* @author William Hartman
*/
public class FrameRate implements Disposable{
long lastTimeCounted;
private float sinceChange;
private float frameRate;
private BitmapFont font;
private SpriteBatch batch;
private OrthographicCamera cam;
public FrameRate() {
lastTimeCounted = TimeUtils.millis();
sinceChange = 0;
frameRate = Gdx.graphics.getFramesPerSecond();
font = new BitmapFont();
batch = new SpriteBatch();
cam = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
}
public void resize(int screenWidth, int screenHeight) {
cam = new OrthographicCamera(screenWidth, screenHeight);
cam.translate(screenWidth / 2, screenHeight / 2);
cam.update();
batch.setProjectionMatrix(cam.combined);
}
public void update() {
long delta = TimeUtils.timeSinceMillis(lastTimeCounted);
lastTimeCounted = TimeUtils.millis();
sinceChange += delta;
if(sinceChange >= 1000) {
sinceChange = 0;
frameRate = Gdx.graphics.getFramesPerSecond();
}
}
public void render() {
batch.begin();
font.draw(batch, (int)frameRate + " fps", 3, Gdx.graphics.getHeight() - 3);
batch.end();
}
public void dispose() {
font.dispose();
batch.dispose();
}
}
\ No newline at end of file
......@@ -292,6 +292,11 @@ public class Graphics {
return index + 2;
}
public void drawfillBorder(float thickness, Color color, float x, float y, float w, float h, float cornerRadius) {
drawRoundRect(thickness, color, x, y, w, h, cornerRadius);
fillRoundRect(color, x, y, w, h, cornerRadius);
}
public void drawRoundRect(float thickness, FSkinColor skinColor, float x, float y, float w, float h, float cornerRadius) {
drawRoundRect(thickness, skinColor.getColor(), x, y, w, h, cornerRadius);
}
......@@ -312,21 +317,18 @@ public class Graphics {
}
//adjust width/height so rectangle covers equivalent filled area
w = Math.round(w - 1);
h = Math.round(h - 1);
w = Math.round(w + 1);
h = Math.round(h + 1);
startShape(ShapeType.Line);
shapeRenderer.setColor(color);
x = adjustX(x);
float y2 = adjustY(y, h);
float x2 = x + w;
y = y2 + h;
//TODO: draw arcs at corners
shapeRenderer.line(x, y, x, y2);
shapeRenderer.line(x, y2, x2 + 1, y2); //+1 prevents corner not being filled
shapeRenderer.line(x2, y2, x2, y);
shapeRenderer.line(x2 + 1, y, x, y); //+1 prevents corner not being filled
shapeRenderer.arc(adjustX(x) + cornerRadius, adjustY(y + cornerRadius, 0), cornerRadius, 90f, 90f);
shapeRenderer.arc(adjustX(x) + w - cornerRadius, adjustY(y + cornerRadius, 0), cornerRadius, 0f, 90f);
shapeRenderer.arc(adjustX(x) + w - cornerRadius, adjustY(y + h - cornerRadius, 0), cornerRadius, 270, 90f);
shapeRenderer.arc(adjustX(x) + cornerRadius, adjustY(y + h - cornerRadius, 0), cornerRadius, 180, 90f);
shapeRenderer.rect(adjustX(x), adjustY(y+cornerRadius, h-cornerRadius*2), w, h-cornerRadius*2);
shapeRenderer.rect(adjustX(x+cornerRadius), adjustY(y, h), w-cornerRadius*2, h);
endShape();
......@@ -343,6 +345,32 @@ public class Graphics {
batch.begin();
}
public void fillRoundRect(FSkinColor skinColor, float x, float y, float w, float h, float cornerRadius) {
fillRoundRect(skinColor.getColor(), x, y, w, h, cornerRadius);
}
public void fillRoundRect(Color color, float x, float y, float w, float h, float cornerRadius) {
batch.end(); //must pause batch while rendering shapes
if (alphaComposite < 1) {
color = FSkinColor.alphaColor(color, color.a * alphaComposite);
}
if (color.a < 1) { //enable blending so alpha colored shapes work properly
Gdx.gl.glEnable(GL_BLEND);
}
startShape(ShapeType.Filled);
shapeRenderer.setColor(color);
shapeRenderer.arc(adjustX(x) + cornerRadius, adjustY(y + cornerRadius, 0), cornerRadius, 90f, 90f);
shapeRenderer.arc(adjustX(x) + w - cornerRadius, adjustY(y + cornerRadius, 0), cornerRadius, 0f, 90f);
shapeRenderer.arc(adjustX(x) + w - cornerRadius, adjustY(y + h - cornerRadius, 0), cornerRadius, 270, 90f);
shapeRenderer.arc(adjustX(x) + cornerRadius, adjustY(y + h - cornerRadius, 0), cornerRadius, 180, 90f);
shapeRenderer.rect(adjustX(x), adjustY(y+cornerRadius, h-cornerRadius*2), w, h-cornerRadius*2);
shapeRenderer.rect(adjustX(x+cornerRadius), adjustY(y, h), w-cornerRadius*2, h);
endShape();
if (color.a < 1) {
Gdx.gl.glDisable(GL_BLEND);
}
batch.begin();
}
public void drawRect(float thickness, FSkinColor skinColor, float x, float y, float w, float h) {
drawRect(thickness, skinColor.getColor(), x, y, w, h);
}
......
......@@ -26,7 +26,8 @@ import java.util.Map;
public class FSkinFont {
private static final int MIN_FONT_SIZE = 8;
private static final int MAX_FONT_SIZE = 28;//72;
private static final int MAX_FONT_SIZE = 72;
private static final int MAX_FONT_SIZE_ZH_CN = 28;
private static final String TTF_FILE = "font1.ttf";
private static final Map<Integer, FSkinFont> fonts = new HashMap<>();
......@@ -58,8 +59,9 @@ public class FSkinFont {
}
//pre-load all supported font sizes
public static void preloadAll() {
for (int size = MIN_FONT_SIZE; size <= MAX_FONT_SIZE; size++) {
public static void preloadAll(String language) {
int maxfontSize = (language.equals("zh-CN")) ? MAX_FONT_SIZE_ZH_CN : MAX_FONT_SIZE;
for (int size = MIN_FONT_SIZE; size <= maxfontSize; size++) {
_get(size);
}
}
......
......@@ -282,6 +282,9 @@ public enum FSkinImage implements FImage {
IMG_ABILITY_HEXPROOF_UB (FSkinProp.IMG_ABILITY_HEXPROOF_UB, SourceFile.ABILITIES),
//token icon
IMG_ABILITY_TOKEN (FSkinProp.IMG_ABILITY_TOKEN, SourceFile.ABILITIES),
//border
IMG_BORDER_BLACK (FSkinProp.IMG_BORDER_BLACK, SourceFile.BORDERS),
IMG_BORDER_WHITE (FSkinProp.IMG_BORDER_WHITE, SourceFile.BORDERS),
//PROTECT ICONS
IMG_ABILITY_PROTECT_ALL (FSkinProp.IMG_ABILITY_PROTECT_ALL, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_B (FSkinProp.IMG_ABILITY_PROTECT_B, SourceFile.ABILITIES),
......@@ -308,6 +311,7 @@ public enum FSkinImage implements FImage {
OLD_FOILS(ForgeConstants.SPRITE_OLD_FOILS_FILE),
TROPHIES(ForgeConstants.SPRITE_TROPHIES_FILE),
ABILITIES(ForgeConstants.SPRITE_ABILITY_FILE),
BORDERS(ForgeConstants.SPRITE_BORDER_FILE),
MANAICONS(ForgeConstants.SPRITE_MANAICONS_FILE),
PLANAR_CONQUEST(ForgeConstants.SPRITE_PLANAR_CONQUEST_FILE);
......
......@@ -18,21 +18,25 @@
package forge.assets;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import forge.ImageKeys;
import forge.card.CardEdition;
import forge.game.card.CardView;
import forge.game.player.IHasIcon;
import forge.item.IPaperCard;
import forge.item.InventoryItem;
import forge.model.FModel;
import forge.properties.ForgeConstants;
import forge.util.ImageUtil;
import org.apache.commons.lang3.StringUtils;
import org.cache2k.Cache;
import org.cache2k.Cache2kBuilder;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* This class stores ALL card images in a cache with soft values. this means
......@@ -52,11 +56,16 @@ public class ImageCache {
// short prefixes to save memory
private static final Set<String> missingIconKeys = new HashSet<>();
private static final LoadingCache<String, Texture> cache = CacheBuilder.newBuilder()
.maximumSize(400)
.expireAfterAccess(15,TimeUnit.MINUTES)
.build(new ImageLoader());
private static final Cache<String, Texture> cache = new Cache2kBuilder<String, Texture>() {}
.name("cache")
.eternal(true)
.permitNullValues(true)
.disableStatistics(true)
.loader(new ImageLoader())
.build();
public static final Texture defaultImage;
public static FImage BlackBorder = FSkinImage.IMG_BORDER_BLACK;
public static FImage WhiteBorder = FSkinImage.IMG_BORDER_WHITE;
private static boolean imageLoaded, delayLoadRequested;
public static void allowSingleLoad() {
......@@ -76,7 +85,7 @@ public class ImageCache {
}
public static void clear() {
cache.invalidateAll();
cache.clear();
missingIconKeys.clear();
}
......@@ -125,7 +134,8 @@ public class ImageCache {
Texture image;
if (useDefaultIfNotFound) {
// Load from file and add to cache if not found in cache initially.
image = cache.getIfPresent(imageKey);
image = cache.get(imageKey);
if (image != null) { return image; }
if (imageLoaded) { //prevent loading more than one image each render for performance
......@@ -139,15 +149,7 @@ public class ImageCache {
imageLoaded = true;
}
try {
image = cache.get(imageKey);
}
catch (final ExecutionException ex) {
if (!(ex.getCause() instanceof NullPointerException)) {
ex.printStackTrace();
}
image = null;
}
try { image = cache.get(imageKey); }
catch (final Exception ex) {
image = null;
}
......@@ -162,4 +164,81 @@ public class ImageCache {
}
return image;
}
public static void preloadCache(Iterable keys) {
cache.getAll(keys);
}
public static TextureRegion croppedBorderImage(Texture image) {
float rscale = 0.96f;
int rw = Math.round(image.getWidth()*rscale);
int rh = Math.round(image.getHeight()*rscale);
int rx = Math.round((image.getWidth() - rw)/2);
int ry = Math.round((image.getHeight() - rh)/2)-2;
TextureRegion rimage = new TextureRegion(image, rx, ry, rw, rh);
return rimage;
}
public static boolean isWhiteBordered(IPaperCard c) {
if (c == null)
return false;
CardEdition ed = FModel.getMagicDb().getEditions().get(c.getEdition());
if (ed != null && ed.isWhiteBorder())
return true;
return false;
}
public static boolean isWhiteBordered(CardView c) {
if (c == null)
return false;
CardView.CardStateView state = c.getCurrentState();
CardEdition ed = FModel.getMagicDb().getEditions().get(state.getSetCode());
if (ed != null && ed.isWhiteBorder() && state.getFoilIndex() == 0)
return true;
return false;
}
public static Color borderColor(IPaperCard c) {
if (c == null)
return Color.valueOf("#171717");
CardEdition ed = FModel.getMagicDb().getEditions().get(c.getEdition());
if (ed != null && ed.isWhiteBorder())
return Color.valueOf("#fffffd");
return Color.valueOf("#171717");
}
public static Color borderColor(CardView c) {
if (c == null)
return Color.valueOf("#171717");
CardView.CardStateView state = c.getCurrentState();
CardEdition ed = FModel.getMagicDb().getEditions().get(state.getSetCode());
if (ed != null && ed.isWhiteBorder() && state.getFoilIndex() == 0)
return Color.valueOf("#fffffd");
return Color.valueOf("#171717");
}
public static boolean isExtendedArt(CardView c) {
if (c == null)
return false;
CardView.CardStateView state = c.getCurrentState();
if (state.getSetCode().contains("MPS_"))
return true;
return false;
}
public static boolean isExtendedArt(IPaperCard c) {
if (c == null)
return false;
if (c.getEdition().contains("MPS_"))
return true;
return false;
}
public static FImage getBorderImage(CardView c) {
if (isWhiteBordered(c))
return WhiteBorder;
return BlackBorder;
}
public static FImage getBorderImage(IPaperCard c) {
if (isWhiteBordered(c))
return WhiteBorder;
return BlackBorder;
}
}
......@@ -3,8 +3,12 @@ package forge.assets;
import java.io.File;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.google.common.cache.CacheLoader;
import com.badlogic.gdx.graphics.TextureData;
import com.badlogic.gdx.graphics.glutils.PixmapTextureData;
import org.cache2k.integration.CacheLoader;
import forge.Forge;
import forge.ImageKeys;
......@@ -12,28 +16,23 @@ import forge.ImageKeys;
final class ImageLoader extends CacheLoader<String, Texture> {
@Override
public Texture load(String key) {
boolean extendedArt = false;
boolean textureFilter = Forge.isTextureFilteringEnabled();
if (key.length() > 4){
if ((key.substring(0,4).contains("MPS_"))) //TODO add sets to get all extended art???
extendedArt = true;
//use generated extended art... it will preload the cache at startup so... yeah! :)
}
File file = ImageKeys.getImageFile(key);
if (file != null) {
FileHandle fh = new FileHandle(file);
try {
if (Forge.isTextureFilteringEnabled()) {
Texture t = new Texture(fh, true);
Texture t = new Texture(fh, textureFilter);
if (textureFilter)
t.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
/* // Optional experimental feature: Anisotropic filtering
GL20 gl = Gdx.gl20;
if (gl != null && Gdx.graphics.supportsExtension("GL_EXT_texture_filter_anisotropic")) {
FloatBuffer buffer = BufferUtils.newFloatBuffer(16);
gl.glGetFloatv(GL20.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, buffer);
float maxAniso = buffer.get(0);
t.bind();
gl.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAniso);
} */
return t;
} else {
return new Texture(fh);
}
if (extendedArt)
return generateTexture(fh, t, textureFilter);
return t;
}
catch (Exception ex) {
Forge.log("Could not read image file " + fh.path() + "\n\nException:\n" + ex.toString());
......@@ -41,4 +40,62 @@ final class ImageLoader extends CacheLoader<String, Texture> {
}
return null;
}
public Texture generateTexture(FileHandle fh, Texture t, boolean textureFilter) {
if (t == null || fh == null)
return t;
Pixmap pImage = new Pixmap(fh);
int w = pImage.getWidth();
int h = pImage.getHeight();
int radius = (h - w) / 8;
Pixmap pMask = createRoundedRectangle(w, h, radius, Color.RED);
drawPixelstoMask(pImage, pMask);
TextureData textureData = new PixmapTextureData(
pMask, //pixmap to use
Pixmap.Format.RGBA8888,
textureFilter, //use mipmaps
false, true);
t = new Texture(textureData);
if (textureFilter)
t.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
pImage.dispose();
pMask.dispose();
return t;
}
public Pixmap createRoundedRectangle(int width, int height, int cornerRadius, Color color) {
Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);
Pixmap ret = new Pixmap(width, height, Pixmap.Format.RGBA8888);
pixmap.setColor(color);
//round corners
pixmap.fillCircle(cornerRadius, cornerRadius, cornerRadius);
pixmap.fillCircle(width - cornerRadius - 1, cornerRadius, cornerRadius);
pixmap.fillCircle(cornerRadius, height - cornerRadius - 1, cornerRadius);
pixmap.fillCircle(width - cornerRadius - 1, height - cornerRadius - 1, cornerRadius);
//two rectangle parts
pixmap.fillRectangle(cornerRadius, 0, width - cornerRadius * 2, height);
pixmap.fillRectangle(0, cornerRadius, width, height - cornerRadius * 2);
//draw rounded rectangle
ret.setColor(color);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
if (pixmap.getPixel(x, y) != 0) ret.drawPixel(x, y);
}
}
pixmap.dispose();
return ret;
}
public void drawPixelstoMask(Pixmap pixmap, Pixmap mask){
int pixmapWidth = mask.getWidth();
int pixmapHeight = mask.getHeight();
Color pixelColor = new Color();
for (int x=0; x<pixmapWidth; x++){
for (int y=0; y<pixmapHeight; y++){
if (mask.getPixel(x, y) != 0) {
Color.rgba8888ToColor(pixelColor, pixmap.getPixel(x, y));
mask.setColor(pixelColor);
mask.drawPixel(x, y);
}
}
}
}
}
......@@ -8,6 +8,8 @@ import forge.assets.ImageCache;
import forge.card.CardRenderer.CardStackPosition;
import forge.game.card.CardView;
import forge.item.PaperCard;
import forge.model.FModel;
import forge.properties.ForgePreferences;
import forge.toolbox.FCardPanel;
public class CardImage implements FImage {
......@@ -17,6 +19,9 @@ public class CardImage implements FImage {
public CardImage(PaperCard card0) {
card = card0;
}
private static boolean isPreferenceEnabled(ForgePreferences.FPref preferenceName) {
return FModel.getPreferences().getPrefBoolean(preferenceName);
}
@Override
public float getWidth() {
......@@ -33,9 +38,13 @@ public class CardImage implements FImage {
@Override
public void draw(Graphics g, float x, float y, float w, float h) {
boolean mask = isPreferenceEnabled(ForgePreferences.FPref.UI_ENABLE_BORDER_MASKING);
if (image == null) { //attempt to retrieve card image if needed
image = ImageCache.getImage(card);
if (image == null) {
if (mask) //render this if mask is still loading
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(card), false, x, y, w, h, CardStackPosition.Top);
return; //can't draw anything if can't be loaded yet
}
}
......@@ -44,7 +53,17 @@ public class CardImage implements FImage {
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(card), false, x, y, w, h, CardStackPosition.Top);
}
else {
g.drawImage(image, x, y, w, h);
if (mask) {
if (ImageCache.isExtendedArt(card))
g.drawImage(image, x, y, w, h);
else {
float radius = (h - w)/8;
g.drawfillBorder(3, ImageCache.borderColor(card), x, y, w, h, radius);
g.drawImage(ImageCache.croppedBorderImage(image), x+radius/2.2f, y+radius/2, w*0.96f, h*0.96f);
}
}
else
g.drawImage(image, x, y, w, h);
}
}
}
......@@ -325,6 +325,8 @@ public class CardImageRenderer {
}
public static void drawZoom(Graphics g, CardView card, GameView gameView, boolean altState, float x, float y, float w, float h, float dispW, float dispH, boolean isCurrentCard) {
boolean mask = isPreferenceEnabled(ForgePreferences.FPref.UI_ENABLE_BORDER_MASKING);
//this one is currently using the mask, others are cropped and use generated borders from shaperenderer ...
final Texture image = ImageCache.getImage(card.getState(altState).getImageKey(MatchController.instance.getLocalPlayers()), true);
if (image == null) { //draw details if can't draw zoom
drawDetails(g, card, gameView, altState, x, y, w, h);
......@@ -342,21 +344,59 @@ public class CardImageRenderer {
drawCardImage(g, card, altState, x, y, w, h, CardStackPosition.Top);
}
else {
float radius = (h - w)/8;
float wh_Adj = ForgeConstants.isGdxPortLandscape && isCurrentCard ? 1.38f:1.0f;
float new_w = w*wh_Adj;
float new_h = h*wh_Adj;
float new_x = ForgeConstants.isGdxPortLandscape && isCurrentCard ? (dispW - new_w) / 2:x;
float new_y = ForgeConstants.isGdxPortLandscape && isCurrentCard ? (dispH - new_h) / 2:y;
float new_xRotate = (dispW - new_h) /2;
float new_yRotate = (dispH - new_w) /2;
boolean rotateSplit = FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_SPLIT_CARDS);
boolean rotatePlane = FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON);
if (rotatePlane && (card.getCurrentState().isPhenomenon() || card.getCurrentState().isPlane())) {
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90);
if (mask){
if (ImageCache.isExtendedArt(card))
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90);
else {
if (rotatePlane)
g.drawfillBorder(3, ImageCache.borderColor(card), new_xRotate, new_yRotate, new_h, new_w, radius);
else
g.drawfillBorder(3, ImageCache.borderColor(card), x, y, w, h, radius);
g.drawRotatedImage(ImageCache.croppedBorderImage(image), new_x+radius/2.3f, new_y+radius/2, new_w*0.96f, new_h*0.96f, (new_x+radius/2.3f) + (new_w*0.96f) / 2, (new_y+radius/2) + (new_h*0.96f) / 2, -90);
}
}
else
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90);
} else if (rotateSplit && isCurrentCard && card.isSplitCard() && canLook) {
boolean isAftermath = card.getText().contains("Aftermath") || card.getAlternateState().getOracleText().contains("Aftermath");
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, isAftermath ? 90 : -90);
if (mask) {
if (ImageCache.isExtendedArt(card))
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, isAftermath ? 90 : -90);
else {
if (rotateSplit)
g.drawfillBorder(3, ImageCache.borderColor(card), new_xRotate, new_yRotate, new_h, new_w, radius);
else
g.drawfillBorder(3, ImageCache.borderColor(card), x, y, w, h, radius);
g.drawRotatedImage(ImageCache.croppedBorderImage(image), new_x + radius / 2.3f, new_y + radius / 2, new_w * 0.96f, new_h * 0.96f, (new_x + radius / 2.3f) + (new_w * 0.96f) / 2, (new_y + radius / 2) + (new_h * 0.96f) / 2, isAftermath ? 90 : -90);
}
}
else
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, isAftermath ? 90 : -90);
}
else {
g.drawImage(image, x, y, w, h);
if (mask) {
if (ImageCache.isExtendedArt(card))
g.drawImage(image, x, y, w, h);
else {
g.drawImage(ImageCache.getBorderImage(card), x, y, w, h);
g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f, y + radius / 2, w * 0.96f, h * 0.96f);
}
}
else
g.drawImage(image, x, y, w, h);
}
}
CardRenderer.drawFoilEffect(g, card, x, y, w, h, isCurrentCard && canLook && image != ImageCache.defaultImage);
......@@ -521,4 +561,8 @@ public class CardImageRenderer {
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
g.drawText(ptText, PT_FONT, Color.BLACK, x, y, w, h, false, Align.center, true);
}
private static boolean isPreferenceEnabled(ForgePreferences.FPref preferenceName) {
return FModel.getPreferences().getPrefBoolean(preferenceName);
}
}
......@@ -392,14 +392,25 @@ public class CardRenderer {
}
public static void drawCard(Graphics g, IPaperCard pc, float x, float y, float w, float h, CardStackPosition pos) {
boolean mask = isPreferenceEnabled(FPref.UI_ENABLE_BORDER_MASKING);
Texture image = new RendererCachedCardImage(pc, false).getImage();
float radius = (h - w)/8;
if (image != null) {
if (image == ImageCache.defaultImage) {
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(pc), false, x, y, w, h, pos);
}
else {
g.drawImage(image, x, y, w, h);
if (mask) {
if (ImageCache.isExtendedArt(pc))
g.drawImage(image, x, y, w, h);
else {
g.drawImage(ImageCache.getBorderImage(pc), x, y, w, h);
g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f, y + radius / 2, w * 0.96f, h * 0.96f);
}
}
else
g.drawImage(image, x, y, w, h);
}
if (pc.isFoil()) { //draw foil effect if needed
final CardView card = CardView.getCardForUi(pc);
......@@ -409,13 +420,18 @@ public class CardRenderer {
drawFoilEffect(g, card, x, y, w, h, false);
}
}
else { //draw cards without textures as just a black rectangle
g.fillRect(Color.BLACK, x, y, w, h);
else {
if (mask) //render this if mask is still loading
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(pc), false, x, y, w, h, pos);
else //draw cards without textures as just a black rectangle
g.fillRect(Color.BLACK, x, y, w, h);
}
}
public static void drawCard(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean rotate) {
boolean mask = isPreferenceEnabled(FPref.UI_ENABLE_BORDER_MASKING);
Texture image = new RendererCachedCardImage(card, false).getImage();
float radius = (h - w)/8;
if (image != null) {
if (image == ImageCache.defaultImage) {
......@@ -423,15 +439,38 @@ public class CardRenderer {
}
else {
if(FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON)
&& (card.getCurrentState().isPhenomenon() || card.getCurrentState().isPlane()) && rotate)
g.drawRotatedImage(image, x, y, w, h, x + w / 2, y + h / 2, -90);
else
g.drawImage(image, x, y, w, h);
&& (card.getCurrentState().isPhenomenon() || card.getCurrentState().isPlane()) && rotate){
if (mask) {
if (ImageCache.isExtendedArt(card))
g.drawRotatedImage(image, x, y, w, h, x + w / 2, y + h / 2, -90);
else {
g.drawfillBorder(3, ImageCache.borderColor(card), x, y, w, h, radius);
g.drawRotatedImage(ImageCache.croppedBorderImage(image), x+radius/2.3f, y+radius/2, w*0.96f, h*0.96f, (x+radius/2.3f) + (w*0.96f) / 2, (y+radius/2) + (h*0.96f) / 2, -90);
}
}
else
g.drawRotatedImage(image, x, y, w, h, x + w / 2, y + h / 2, -90);
}
else {
if (mask) {
if (ImageCache.isExtendedArt(card))
g.drawImage(image, x, y, w, h);
else {
g.drawImage(ImageCache.getBorderImage(card), x, y, w, h);
g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f, y + radius / 2, w * 0.96f, h * 0.96f);
}
}
else
g.drawImage(image, x, y, w, h);
}
}
drawFoilEffect(g, card, x, y, w, h, false);
}
else { //draw cards without textures as just a black rectangle
g.fillRect(Color.BLACK, x, y, w, h);
else {
if (mask) //render this if mask is still loading
CardImageRenderer.drawCardImage(g, card, false, x, y, w, h, pos);
else //draw cards without textures as just a black rectangle
g.fillRect(Color.BLACK, x, y, w, h);
}
}
......
<
......@@ -107,21 +107,39 @@ public class VField extends FContainer {
}
final String cardName = card.getCurrentState().getName();
for (CardView c : cardsOfType) {
if (!c.hasCardAttachments() &&
cardName.equals(c.getCurrentState().getName()) &&
card.hasSameCounters(c) &&
card.getCurrentState().getKeywordKey().equals(c.getCurrentState().getKeywordKey()) &&
card.isTapped() == c.isTapped() && // don't stack tapped tokens on untapped tokens
card.isSick() == c.isSick() && //don't stack sick tokens on non sick
card.isToken() == c.isToken()) { //don't stack tokens on top of non-tokens
CardAreaPanel cPanel = CardAreaPanel.get(c);
while (cPanel.getNextPanelInStack() != null) {
cPanel = cPanel.getNextPanelInStack();
if (c.getCurrentState().isCreature()) {
if (!c.hasCardAttachments() &&
cardName.equals(c.getCurrentState().getName()) &&
card.hasSameCounters(c) &&
card.getCurrentState().getKeywordKey().equals(c.getCurrentState().getKeywordKey()) &&
card.isTapped() == c.isTapped() && // don't stack tapped tokens on untapped tokens
card.isSick() == c.isSick() && //don't stack sick tokens on non sick
card.isToken() == c.isToken()) { //don't stack tokens on top of non-tokens
CardAreaPanel cPanel = CardAreaPanel.get(c);
while (cPanel.getNextPanelInStack() != null) {
cPanel = cPanel.getNextPanelInStack();
}
CardAreaPanel cardPanel = CardAreaPanel.get(card);
cPanel.setNextPanelInStack(cardPanel);
cardPanel.setPrevPanelInStack(cPanel);
return true;
}
} else {
if (!c.hasCardAttachments() &&
cardName.equals(c.getCurrentState().getName()) &&
card.hasSameCounters(c) &&
card.getCurrentState().getKeywordKey().equals(c.getCurrentState().getKeywordKey()) &&
card.isSick() == c.isSick() && //don't stack sick tokens on non sick
card.isToken() == c.isToken()) { //don't stack tokens on top of non-tokens
CardAreaPanel cPanel = CardAreaPanel.get(c);
while (cPanel.getNextPanelInStack() != null) {
cPanel = cPanel.getNextPanelInStack();
}
CardAreaPanel cardPanel = CardAreaPanel.get(card);