Merge branch 'adventure' into 'master'

Add Adventure mode

See merge request core-developers/forge!5318
This commit is contained in:
Michael Kamensky
2021-10-16 16:47:21 +00:00
767 changed files with 74768 additions and 2 deletions

6
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,6 @@
deploy:
image: maven:3.6-jdk-8
script:
- 'mvn -U -B clean -P windows-linux install'
only:
- master

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

188
forge-adventure/pom.xml Normal file
View File

@@ -0,0 +1,188 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.45-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>forge-adventure</artifactId>
<packaging>jar</packaging>
<name>Forge Adventure</name>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>com.akathist.maven.plugins.launch4j</groupId>
<artifactId>launch4j-maven-plugin</artifactId>
<version>1.7.25</version>
<executions>
<execution>
<id>l4j-adv</id>
<phase>package</phase>
<goals>
<goal>launch4j</goal>
</goals>
<configuration>
<headerType>gui</headerType>
<outfile>${project.build.directory}/forge-adventure.exe</outfile>
<jar>${project.build.finalName}-jar-with-dependencies.jar</jar>
<dontWrapJar>true</dontWrapJar>
<errTitle>forge</errTitle>
<icon>src/main/config/forge-adventure.ico</icon>
<classPath>
<mainClass>forge.adventure.Main</mainClass>
<addDependencies>false</addDependencies>
<preCp>anything</preCp>
</classPath>
<jre>
<minVersion>1.8.0</minVersion>
<maxHeapSize>4096</maxHeapSize>
<opts>
<opt>-Dfile.encoding=UTF-8</opt>
</opts>
</jre>
<versionInfo>
<fileVersion>
1.0.0.0
</fileVersion>
<txtFileVersion>
1.0.0.0
</txtFileVersion>
<fileDescription>Forge</fileDescription>
<copyright>Forge</copyright>
<productVersion>
1.0.0.0
</productVersion>
<txtProductVersion>
1.0.0.0
</txtProductVersion>
<productName>forge-adventure</productName>
<internalName>forge-adventure</internalName>
<originalFilename>forge-adventure.exe</originalFilename>
</versionInfo>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<attach>false</attach>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>forge.adventure.Main</mainClass>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<!-- this is used for inheritance merges -->
<phase>package</phase>
<!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.github.jetopto1</groupId>
<artifactId>cling</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx</artifactId>
<version>1.10.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx-platform</artifactId>
<version>1.10.0</version>
<classifier>natives-desktop</classifier>
</dependency>
<dependency>
<groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx-freetype</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx-backend-lwjgl3</artifactId>
<version>1.10.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx-freetype-platform</artifactId>
<version>1.10.0</version>
<classifier>natives-desktop</classifier>
</dependency>
<dependency>
<groupId>forge</groupId>
<artifactId>forge-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>forge</groupId>
<artifactId>forge-game</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>forge</groupId>
<artifactId>forge-ai</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>forge</groupId>
<artifactId>forge-gui</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.github.raeleus.TenPatch</groupId>
<artifactId>tenpatch</artifactId>
<version>5.2.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>

View File

@@ -0,0 +1,14 @@
# ideally this should be using HTTPS, but this is fine for now
dsn=http://a0b8dbad9b8a49cfa51bf65d462e8dae@sentry.cardforge.org:9000/2
stacktrace.app.packages=forge
# where to store events if offline or can't reach the above server
buffer.dir=sentry-events
buffer.size=100
# allow ample time for graceful shutdown
buffer.shutdowntimeout=5000
async.shutdowntimeout=5000
# allow longer messages
maxmessagelength=1500

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

View File

@@ -0,0 +1,195 @@
package forge.adventure;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ScreenUtils;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.scene.ForgeScene;
import forge.adventure.scene.Scene;
import forge.adventure.scene.SceneType;
import forge.adventure.util.Config;
/**
* Application adapter the handle switching and fading between scenes
*/
public class AdventureApplicationAdapter extends ApplicationAdapter {
public static AdventureApplicationAdapter instance;
Scene currentScene = null;
Array<Scene> lastScene = new Array<>();
private int currentWidth;
private int currentHeight;
private float animationTimeout;
Batch animationBatch;
Texture transitionTexture;
TextureRegion lastScreenTexture;
private boolean sceneWasSwapped =false;
private Graphics graphics;
public Graphics getGraphics()
{
if(graphics==null)
graphics=new Graphics();
return graphics;
}
public TextureRegion getLastScreenTexture() {
return lastScreenTexture;
}
public AdventureApplicationAdapter() {
instance = this;
}
public int getCurrentWidth() {
return currentWidth;
}
public int getCurrentHeight() {
return currentHeight;
}
public Scene getCurrentScene() {
return currentScene;
}
@Override
public void resize(int w, int h) {
currentWidth = w;
currentHeight = h;
StartAdventure.app.resize(w, h);
super.resize(w, h);
}
public boolean switchScene(Scene newScene) {
if (currentScene != null) {
if (!currentScene.leave())
return false;
lastScene.add(currentScene);
}
storeScreen();
sceneWasSwapped =true;
currentScene = newScene;
currentScene.enter();
return true;
}
private void storeScreen() {
if(!(currentScene instanceof ForgeScene))
lastScreenTexture = ScreenUtils.getFrameBufferTexture();
}
public void resLoaded() {
for (forge.adventure.scene.SceneType entry : SceneType.values()) {
entry.instance.resLoaded();
}
//AdventureApplicationAdapter.CurrentAdapter.switchScene(SceneType.RewardScene.instance);
switchScene(SceneType.StartScene.instance);
animationBatch=new SpriteBatch();
transitionTexture =new Texture(Config.instance().getFile("ui/transition.png"));
}
@Override
public void create() {
Pixmap pm = new Pixmap(Config.instance().getFile("skin/cursor.png"));
Gdx.graphics.setCursor(Gdx.graphics.newCursor(pm, 0, 0));
pm.dispose();
for (forge.adventure.scene.SceneType entry : SceneType.values()) {
entry.instance.create();
}
}
@Override
public void render() {
float delta=Gdx.graphics.getDeltaTime();
float transitionTime = 0.2f;
if(sceneWasSwapped)
{
sceneWasSwapped =false;
animationTimeout= transitionTime;
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
return;
}
if(animationTimeout>=0)
{
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
animationBatch.begin();
animationTimeout-=delta;
animationBatch.setColor(1,1,1,1);
animationBatch.draw(lastScreenTexture,0,0, Gdx.graphics.getWidth(),Gdx.graphics.getHeight());
animationBatch.setColor(1,1,1,1-(1/ transitionTime)*animationTimeout);
animationBatch.draw(transitionTexture,0,0, Gdx.graphics.getWidth(),Gdx.graphics.getHeight());
animationBatch.draw(transitionTexture,0,0, Gdx.graphics.getWidth(),Gdx.graphics.getHeight());
animationBatch.end();
if(animationTimeout<0)
{
currentScene.render();
storeScreen();
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
else
{
return;
}
}
if(animationTimeout>=-transitionTime)
{
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
animationBatch.begin();
animationTimeout-=delta;
animationBatch.setColor(1,1,1,1);
animationBatch.draw(lastScreenTexture,0,0, Gdx.graphics.getWidth(),Gdx.graphics.getHeight());
animationBatch.setColor(1,1,1,(1/ transitionTime)*(animationTimeout+ transitionTime));
animationBatch.draw(transitionTexture,0,0, Gdx.graphics.getWidth(),Gdx.graphics.getHeight());
animationBatch.draw(transitionTexture,0,0, Gdx.graphics.getWidth(),Gdx.graphics.getHeight());
animationBatch.end();
return;
}
currentScene.render();
currentScene.act(delta);
}
@Override
public void dispose() {
for (forge.adventure.scene.SceneType entry : SceneType.values()) {
entry.instance.dispose();
}
System.exit(0);
}
private Scene getLastScene() {
return lastScene.size==0?null: lastScene.get(lastScene.size-1);
}
public Scene switchToLast() {
if(lastScene.size!=0)
{
storeScreen();
currentScene = lastScene.get(lastScene.size-1);
currentScene.enter();
sceneWasSwapped =true;
lastScene.removeIndex(lastScene.size-1);
return currentScene;
}
return null;
}
}

View File

@@ -0,0 +1,91 @@
package forge.adventure;
import com.badlogic.gdx.Gdx;
import forge.interfaces.IDeviceAdapter;
import forge.util.FileUtil;
import forge.util.OperatingSystem;
import forge.util.RestartUtil;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Adapter for desktop usage
*/
public class DesktopAdapter implements IDeviceAdapter {
private final String switchOrientationFile;
public DesktopAdapter(String switchOrientationFile0) {
switchOrientationFile = switchOrientationFile0;
}
//just assume desktop always connected to wifi
@Override
public boolean isConnectedToInternet() {
return true;
}
@Override
public boolean isConnectedToWifi() {
return true;
}
@Override
public String getDownloadsDir() {
return System.getProperty("user.home") + "/Downloads/";
}
@Override
public boolean openFile(String filename) {
try {
Desktop.getDesktop().open(new File(filename));
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
@Override
public void restart() {
if (RestartUtil.prepareForRestart()) {
Gdx.app.exit();
}
}
@Override
public void exit() {
Gdx.app.exit(); //can just use Gdx.app.exit for desktop
}
@Override
public boolean isTablet() {
return true; //treat desktop the same as a tablet
}
@Override
public void setLandscapeMode(boolean landscapeMode) {
//create file to indicate that landscape mode should be used
if (landscapeMode) {
FileUtil.writeFile(switchOrientationFile, "1");
} else {
FileUtil.deleteFile(switchOrientationFile);
}
}
@Override
public void preventSystemSleep(boolean preventSleep) {
OperatingSystem.preventSystemSleep(preventSleep);
}
@Override
public void convertToJPEG(InputStream input, OutputStream output) throws IOException {
BufferedImage image = ImageIO.read(input);
ImageIO.write(image, "jpg", output);
}
}

View File

@@ -0,0 +1,299 @@
package forge.adventure;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Clipboard;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.utils.Clipboard;
import forge.adventure.libgdxgui.Forge;
import forge.adventure.libgdxgui.FrameRate;
import forge.adventure.libgdxgui.GuiMobile;
import forge.adventure.libgdxgui.assets.AssetsDownloader;
import forge.adventure.libgdxgui.assets.FSkin;
import forge.adventure.libgdxgui.assets.FSkinFont;
import forge.adventure.libgdxgui.assets.ImageCache;
import forge.adventure.libgdxgui.screens.FScreen;
import forge.adventure.libgdxgui.screens.SplashScreen;
import forge.adventure.scene.SettingsScene;
import forge.adventure.util.Config;
import forge.error.ExceptionHandler;
import forge.gui.FThreads;
import forge.gui.GuiBase;
import forge.interfaces.IDeviceAdapter;
import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.properties.ForgePreferences;
import forge.model.FModel;
import forge.sound.MusicPlaylist;
import forge.sound.SoundSystem;
import forge.util.BuildInfo;
import forge.util.CardTranslation;
import forge.util.FileUtil;
import forge.util.Localizer;
import io.sentry.Sentry;
import io.sentry.SentryClient;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
/**
* Wrapper to start forge first (splash screen and resources loading)
*
*/
class StartAdventure extends AdventureApplicationAdapter {
private static final int continuousRenderingCount = 1; //initialize to 1 since continuous rendering is the default
private static final Deque<FScreen> Dscreens = new ArrayDeque<>();
private static final boolean isloadingaMatch = false;
public static String extrawide = "default";
public static float heigtModifier = 0.0f;
public static boolean showFPS = false;
public static boolean altPlayerLayout = false;
public static boolean altZoneTabs = false;
public static String enableUIMask = "Crop";
public static boolean enablePreloadExtendedArt = false;
public static boolean isTabletDevice = false;
public static String locale = "en-US";
public static boolean hdbuttons = false;
public static boolean hdstart = false;
public static boolean isPortraitMode = false;
public static boolean gameInProgress = false;
public static boolean disposeTextures = false;
public static int cacheSize = 400;
public static int totalDeviceRAM = 0;
public static int androidVersion = 0;
public static boolean autoCache = false;
public static int lastButtonIndex = 0;
public static String CJK_Font = "";
public static Forge app;
private static Clipboard clipboard;
private static IDeviceAdapter deviceAdapter;
private static FrameRate frameRate;
private static FScreen currentScreen;
private static SplashScreen splashScreen;
private static Forge.KeyInputAdapter keyInputAdapter;
private static boolean exited;
private static boolean textureFiltering = false;
private static boolean destroyThis = false;
public StartAdventure() {
super();
Forge.isTabletDevice = true;
Forge.isPortraitMode = false;
Forge.hdbuttons = true;
Forge.hdstart = true;
String path= Files.exists(Paths.get("./res"))?"./":"../forge-gui/";
app = (Forge) Forge.getApp(new Lwjgl3Clipboard(), new DesktopAdapter(""), path, true, false, 0, true, 0, "", "");
clipboard = new Lwjgl3Clipboard();
GuiBase.setUsingAppDirectory(false); //obb directory on android uses the package name as entrypoint
GuiBase.setInterface(new GuiMobile(path));
GuiBase.enablePropertyConfig(true);
isPortraitMode = true;
totalDeviceRAM = 0;
GuiBase.setDeviceInfo("", "", 0, 0);
}
@Override
public void render() {
if (splashScreen != null) {
Gdx.gl.glClearColor(1, 0, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); // Clear the screen.
getGraphics().begin(getCurrentWidth(), getCurrentHeight());
splashScreen.setSize(getCurrentWidth(), getCurrentHeight());
splashScreen.screenPos.setSize(getCurrentWidth(), getCurrentHeight());
if (splashScreen.getRotate180()) {
getGraphics().startRotateTransform(getCurrentWidth() / 2f, getCurrentHeight() / 2f, 180);
}
splashScreen.draw(getGraphics());
if (splashScreen.getRotate180()) {
getGraphics().endTransform();
}
getGraphics().end();
} else {
super.render();
}
}
@Override
public void resize(int width, int height) {
super.resize(width, height);
if (splashScreen != null)
splashScreen.setSize(width, height);
}
@Override
public void create() {
//install our error handler
ExceptionHandler.registerErrorHandling();
splashScreen = new SplashScreen();
frameRate = new FrameRate();
/*
Set CatchBackKey here and exit the app when you hit the
back button while the textures,fonts,etc are still loading,
to prevent rendering issue when you try to restart
the app again (seems it doesnt dispose correctly...?!?)
*/
Gdx.input.setCatchKey(Input.Keys.BACK, true);
destroyThis = true; //Prevent back()
ForgePreferences prefs = SettingsScene.Preference = new ForgePreferences();
String skinName;
if (FileUtil.doesFileExist(ForgeConstants.MAIN_PREFS_FILE)) {
skinName = prefs.getPref(ForgePreferences.FPref.UI_SKIN);
} else {
skinName = "default"; //use default skin if preferences file doesn't exist yet
}
FSkin.loadLight(skinName, splashScreen);
textureFiltering = prefs.getPrefBoolean(ForgePreferences.FPref.UI_LIBGDX_TEXTURE_FILTERING);
showFPS = prefs.getPrefBoolean(ForgePreferences.FPref.UI_SHOW_FPS);
altPlayerLayout = prefs.getPrefBoolean(ForgePreferences.FPref.UI_ALT_PLAYERINFOLAYOUT);
altZoneTabs = prefs.getPrefBoolean(ForgePreferences.FPref.UI_ALT_PLAYERZONETABS);
enableUIMask = prefs.getPref(ForgePreferences.FPref.UI_ENABLE_BORDER_MASKING);
if (prefs.getPref(ForgePreferences.FPref.UI_ENABLE_BORDER_MASKING).equals("true")) //override old settings if not updated
enableUIMask = "Full";
else if (prefs.getPref(ForgePreferences.FPref.UI_ENABLE_BORDER_MASKING).equals("false"))
enableUIMask = "Off";
enablePreloadExtendedArt = prefs.getPrefBoolean(ForgePreferences.FPref.UI_ENABLE_PRELOAD_EXTENDED_ART);
locale = prefs.getPref(ForgePreferences.FPref.UI_LANGUAGE);
autoCache = prefs.getPrefBoolean(ForgePreferences.FPref.UI_AUTO_CACHE_SIZE);
disposeTextures = prefs.getPrefBoolean(ForgePreferences.FPref.UI_ENABLE_DISPOSE_TEXTURES);
CJK_Font = prefs.getPref(ForgePreferences.FPref.UI_CJK_FONT);
if (autoCache) {
//increase cacheSize for devices with RAM more than 5GB, default is 400. Some phones have more than 10GB RAM (Mi 10, OnePlus 8, S20, etc..)
if (totalDeviceRAM > 5000) //devices with more than 10GB RAM will have 800 Cache size, 600 Cache size for morethan 5GB RAM
cacheSize = totalDeviceRAM > 10000 ? 800 : 600;
}
//init cache
ImageCache.initCache(cacheSize);
final Localizer localizer = Localizer.getInstance();
//load model on background thread (using progress bar to report progress)
super.create();
FThreads.invokeInBackgroundThread(new Runnable() {
@Override
public void run() {
//see if app or assets need updating
AssetsDownloader.checkForUpdates(splashScreen);
if (exited) {
return;
} //don't continue if user chose to exit or couldn't download required assets
FModel.initialize(splashScreen.getProgressBar(), null);
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblLoadingFonts"));
FSkinFont.preloadAll(locale);
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblLoadingCardTranslations"));
CardTranslation.preloadTranslation(locale, ForgeConstants.LANG_DIR);
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup"));
//add reminder to preload
if (enablePreloadExtendedArt) {
if (autoCache)
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblPreloadExtendedArt") + "\nDetected RAM: " + totalDeviceRAM + "MB. Cache size: " + cacheSize);
else
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblPreloadExtendedArt"));
} else {
if (autoCache)
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup") + "\nDetected RAM: " + totalDeviceRAM + "MB. Cache size: " + cacheSize);
else
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup"));
}
Gdx.app.postRunnable(new Runnable() {
@Override
public void run() {
FSkin.loadFull(splashScreen);
SoundSystem.instance.setBackgroundMusic(MusicPlaylist.MENUS); //start background music
destroyThis = false; //Allow back()
Gdx.input.setCatchKey(Input.Keys.MENU, true);
//openHomeScreen(-1); //default for startup
splashScreen = null;
//adjust height modifier
//update landscape mode preference if it doesn't match what the app loaded as
if (!FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_LANDSCAPE_MODE)) {
FModel.getPreferences().setPref(ForgePreferences.FPref.UI_LANDSCAPE_MODE, true);
FModel.getPreferences().save();
}
resLoaded();
if (!enablePreloadExtendedArt)
return;
List<String> borderlessCardlistkeys = FileUtil.readFile(ForgeConstants.BORDERLESS_CARD_LIST_FILE);
if (borderlessCardlistkeys.isEmpty())
return;
List<String> filteredkeys = new ArrayList<>();
for (String cardname : borderlessCardlistkeys) {
File image = new File(ForgeConstants.CACHE_CARD_PICS_DIR + ForgeConstants.PATH_SEPARATOR + cardname + ".jpg");
if (image.exists())
filteredkeys.add(cardname);
}
if (!filteredkeys.isEmpty())
ImageCache.preloadCache(filteredkeys);
/* call preloadExtendedArt here, if we put it above we will *
* get error: No OpenGL context found in the current thread. */
}
});
}
});
}
}
/**
* Main entry point
*/
public class Main {
public static void main(String[] args) {
Sentry.init();
SentryClient sentryClient = Sentry.getStoredClient();
sentryClient.setRelease(BuildInfo.getVersionString());
sentryClient.setEnvironment(System.getProperty("os.name"));
sentryClient.addTag("Java Version", System.getProperty("java.version"));
// HACK - temporary solution to "Comparison method violates it's general contract!" crash
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
//Turn off the Java 2D system's use of Direct3D to improve rendering speed (particularly when Full Screen)
System.setProperty("sun.java2d.d3d", "false");
Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration();
config.setResizable(false);
StartAdventure start=new StartAdventure();
if (Config.instance().getSettingData().fullScreen)
{
config.setFullscreenMode(Lwjgl3ApplicationConfiguration.getDisplayMode());
} else {
config.setWindowedMode(Config.instance().getSettingData().width, Config.instance().getSettingData().height);
}
config.setWindowIcon(Config.instance().getFilePath("forge-adventure.png"));
new Lwjgl3Application(start, config);
}
}

View File

@@ -0,0 +1,239 @@
package forge.adventure.character;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.*;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectSet;
import forge.adventure.stage.SpriteGroup;
import forge.adventure.util.Config;
import java.util.HashMap;
/**
* CharacterSprite base class for animated sprites on the map
*/
public class CharacterSprite extends MapActor {
private final HashMap<AnimationTypes, HashMap<AnimationDirections, Animation<TextureRegion>>> animations = new HashMap<>();
float timer;
private Animation<TextureRegion> currentAnimation = null;
private AnimationTypes currentAnimationType = AnimationTypes.Idle;
private AnimationDirections currentAnimationDir = AnimationDirections.None;
private Sprite avatar;
public CharacterSprite(String path) {
collisionHeight=0.4f;
load(path);
}
protected void load(String path) {
TextureAtlas atlas = Config.instance().getAtlas(path);
for (Texture texture : new ObjectSet.ObjectSetIterator<>( atlas.getTextures()))
texture.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest);
animations.clear();
for (AnimationTypes stand : AnimationTypes.values()) {
if (stand == AnimationTypes.Avatar) {
avatar = atlas.createSprite(stand.toString());
continue;
}
HashMap<AnimationDirections, Animation<TextureRegion>> dirs = new HashMap<>();
for (AnimationDirections dir : AnimationDirections.values()) {
Array<Sprite> anim;
if (dir == AnimationDirections.None)
anim = atlas.createSprites(stand.toString());
else
anim = atlas.createSprites(stand.toString() + dir.toString());
if (anim.size != 0) {
dirs.put(dir, new Animation<>(0.2f, anim));
}
}
animations.put(stand, dirs);
}
for (AnimationTypes stand : AnimationTypes.values()) {
if (stand == AnimationTypes.Avatar) {
continue;
}
HashMap<AnimationDirections, Animation<TextureRegion>> dirs = animations.get(stand);
if (!dirs.containsKey(AnimationDirections.None) && dirs.containsKey(AnimationDirections.Right)) {
dirs.put(AnimationDirections.None, (dirs.get(AnimationDirections.Right)));
}
if (!dirs.containsKey(AnimationDirections.Right) && dirs.containsKey(AnimationDirections.None)) {
dirs.put(AnimationDirections.Right, (dirs.get(AnimationDirections.None)));
}
if (!dirs.containsKey(AnimationDirections.Left) && dirs.containsKey(AnimationDirections.Right)) {
dirs.put(AnimationDirections.Left, FlipAnimation(dirs.get(AnimationDirections.Right)));
}
if (dirs.containsKey(AnimationDirections.Left) && !dirs.containsKey(AnimationDirections.Right)) {
dirs.put(AnimationDirections.Right, FlipAnimation(dirs.get(AnimationDirections.Left)));
}
if (!dirs.containsKey(AnimationDirections.LeftUp) && dirs.containsKey(AnimationDirections.Left)) {
dirs.put(AnimationDirections.LeftUp, dirs.get(AnimationDirections.Left));
}
if (!dirs.containsKey(AnimationDirections.LeftDown) && dirs.containsKey(AnimationDirections.Left)) {
dirs.put(AnimationDirections.LeftDown, dirs.get(AnimationDirections.Left));
}
if (!dirs.containsKey(AnimationDirections.RightDown) && dirs.containsKey(AnimationDirections.Right)) {
dirs.put(AnimationDirections.RightDown, dirs.get(AnimationDirections.Right));
}
if (!dirs.containsKey(AnimationDirections.RightUp) && dirs.containsKey(AnimationDirections.Right)) {
dirs.put(AnimationDirections.RightUp, dirs.get(AnimationDirections.Right));
}
if (!dirs.containsKey(AnimationDirections.Up) && dirs.containsKey(AnimationDirections.Right)) {
dirs.put(AnimationDirections.Up, dirs.get(AnimationDirections.Right));
}
if (!dirs.containsKey(AnimationDirections.Down) && dirs.containsKey(AnimationDirections.Left)) {
dirs.put(AnimationDirections.Down, dirs.get(AnimationDirections.Left));
}
}
setAnimation(AnimationTypes.Idle);
setDirection(AnimationDirections.Right);
}
static public Animation<TextureRegion> FlipAnimation(Animation<TextureRegion> anim) {
TextureRegion[] texReg = anim.getKeyFrames();
Array<TextureRegion> newReg = new Array<>();
for (TextureRegion reg : texReg) {
TextureRegion cpy = new TextureRegion(reg);
cpy.flip(true, false);
newReg.add(cpy);
}
return new Animation<>(anim.getFrameDuration(), newReg);
}
public void setAnimation(AnimationTypes type) {
if (currentAnimationType != type) {
currentAnimationType = type;
updateAnimation();
}
}
private void updateAnimation() {
AnimationTypes aniType = currentAnimationType;
AnimationDirections aniDir = currentAnimationDir;
if (!animations.containsKey(aniType)) {
aniType = AnimationTypes.Idle;
}
if (!animations.containsKey(aniType)) {
return;
}
HashMap<AnimationDirections, Animation<TextureRegion>> dirs = animations.get(aniType);
if (!dirs.containsKey(aniDir)) {
aniDir = AnimationDirections.Right;
}
if (!dirs.containsKey(aniDir)) {
return;
}
currentAnimation = dirs.get(aniDir);
}
public void setDirection(AnimationDirections dir) {
if (currentAnimationDir != dir) {
currentAnimationDir = dir;
updateAnimation();
}
}
@Override
protected void positionChanged() {
Actor parent = getParent();
if (parent instanceof SpriteGroup) {
((SpriteGroup) parent).UpdateActorZ(this);
}
super.positionChanged();
}
@Override
public void moveBy(float x, float y) {
super.moveBy(x, y);
if (x == 0 && y == 0) {
return;
}
Vector2 vec = new Vector2(x, y);
float degree = vec.angleDeg();
setAnimation(AnimationTypes.Walk);
if (degree < 22.5)
setDirection(AnimationDirections.Right);
else if (degree < 22.5 + 45)
setDirection(AnimationDirections.RightUp);
else if (degree < 22.5 + 45 * 2)
setDirection(AnimationDirections.Up);
else if (degree < 22.5 + 45 * 3)
setDirection(AnimationDirections.LeftUp);
else if (degree < 22.5 + 45 * 4)
setDirection(AnimationDirections.Left);
else if (degree < 22.5 + 45 * 5)
setDirection(AnimationDirections.LeftDown);
else if (degree < 22.5 + 45 * 6)
setDirection(AnimationDirections.Down);
else if (degree < 22.5 + 45 * 7)
setDirection(AnimationDirections.RightDown);
else
setDirection(AnimationDirections.Right);
}
public Vector2 pos() {
return new Vector2(getX(), getY());
}
@Override
public void act(float delta) {
timer += delta;
super.act(delta);
}
@Override
public void draw(Batch batch, float parentAlpha) {
if (currentAnimation == null)
return;
TextureRegion currentFrame = currentAnimation.getKeyFrame(timer, true);
setHeight(currentFrame.getRegionHeight());
setWidth(currentFrame.getRegionWidth());
batch.draw(currentFrame, getX(), getY());
super.draw(batch,parentAlpha);
//batch.draw(getDebugTexture(),getX(),getY());
}
public Sprite getAvatar() {
return avatar;
}
public enum AnimationTypes {
Idle,
Walk,
Death,
Attack,
Hit,
Avatar
}
public enum AnimationDirections {
None,
Right,
RightDown,
Down,
LeftDown,
Left,
LeftUp,
Up,
RightUp
}
}

View File

@@ -0,0 +1,58 @@
package forge.adventure.character;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.utils.Array;
import forge.adventure.data.EnemyData;
import forge.adventure.data.RewardData;
import forge.adventure.util.Current;
import forge.adventure.util.Reward;
/**
* EnemySprite
* Character sprite that represents an Enemy
*/
public class EnemySprite extends CharacterSprite {
EnemyData data;
private int id;
public EnemySprite(EnemyData enemyData) {
super(enemyData.sprite);
data = enemyData;
}
public EnemySprite(int id, EnemyData enemyData) {
this(enemyData);
this.id = id;
}
public void moveTo(Actor other, float delta) {
Vector2 diff = new Vector2(other.getX(), other.getY()).sub(pos());
diff.setLength(data.speed*delta);
moveBy(diff.x, diff.y);
}
public EnemyData getData() {
return data;
}
public Array<Reward> getRewards() {
Array<Reward> ret=new Array<Reward>();
if(data.rewards==null)
return ret;
for(RewardData rdata:data.rewards)
{
ret.addAll(rdata.generate(false,Current.latestDeck()!=null? Current.latestDeck().getMain().toFlatList():null));
}
return ret;
}
public int getId() {
return id;
}
}

View File

@@ -0,0 +1,68 @@
package forge.adventure.character;
import forge.adventure.scene.SceneType;
import forge.adventure.scene.TileMapScene;
import forge.adventure.stage.MapStage;
/**
* EntryActor
* Used to teleport the player in and out of the map
*/
public class EntryActor extends MapActor{
private final MapStage stage;
private final int id;
String targetMap;
public EntryActor(MapStage stage,String sourceMap, int id,String targetMap,float x,float y,float w,float h,String direction)
{
this.stage = stage;
this.id = id;
this.targetMap = targetMap;
if((targetMap==null||targetMap.isEmpty()&&sourceMap.isEmpty())||//if target is null and "from world"
!sourceMap.isEmpty()&&targetMap.equals(sourceMap)) //or if source is this target
{
switch(direction)
{
case "up":
stage.GetPlayer().setPosition(x,y+h);
break;
case "down":
stage.GetPlayer().setPosition(x,y-stage.GetPlayer().getHeight());
break;
case "right":
stage.GetPlayer().setPosition(x-stage.GetPlayer().getWidth(),y);
break;
case "left":
stage.GetPlayer().setPosition(x+w,y);
break;
}
}
}
public MapStage getMapStage()
{
return stage;
}
@Override
public void onPlayerCollide()
{
if(targetMap==null||targetMap.isEmpty())
{
stage.exit();
}
else
{
((TileMapScene)SceneType.TileMapScene.instance).loadNext(targetMap);
}
}
public int getObjectID() {
return id;
}
}

View File

@@ -0,0 +1,96 @@
package forge.adventure.character;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.scenes.scene2d.Actor;
/**
* Map Actor base class for Actors on the map
* implements collision detection.
*/
public class MapActor extends Actor {
Texture debugTexture;
float collisionHeight=1.0f;
private Texture getDebugTexture() {
if (debugTexture == null) {
Pixmap pixmap = new Pixmap((int) getWidth(), (int) getHeight(), Pixmap.Format.RGBA8888);
pixmap.setColor(1.0f,0,0,0.5f);
pixmap.fillRectangle(0, (int) getHeight()- (int)boundingRect.getHeight(), (int) boundingRect.getWidth(), (int) boundingRect.getHeight());
debugTexture = new Texture(pixmap);
pixmap.dispose();
}
return debugTexture;
}
Rectangle boundingRect;
boolean isCollidingWithPlayer=false;
protected void onPlayerCollide()
{
}
boolean boundDebug=false;
public void setBoundDebug(boolean debug)
{
boundDebug=debug;
}
@Override
public void draw(Batch batch, float alpha) {
if(boundDebug)
batch.draw(getDebugTexture(),getX(),getY());
}
@Override
protected void positionChanged() {
updateBoundingRect();
super.positionChanged();
}
@Override
protected void sizeChanged() {
super.sizeChanged();
updateBoundingRect();
}
void updateBoundingRect() {
boundingRect = new Rectangle(getX(), getY(), getWidth(), getHeight()*collisionHeight);
}
public Rectangle boundingRect() {
return boundingRect;
}
public boolean collideWithPlayer(PlayerSprite other) {
boolean newIsColliding= collideWith(other);
if(newIsColliding)
{
if(!isCollidingWithPlayer)
onPlayerCollide();
isCollidingWithPlayer=true;
}
else
{
isCollidingWithPlayer=false;
}
return isCollidingWithPlayer;
}
public boolean collideWith(Rectangle other) {
return boundingRect().overlaps(other);
}
public boolean collideWith(MapActor other) {
return collideWith(other.boundingRect());
}
public boolean collideWith(Actor other) {
return boundingRect.x < other.getX() + other.getWidth() && boundingRect.x + boundingRect.width > other.getX() && boundingRect.y < other.getY() + other.getHeight() && boundingRect.y + boundingRect.height > other.getY();
}
}

View File

@@ -0,0 +1,21 @@
package forge.adventure.character;
/**
* Designed to add anonymous class for a single action on collision
*/
public class OnCollide extends MapActor {
Runnable onCollide;
public OnCollide(Runnable func) {
onCollide = func;
}
@Override
protected void onPlayerCollide() {
try {
onCollide.run();
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,77 @@
package forge.adventure.character;
import com.badlogic.gdx.math.Vector2;
import forge.adventure.stage.GameStage;
import forge.adventure.util.Config;
import forge.adventure.util.Current;
import forge.adventure.world.AdventurePlayer;
/**
* Class that will represent the player sprite on the map
*/
public class PlayerSprite extends CharacterSprite {
private final float playerSpeed;
private final Vector2 direction = Vector2.Zero.cpy();
private float playerSpeedModifier = 1f;
GameStage gameStage;
public PlayerSprite(GameStage gameStage) {
super(AdventurePlayer.current().spriteName());
this.gameStage=gameStage;
setOriginX(getWidth() / 2);
Current.player().onPlayerChanged(()->updatePlayer());
playerSpeed=Config.instance().getConfigData().playerBaseSpeed;
}
private void updatePlayer() {
load(AdventurePlayer.current().spriteName());
}
public void LoadPos() {
setPosition(AdventurePlayer.current().getWorldPosX(), AdventurePlayer.current().getWorldPosY());
}
public void storePos() {
AdventurePlayer.current().setWorldPosX(getX());
AdventurePlayer.current().setWorldPosY(getY());
}
public Vector2 getMovementDirection() {
return direction;
}
public void setMovementDirection(final Vector2 dir) {
direction.set(dir);
}
public void setMoveModifier(float speed) {
playerSpeedModifier = speed;
}
@Override
public void act(float delta) {
super.act(delta);
direction.setLength(playerSpeed * delta * playerSpeedModifier);
if(!direction.isZero())
{
gameStage.prepareCollision(pos(),direction,boundingRect);
direction.set(gameStage.adjustMovement(direction,boundingRect));
moveBy(direction.x, direction.y);
}
}
public boolean isMoving() {
return !direction.isZero();
}
public void stop() {
direction.setZero();
setAnimation(AnimationTypes.Idle);
}
public void setPosition(Vector2 oldPosition) {
setPosition(oldPosition.x,oldPosition.y);
}
}

View File

@@ -0,0 +1,43 @@
package forge.adventure.character;
import com.badlogic.gdx.utils.Array;
import forge.adventure.AdventureApplicationAdapter;
import forge.adventure.scene.RewardScene;
import forge.adventure.scene.SceneType;
import forge.adventure.stage.MapStage;
import forge.adventure.util.Reward;
/**
* Map actor that will open the Shop on collision
*/
public class ShopActor extends MapActor{
private final MapStage stage;
private final int id;
Array<Reward> rewardData;
public ShopActor(MapStage stage, int id, Array<Reward> rewardData)
{
this.stage = stage;
this.id = id;
this.rewardData = rewardData;
}
public MapStage getMapStage()
{
return stage;
}
@Override
public void onPlayerCollide()
{
stage.GetPlayer().stop();
((RewardScene) SceneType.RewardScene.instance).loadRewards(rewardData, RewardScene.Type.Shop,this);
AdventureApplicationAdapter.instance.switchScene(SceneType.RewardScene.instance);
}
public int getObjectID() {
return id;
}
}

View File

@@ -0,0 +1,25 @@
package forge.adventure.character;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
/**
* Class to add sprites to a map
*/
public class TextureSprite extends MapActor{
private final TextureRegion region;
public TextureSprite(TextureRegion region)
{
this.region = region;
setWidth(region.getRegionWidth());
setHeight(region.getRegionHeight());
}
@Override
public void draw (Batch batch, float parentAlpha) {
batch.draw(region,getX(),getY(),getWidth(),getHeight());
}
}

View File

@@ -0,0 +1,82 @@
package forge.adventure.data;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.Array;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Data class that will be used to read Json configuration files
* BiomeData
* contains the information for the biomes
*/
public class BiomeData implements Serializable {
private final Random rand = new Random();
public float startPointX;
public float startPointY;
public float noiseWeight;
public float distWeight;
public String name;
public String tilesetAtlas;
public String tilesetName;
public BiomeTerrainData[] terrain;
public float width;
public float height;
public String color;
public boolean invertHeight;
public String[] spriteNames;
public List<String> enemies;
public List<String> pointsOfInterest;
private ArrayList<EnemyData> enemyList;
private ArrayList<PointOfInterestData> pointOfInterestList;
public Color GetColor() {
return Color.valueOf(color);
}
public ArrayList<EnemyData> getEnemyList() {
if (enemyList == null) {
enemyList = new ArrayList<EnemyData>();
if (enemies == null)
return enemyList;
for (EnemyData data : new Array.ArrayIterator<>(WorldData.getAllEnemies())) {
if (enemies.contains(data.name)) {
enemyList.add(data);
}
}
}
return enemyList;
}
public ArrayList<PointOfInterestData> getPointsOfInterest() {
if (pointOfInterestList == null) {
pointOfInterestList = new ArrayList<PointOfInterestData>();
if(pointsOfInterest==null)
return pointOfInterestList;
Array<PointOfInterestData> allTowns = PointOfInterestData.getAllPointOfInterest();
for (PointOfInterestData data : new Array.ArrayIterator<>(allTowns)) {
if (pointsOfInterest.contains(data.name)) {
pointOfInterestList.add(data);
}
}
}
return pointOfInterestList;
}
public EnemyData getEnemy(float difficultyFactor) {
EnemyData bestData=null;
float biggestNumber=0;
for (EnemyData data : enemyList) {
float newNumber=data.spawnRate *rand.nextFloat()*difficultyFactor;
if (newNumber>biggestNumber) {
biggestNumber=newNumber;
bestData=data;
}
}
return bestData;
}
}

View File

@@ -0,0 +1,21 @@
package forge.adventure.data;
import java.io.Serializable;
/**
* Data class that will be used to read Json configuration files
* BiomeSpriteData
* contains the information for the sprites on the map like trees and rocks
*/
public class BiomeSpriteData implements Serializable {
public String name;
public double startArea;
public double endArea;
public double density;
public double resolution;
public int layer;
public String key() {
return "BiomeSprite&" + name;
}
}

View File

@@ -0,0 +1,19 @@
package forge.adventure.data;
/**
* Data class that will be used to read Json configuration files
* BiomeData
* contains the information for the terrain distribution
*/
public class BiomeTerrainData {
//sprite name in the biome atlas file
public String spriteName;
//minimum noise value where to place the terrain
public float min;
//maximum noise value where to place the terrain
public float max;
// factor for the noise resolution
public float resolution;
}

View File

@@ -0,0 +1,20 @@
package forge.adventure.data;
/**
* Data class that will be used to read Json configuration files
* BiomeData
* contains general information about the game
*/
public class ConfigData {
public int screenWidth;
public int screenHeight;
public String skin;
public String font;
public String fontColor;
public int minDeckSize;
public float playerBaseSpeed;
public String[] starterDecks;
public DifficultyData[] difficulties;
public RewardData legalCards;
}

View File

@@ -0,0 +1,15 @@
package forge.adventure.data;
/**
* Data class that will be used to read Json configuration files
* BiomeData
* contains the information for the difficulties
*/
public class DifficultyData {
public String name="";
public int startingLife=10;
public int staringMoney=10;
public float enemyLifeFactor=1;
public boolean startingDifficulty;
}

View File

@@ -0,0 +1,48 @@
package forge.adventure.data;
import forge.adventure.util.CardUtil;
import forge.deck.Deck;
/**
* Data class that will be used to read Json configuration files
* BiomeData
* contains the information of enemies
*/
public class EnemyData {
public String name;
public String sprite;
public String deck;
public float spawnRate;
public float difficulty;
public float speed;
public int life;
public RewardData[] rewards;
public EnemyData()
{
}
public EnemyData(EnemyData enemyData) {
name =enemyData.name;
sprite =enemyData.sprite;
deck =enemyData.deck;
spawnRate =enemyData.spawnRate;
difficulty =enemyData.difficulty ;
speed =enemyData.speed;
life =enemyData.life;
if(enemyData.rewards==null)
{
rewards=null;
}
else
{
rewards =new RewardData[enemyData.rewards.length];
for(int i=0;i<rewards.length;i++)
rewards[i]=new RewardData(enemyData.rewards[i]);
}
}
public Deck generateDeck() {
return CardUtil.getDeck(deck);
}
}

View File

@@ -0,0 +1,17 @@
package forge.adventure.data;
/**
* Data class that will be used to read Json configuration files
* BiomeData
* contains the information for a generated deck
*
* if the template is null then it will use just the reward information of mainDeck and sideBoard
*/
public class GeneratedDeckData {
public String name;
public GeneratedDeckTemplateData template;
public RewardData[] mainDeck;
public RewardData[] sideBoard;
}

View File

@@ -0,0 +1,15 @@
package forge.adventure.data;
/**
* Data class that will be used to read Json configuration files
* BiomeData
* contains the information for the generated deck
*/
public class GeneratedDeckTemplateData {
public String[] colors;
public int count;
public float rares;
public String tribe;
public float tribeSynergyCards=0.6f;
public float tribeCards=1.0f;
}

View File

@@ -0,0 +1,14 @@
package forge.adventure.data;
/**
* Data class that will be used to read Json configuration files
* BiomeData
* contains the information possible hero sprite
*/
public class HeroData {
public String name;
public String female;
public String male;
public String femaleAvatar;
public String maleAvatar;
}

View File

@@ -0,0 +1,73 @@
package forge.adventure.data;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import forge.adventure.util.Config;
import forge.adventure.util.Paths;
/**
* Data class that will be used to read Json configuration files
* BiomeData
* contains the a list of all heroes
*/
public class HeroListData {
static private HeroListData instance;
public HeroData[] heroes;
public String avatar;
private TextureAtlas avatarSprites;
static private HeroListData read() {
Json json = new Json();
FileHandle handle = Config.instance().getFile(Paths.HEROES);
if (handle.exists()) {
instance = json.fromJson(HeroListData.class, handle);
instance.avatarSprites = Config.instance().getAtlas(instance.avatar);
instance.avatarSprites.getTextures().first().setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest);
}
return instance;
}
static public String getHero(int raceIndex, boolean female) {
if (instance == null)
instance = read();
HeroData data = instance.heroes[raceIndex];
if (female)
return data.female;
return data.male;
}
public static TextureRegion getAvatar(int heroRace, boolean isFemale, int avatarIndex) {
if (instance == null)
instance = read();
HeroData data = instance.heroes[heroRace];
Array<Sprite> sprites;
if (isFemale)
sprites = instance.avatarSprites.createSprites(data.femaleAvatar);
else
sprites = instance.avatarSprites.createSprites(data.maleAvatar);
avatarIndex %= sprites.size;
if (avatarIndex < 0) {
avatarIndex += sprites.size;
}
return sprites.get(avatarIndex);
}
public static Array<String> getRaces() {
if (instance == null)
instance = read();
Array<String> ret = new Array<>();
for (HeroData hero : instance.heroes) {
ret.add(hero.name);
}
return ret;
}
}

View File

@@ -0,0 +1,47 @@
package forge.adventure.data;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import forge.adventure.util.Config;
import forge.adventure.util.Paths;
/**
* Data class that will be used to read Json configuration files
* BiomeData
* contains the information for the point of interests like towns and dungeons
*/
public class PointOfInterestData {
public String name;
public String type;
public int count;
public String spriteAtlas;
public String sprite;
public String map;
public float radiusFactor;
private static Array<PointOfInterestData> pointOfInterestList;
public static Array<PointOfInterestData> getAllPointOfInterest() {
if (pointOfInterestList == null) {
Json json = new Json();
FileHandle handle = Config.instance().getFile(Paths.POINTS_OF_INTEREST);
if (handle.exists()) {
Array readJson = json.fromJson(Array.class, PointOfInterestData.class, handle);
pointOfInterestList = readJson;
}
}
return pointOfInterestList;
}
public static PointOfInterestData getPointOfInterest(String name) {
for(PointOfInterestData data: new Array.ArrayIterator<>(getAllPointOfInterest()))
{
if(data.name.equals(name))
return data;
}
return null;
}
}

View File

@@ -0,0 +1,157 @@
package forge.adventure.data;
import com.badlogic.gdx.utils.Array;
import com.google.common.collect.Iterables;
import forge.StaticData;
import forge.adventure.util.CardUtil;
import forge.adventure.util.Config;
import forge.adventure.util.Reward;
import forge.adventure.world.WorldSave;
import forge.item.PaperCard;
import forge.model.FModel;
import java.util.ArrayList;
import java.util.List;
/**
* Data class that will be used to read Json configuration files
* BiomeData
* contains the information for a "reward"
* that can be a random card, gold or items.
* Also used for deck generation and shops
*/
public class RewardData {
public String type;
public float probability;
public int count;
public int addMaxCount;
public String cardName;
public String itemName;
public String[] editions;
public String[] colors;
public String[] rarity;
public String[] subTypes;
public String[] cardTypes;
public String[] superTypes;
public int[] manaCosts;
public String[] keyWords;
public String colorType;
public String cardText;
public boolean matchAllSubTypes;
public RewardData()
{
}
public RewardData(RewardData rewardData) {
type =rewardData.type;
probability =rewardData.probability;
count =rewardData.count;
addMaxCount =rewardData.addMaxCount;
cardName =rewardData.cardName;
itemName =rewardData.itemName;
editions =rewardData.editions==null?null:rewardData.editions.clone();
colors =rewardData.colors==null?null:rewardData.colors.clone();
rarity =rewardData.rarity==null?null:rewardData.rarity.clone();
subTypes =rewardData.subTypes==null?null:rewardData.subTypes.clone();
cardTypes =rewardData.cardTypes==null?null:rewardData.cardTypes.clone();
superTypes =rewardData.superTypes==null?null:rewardData.superTypes.clone();
manaCosts =rewardData.manaCosts==null?null:rewardData.manaCosts.clone();
keyWords =rewardData.keyWords==null?null:rewardData.keyWords.clone();
colorType =rewardData.colorType;
cardText =rewardData.cardText;
matchAllSubTypes =rewardData.matchAllSubTypes;
}
private static Iterable<PaperCard> allCards;
private static Iterable<PaperCard> allEnemyCards;
public Array<Reward> generate(boolean isForEnemy)
{
return generate(isForEnemy,null);
}
public Array<Reward> generate(boolean isForEnemy,Iterable<PaperCard> cards)
{
if(allCards==null)
{
RewardData legals=Config.instance().getConfigData().legalCards;
if(legals==null)
{
allCards = FModel.getMagicDb().getCommonCards().getUniqueCardsNoAlt();
}
else
{
allCards = Iterables.filter(FModel.getMagicDb().getCommonCards().getUniqueCardsNoAlt(), new CardUtil.CardPredicate(legals, true));
}
allEnemyCards=Iterables.filter(allCards, input -> {
if(input==null)return false;
return !input.getRules().getAiHints().getRemAIDecks();
});
}
Array<Reward> ret=new Array<>();
if(probability==0|| WorldSave.getCurrentSave().getWorld().getRandom().nextFloat()<=probability)
{
if(type==null||type.isEmpty())
type="randomCard";
int addedCount=(int)((float)(addMaxCount)* WorldSave.getCurrentSave().getWorld().getRandom().nextFloat());
switch(type)
{
case "card":
case "randomCard":
if(cardName!=null&&!cardName.isEmpty())
{
for(int i=0;i<count+addedCount;i++)
{
ret.add(new Reward(StaticData.instance().getCommonCards().getCard(cardName)));
}
}
else
{
for(PaperCard card:CardUtil.generateCards(isForEnemy?allEnemyCards:allCards,this, count+addedCount))
{
ret.add(new Reward(card));
}
}
break;
case "deckCard":
if(cards==null)return ret;
for(PaperCard card: CardUtil.generateCards(cards,this, count+addedCount))
{
ret.add(new Reward(card));
}
break;
case "gold":
ret.add(new Reward(count+addedCount));
break;
case "life":
ret.add(new Reward(Reward.Type.Life, count+addedCount));
break;
}
}
return ret;
}
static public List<PaperCard> generateAllCards(Iterable<RewardData> dataList, boolean isForEnemy)
{
return rewardsToCards(generateAll(dataList, isForEnemy));
}
static public Iterable<Reward> generateAll(Iterable<RewardData> dataList, boolean isForEnemy)
{
Array<Reward> ret=new Array<Reward>();
for (RewardData data:dataList)
ret.addAll(data.generate(isForEnemy));
return ret;
}
static public List<PaperCard> rewardsToCards(Iterable<Reward> dataList)
{
ArrayList<PaperCard> ret=new ArrayList<PaperCard>();
for (Reward data:dataList)
{
ret.add(data.getCard());
}
return ret;
}
}

View File

@@ -0,0 +1,15 @@
package forge.adventure.data;
/**
* Data class that will be used to read Json configuration files
* SettingData
* contains settings outside of the chosen adventure
*/
public class SettingData {
public int width;
public int height;
public String plane;
public boolean fullScreen;
}

View File

@@ -0,0 +1,21 @@
package forge.adventure.data;
import com.badlogic.gdx.utils.Array;
/**
* Data class that will be used to read Json configuration files
* SettingData
* contains data for a Shop on the map
*/
public class ShopData {
public String name;
public String spriteAtlas;
public String sprite;
public Array<RewardData> rewards;
}

View File

@@ -0,0 +1,16 @@
package forge.adventure.data;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.OrderedMap;
/**
* Data class that will be used to read Json configuration files
* UIData
* contains a GUI definition, used for most user interfaces to customize the UI
*/
public class UIData {
public int width;
public int height;
public boolean yDown;
public Array<OrderedMap<String,String>> elements;
}

View File

@@ -0,0 +1,93 @@
package forge.adventure.data;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import forge.adventure.util.Config;
import forge.adventure.util.Paths;
import forge.adventure.world.BiomeSprites;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* Data class that will be used to read Json configuration files
* UIData
* contains the definition of the world
*/
public class WorldData implements Serializable {
static Array<EnemyData> allEnemies;
public int width;
public int height;
public float playerStartPosX;
public float playerStartPosY;
public float noiseZoomBiome;
public int tileSize;
public List<String> biomesNames;
public BiomeData roadTileset;
public String biomesSprites;
public float maxRoadDistance;
private BiomeSprites sprites;
private List<BiomeData> biomes;
private static Array<ShopData> shopList;
public static Array<ShopData> getShopList() {
if (shopList == null) {
shopList = new Array<>();
Json json = new Json();
FileHandle handle = Config.instance().getFile(Paths.SHOPS);
if (handle.exists())
{
Array readList = json.fromJson(Array.class, ShopData.class, handle);
shopList = readList;
}
}
return shopList;
}
static public Array<EnemyData> getAllEnemies() {
if (allEnemies == null) {
Json json = new Json();
FileHandle handle = Config.instance().getFile(Paths.ENEMIES);
if (handle.exists())
{
Array readList = json.fromJson(Array.class, EnemyData.class, handle);
allEnemies = readList;
}
}
return allEnemies;
}
public static EnemyData getEnemy(String enemy) {
for(EnemyData data: new Array.ArrayIterator<>(getAllEnemies()))
{
if(data.name.equals(enemy))
return data;
}
return null;
}
public BiomeSprites GetBiomeSprites() {
if (sprites == null) {
Json json = new Json();
sprites = (json.fromJson(BiomeSprites.class, Config.instance().getFile(biomesSprites)));
}
return sprites;
}
public List<BiomeData> GetBiomes() {
if (biomes == null) {
biomes = new ArrayList<BiomeData>();
Json json = new Json();
for (String name : biomesNames) {
biomes.add(json.fromJson(BiomeData.class, Config.instance().getFile(name)));
}
}
return biomes;
}
}

View File

@@ -0,0 +1,32 @@
package forge.adventure.editor;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* Editor class to edit configuration, maybe moved or removed
*/
public class DocumentChangeListener implements DocumentListener {
private final Runnable run;
public DocumentChangeListener(Runnable run)
{
this.run = run;
}
@Override
public void insertUpdate(DocumentEvent e) {
changedUpdate(e);
}
@Override
public void removeUpdate(DocumentEvent e) {
changedUpdate(e);
}
@Override
public void changedUpdate(DocumentEvent e) {
run.run();
}
}

View File

@@ -0,0 +1,21 @@
package forge.adventure.editor;
import javax.swing.*;
import java.awt.*;
/**
* Editor class to edit configuration, maybe moved or removed
*/
public class EditorMainWindow extends JFrame {
JTabbedPane tabs =new JTabbedPane();
public EditorMainWindow()
{
BorderLayout layout=new BorderLayout();
setLayout(layout);
add(tabs);
tabs.addTab("Enemies",new EnemyEditor());
setVisible(true);
setSize(800,600);
}
}

View File

@@ -0,0 +1,95 @@
package forge.adventure.editor;
import forge.adventure.data.EnemyData;
import javax.swing.*;
import java.awt.*;
/**
* Editor class to edit configuration, maybe moved or removed
*/
public class EnemyEdit extends JComponent {
EnemyData currentData;
JTextField nameField=new JTextField();
JSpinner lifeFiled= new JSpinner(new SpinnerNumberModel(0, 0, 1000, 1));
JSpinner spawnRate= new JSpinner(new SpinnerNumberModel(0.0, 0., 1, 0.1));
JSpinner difficulty= new JSpinner(new SpinnerNumberModel(0.0, 0., 1, 0.1));
JSpinner speed= new JSpinner(new SpinnerNumberModel(0.0, 0., 100., 1.0));
FilePicker deck=new FilePicker(new String[]{"dck","json"});
FilePicker atlas=new FilePicker(new String[]{"atlas"});
RewardsEditor rewards=new RewardsEditor();
SwingAtlasPreview preview=new SwingAtlasPreview();
private boolean updating=false;
public EnemyEdit()
{
JComponent center=new JComponent() { };
center.setLayout(new GridLayout(8,2));
center.add(new JLabel("Name:")); center.add(nameField);
center.add(new JLabel("Life:")); center.add(lifeFiled);
center.add(new JLabel("Spawn rate:")); center.add(spawnRate);
center.add(new JLabel("Difficulty:")); center.add(difficulty);
center.add(new JLabel("Speed:")); center.add(speed);
center.add(new JLabel("Deck:")); center.add(deck);
center.add(new JLabel("Sprite:")); center.add(atlas);
BorderLayout layout=new BorderLayout();
setLayout(layout);
add(center,BorderLayout.PAGE_START);
add(rewards,BorderLayout.CENTER);
add(preview,BorderLayout.LINE_START);
atlas.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(()->updateEnemy()));
nameField.getDocument().addDocumentListener(new DocumentChangeListener(()->updateEnemy()));
deck.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(()->updateEnemy()));
lifeFiled.addChangeListener(e -> updateEnemy());
speed.addChangeListener(e -> updateEnemy());
difficulty.addChangeListener(e -> updateEnemy());
spawnRate.addChangeListener(e -> updateEnemy());
rewards.addChangeListener(e -> updateEnemy());
lifeFiled.addChangeListener(e -> updateEnemy());
refresh();
}
private void updateEnemy() {
if(currentData==null||updating)
return;
currentData.name=nameField.getText();
currentData.life= (int) lifeFiled.getValue();
currentData.sprite= atlas.getEdit().getText();
currentData.speed= ((Double) speed.getValue()).floatValue();
currentData.spawnRate=((Double) spawnRate.getValue()).floatValue();
currentData.difficulty=((Double) difficulty.getValue()).floatValue();
currentData.deck= deck.getEdit().getText();
currentData.rewards= rewards.getRewards();
preview.setSpritePath(currentData.sprite);
}
public void setCurrentEnemy(EnemyData data)
{
currentData=data;
refresh();
}
private void refresh() {
setEnabled(currentData!=null);
if(currentData==null)
{
return;
}
updating=true;
nameField.setText(currentData.name);
lifeFiled.setValue(currentData.life);
atlas.getEdit().setText(currentData.sprite);
deck.getEdit().setText(currentData.deck);
speed.setValue(new Float(currentData.speed).doubleValue());
spawnRate.setValue(new Float(currentData.spawnRate).doubleValue());
difficulty.setValue(new Float(currentData.difficulty).doubleValue());
rewards.setRewards(currentData.rewards);
preview.setSpritePath(currentData.sprite);
updating=false;
}
}

View File

@@ -0,0 +1,128 @@
package forge.adventure.editor;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.JsonWriter;
import forge.adventure.data.EnemyData;
import forge.adventure.util.Config;
import forge.adventure.util.Paths;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
/**
* Editor class to edit configuration, maybe moved or removed
*/
public class EnemyEditor extends JComponent {
DefaultListModel<EnemyData> model = new DefaultListModel<>();
JList<EnemyData> list = new JList<>(model);
JToolBar toolBar = new JToolBar("toolbar");
EnemyEdit edit=new EnemyEdit();
public class EnemyDataRenderer extends DefaultListCellRenderer {
@Override
public Component getListCellRendererComponent(
JList list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if(!(value instanceof EnemyData))
return label;
EnemyData enemy=(EnemyData) value;
// Get the renderer component from parent class
label.setText(enemy.name);
SwingAtlas atlas=new SwingAtlas(Config.instance().getFile(enemy.sprite));
if(atlas.has("Avatar"))
label.setIcon(atlas.get("Avatar"));
else
{
ImageIcon img=atlas.getAny();
if(img!=null)
label.setIcon(img);
}
return label;
}
}
public void addButton(String name,ActionListener action)
{
JButton newButton=new JButton(name);
newButton.addActionListener(action);
toolBar.add(newButton);
}
public EnemyEditor()
{
list.setCellRenderer(new EnemyDataRenderer());
list.addListSelectionListener(e -> updateEdit());
addButton("add",e->addEnemy());
addButton("remove",e->remove());
addButton("copy",e->copy());
addButton("load",e->load());
addButton("save",e->save());
BorderLayout layout=new BorderLayout();
setLayout(layout);
add(new JScrollPane(list), BorderLayout.LINE_START);
add(toolBar, BorderLayout.PAGE_START);
add(edit,BorderLayout.CENTER);
load();
}
private void copy() {
int selected=list.getSelectedIndex();
if(selected<0)
return;
EnemyData data=new EnemyData(model.get(selected));
model.add(model.size(),data);
}
private void updateEdit() {
int selected=list.getSelectedIndex();
if(selected<0)
return;
edit.setCurrentEnemy(model.get(selected));
}
void save()
{
Array<EnemyData> allEnemies=new Array<>();
for(int i=0;i<model.getSize();i++)
allEnemies.add(model.get(i));
Json json = new Json(JsonWriter.OutputType.json);
FileHandle handle = Config.instance().getFile(Paths.ENEMIES);
handle.writeString(json.prettyPrint(json.toJson(allEnemies,Array.class, EnemyData.class)),false);
}
void load()
{
model.clear();
Array<EnemyData> allEnemies=new Array<>();
Json json = new Json();
FileHandle handle = Config.instance().getFile(Paths.ENEMIES);
if (handle.exists())
{
Array readEnemies=json.fromJson(Array.class, EnemyData.class, handle);
allEnemies = readEnemies;
}
for (int i=0;i<allEnemies.size;i++) {
model.add(i,allEnemies.get(i));
}
}
void addEnemy()
{
EnemyData data=new EnemyData();
data.name="Enemy "+model.getSize();
model.add(model.size(),data);
}
void remove()
{
int selected=list.getSelectedIndex();
if(selected<0)
return;
model.remove(selected);
}
}

View File

@@ -0,0 +1,51 @@
package forge.adventure.editor;
import forge.adventure.util.Config;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.io.File;
import java.io.IOException;
/**
* Editor class to edit configuration, maybe moved or removed
*/
public class FilePicker extends Box {
JTextField edit=new JTextField();
JButton findButton=new JButton(UIManager.getIcon("FileView.directoryIcon"));
private final String[] fileEndings;
public FilePicker(String[] fileEndings) {
super(BoxLayout.X_AXIS);
this.fileEndings = fileEndings;
findButton.addActionListener(e->find());
add(edit);
add(findButton);
}
JTextField getEdit()
{
return edit;
}
private void find() {
JFileChooser fc = new JFileChooser();
fc.setCurrentDirectory(new File(Config.instance().getFilePath(edit.getText())));
fc.setFileFilter( new FileNameExtensionFilter("Pick File",fileEndings));
fc.setMultiSelectionEnabled(false);
if (fc.showOpenDialog(this) ==
JFileChooser.APPROVE_OPTION) {
File selected = fc.getSelectedFile();
try {
if (selected != null&&selected.getCanonicalPath().startsWith(new File(Config.instance().getFilePath("")).getCanonicalPath())) {
edit.setText(selected.getCanonicalPath().substring(new File(Config.instance().getFilePath("")).getCanonicalPath().length()+1).replace('\\','/'));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,13 @@
package forge.adventure.editor;
import forge.adventure.util.Config;
/**
* Editor class to edit configuration, maybe moved or removed
*/
public class Main {
public static void main(String[] args) {
Config.instance();
new EditorMainWindow();
}
}

View File

@@ -0,0 +1,146 @@
package forge.adventure.editor;
import forge.adventure.data.RewardData;
import forge.card.CardType;
import forge.game.keyword.Keyword;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.util.Arrays;
/**
* Editor class to edit configuration, maybe moved or removed
*/
public class RewardEdit extends JComponent {
RewardData currentData;
JComboBox typeField =new JComboBox(new String[] { "card", "gold", "life", "deckCard", "item"});
JSpinner probability = new JSpinner(new SpinnerNumberModel(0f, 0, 1, 0.1f));
JSpinner count = new JSpinner(new SpinnerNumberModel(0, 0, 1000, 1));
JSpinner addMaxCount = new JSpinner(new SpinnerNumberModel(0, 0, 1000, 1));
JTextField cardName =new JTextField();
JTextField itemName =new JTextField();
TextListEdit editions =new TextListEdit();
TextListEdit colors =new TextListEdit(new String[] { "White", "Blue", "Black", "Red", "Green" });
TextListEdit rarity =new TextListEdit(new String[] { "Basic Land", "Common", "Uncommon", "Rare", "Mythic Rare" });
TextListEdit subTypes =new TextListEdit();
TextListEdit cardTypes =new TextListEdit(Arrays.asList(CardType.CoreType.values()).stream().map(CardType.CoreType::toString).toArray(String[]::new));
TextListEdit superTypes =new TextListEdit(Arrays.asList(CardType.Supertype.values()).stream().map(CardType.Supertype::toString).toArray(String[]::new));
TextListEdit manaCosts =new TextListEdit();
TextListEdit keyWords =new TextListEdit(Arrays.asList(Keyword.values()).stream().map(Keyword::toString).toArray(String[]::new));
JComboBox colorType =new JComboBox(new String[] { "Any", "Colorless", "MultiColor", "MonoColor"});
JTextField cardText =new JTextField();
private boolean updating=false;
public RewardEdit()
{
setLayout(new GridLayout(16,2));
add(new JLabel("Type:")); add(typeField);
add(new JLabel("probability:")); add(probability);
add(new JLabel("count:")); add(count);
add(new JLabel("addMaxCount:")); add(addMaxCount);
add(new JLabel("cardName:")); add(cardName);
add(new JLabel("itemName:")); add(itemName);
add(new JLabel("editions:")); add(editions);
add(new JLabel("colors:")); add(colors);
add(new JLabel("rarity:")); add(rarity);
add(new JLabel("subTypes:")); add(subTypes);
add(new JLabel("cardTypes:")); add(cardTypes);
add(new JLabel("superTypes:")); add(superTypes);
add(new JLabel("manaCosts:")); add(manaCosts);
add(new JLabel("keyWords:")); add(keyWords);
add(new JLabel("colorType:")); add(colorType);
add(new JLabel("cardText:")); add(cardText);
typeField.addActionListener(((e)->updateReward()));
probability.addChangeListener(e->updateReward());
count.addChangeListener(e->updateReward());
addMaxCount.addChangeListener(e->updateReward());
cardName.getDocument().addDocumentListener(new DocumentChangeListener(()->updateReward()));
itemName.getDocument().addDocumentListener(new DocumentChangeListener(()->updateReward()));
editions.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(()->updateReward()));
colors.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(()->updateReward()));
rarity.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(()->updateReward()));
subTypes.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(()->updateReward()));
cardTypes.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(()->updateReward()));
superTypes.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(()->updateReward()));
manaCosts.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(()->updateReward()));
keyWords.getEdit().getDocument().addDocumentListener(new DocumentChangeListener(()->updateReward()));
colorType.addActionListener(((e)->updateReward()));
cardText.getDocument().addDocumentListener(new DocumentChangeListener(()->updateReward()));
}
private void updateReward() {
if(currentData==null||updating)
return;
currentData.type=typeField.getSelectedItem()==null?null:typeField.getSelectedItem().toString();
currentData.probability=((Double)probability.getValue()).floatValue();
currentData.count= (int) count.getValue();
currentData.addMaxCount= (int) addMaxCount.getValue();
currentData.cardName = cardName.getText().isEmpty()?null:cardName.getText();
currentData.itemName = itemName.getText().isEmpty()?null:itemName.getText();
currentData.editions = editions.getList();
currentData.colors = colors.getList();
currentData.rarity = rarity.getList();
currentData.subTypes = subTypes.getList();
currentData.cardTypes = cardTypes.getList();
currentData.superTypes = superTypes.getList();
currentData.manaCosts = manaCosts.getListAsInt();
currentData.keyWords = keyWords.getList();
currentData.colorType=colorType.getSelectedItem()==null?null:colorType.getSelectedItem().toString();
currentData.cardText = cardText.getText().isEmpty()?null:cardText.getText();
ChangeListener[] listeners = listenerList.getListeners(ChangeListener.class);
if (listeners != null && listeners.length > 0) {
ChangeEvent evt = new ChangeEvent(this);
for (ChangeListener listener : listeners) {
listener.stateChanged(evt);
}
}
}
public void addChangeListener(ChangeListener l) {
listenerList.add(ChangeListener.class, l);
}
public void setCurrentReward(RewardData data)
{
currentData=data;
refresh();
}
private void refresh() {
setEnabled(currentData!=null);
if(currentData==null)
{
return;
}
updating=true;
typeField.setSelectedItem(currentData.type);
probability.setValue(new Double(currentData.probability));
count.setValue(currentData.count);
addMaxCount.setValue(currentData.addMaxCount);
cardName.setText(currentData.cardName);
itemName.setText(currentData.itemName);
editions.setText(currentData.editions);
colors.setText(currentData.colors);
rarity.setText(currentData.rarity);
subTypes.setText(currentData.subTypes);
cardTypes.setText(currentData.cardTypes);
superTypes.setText(currentData.superTypes);
manaCosts.setText(currentData.manaCosts);
keyWords.setText(currentData.keyWords);
colorType.setSelectedItem(currentData.colorType);
cardText.setText(currentData.cardText);
updating=false;
}
}

View File

@@ -0,0 +1,135 @@
package forge.adventure.editor;
import forge.adventure.data.RewardData;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionListener;
/**
* Editor class to edit configuration, maybe moved or removed
*/
public class RewardsEditor extends JComponent{
DefaultListModel<RewardData> model = new DefaultListModel<>();
JList<RewardData> list = new JList<>(model);
JToolBar toolBar = new JToolBar("toolbar");
RewardEdit edit=new RewardEdit();
public class RewardDataRenderer extends DefaultListCellRenderer {
@Override
public Component getListCellRendererComponent(
JList list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if(!(value instanceof RewardData))
return label;
RewardData reward=(RewardData) value;
StringBuilder builder=new StringBuilder();
if(reward.type==null||reward.type.isEmpty())
builder.append("Reward");
else
builder.append(reward.type);
builder.append(" ");
builder.append(reward.count);
if(reward.addMaxCount>0)
{
builder.append("-");
builder.append(reward.count+reward.addMaxCount);
}
label.setText(builder.toString());
return label;
}
}
public void addButton(String name, ActionListener action)
{
JButton newButton=new JButton(name);
newButton.addActionListener(action);
toolBar.add(newButton);
}
public RewardsEditor()
{
list.setCellRenderer(new RewardsEditor.RewardDataRenderer());
list.addListSelectionListener(e -> updateEdit());
addButton("add",e->addReward());
addButton("remove",e->remove());
addButton("copy",e->copy());
BorderLayout layout=new BorderLayout();
setLayout(layout);
add(list, BorderLayout.LINE_START);
add(toolBar, BorderLayout.PAGE_START);
add(edit,BorderLayout.CENTER);
edit.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
emitChanged();
}
});
}
protected void emitChanged() {
ChangeListener[] listeners = listenerList.getListeners(ChangeListener.class);
if (listeners != null && listeners.length > 0) {
ChangeEvent evt = new ChangeEvent(this);
for (ChangeListener listener : listeners) {
listener.stateChanged(evt);
}
}
}
private void copy() {
int selected=list.getSelectedIndex();
if(selected<0)
return;
RewardData data=new RewardData(model.get(selected));
model.add(model.size(),data);
}
private void updateEdit() {
int selected=list.getSelectedIndex();
if(selected<0)
return;
edit.setCurrentReward(model.get(selected));
}
void addReward()
{
RewardData data=new RewardData();
model.add(model.size(),data);
}
void remove()
{
int selected=list.getSelectedIndex();
if(selected<0)
return;
model.remove(selected);
}
public void setRewards(RewardData[] rewards) {
model.clear();
for (int i=0;i<rewards.length;i++) {
model.add(i,rewards[i]);
}
}
public RewardData[] getRewards() {
RewardData[] rewards= new RewardData[model.getSize()];
for(int i=0;i<model.getSize();i++)
{
rewards[i]=model.get(i);
}
return rewards;
}
public void addChangeListener(ChangeListener listener) {
listenerList.add(ChangeListener.class, listener);
}
}

View File

@@ -0,0 +1,69 @@
package forge.adventure.editor;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.utils.Array;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import static java.awt.Image.SCALE_FAST;
/**
* Editor class to edit configuration, maybe moved or removed
*/
public class SwingAtlas {
HashMap<String, ArrayList<ImageIcon>> images=new HashMap<>();
public HashMap<String, ArrayList<ImageIcon>> getImages()
{
return images;
}
public SwingAtlas(FileHandle path)
{
if(!path.exists())
return;
TextureAtlas.TextureAtlasData data=new TextureAtlas.TextureAtlasData(path,path.parent(),false);
for(TextureAtlas.TextureAtlasData.Region region: new Array.ArrayIterator<>(data.getRegions()))
{
String name=region.name;
if(!images.containsKey(name))
{
images.put(name,new ArrayList<>());
}
ArrayList<ImageIcon> imageList=images.get(name);
try {
imageList.add(spriteToImage(region));
} catch (IOException e) {
e.printStackTrace();
}
}
}
private ImageIcon spriteToImage(TextureAtlas.TextureAtlasData.Region sprite) throws IOException {
BufferedImage img = ImageIO.read(sprite.page.textureFile.file());
return new ImageIcon(img.getSubimage(sprite.left,sprite.top, sprite.width, sprite.height).getScaledInstance(32,32,SCALE_FAST));
}
public ImageIcon get(String name) {
return images.get(name).get(0);
}
public boolean has(String name) {
return images.containsKey(name);
}
public ImageIcon getAny() {
if(images.isEmpty())
return null;
ArrayList<ImageIcon> imageList= images.get(images.keySet().iterator().next());
if(imageList.isEmpty())
return null;
return imageList.get(0);
}
}

View File

@@ -0,0 +1,52 @@
package forge.adventure.editor;
import forge.adventure.util.Config;
import org.apache.commons.lang3.tuple.Pair;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Editor class to edit configuration, maybe moved or removed
*/
public class SwingAtlasPreview extends Box {
private String sprite="";
Timer timer;
public SwingAtlasPreview() {
super(BoxLayout.Y_AXIS);
timer = new Timer(200, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
counter++;
for (Pair<JLabel, ArrayList<ImageIcon>> element : labels) {
element.getKey().setIcon(element.getValue().get(counter % element.getValue().size()));
}
}
});
}
int counter=0;
List<Pair<JLabel,ArrayList<ImageIcon>>> labels=new ArrayList<>();
public void setSpritePath(String sprite) {
removeAll();
counter=0;
labels.clear();
if(this.sprite.equals(sprite))
return;
this.sprite=sprite;
SwingAtlas atlas=new SwingAtlas(Config.instance().getFile(sprite));
for(Map.Entry<String, ArrayList<ImageIcon>> element:atlas.getImages().entrySet())
{
JLabel image=new JLabel(element.getValue().get(0));
add(new JLabel(element.getKey()));
add(image);
labels.add(Pair.of(image, element.getValue()));
}
timer.restart();
repaint();
}
}

View File

@@ -0,0 +1,101 @@
package forge.adventure.editor;
import forge.adventure.util.Config;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
/**
* Editor class to edit configuration, maybe moved or removed
*/
public class TextListEdit extends Box {
JTextField edit=new JTextField();
JButton findButton=new JButton(UIManager.getIcon("add"));
JComboBox elements;
public TextListEdit(String[] possibleElements) {
super(BoxLayout.X_AXIS);
findButton.addActionListener(e->find());
add(edit);
//add(findButton);
elements= new JComboBox(possibleElements);
add(elements);
}
public TextListEdit()
{
this(new String[0]);
}
JTextField getEdit()
{
return edit;
}
private void find() {
JFileChooser fc = new JFileChooser();
fc.setCurrentDirectory(new File(Config.instance().getFilePath("")));
fc.setMultiSelectionEnabled(false);
if (fc.showOpenDialog(this) ==
JFileChooser.APPROVE_OPTION) {
File selected = fc.getSelectedFile();
try {
if (selected != null&&selected.getCanonicalPath().startsWith(new File(Config.instance().getFilePath("")).getCanonicalPath())) {
edit.setText(selected.getCanonicalPath().substring(new File(Config.instance().getFilePath("")).getCanonicalPath().length()+1));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void setText(String[] itemName) {
if(itemName==null)
edit.setText("");
else
edit.setText(String.join(";",itemName));
}
public void setText(int[] intValues) {
if(intValues==null)
{
edit.setText("");
return;
}
StringBuilder values= new StringBuilder();
for(int i=0;i<intValues.length;i++)
{
values.append(intValues[i]);
if(intValues.length>i+2)
values.append(";");
}
edit.setText(values.toString());
}
public String[] getList() {
return edit.getText().isEmpty()?null:edit.getText().split(";");
}
public int[] getListAsInt() {
if(edit.getText().isEmpty())
return null;
String[] stringList=getList();
int[] retList=new int[stringList.length];
for(int i=0;i<retList.length;i++)
{
String intName=stringList[i];
try
{
retList[i] = Integer.valueOf(intName);
}
catch (NumberFormatException e)
{
retList[i] =0;
}
}
return retList;
}
}

View File

@@ -0,0 +1,45 @@
package forge.adventure.libgdxgui;
import com.badlogic.gdx.graphics.Texture;
import forge.adventure.libgdxgui.screens.match.MatchController;
import forge.adventure.libgdxgui.assets.ImageCache;
import forge.game.card.CardView;
import forge.gui.GuiBase;
import forge.item.InventoryItem;
import forge.util.ImageFetcher;
public abstract class CachedCardImage implements ImageFetcher.Callback {
protected final String key;
static final ImageFetcher fetcher = GuiBase.getInterface().getImageFetcher();
public CachedCardImage(final CardView card) {
key = card.getCurrentState().getImageKey(MatchController.instance.getLocalPlayers());
fetch();
}
public CachedCardImage(final InventoryItem ii) {
key = ii.getImageKey(false);
fetch();
}
public CachedCardImage(String key) {
this.key = key;
fetch();
}
public void fetch() {
if (!ImageCache.imageKeyFileExists(key)) {
fetcher.fetchImage(key, this);
}
}
public Texture getImage() {
return ImageCache.getImage(key, true);
}
public Texture getImage(String mykey) {
return ImageCache.getImage(mykey, true);
}
public abstract void onImageFetched();
}

View File

@@ -0,0 +1,920 @@
package forge.adventure.libgdxgui;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.utils.Clipboard;
import forge.adventure.libgdxgui.animation.ForgeAnimation;
import forge.adventure.libgdxgui.assets.AssetsDownloader;
import forge.adventure.libgdxgui.assets.FSkin;
import forge.adventure.libgdxgui.assets.FSkinFont;
import forge.adventure.libgdxgui.assets.ImageCache;
import forge.adventure.libgdxgui.screens.FScreen;
import forge.adventure.libgdxgui.screens.SplashScreen;
import forge.adventure.libgdxgui.screens.match.MatchController;
import forge.adventure.libgdxgui.toolbox.*;
import forge.adventure.libgdxgui.util.Utils;
import forge.error.ExceptionHandler;
import forge.gui.FThreads;
import forge.gui.GuiBase;
import forge.gui.error.BugReporter;
import forge.interfaces.IDeviceAdapter;
import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.properties.ForgePreferences;
import forge.localinstance.properties.ForgePreferences.FPref;
import forge.model.FModel;
import forge.sound.MusicPlaylist;
import forge.sound.SoundSystem;
import forge.util.Callback;
import forge.util.CardTranslation;
import forge.util.FileUtil;
import forge.util.Localizer;
import java.io.File;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
public class Forge implements ApplicationListener {
public static final String CURRENT_VERSION = "1.6.42.001";
public static final Forge app = new Forge();
private static final Deque<FScreen> Dscreens = new ArrayDeque<>();
public static String extrawide = "default";
public static float heigtModifier = 0.0f;
public static boolean showFPS = false;
public static boolean altPlayerLayout = false;
public static boolean altZoneTabs = false;
public static String enableUIMask = "Full";
public static boolean enablePreloadExtendedArt = false;
public static boolean isTabletDevice = false;
public static String locale = "en-US";
public static boolean hdbuttons = false;
public static boolean hdstart = false;
public static boolean isPortraitMode = false;
public static boolean gameInProgress = false;
public static boolean disposeTextures = false;
public static int cacheSize = 400;
public static int totalDeviceRAM = 0;
public static int androidVersion = 0;
public static boolean autoCache = false;
public static int lastButtonIndex = 0;
public static String CJK_Font = "";
private static Clipboard clipboard;
private static IDeviceAdapter deviceAdapter;
private static int screenWidth;
private static int screenHeight;
private static forge.adventure.libgdxgui.Graphics graphics;
private static forge.adventure.libgdxgui.FrameRate frameRate;
private static FScreen currentScreen;
private static SplashScreen splashScreen;
private static KeyInputAdapter keyInputAdapter;
private static boolean exited;
private static int continuousRenderingCount = 1; //initialize to 1 since continuous rendering is the default
private static boolean textureFiltering = false;
private static boolean destroyThis = false;
private static boolean isloadingaMatch = false;
private MainInputProcessor input;
private Forge() {
}
public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0, boolean value, boolean androidOrientation, int totalRAM, boolean isTablet, int AndroidAPI, String AndroidRelease, String deviceName) {
if (GuiBase.getInterface() == null) {
clipboard = clipboard0;
deviceAdapter = deviceAdapter0;
GuiBase.setUsingAppDirectory(assetDir0.contains("forge.app")); //obb directory on android uses the package name as entrypoint
GuiBase.setInterface(new forge.adventure.libgdxgui.GuiMobile(assetDir0));
GuiBase.enablePropertyConfig(value);
isPortraitMode = androidOrientation;
totalDeviceRAM = totalRAM;
isTabletDevice = isTablet;
androidVersion = AndroidAPI;
}
GuiBase.setDeviceInfo(deviceName, AndroidRelease, AndroidAPI, totalRAM);
return app;
}
public static void openHomeScreen(int index) {
}
public static Clipboard getClipboard() {
return clipboard;
}
public static IDeviceAdapter getDeviceAdapter() {
return deviceAdapter;
}
public static void startContinuousRendering() {
if (++continuousRenderingCount == 1) {
//only set continuous rendering to true if needed
Gdx.graphics.setContinuousRendering(true);
}
}
public static void stopContinuousRendering() {
if (continuousRenderingCount > 0 && --continuousRenderingCount == 0) {
//only set continuous rendering to false if all continuous rendering requests have been ended
Gdx.graphics.setContinuousRendering(false);
}
}
public static float getHeightModifier() {
return heigtModifier;
}
public static void setHeightModifier(float height) {
heigtModifier = height;
}
public static void adjustHeightModifier(float DisplayW, float DisplayH) {
if (isLandscapeMode()) {//TODO: Fullscreen support for Display without screen controls
float aspectratio = DisplayW / DisplayH;
if (aspectratio > 1.82f) {/* extra wide */
setHeightModifier(200.0f);
extrawide = "extrawide";
} else if (aspectratio > 1.7f) {/* wide */
setHeightModifier(100.0f);
extrawide = "wide";
}
}
}
public static void showMenu() {
if (currentScreen == null) {
return;
}
endKeyInput(); //end key input before menu shown
if (FOverlay.getTopOverlay() == null) { //don't show menu if overlay open
currentScreen.showMenu();
}
}
public static boolean onHomeScreen() {
return Dscreens.size() == 1;
}
public static void back() {
}
//set screen that will be gone to on pressing Back before going to current Back screen
public static void setBackScreen(final FScreen screen0, boolean replace) {
Dscreens.remove(screen0); //remove screen from previous position in navigation history
int index = Dscreens.size() - 1;
if (index > 0) {
Dscreens.addLast(screen0);
if (replace) { //remove previous back screen if replacing back screen
Dscreens.removeFirst();
}
}
}
public static void restart(boolean silent) {
if (exited) {
return;
} //don't allow exiting multiple times
Callback<Boolean> callback = new Callback<Boolean>() {
@Override
public void run(Boolean result) {
if (result) {
exited = true;
deviceAdapter.restart();
}
}
};
final Localizer localizer = Localizer.getInstance();
if (silent) {
callback.run(true);
} else {
FOptionPane.showConfirmDialog(
localizer.getMessage("lblAreYouSureYouWishRestartForge"), localizer.getMessage("lblRestartForge"),
localizer.getMessage("lblRestart"), localizer.getMessage("lblCancel"), callback);
}
}
public static void exit(boolean silent) {
if (exited) {
return;
} //don't allow exiting multiple times
Callback<Boolean> callback = new Callback<Boolean>() {
@Override
public void run(Boolean result) {
if (result) {
exited = true;
deviceAdapter.exit();
}
}
};
final Localizer localizer = Localizer.getInstance();
if (silent) {
callback.run(true);
} else {
FOptionPane.showConfirmDialog(
localizer.getMessage("lblAreYouSureYouWishExitForge"), localizer.getMessage("lblExitForge"),
localizer.getMessage("lblExit"), localizer.getMessage("lblCancel"), callback);
}
}
public static void openScreen(final FScreen screen0) {
openScreen(screen0, false);
}
public static void openScreen(final FScreen screen0, final boolean replaceBackScreen) {
if (currentScreen == screen0) {
return;
}
if (currentScreen == null) {
Dscreens.addFirst(screen0);
setCurrentScreen(screen0);
return;
}
currentScreen.onSwitchAway(new Callback<Boolean>() {
@Override
public void run(Boolean result) {
if (result) {
if (replaceBackScreen && !Dscreens.isEmpty()) {
Dscreens.removeFirst();
}
if (Dscreens.peekFirst() != screen0) { //prevent screen being its own back screen
Dscreens.addFirst(screen0);
}
setCurrentScreen(screen0);
}
}
});
}
public static boolean isTextureFilteringEnabled() {
return textureFiltering;
}
public static boolean isLandscapeMode() {
if (GuiBase.isAndroid())
return !isPortraitMode;
return screenWidth > screenHeight;
}
public static boolean isLoadingaMatch() {
return isloadingaMatch;
}
public static void setLoadingaMatch(boolean value) {
isloadingaMatch = value;
}
public static int getScreenWidth() {
return screenWidth;
}
public static int getScreenHeight() {
return screenHeight;
}
public static FScreen getCurrentScreen() {
return currentScreen;
}
private static void setCurrentScreen(FScreen screen0) {
String toNewScreen = screen0 != null ? screen0.toString() : "";
String previousScreen = currentScreen != null ? currentScreen.toString() : "";
gameInProgress = toNewScreen.toLowerCase().contains("match") || previousScreen.toLowerCase().contains("match");
boolean dispose = toNewScreen.toLowerCase().contains("homescreen") && disposeTextures;
try {
endKeyInput(); //end key input before switching screens
ForgeAnimation.endAll(); //end all active animations before switching screens
currentScreen = screen0;
currentScreen.setSize(screenWidth, screenHeight);
currentScreen.onActivate();
/*keep Dscreens growing
if (Dscreens.size() > 3) {
for(int x = Dscreens.size(); x > 3; x--) {
Dscreens.removeLast();
}
}*/
/* for checking only
if (!Dscreens.isEmpty()) {
int x = 0;
for(FScreen fScreen : Dscreens) {
System.out.println("Screen ["+x+"]: "+fScreen.toString());
x++;
}
System.out.println("---------------");
}*/
} catch (Exception ex) {
graphics.end();
//check if sentry is enabled, if not it will call the gui interface but here we end the graphics so we only send it via sentry..
if (BugReporter.isSentryEnabled())
BugReporter.reportException(ex);
} finally {
if (dispose)
ImageCache.disposeTexture();
}
}
//log message to Forge.log file
public static void log(Object message) {
System.out.println(message);
}
public static void startKeyInput(KeyInputAdapter adapter) {
if (keyInputAdapter == adapter) {
return;
}
if (keyInputAdapter != null) {
keyInputAdapter.onInputEnd(); //make sure previous adapter is ended
}
keyInputAdapter = adapter;
Gdx.input.setOnscreenKeyboardVisible(true);
}
public static boolean endKeyInput() {
if (keyInputAdapter == null) {
return false;
}
keyInputAdapter.onInputEnd();
keyInputAdapter = null;
MainInputProcessor.keyTyped = false;
MainInputProcessor.lastKeyTyped = '\0';
Gdx.input.setOnscreenKeyboardVisible(false);
return true;
}
@Override
public void create() {
//install our error handler
ExceptionHandler.registerErrorHandling();
GuiBase.setIsAndroid(Gdx.app.getType() == Application.ApplicationType.Android);
graphics = new Graphics();
splashScreen = new SplashScreen();
frameRate = new FrameRate();
input=new MainInputProcessor();
Gdx.input.setInputProcessor(input);
/*
Set CatchBackKey here and exit the app when you hit the
back button while the textures,fonts,etc are still loading,
to prevent rendering issue when you try to restart
the app again (seems it doesnt dispose correctly...?!?)
*/
Gdx.input.setCatchKey(Keys.BACK, true);
destroyThis = true; //Prevent back()
ForgePreferences prefs = new ForgePreferences();
String skinName;
if (FileUtil.doesFileExist(ForgeConstants.MAIN_PREFS_FILE)) {
skinName = prefs.getPref(FPref.UI_SKIN);
} else {
skinName = "default"; //use default skin if preferences file doesn't exist yet
}
FSkin.loadLight(skinName, splashScreen);
textureFiltering = prefs.getPrefBoolean(FPref.UI_LIBGDX_TEXTURE_FILTERING);
showFPS = prefs.getPrefBoolean(FPref.UI_SHOW_FPS);
altPlayerLayout = prefs.getPrefBoolean(FPref.UI_ALT_PLAYERINFOLAYOUT);
altZoneTabs = prefs.getPrefBoolean(FPref.UI_ALT_PLAYERZONETABS);
enableUIMask = prefs.getPref(FPref.UI_ENABLE_BORDER_MASKING);
if (prefs.getPref(FPref.UI_ENABLE_BORDER_MASKING).equals("true")) //override old settings if not updated
enableUIMask = "Full";
else if (prefs.getPref(FPref.UI_ENABLE_BORDER_MASKING).equals("false"))
enableUIMask = "Off";
enableUIMask = "Full";
enablePreloadExtendedArt = prefs.getPrefBoolean(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART);
locale = prefs.getPref(FPref.UI_LANGUAGE);
autoCache = prefs.getPrefBoolean(FPref.UI_AUTO_CACHE_SIZE);
disposeTextures = prefs.getPrefBoolean(FPref.UI_ENABLE_DISPOSE_TEXTURES);
CJK_Font = prefs.getPref(FPref.UI_CJK_FONT);
if (autoCache) {
//increase cacheSize for devices with RAM more than 5GB, default is 400. Some phones have more than 10GB RAM (Mi 10, OnePlus 8, S20, etc..)
if (totalDeviceRAM > 5000) //devices with more than 10GB RAM will have 800 Cache size, 600 Cache size for morethan 5GB RAM
cacheSize = totalDeviceRAM > 10000 ? 800 : 600;
}
//init cache
ImageCache.initCache(cacheSize);
final Localizer localizer = Localizer.getInstance();
//load model on background thread (using progress bar to report progress)
FThreads.invokeInBackgroundThread(new Runnable() {
@Override
public void run() {
//see if app or assets need updating
AssetsDownloader.checkForUpdates(splashScreen);
if (exited) {
return;
} //don't continue if user chose to exit or couldn't download required assets
FModel.initialize(splashScreen.getProgressBar(), null);
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblLoadingFonts"));
FSkinFont.preloadAll(locale);
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblLoadingCardTranslations"));
CardTranslation.preloadTranslation(locale, ForgeConstants.LANG_DIR);
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup"));
//add reminder to preload
if (enablePreloadExtendedArt) {
if (autoCache)
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblPreloadExtendedArt") + "\nDetected RAM: " + totalDeviceRAM + "MB. Cache size: " + cacheSize);
else
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblPreloadExtendedArt"));
} else {
if (autoCache)
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup") + "\nDetected RAM: " + totalDeviceRAM + "MB. Cache size: " + cacheSize);
else
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup"));
}
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 (!enablePreloadExtendedArt)
return;
List<String> borderlessCardlistkeys = FileUtil.readFile(ForgeConstants.BORDERLESS_CARD_LIST_FILE);
if (borderlessCardlistkeys.isEmpty())
return;
List<String> filteredkeys = new ArrayList<>();
for (String cardname : borderlessCardlistkeys) {
File image = new File(ForgeConstants.CACHE_CARD_PICS_DIR + ForgeConstants.PATH_SEPARATOR + cardname + ".jpg");
if (image.exists())
filteredkeys.add(cardname);
}
if (!filteredkeys.isEmpty())
ImageCache.preloadCache(filteredkeys);
}
private void afterDbLoaded() {
stopContinuousRendering(); //save power consumption by disabling continuous rendering once assets loaded
FSkin.loadFull(splashScreen);
SoundSystem.instance.setBackgroundMusic(MusicPlaylist.MENUS); //start background music
destroyThis = false; //Allow back()
Gdx.input.setCatchKey(Keys.MENU, true);
openHomeScreen(-1); //default for startup
splashScreen = null;
boolean isLandscapeMode = isLandscapeMode();
//adjust height modifier
adjustHeightModifier(getScreenWidth(), getScreenHeight());
//update landscape mode preference if it doesn't match what the app loaded as
if (FModel.getPreferences().getPrefBoolean(FPref.UI_LANDSCAPE_MODE) != isLandscapeMode) {
FModel.getPreferences().setPref(FPref.UI_LANDSCAPE_MODE, isLandscapeMode);
FModel.getPreferences().save();
}
}
@Override
public void render() {
if (showFPS)
frameRate.update();
try {
ImageCache.allowSingleLoad();
ForgeAnimation.advanceAll();
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); // Clear the screen.
FContainer screen = currentScreen;
if (screen == null) {
screen = splashScreen;
if (screen == null) {
return;
}
}
graphics.begin(screenWidth, screenHeight);
screen.screenPos.setSize(screenWidth, screenHeight);
if (screen.getRotate180()) {
graphics.startRotateTransform(screenWidth / 2, screenHeight / 2, 180);
}
screen.draw(graphics);
if (screen.getRotate180()) {
graphics.endTransform();
}
for (FOverlay overlay : FOverlay.getOverlays()) {
if (overlay.isVisibleOnScreen(currentScreen)) {
overlay.screenPos.setSize(screenWidth, screenHeight);
overlay.setSize(screenWidth, screenHeight); //update overlay sizes as they're rendered
if (overlay.getRotate180()) {
graphics.startRotateTransform(screenWidth / 2, screenHeight / 2, 180);
}
overlay.draw(graphics);
if (overlay.getRotate180()) {
graphics.endTransform();
}
}
}
graphics.end();
} catch (Exception ex) {
graphics.end();
//check if sentry is enabled, if not it will call the gui interface but here we end the graphics so we only send it via sentry..
if (BugReporter.isSentryEnabled())
BugReporter.reportException(ex);
}
if (showFPS)
frameRate.render();
}
@Override
public void resize(int width, int height) {
try {
screenWidth = width;
screenHeight = height;
if (currentScreen != null) {
currentScreen.setSize(width, height);
} else if (splashScreen != null) {
splashScreen.setSize(width, height);
}
} catch (Exception ex) {
graphics.end();
//check if sentry is enabled, if not it will call the gui interface but here we end the graphics so we only send it via sentry..
if (BugReporter.isSentryEnabled())
BugReporter.reportException(ex);
}
}
@Override
public void pause() {
if (MatchController.getHostedMatch() != null) {
MatchController.getHostedMatch().pause();
}
}
@Override
public void resume() {
if (MatchController.getHostedMatch() != null) {
MatchController.getHostedMatch().resume();
}
}
@Override
public void dispose() {
if (currentScreen != null) {
FOverlay.hideAll();
currentScreen.onClose(null);
currentScreen = null;
}
Dscreens.clear();
graphics.dispose();
SoundSystem.instance.dispose();
try {
ExceptionHandler.unregisterErrorHandling();
} catch (Exception e) {
}
}
public static abstract class KeyInputAdapter {
public static boolean isCtrlKeyDown() {
return Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) || Gdx.input.isKeyPressed(Keys.CONTROL_RIGHT);
}
public static boolean isShiftKeyDown() {
return Gdx.input.isKeyPressed(Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Keys.SHIFT_RIGHT);
}
public static boolean isAltKeyDown() {
return Gdx.input.isKeyPressed(Keys.ALT_LEFT) || Gdx.input.isKeyPressed(Keys.ALT_RIGHT);
}
public static boolean isModifierKey(int keyCode) {
switch (keyCode) {
case Keys.CONTROL_LEFT:
case Keys.CONTROL_RIGHT:
case Keys.SHIFT_LEFT:
case Keys.SHIFT_RIGHT:
case Keys.ALT_LEFT:
case Keys.ALT_RIGHT:
return true;
}
return false;
}
public abstract FDisplayObject getOwner();
public abstract boolean allowTouchInput();
public abstract boolean keyTyped(char ch);
public abstract boolean keyDown(int keyCode);
public abstract void onInputEnd();
//also allow handling of keyUp but don't require it
public boolean keyUp(int keyCode) {
return false;
}
}
private static class MainInputProcessor extends FGestureAdapter {
private static final List<FDisplayObject> potentialListeners = new ArrayList<>();
private static char lastKeyTyped;
private static boolean keyTyped, shiftKeyDown;
//mouseMoved and scrolled events for desktop version
private int mouseMovedX, mouseMovedY;
@Override
public boolean keyDown(int keyCode) {
if (keyCode == Keys.MENU) {
showMenu();
return true;
}
if (keyCode == Keys.SHIFT_LEFT || keyCode == Keys.SHIFT_RIGHT) {
shiftKeyDown = true;
}
// Cursor keys emulate swipe gestures
// First we touch the screen and later swipe (fling) in the direction of the key pressed
if (keyCode == Keys.LEFT) {
touchDown(0, 0, 0, 0);
return fling(1000, 0);
}
if (keyCode == Keys.RIGHT) {
touchDown(0, 0, 0, 0);
return fling(-1000, 0);
}
if (keyCode == Keys.UP) {
touchDown(0, 0, 0, 0);
return fling(0, -1000);
}
if (keyCode == Keys.DOWN) {
touchDown(0, 0, 0, 0);
return fling(0, 1000);
}
if (keyCode == Keys.BACK) {
if (destroyThis)
deviceAdapter.exit();
else if (onHomeScreen() && isLandscapeMode())
back();
}
if (keyInputAdapter == null) {
if (KeyInputAdapter.isModifierKey(keyCode)) {
return false; //don't process modifiers keys for unknown adapter
}
//if no active key input adapter, give current screen or overlay a chance to handle key
FContainer container = FOverlay.getTopOverlay();
if (container == null) {
container = currentScreen;
if (container == null) {
return false;
}
}
return container.keyDown(keyCode);
}
return keyInputAdapter.keyDown(keyCode);
}
@Override
public boolean keyUp(int keyCode) {
keyTyped = false; //reset on keyUp
if (keyCode == Keys.SHIFT_LEFT || keyCode == Keys.SHIFT_RIGHT) {
shiftKeyDown = false;
}
if (keyInputAdapter != null) {
return keyInputAdapter.keyUp(keyCode);
}
return false;
}
@Override
public boolean keyTyped(char ch) {
if (keyInputAdapter != null) {
if (ch >= ' ' && ch <= '~') { //only process this event if character is printable
//prevent firing this event more than once for the same character on the same key down, otherwise it fires too often
if (lastKeyTyped != ch || !keyTyped) {
keyTyped = true;
lastKeyTyped = ch;
return keyInputAdapter.keyTyped(ch);
}
}
}
return false;
}
private void updatePotentialListeners(int x, int y) {
potentialListeners.clear();
//base potential listeners on object containing touch down point
for (FOverlay overlay : FOverlay.getOverlaysTopDown()) {
if (overlay.isVisibleOnScreen(currentScreen)) {
overlay.buildTouchListeners(x, y, potentialListeners);
if (overlay.preventInputBehindOverlay()) {
return;
}
}
}
if (currentScreen != null) {
currentScreen.buildTouchListeners(x, y, potentialListeners);
}
}
@Override
public boolean touchDown(int x, int y, int pointer, int button) {
if (pointer == 0) { //don't change listeners when second finger goes down for zoom
updatePotentialListeners(x, y);
if (keyInputAdapter != null) {
if (!keyInputAdapter.allowTouchInput() || !potentialListeners.contains(keyInputAdapter.getOwner())) {
endKeyInput(); //end key input if needed
}
}
}
return super.touchDown(x, y, pointer, button);
}
@Override
public boolean press(float x, float y) {
try {
for (FDisplayObject listener : potentialListeners) {
if (listener.press(listener.screenToLocalX(x), listener.screenToLocalY(y))) {
return true;
}
}
return false;
} catch (Exception ex) {
BugReporter.reportException(ex);
return true;
}
}
@Override
public boolean release(float x, float y) {
try {
for (FDisplayObject listener : potentialListeners) {
if (listener.release(listener.screenToLocalX(x), listener.screenToLocalY(y))) {
return true;
}
}
return false;
} catch (Exception ex) {
BugReporter.reportException(ex);
return true;
}
}
@Override
public boolean longPress(float x, float y) {
try {
for (FDisplayObject listener : potentialListeners) {
if (listener.longPress(listener.screenToLocalX(x), listener.screenToLocalY(y))) {
return true;
}
}
return false;
} catch (Exception ex) {
BugReporter.reportException(ex);
return true;
}
}
@Override
public boolean tap(float x, float y, int count) {
if (shiftKeyDown && flick(x, y)) {
return true; //give flick logic a chance to handle Shift+click
}
try {
for (FDisplayObject listener : potentialListeners) {
if (listener.tap(listener.screenToLocalX(x), listener.screenToLocalY(y), count)) {
return true;
}
}
return false;
} catch (Exception ex) {
BugReporter.reportException(ex);
return true;
}
}
@Override
public boolean flick(float x, float y) {
try {
for (FDisplayObject listener : potentialListeners) {
if (listener.flick(listener.screenToLocalX(x), listener.screenToLocalY(y))) {
return true;
}
}
return false;
} catch (Exception ex) {
BugReporter.reportException(ex);
return true;
}
}
@Override
public boolean fling(float velocityX, float velocityY) {
try {
for (FDisplayObject listener : potentialListeners) {
if (listener.fling(velocityX, velocityY)) {
return true;
}
}
return false;
} catch (Exception ex) {
BugReporter.reportException(ex);
return true;
}
}
@Override
public boolean pan(float x, float y, float deltaX, float deltaY, boolean moreVertical) {
try {
for (FDisplayObject listener : potentialListeners) {
if (listener.pan(listener.screenToLocalX(x), listener.screenToLocalY(y), deltaX, deltaY, moreVertical)) {
return true;
}
}
return false;
} catch (Exception ex) {
BugReporter.reportException(ex);
return true;
}
}
@Override
public boolean panStop(float x, float y) {
try {
for (FDisplayObject listener : potentialListeners) {
if (listener.panStop(listener.screenToLocalX(x), listener.screenToLocalY(y))) {
return true;
}
}
return false;
} catch (Exception ex) {
BugReporter.reportException(ex);
return true;
}
}
@Override
public boolean zoom(float x, float y, float amount) {
try {
for (FDisplayObject listener : potentialListeners) {
if (listener.zoom(listener.screenToLocalX(x), listener.screenToLocalY(y), amount)) {
return true;
}
}
return false;
} catch (Exception ex) {
BugReporter.reportException(ex);
return true;
}
}
@Override
public boolean mouseMoved(int x, int y) {
mouseMovedX = x;
mouseMovedY = y;
return true;
}
@Override
public boolean scrolled(float amountX, float amountY) {
updatePotentialListeners(mouseMovedX, mouseMovedY);
if (KeyInputAdapter.isCtrlKeyDown()) { //zoom in or out based on amount
return zoom(mouseMovedX, mouseMovedY, -Utils.AVG_FINGER_WIDTH * amountY);
}
boolean handled;
if (KeyInputAdapter.isShiftKeyDown()) {
handled = pan(mouseMovedX, mouseMovedY, -Utils.AVG_FINGER_WIDTH * amountX, 0, false);
} else {
handled = pan(mouseMovedX, mouseMovedY, 0, -Utils.AVG_FINGER_HEIGHT * amountY, true);
}
if (panStop(mouseMovedX, mouseMovedY)) {
handled = true;
}
return handled;
}
}
}

View File

@@ -0,0 +1,62 @@
package forge.adventure.libgdxgui;
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 final BitmapFont font;
private final 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();
}
}

View File

@@ -0,0 +1,887 @@
package forge.adventure.libgdxgui;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
import forge.adventure.libgdxgui.assets.FImage;
import forge.adventure.libgdxgui.assets.FSkinColor;
import forge.adventure.libgdxgui.assets.FSkinFont;
import forge.adventure.libgdxgui.toolbox.FDisplayObject;
import forge.adventure.libgdxgui.util.TextBounds;
import forge.adventure.libgdxgui.util.Utils;
import java.util.ArrayDeque;
import java.util.Deque;
public class Graphics {
private static final int GL_BLEND = GL20.GL_BLEND;
private static final int GL_LINE_SMOOTH = 2848; //create constant here since not in GL20
private final Batch batch;
private final ShapeRenderer shapeRenderer = new ShapeRenderer();
private final Deque<Matrix4> Dtransforms = new ArrayDeque<>();
private final Vector3 tmp = new Vector3();
private float regionHeight;
private Rectangle bounds;
private Rectangle visibleBounds;
private int failedClipCount;
private float alphaComposite = 1;
private int transformCount = 0;
private final String sVertex = "uniform mat4 u_projTrans;\n" +
"\n" +
"attribute vec4 a_position;\n" +
"attribute vec2 a_texCoord0;\n" +
"attribute vec4 a_color;\n" +
"\n" +
"varying vec4 v_color;\n" +
"varying vec2 v_texCoord;\n" +
"\n" +
"uniform vec2 u_viewportInverse;\n" +
"\n" +
"void main() {\n" +
" gl_Position = u_projTrans * a_position;\n" +
" v_texCoord = a_texCoord0;\n" +
" v_color = a_color;\n" +
"}";
private final String sFragment = "#ifdef GL_ES\n" +
"precision mediump float;\n" +
"precision mediump int;\n" +
"#endif\n" +
"\n" +
"uniform sampler2D u_texture;\n" +
"\n" +
"// The inverse of the viewport dimensions along X and Y\n" +
"uniform vec2 u_viewportInverse;\n" +
"\n" +
"// Color of the outline\n" +
"uniform vec3 u_color;\n" +
"\n" +
"// Thickness of the outline\n" +
"uniform float u_offset;\n" +
"\n" +
"// Step to check for neighbors\n" +
"uniform float u_step;\n" +
"\n" +
"varying vec4 v_color;\n" +
"varying vec2 v_texCoord;\n" +
"\n" +
"#define ALPHA_VALUE_BORDER 0.5\n" +
"\n" +
"void main() {\n" +
" vec2 T = v_texCoord.xy;\n" +
"\n" +
" float alpha = 0.0;\n" +
" bool allin = true;\n" +
" for( float ix = -u_offset; ix < u_offset; ix += u_step )\n" +
" {\n" +
" for( float iy = -u_offset; iy < u_offset; iy += u_step )\n" +
" {\n" +
" float newAlpha = texture2D(u_texture, T + vec2(ix, iy) * u_viewportInverse).a;\n" +
" allin = allin && newAlpha > ALPHA_VALUE_BORDER;\n" +
" if (newAlpha > ALPHA_VALUE_BORDER && newAlpha >= alpha)\n" +
" {\n" +
" alpha = newAlpha;\n" +
" }\n" +
" }\n" +
" }\n" +
" if (allin)\n" +
" {\n" +
" alpha = 0.0;\n" +
" }\n" +
"\n" +
" gl_FragColor = vec4(u_color,alpha);\n" +
"}";
private final ShaderProgram shaderOutline = new ShaderProgram(sVertex, sFragment);
public Graphics() {
batch = new SpriteBatch();
ShaderProgram.pedantic = false;
}
public Graphics(Batch batch,float regionWidth0, float regionHeight0) {
this.batch=batch;bound(regionWidth0, regionHeight0);
}
public void bound(float regionWidth0, float regionHeight0) {
bounds = new Rectangle(0, 0, regionWidth0, regionHeight0);
regionHeight = regionHeight0;
visibleBounds = new Rectangle(bounds);
}
public void begin(float regionWidth0, float regionHeight0) {
batch.begin();
bounds = new Rectangle(0, 0, regionWidth0, regionHeight0);
regionHeight = regionHeight0;
visibleBounds = new Rectangle(bounds);
}
public void end() {
if (batch.isDrawing()) {
batch.end();
}
if (shapeRenderer.getCurrentType() != null) {
shapeRenderer.end();
}
}
public void dispose() {
batch.dispose();
shapeRenderer.dispose();
shaderOutline.dispose();
}
public SpriteBatch getBatch() {
return (SpriteBatch)batch;
}
public boolean startClip() {
return startClip(0, 0, bounds.width, bounds.height);
}
public boolean startClip(float x, float y, float w, float h) {
batch.flush(); //must flush batch to prevent other things not rendering
Rectangle clip = new Rectangle(adjustX(x), adjustY(y, h), w, h);
if (!Dtransforms.isEmpty()) { //transform position if needed
tmp.set(clip.x, clip.y, 0);
tmp.mul(batch.getTransformMatrix());
float minX = tmp.x;
float maxX = minX;
float minY = tmp.y;
float maxY = minY;
tmp.set(clip.x + clip.width, clip.y, 0);
tmp.mul(batch.getTransformMatrix());
if (tmp.x < minX) { minX = tmp.x; }
else if (tmp.x > maxX) { maxX = tmp.x; }
if (tmp.y < minY) { minY = tmp.y; }
else if (tmp.y > maxY) { maxY = tmp.y; }
tmp.set(clip.x + clip.width, clip.y + clip.height, 0);
tmp.mul(batch.getTransformMatrix());
if (tmp.x < minX) { minX = tmp.x; }
else if (tmp.x > maxX) { maxX = tmp.x; }
if (tmp.y < minY) { minY = tmp.y; }
else if (tmp.y > maxY) { maxY = tmp.y; }
tmp.set(clip.x, clip.y + clip.height, 0);
tmp.mul(batch.getTransformMatrix());
if (tmp.x < minX) { minX = tmp.x; }
else if (tmp.x > maxX) { maxX = tmp.x; }
if (tmp.y < minY) { minY = tmp.y; }
else if (tmp.y > maxY) { maxY = tmp.y; }
clip.set(minX, minY, maxX - minX, maxY - minY);
}
if (!ScissorStack.pushScissors(clip)) {
failedClipCount++; //tracked failed clips to prevent calling popScissors on endClip
return false;
}
return true;
}
public void endClip() {
if (failedClipCount == 0) {
batch.flush(); //must flush batch to ensure stuffed rendered during clip respects that clip
ScissorStack.popScissors();
}
else {
failedClipCount--;
}
}
public void draw(FDisplayObject displayObj) {
if (displayObj.getWidth() <= 0 || displayObj.getHeight() <= 0) {
return;
}
final Rectangle parentBounds = bounds;
bounds = new Rectangle(parentBounds.x + displayObj.getLeft(), parentBounds.y + displayObj.getTop(), displayObj.getWidth(), displayObj.getHeight());
if (!Dtransforms.isEmpty()) { //transform screen position if needed by applying transform matrix to rectangle
updateScreenPosForRotation(displayObj);
}
else {
displayObj.screenPos.set(bounds);
}
Rectangle intersection = Utils.getIntersection(bounds, visibleBounds);
if (intersection != null) { //avoid drawing object if it's not within visible region
final Rectangle backup = visibleBounds;
visibleBounds = intersection;
if (displayObj.getRotate90()) { //use top-right corner of bounds as pivot point
startRotateTransform(displayObj.getWidth(), 0, -90);
updateScreenPosForRotation(displayObj);
}
else if (displayObj.getRotate180()) { //use center of bounds as pivot point
startRotateTransform(displayObj.getWidth() / 2, displayObj.getHeight() / 2, 180);
//screen position won't change for this object from a 180 degree rotation
}
displayObj.draw(this);
if (displayObj.getRotate90() || displayObj.getRotate180()) {
endTransform();
}
visibleBounds = backup;
}
bounds = parentBounds;
}
private void updateScreenPosForRotation(FDisplayObject displayObj) {
tmp.set(bounds.x, regionHeight - bounds.y, 0);
tmp.mul(batch.getTransformMatrix());
tmp.y = regionHeight - tmp.y;
float minX = tmp.x;
float maxX = minX;
float minY = tmp.y;
float maxY = minY;
tmp.set(bounds.x + bounds.width, regionHeight - bounds.y, 0);
tmp.mul(batch.getTransformMatrix());
tmp.y = regionHeight - tmp.y;
if (tmp.x < minX) { minX = tmp.x; }
else if (tmp.x > maxX) { maxX = tmp.x; }
if (tmp.y < minY) { minY = tmp.y; }
else if (tmp.y > maxY) { maxY = tmp.y; }
tmp.set(bounds.x + bounds.width, regionHeight - bounds.y - bounds.height, 0);
tmp.mul(batch.getTransformMatrix());
tmp.y = regionHeight - tmp.y;
if (tmp.x < minX) { minX = tmp.x; }
else if (tmp.x > maxX) { maxX = tmp.x; }
if (tmp.y < minY) { minY = tmp.y; }
else if (tmp.y > maxY) { maxY = tmp.y; }
tmp.set(bounds.x, regionHeight - bounds.y - bounds.height, 0);
tmp.mul(batch.getTransformMatrix());
tmp.y = regionHeight - tmp.y;
if (tmp.x < minX) { minX = tmp.x; }
else if (tmp.x > maxX) { maxX = tmp.x; }
if (tmp.y < minY) { minY = tmp.y; }
else if (tmp.y > maxY) { maxY = tmp.y; }
displayObj.screenPos.set(minX, minY, maxX - minX, maxY - minY);
}
public void drawLine(float thickness, FSkinColor skinColor, float x1, float y1, float x2, float y2) {
drawLine(thickness, skinColor.getColor(), x1, y1, x2, y2);
}
public void drawLine(float thickness, Color color, float x1, float y1, float x2, float y2) {
batch.end(); //must pause batch while rendering shapes
if (thickness > 1) {
Gdx.gl.glLineWidth(thickness);
}
if (alphaComposite < 1) {
color = FSkinColor.alphaColor(color, color.a * alphaComposite);
}
boolean needSmoothing = (x1 != x2 && y1 != y2);
if (color.a < 1 || needSmoothing) { //enable blending so alpha colored shapes work properly
Gdx.gl.glEnable(GL_BLEND);
}
if (needSmoothing) {
Gdx.gl.glEnable(GL_LINE_SMOOTH);
}
startShape(ShapeType.Line);
shapeRenderer.setColor(color);
shapeRenderer.line(adjustX(x1), adjustY(y1, 0), adjustX(x2), adjustY(y2, 0));
endShape();
if (needSmoothing) {
Gdx.gl.glDisable(GL_LINE_SMOOTH);
}
if (color.a < 1 || needSmoothing) {
Gdx.gl.glDisable(GL_BLEND);
}
if (thickness > 1) {
Gdx.gl.glLineWidth(1);
}
batch.begin();
}
public void drawArrow(float borderThickness, float arrowThickness, float arrowSize, FSkinColor skinColor, float x1, float y1, float x2, float y2) {
drawArrow(borderThickness, arrowThickness, arrowSize, skinColor.getColor(), x1, y1, x2, y2);
}
public void drawArrow(float borderThickness, float arrowThickness, float arrowSize, Color color, float x1, float y1, float x2, float y2) {
batch.end(); //must pause batch while rendering shapes
if (alphaComposite < 1) {
color = FSkinColor.alphaColor(color, color.a * alphaComposite);
}
Gdx.gl.glEnable(GL_BLEND);
Gdx.gl.glEnable(GL_LINE_SMOOTH);
float angle = new Vector2(x2 - x1, y2 - y1).angleRad();
float perpRotation = (float)(Math.PI * 0.5f);
float arrowHeadRotation = (float)(Math.PI * 0.8f);
float arrowTipAngle = (float)(Math.PI - arrowHeadRotation);
float halfThickness = arrowThickness / 2;
int index = 0;
float[] vertices = new float[14];
Vector2 arrowCorner1 = new Vector2(x2 + arrowSize * (float)Math.cos(angle + arrowHeadRotation), y2 + arrowSize * (float)Math.sin(angle + arrowHeadRotation));
Vector2 arrowCorner2 = new Vector2(x2 + arrowSize * (float)Math.cos(angle - arrowHeadRotation), y2 + arrowSize * (float)Math.sin(angle - arrowHeadRotation));
float arrowCornerLen = (arrowCorner1.dst(arrowCorner2) - arrowThickness) / 2;
float arrowHeadLen = arrowSize * (float)Math.cos(arrowTipAngle);
index = addVertex(arrowCorner1.x, arrowCorner1.y, vertices, index);
index = addVertex(x2, y2, vertices, index);
index = addVertex(arrowCorner2.x, arrowCorner2.y, vertices, index);
index = addVertex(arrowCorner2.x + arrowCornerLen * (float)Math.cos(angle + perpRotation), arrowCorner2.y + arrowCornerLen * (float)Math.sin(angle + perpRotation), vertices, index);
index = addVertex(x1 + halfThickness * (float)Math.cos(angle - perpRotation), y1 + halfThickness * (float)Math.sin(angle - perpRotation), vertices, index);
index = addVertex(x1 + halfThickness * (float)Math.cos(angle + perpRotation), y1 + halfThickness * (float)Math.sin(angle + perpRotation), vertices, index);
index = addVertex(arrowCorner1.x + arrowCornerLen * (float)Math.cos(angle - perpRotation), arrowCorner1.y + arrowCornerLen * (float)Math.sin(angle - perpRotation), vertices, index);
//draw arrow tail
startShape(ShapeType.Filled);
shapeRenderer.setColor(color);
shapeRenderer.rectLine(adjustX(x1), adjustY(y1, 0),
adjustX(x2 - arrowHeadLen * (float)Math.cos(angle)), //shorten tail to make room for arrow head
adjustY(y2 - arrowHeadLen * (float)Math.sin(angle), 0), arrowThickness);
//draw arrow head
shapeRenderer.triangle(vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5]);
endShape();
//draw border around arrow
if (borderThickness > 1) {
Gdx.gl.glLineWidth(borderThickness);
}
startShape(ShapeType.Line);
shapeRenderer.setColor(Color.BLACK);
shapeRenderer.polygon(vertices);
endShape();
if (borderThickness > 1) {
Gdx.gl.glLineWidth(1);
}
Gdx.gl.glDisable(GL_LINE_SMOOTH);
Gdx.gl.glDisable(GL_BLEND);
batch.begin();
}
private int addVertex(float x, float y, float[] vertices, int index) {
vertices[index] = adjustX(x);
vertices[index + 1] = adjustY(y, 0);
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);
}
public void drawRoundRect(float thickness, Color color, float x, float y, float w, float h, float cornerRadius) {
batch.end(); //must pause batch while rendering shapes
if (thickness > 1) {
Gdx.gl.glLineWidth(thickness);
}
if (alphaComposite < 1) {
color = FSkinColor.alphaColor(color, color.a * alphaComposite);
}
if (color.a < 1 || cornerRadius > 0) { //enable blending so alpha colored shapes work properly
Gdx.gl.glEnable(GL_BLEND);
}
if (cornerRadius > 0) {
Gdx.gl.glEnable(GL_LINE_SMOOTH);
}
//adjust width/height so rectangle covers equivalent filled area
w = Math.round(w + 1);
h = Math.round(h + 1);
startShape(ShapeType.Line);
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 (cornerRadius > 0) {
Gdx.gl.glDisable(GL_LINE_SMOOTH);
}
if (color.a < 1 || cornerRadius > 0) {
Gdx.gl.glDisable(GL_BLEND);
}
if (thickness > 1) {
Gdx.gl.glLineWidth(1);
}
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);
}
public void drawRect(float thickness, Color color, float x, float y, float w, float h) {
batch.end(); //must pause batch while rendering shapes
if (thickness > 1) {
Gdx.gl.glLineWidth(thickness);
}
if (alphaComposite < 1) {
color = FSkinColor.alphaColor(color, color.a * alphaComposite);
}
Gdx.gl.glEnable(GL_BLEND);
Gdx.gl.glEnable(GL_LINE_SMOOTH); //must be smooth to ensure edges aren't missed
startShape(ShapeType.Line);
shapeRenderer.setColor(color);
shapeRenderer.rect(adjustX(x), adjustY(y, h), w, h);
endShape();
Gdx.gl.glDisable(GL_LINE_SMOOTH);
Gdx.gl.glDisable(GL_BLEND);
if (thickness > 1) {
Gdx.gl.glLineWidth(1);
}
batch.begin();
}
public void fillRect(FSkinColor skinColor, float x, float y, float w, float h) {
fillRect(skinColor.getColor(), x, y, w, h);
}
public void fillRect(Color color, float x, float y, float w, float h) {
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.rect(adjustX(x), adjustY(y, h), w, h);
endShape();
if (color.a < 1) {
Gdx.gl.glDisable(GL_BLEND);
}
batch.begin();
}
public void drawCircle(float thickness, FSkinColor skinColor, float x, float y, float radius) {
drawCircle(thickness, skinColor.getColor(), x, y, radius);
}
public void drawCircle(float thickness, Color color, float x, float y, float radius) {
batch.end(); //must pause batch while rendering shapes
if (thickness > 1) {
Gdx.gl.glLineWidth(thickness);
}
if (alphaComposite < 1) {
color = FSkinColor.alphaColor(color, color.a * alphaComposite);
}
Gdx.gl.glEnable(GL_BLEND);
Gdx.gl.glEnable(GL_LINE_SMOOTH);
startShape(ShapeType.Line);
shapeRenderer.setColor(color);
shapeRenderer.circle(adjustX(x), adjustY(y, 0), radius);
endShape();
Gdx.gl.glDisable(GL_LINE_SMOOTH);
Gdx.gl.glDisable(GL_BLEND);
if (thickness > 1) {
Gdx.gl.glLineWidth(1);
}
batch.begin();
}
public void fillCircle(FSkinColor skinColor, float x, float y, float radius) {
fillCircle(skinColor.getColor(), x, y, radius);
}
public void fillCircle(Color color, float x, float y, float radius) {
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.circle(adjustX(x), adjustY(y, 0), radius); //TODO: Make smoother
endShape();
if (color.a < 1) {
Gdx.gl.glDisable(GL_BLEND);
}
batch.begin();
}
public void fillTriangle(FSkinColor skinColor, float x1, float y1, float x2, float y2, float x3, float y3) {
fillTriangle(skinColor.getColor(), x1, y1, x2, y2, x3, y3);
}
public void fillTriangle(Color color, float x1, float y1, float x2, float y2, float x3, float y3) {
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.triangle(adjustX(x1), adjustY(y1, 0), adjustX(x2), adjustY(y2, 0), adjustX(x3), adjustY(y3, 0));
endShape();
if (color.a < 1) {
Gdx.gl.glDisable(GL_BLEND);
}
batch.begin();
}
public void fillGradientRect(FSkinColor skinColor1, FSkinColor skinColor2, boolean vertical, float x, float y, float w, float h) {
fillGradientRect(skinColor1.getColor(), skinColor2.getColor(), vertical, x, y, w, h);
}
public void fillGradientRect(FSkinColor skinColor1, Color color2, boolean vertical, float x, float y, float w, float h) {
fillGradientRect(skinColor1.getColor(), color2, vertical, x, y, w, h);
}
public void fillGradientRect(Color color1, FSkinColor skinColor2, boolean vertical, float x, float y, float w, float h) {
fillGradientRect(color1, skinColor2.getColor(), vertical, x, y, w, h);
}
public void fillGradientRect(Color color1, Color color2, boolean vertical, float x, float y, float w, float h) {
batch.end(); //must pause batch while rendering shapes
if (alphaComposite < 1) {
color1 = FSkinColor.alphaColor(color1, color1.a * alphaComposite);
color2 = FSkinColor.alphaColor(color2, color2.a * alphaComposite);
}
boolean needBlending = (color1.a < 1 || color2.a < 1);
if (needBlending) { //enable blending so alpha colored shapes work properly
Gdx.gl.glEnable(GL_BLEND);
}
Color topLeftColor = color1;
Color topRightColor = vertical ? color1 : color2;
Color bottomLeftColor = vertical ? color2 : color1;
Color bottomRightColor = color2;
startShape(ShapeType.Filled);
shapeRenderer.rect(adjustX(x), adjustY(y, h), w, h, bottomLeftColor, bottomRightColor, topRightColor, topLeftColor);
endShape();
if (needBlending) {
Gdx.gl.glDisable(GL_BLEND);
}
batch.begin();
}
private void startShape(ShapeType shapeType) {
if (!Dtransforms.isEmpty()) {
//must copy matrix before starting shape if transformed
shapeRenderer.setTransformMatrix(batch.getTransformMatrix());
}
shapeRenderer.begin(shapeType);
}
private void endShape() {
shapeRenderer.end();
}
public void setAlphaComposite(float alphaComposite0) {
alphaComposite = alphaComposite0;
batch.setColor(new Color(1, 1, 1, alphaComposite));
}
public void resetAlphaComposite() {
alphaComposite = 1;
batch.setColor(Color.WHITE);
}
public float getfloatAlphaComposite() { return alphaComposite; }
public void drawBorderImage(FImage image, Color borderColor, Color tintColor, float x, float y, float w, float h, boolean tint) {
float oldalpha = alphaComposite;
if(tint && !tintColor.equals(borderColor)){
drawRoundRect(2f, borderLining(borderColor.toString()), x, y, w, h, (h-w)/12);
fillRoundRect(tintColor, x, y, w, h, (h-w)/12);
} else {
image.draw(this, x, y, w, h);
fillRoundRect(borderColor, x, y, w, h, (h-w)/10);//show corners edges
}
setAlphaComposite(oldalpha);
}
public void drawborderImage(Color borderColor, float x, float y, float w, float h) {
float oldalpha = alphaComposite;
fillRoundRect(borderColor, x, y, w, h, (h-w)/12);
setAlphaComposite(oldalpha);
}
public void drawImage(FImage image, Color borderColor, float x, float y, float w, float h) {
image.draw(this, x, y, w, h);
fillRoundRect(borderColor, x+1, y+1, w-1.5f, h-1.5f, (h-w)/10);//used by zoom let some edges show...
}
public void drawImage(FImage image, float x, float y, float w, float h) {
drawImage(image, x, y, w, h, false);
}
public void drawImage(FImage image, float x, float y, float w, float h, boolean withDarkOverlay) {
image.draw(this, x, y, w, h);
if(withDarkOverlay){
float oldalpha = alphaComposite;
setAlphaComposite(0.4f);
fillRect(Color.BLACK, x, y, w, h);
setAlphaComposite(oldalpha);
}
}
public void drawImage(Texture image, float x, float y, float w, float h) {
batch.draw(image, adjustX(x), adjustY(y, h), w, h);
}
public void drawImage(TextureRegion image, float x, float y, float w, float h) {
if (image != null)
batch.draw(image, adjustX(x), adjustY(y, h), w, h);
}
public void drawImage(TextureRegion image, TextureRegion glowImageReference, float x, float y, float w, float h, Color glowColor, boolean selected) {
if (image == null || glowImageReference == null)
return;
//1st image is the image on top of the shader, 2nd image is for the outline reference for the shader glow...
// if the 1st image don't have transparency in the middle (only on the sides, top and bottom, use the 1st image as outline reference...
if (!selected) {
batch.draw(image, adjustX(x), adjustY(y, h), w, h);
} else {
batch.end();
shaderOutline.bind();
shaderOutline.setUniformf("u_viewportInverse", new Vector2(1f / w, 1f / h));
shaderOutline.setUniformf("u_offset", 3f);
shaderOutline.setUniformf("u_step", Math.min(1f, w / 70f));
shaderOutline.setUniformf("u_color", new Vector3(glowColor.r, glowColor.g, glowColor.b));
batch.setShader(shaderOutline);
batch.begin();
//glow
batch.draw(glowImageReference, adjustX(x), adjustY(y, h), w, h);
batch.end();
batch.setShader(null);
batch.begin();
//img
batch.draw(image, adjustX(x), adjustY(y, h), w, h);
}
}
public void drawDeckBox(FImage cardArt, float scale, TextureRegion image, TextureRegion glowImageReference, float x, float y, float w, float h, Color glowColor, boolean selected) {
if (image == null || glowImageReference == null)
return;
float yBox = y-(h*0.25f);
if (!selected) {
cardArt.draw(this,x+((w-w*scale)/2), y+((h-h*scale)/3f), w*scale, h*scale/1.85f);
batch.draw(image, adjustX(x), adjustY(yBox, h), w, h);
} else {
batch.end();
shaderOutline.bind();
shaderOutline.setUniformf("u_viewportInverse", new Vector2(1f / w, 1f / h));
shaderOutline.setUniformf("u_offset", 3f);
shaderOutline.setUniformf("u_step", Math.min(1f, w / 70f));
shaderOutline.setUniformf("u_color", new Vector3(glowColor.r, glowColor.g, glowColor.b));
batch.setShader(shaderOutline);
batch.begin();
//glow
batch.draw(glowImageReference, adjustX(x), adjustY(yBox, h), w, h);
batch.end();
batch.setShader(null);
batch.begin();
//cardart
cardArt.draw(this,x+((w-w*scale)/2), y+((h-h*scale)/3f), w*scale, h*scale/1.85f);
//deckbox
batch.draw(image, adjustX(x), adjustY(yBox, h), w, h);
}
}
public void drawRepeatingImage(Texture image, float x, float y, float w, float h) {
if (startClip(x, y, w, h)) { //only render if clip successful, otherwise it will escape bounds
int tilesW = (int)(w / image.getWidth()) + 1;
int tilesH = (int)(h / image.getHeight()) + 1;
batch.draw(image, adjustX(x), adjustY(y, h),
image.getWidth() * tilesW,
image.getHeight() * tilesH,
0, tilesH, tilesW, 0);
}
endClip();
}
//draw vertically flipped image
public void drawFlippedImage(Texture image, float x, float y, float w, float h) {
batch.draw(image, adjustX(x), adjustY(y, h), w, h, 0, 0, image.getWidth(), image.getHeight(), false, true);
}
public void drawImageWithTransforms(TextureRegion image, float x, float y, float w, float h, float rotation, boolean flipX, boolean flipY) {
float originX = x + w / 2;
float originY = y + h / 2;
batch.draw(image.getTexture(), adjustX(x), adjustY(y, h), originX - x, h - (originY - y), w, h, 1, 1, rotation, image.getRegionX(), image.getRegionY(), image.getRegionWidth(), image.getRegionHeight(), flipX, flipY);
}
public void setProjectionMatrix(Matrix4 matrix) {
batch.setProjectionMatrix(matrix);
shapeRenderer.setProjectionMatrix(matrix);
}
public void startRotateTransform(float originX, float originY, float rotation) {
batch.end();
Dtransforms.addFirst(new Matrix4(batch.getTransformMatrix().idt())); //startshape is using this above as reference
transformCount++;
batch.getTransformMatrix().idt().translate(adjustX(originX), adjustY(originY, 0), 0).rotate(Vector3.Z, rotation).translate(-adjustX(originX), -adjustY(originY, 0), 0);
batch.begin();
}
public void endTransform() {
batch.end();
shapeRenderer.setTransformMatrix(batch.getTransformMatrix().idt());
Dtransforms.removeFirst();
transformCount--;
if(transformCount != Dtransforms.size()) {
System.err.println(String.format("Stack count: %d, transformCount: %d", Dtransforms.size(), transformCount));
transformCount = 0;
Dtransforms.clear();
}
batch.getTransformMatrix().idt(); //reset
shapeRenderer.getTransformMatrix().idt(); //reset
batch.begin();
}
public void drawRotatedImage(Texture image, float x, float y, float w, float h, float originX, float originY, float rotation) {
drawRotatedImage(image, x, y, w, h, originX, originY, 0, 0, image.getWidth(), image.getHeight(), rotation);
}
public void drawRotatedImage(TextureRegion image, float x, float y, float w, float h, float originX, float originY, float rotation) {
drawRotatedImage(image.getTexture(), x, y, w, h, originX, originY, image.getRegionX(), image.getRegionY(), image.getRegionWidth(), image.getRegionHeight(), rotation);
}
public void drawRotatedImage(Texture image, float x, float y, float w, float h, float originX, float originY, int srcX, int srcY, int srcWidth, int srcHeight, float rotation) {
batch.draw(image, adjustX(x), adjustY(y, h), originX - x, h - (originY - y), w, h, 1, 1, rotation, srcX, srcY, srcWidth, srcHeight, false, false);
}
public void drawText(String text, FSkinFont font, FSkinColor skinColor, float x, float y, float w, float h, boolean wrap, int horzAlignment, boolean centerVertically) {
drawText(text, font, skinColor.getColor(), x, y, w, h, wrap, horzAlignment, centerVertically);
}
public void drawText(String text, FSkinFont font, Color color, float x, float y, float w, float h, boolean wrap, int horzAlignment, boolean centerVertically) {
if (text == null)
return;
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);
}
TextBounds textBounds;
if (wrap) {
textBounds = font.getWrappedBounds(text, w);
}
else {
textBounds = font.getMultiLineBounds(text);
}
boolean needClip = false;
while (textBounds.width > w || textBounds.height > h) {
if (font.canShrink()) { //shrink font to fit if possible
font = font.shrink();
if (wrap) {
textBounds = font.getWrappedBounds(text, w);
}
else {
textBounds = font.getMultiLineBounds(text);
}
}
else {
needClip = true;
break;
}
}
if (needClip) { //prevent text flowing outside region if couldn't shrink it to fit
startClip(x, y, w, h);
}
float textHeight = textBounds.height;
if (h > textHeight && centerVertically) {
y += (h - textHeight) / 2;
}
font.draw(batch, text, color, adjustX(x), adjustY(y, 0), w, wrap, horzAlignment);
if (needClip) {
endClip();
}
if (color.a < 1) {
Gdx.gl.glDisable(GL_BLEND);
}
}
//use nifty trick with multiple text renders to draw outlined text
public void drawOutlinedText(String text, FSkinFont skinFont, Color textColor, Color outlineColor, float x, float y, float w, float h, boolean wrap, int horzAlignment, boolean centerVertically) {
drawText(text, skinFont, outlineColor, x - 1, y, w, h, wrap, horzAlignment, centerVertically);
drawText(text, skinFont, outlineColor, x, y - 1, w, h, wrap, horzAlignment, centerVertically);
drawText(text, skinFont, outlineColor, x - 1, y - 1, w, h, wrap, horzAlignment, centerVertically);
drawText(text, skinFont, outlineColor, x + 1, y, w, h, wrap, horzAlignment, centerVertically);
drawText(text, skinFont, outlineColor, x, y + 1, w, h, wrap, horzAlignment, centerVertically);
drawText(text, skinFont, outlineColor, x + 1, y + 1, w, h, wrap, horzAlignment, centerVertically);
drawText(text, skinFont, textColor, x, y, w, h, wrap, horzAlignment, centerVertically);
}
public float adjustX(float x) {
return x + bounds.x;
}
public float adjustY(float y, float height) {
return regionHeight - y - bounds.y - height; //flip y-axis
}
public Color borderLining(String c){
if (c == null || c == "")
return Color.valueOf("#fffffd");
int c_r = Integer.parseInt(c.substring(0,2),16);
int c_g = Integer.parseInt(c.substring(2,4),16);
int c_b = Integer.parseInt(c.substring(4,6),16);
int brightness = ((c_r * 299) + (c_g * 587) + (c_b * 114)) / 1000;
return brightness > 155 ? Color.valueOf("#171717") : Color.valueOf("#fffffd");
}
}

View File

@@ -0,0 +1,329 @@
package forge.adventure.libgdxgui;
import com.badlogic.gdx.Application.ApplicationType;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.google.common.base.Function;
import forge.adventure.libgdxgui.assets.*;
import forge.adventure.libgdxgui.card.CardRenderer;
import forge.adventure.libgdxgui.deck.FDeckViewer;
import forge.adventure.libgdxgui.error.BugReportDialog;
import forge.adventure.libgdxgui.screens.LoadingOverlay;
import forge.adventure.libgdxgui.screens.match.MatchController;
import forge.adventure.libgdxgui.screens.settings.GuiDownloader;
import forge.adventure.libgdxgui.sound.AudioClip;
import forge.adventure.libgdxgui.sound.AudioMusic;
import forge.adventure.libgdxgui.toolbox.FOptionPane;
import forge.adventure.libgdxgui.toolbox.GuiChoose;
import forge.adventure.libgdxgui.util.LibGDXImageFetcher;
import forge.deck.Deck;
import forge.gamemodes.match.HostedMatch;
import forge.gui.download.GuiDownloadService;
import forge.gui.interfaces.IGuiBase;
import forge.gui.interfaces.IGuiGame;
import forge.item.PaperCard;
import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.skin.FSkinProp;
import forge.localinstance.skin.ISkinImage;
import forge.sound.IAudioClip;
import forge.sound.IAudioMusic;
import forge.util.*;
import java.io.File;
import java.util.Collection;
import java.util.List;
public class GuiMobile implements IGuiBase {
private final String assetsDir;
private final ImageFetcher imageFetcher = new LibGDXImageFetcher();
public GuiMobile(final String assetsDir0) {
assetsDir = assetsDir0;
}
@Override
public boolean isRunningOnDesktop() {
if(Gdx.app==null)
return true;
return Gdx.app.getType() == ApplicationType.Desktop;
}
@Override
public boolean isLibgdxPort() {
return true;
}
@Override
public String getCurrentVersion() {
return Forge.CURRENT_VERSION;
}
@Override
public String getAssetsDir() {
return assetsDir;
}
@Override
public ImageFetcher getImageFetcher() {
return imageFetcher;
}
@Override
public void invokeInEdtNow(final Runnable proc) {
proc.run();
Gdx.graphics.requestRendering(); //must request rendering in case this procedure wasn't triggered by a local event
}
@Override
public void invokeInEdtLater(final Runnable proc) {
Gdx.app.postRunnable(proc);
}
@Override
public void invokeInEdtAndWait(final Runnable proc) {
if (isGuiThread()) {
proc.run();
}
else {
new WaitRunnable() {
@Override
public void run() {
proc.run();
}
}.invokeAndWait();
}
}
@Override
public boolean isGuiThread() {
return !ThreadUtil.isGameThread();
}
@Override
public ISkinImage getSkinIcon(final FSkinProp skinProp) {
if (skinProp == null) { return null; }
return FSkin.getImages().get(skinProp);
}
@Override
public ISkinImage getUnskinnedIcon(final String path) {
if (isGuiThread()) {
return new FTextureImage(new Texture(Gdx.files.absolute(path)));
}
//use a delay load image to avoid an error if called from background thread
return new FDelayLoadImage(path);
}
@Override
public ISkinImage getCardArt(final PaperCard card) {
return CardRenderer.getCardArt(card);
}
@Override
public ISkinImage getCardArt(final PaperCard card, final boolean backFace) {
return CardRenderer.getCardArt(card, backFace);
}
@Override
public ISkinImage createLayeredImage(final FSkinProp background, final String overlayFilename, final float opacity) {
return new FBufferedImage(background.getWidth(), background.getHeight(), opacity) {
@Override
protected void draw(final Graphics g, final float w, final float h) {
g.drawImage(FSkin.getImages().get(background), 0, 0, background.getWidth(), background.getHeight());
if (FileUtil.doesFileExist(overlayFilename)) {
try {
final Texture overlay = new Texture(Gdx.files.absolute(overlayFilename));
g.drawImage(overlay, (background.getWidth() - overlay.getWidth()) / 2, (background.getHeight() - overlay.getHeight()) / 2, overlay.getWidth(), overlay.getHeight());
} catch (final Exception e) {
}
}
Gdx.graphics.requestRendering(); //ensure image appears right away
}
};
}
@Override
public void showImageDialog(final ISkinImage image, final String message, final String title) {
new WaitCallback<Integer>() {
@Override
public void run() {
FOptionPane.showMessageDialog(message, title, (FImage)image, this);
}
}.invokeAndWait();
}
@Override
public int showOptionDialog(final String message, final String title, final FSkinProp icon, final List<String> options, final int defaultOption) {
return new WaitCallback<Integer>() {
@Override
public void run() {
FOptionPane.showOptionDialog(message, title, icon == null ? null : FSkin.getImages().get(icon), options, defaultOption, this);
}
}.invokeAndWait();
}
@Override
public String showInputDialog(final String message, final String title, final FSkinProp icon, final String initialInput, final List<String> inputOptions) {
return new WaitCallback<String>() {
@Override
public void run() {
FOptionPane.showInputDialog(message, title, initialInput, inputOptions, this);
}
}.invokeAndWait();
}
@Override
public <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) {
return new WaitCallback<List<T>>() {
@Override
public void run() {
GuiChoose.getChoices(message, min, max, choices, selected, display, this);
}
}.invokeAndWait();
}
@Override
public <T> List<T> order(final String title, final String top, final int remainingObjectsMin, final int remainingObjectsMax,
final List<T> sourceChoices, final List<T> destChoices) {
return new WaitCallback<List<T>>() {
@Override
public void run() {
GuiChoose.order(title, top, remainingObjectsMin, remainingObjectsMax, sourceChoices, destChoices, null, this);
}
}.invokeAndWait();
}
@Override
public void showBugReportDialog(final String title, final String text, final boolean showExitAppBtn) {
BugReportDialog.show(title, text, showExitAppBtn);
}
@Override
public void showCardList(final String title, final String message, final List<PaperCard> list) {
final Deck deck = new Deck(title + " - " + message);
deck.getMain().addAllFlat(list);
FDeckViewer.show(deck, true);
}
@Override
public boolean showBoxedProduct(final String title, final String message, final List<PaperCard> list) {
final Deck deck = new Deck(title + " - " + message); //TODO: Make this nicer
deck.getMain().addAllFlat(list);
FDeckViewer.show(deck);
return false;
}
@Override
public PaperCard chooseCard(final String title, final String message, final List<PaperCard> list) {
return new WaitCallback<PaperCard>() {
@Override
public void run() {
GuiChoose.one(title + " - " + message, list, this);
}
}.invokeAndWait();
}
@Override
public int getAvatarCount() {
if (FSkin.isLoaded()) {
return FSkin.getAvatars().size();
}
return 0;
}
@Override
public int getSleevesCount() {
if (FSkin.isLoaded()) {
return FSkin.getSleeves().size();
}
return 0;
}
@Override
public String showFileDialog(final String title, final String defaultDir) {
return ForgeConstants.USER_GAMES_DIR + "Test.fgs"; //TODO: Show dialog
}
@Override
public File getSaveFile(final File defaultFile) {
return defaultFile; //TODO: Show dialog
}
@Override
public void download(final GuiDownloadService service, final Callback<Boolean> callback) {
new GuiDownloader(service, callback).show();
}
@Override
public void refreshSkin() {
//todo refresh skin selector
}
@Override
public void copyToClipboard(final String text) {
Forge.getClipboard().setContents(text);
}
@Override
public void browseToUrl(final String url) {
Gdx.net.openURI(url);
}
@Override
public IAudioClip createAudioClip(final String filename) {
return AudioClip.createClip(ForgeConstants.SOUND_DIR + filename);
}
@Override
public IAudioMusic createAudioMusic(final String filename) {
return new AudioMusic(filename);
}
@Override
public void startAltSoundSystem(final String filename, final boolean isSynchronized) {
//TODO: Support alt sound system
}
@Override
public void clearImageCache() {
ImageCache.clear();
}
@Override
public void showSpellShop() {
}
@Override
public void showBazaar() {
}
@Override
public IGuiGame getNewGuiGame() {
return MatchController.instance;
}
@Override
public HostedMatch hostMatch() {
return MatchController.hostMatch();
}
@Override
public void runBackgroundTask(String message, Runnable task) {
LoadingOverlay.runBackgroundTask(message, task);
}
@Override
public String encodeSymbols(String str, boolean formatReminderText) {
return str; //not needed for mobile
}
@Override
public void preventSystemSleep(boolean preventSleep) {
Forge.getDeviceAdapter().preventSystemSleep(preventSleep);
}
}

View File

@@ -0,0 +1,37 @@
package forge.adventure.libgdxgui.animation;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.libgdxgui.sound.AudioClip;
import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.properties.ForgePreferences;
import forge.model.FModel;
public enum AbilityEffect {
LIGHTNING("lightning.gif", "lightning.wav");
private final String gif, wav;
private forge.adventure.libgdxgui.animation.GifAnimation animation;
private AudioClip soundClip;
AbilityEffect(String gif0, String wav0) {
gif = gif0;
wav = wav0;
}
public void start() {
if (animation == null) {
animation = new GifAnimation(ForgeConstants.EFFECTS_DIR + gif);
}
if (soundClip == null) {
soundClip = AudioClip.createClip(ForgeConstants.EFFECTS_DIR + wav);
}
soundClip.play(FModel.getPreferences().getPrefInt(ForgePreferences.FPref.UI_VOL_SOUNDS)/100f);
animation.start();
}
public void draw(Graphics g, float x, float y, float w, float h) {
if (animation != null) {
animation.draw(g, x, y, w, h);
}
}
}

View File

@@ -0,0 +1,69 @@
package forge.adventure.libgdxgui.animation;
import com.badlogic.gdx.Gdx;
import forge.adventure.libgdxgui.Forge;
import java.util.ArrayList;
import java.util.List;
public abstract class ForgeAnimation {
private static final List<ForgeAnimation> activeAnimations = new ArrayList<>();
// A guard against inspecting activeAnimations while it's in the process of being edited
private static boolean changingActiveAnimations = false;
public void start() {
if (activeAnimations.contains(this)) { return; } //prevent starting the same animation multiple times
activeAnimations.add(this);
if (activeAnimations.size() == 1 && !changingActiveAnimations) { //if first animation being started, ensure continuous rendering turned on
Forge.startContinuousRendering();
}
}
public void stop() {
if (!activeAnimations.contains(this)) { return; } //prevent stopping the same animation multiple times
activeAnimations.remove(this);
onEnd(false);
if (activeAnimations.isEmpty()) { //when all animations have stopped, turn continuous rendering back off
Forge.stopContinuousRendering();
}
}
public static void advanceAll() {
if (activeAnimations.isEmpty()) { return; }
float dt = Gdx.graphics.getDeltaTime();
for (int i = 0; i < activeAnimations.size(); i++) {
if (!activeAnimations.get(i).advance(dt)) {
// Without this guard, there is leaky behavior when a new animation is started
// via the onEnd callback of a finishing animation; this is because the length
// of the list is in the process of changing from 1 to 0 to 1 again, so
// stopContinuousRendering() won't be called in this function (so it's
// important to not allow startContinuousRendering() to be called either).
changingActiveAnimations = true;
activeAnimations.remove(i).onEnd(false);
changingActiveAnimations = false;
i--;
}
}
if (activeAnimations.isEmpty()) { //when all animations have ended, turn continuous rendering back off
Forge.stopContinuousRendering();
}
}
public static void endAll() {
if (activeAnimations.isEmpty()) { return; }
for (ForgeAnimation animation : activeAnimations) {
animation.onEnd(true);
}
activeAnimations.clear();
Forge.stopContinuousRendering();
}
//return true if animation should continue, false to stop the animation
protected abstract boolean advance(float dt);
protected abstract void onEnd(boolean endingAll);
}

View File

@@ -0,0 +1,101 @@
package forge.adventure.libgdxgui.animation;
import com.badlogic.gdx.math.Rectangle;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.libgdxgui.toolbox.FDisplayObject;
import forge.adventure.libgdxgui.toolbox.FOverlay;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
public class ForgeTransition extends ForgeAnimation {
private static final FOverlay overlay = new FOverlay(null) {
@Override protected void doLayout(final float width, final float height) {
}
};
private static final Map<FDisplayObject, TransitionObject> transitionLookup = new LinkedHashMap<>();
public static void queue(final FDisplayObject obj, final Rectangle destBounds, final float duration, final Runnable onFinished) {
queue(obj, destBounds, duration, 0, false, onFinished);
}
public static void queue(final FDisplayObject obj, final Rectangle destBounds, final float duration, final float arcAmount, final boolean arcOriginBelow, final Runnable onFinished) {
TransitionObject transitionObj = transitionLookup.get(obj);
if (transitionObj == null) {
transitionObj = new TransitionObject(obj);
transitionLookup.put(obj, transitionObj);
overlay.add(transitionObj);
obj.setVisible(false); //hide original object while transition in progress
}
final ForgeTransition transition = new ForgeTransition(transitionObj, destBounds, duration, arcAmount, arcOriginBelow, onFinished);
transitionObj.transitions.add(transition);
if (transitionObj.transitions.size() == 1) {
transition.start(); //start transition right away if first transition added
overlay.setVisible(true);
}
}
private final TransitionObject obj;
/*private final Rectangle destBounds;
private final float duration;
private final float arcAmount;
private final boolean arcOriginBelow;*/
private final Runnable onFinished;
private ForgeTransition(final TransitionObject obj0, final Rectangle destBounds0, final float duration0, final float arcAmount0, final boolean arcOriginBelow0, final Runnable onFinished0) {
obj = obj0;
/*destBounds = destBounds0;
duration = duration0;
arcAmount = arcAmount0;
arcOriginBelow = arcOriginBelow0;*/
onFinished = onFinished0;
}
@Override
protected boolean advance(final float dt) {
return false;
}
@Override
protected void onEnd(final boolean endingAll) {
if (onFinished != null) {
onFinished.run();
}
if (endingAll) {
transitionLookup.clear();
return;
}
final int index = obj.transitions.indexOf(this);
obj.transitions.remove(index);
if (index == 0) {
if (obj.transitions.isEmpty()) {
transitionLookup.remove(obj.originalObj);
overlay.remove(obj);
obj.originalObj.setVisible(true);
if (transitionLookup.isEmpty()) {
overlay.setVisible(false);
}
}
else {
obj.transitions.getFirst().start(); //start next transition if needed
}
}
}
private static class TransitionObject extends FDisplayObject {
private final FDisplayObject originalObj;
private final LinkedList<ForgeTransition> transitions = new LinkedList<>();
private TransitionObject(final FDisplayObject originalObj0) {
originalObj = originalObj0;
setBounds(originalObj.screenPos.x, originalObj.screenPos.y, originalObj.getWidth(), originalObj.getHeight());
}
@Override
public void draw(final Graphics g) {
originalObj.draw(g);
}
}
}

View File

@@ -0,0 +1,40 @@
package forge.adventure.libgdxgui.animation;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.Animation.PlayMode;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import forge.adventure.libgdxgui.Graphics;
public class GifAnimation extends ForgeAnimation {
private final Animation<TextureRegion> animation;
private TextureRegion currentFrame;
private float stateTime;
public GifAnimation(String filename) {
animation = GifDecoder.loadGIFAnimation(PlayMode.NORMAL, Gdx.files.absolute(filename).read());
}
@Override
public void start() {
currentFrame = animation.getKeyFrame(0);
super.start();
}
@Override
protected boolean advance(float dt) {
stateTime += dt;
currentFrame = animation.getKeyFrame(stateTime);
return currentFrame != null;
}
public void draw(Graphics g, float x, float y, float w, float h) {
if (currentFrame != null) {
g.drawImage(currentFrame, x, y, w, h);
}
}
@Override
protected void onEnd(boolean endingAll) {
}
}

View File

@@ -0,0 +1,737 @@
package forge.adventure.libgdxgui.animation;
/* Copyright by Johannes Borchardt */
/* LibGdx conversion 2014 by Anton Persson */
/* Released under Apache 2.0 */
/* https://code.google.com/p/animated-gifs-in-android/ */
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.Animation.PlayMode;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Array;
import java.io.InputStream;
import java.util.Vector;
public class GifDecoder {
/**
* File read status: No errors.
*/
public static final int STATUS_OK = 0;
/**
* File read status: Error decoding file (may be partially decoded)
*/
public static final int STATUS_FORMAT_ERROR = 1;
/**
* File read status: Unable to open source.
*/
public static final int STATUS_OPEN_ERROR = 2;
/** max decoder pixel stack size */
protected static final int MAX_STACK_SIZE = 4096;
protected InputStream in;
protected int status;
protected int width; // full image width
protected int height; // full image height
protected boolean gctFlag; // global color table used
protected int gctSize; // size of global color table
protected int loopCount = 1; // iterations; 0 = repeat forever
protected int[] gct; // global color table
protected int[] lct; // local color table
protected int[] act; // active color table
protected int bgIndex; // background color index
protected int bgColor; // background color
protected int lastBgColor; // previous bg color
protected int pixelAspect; // pixel aspect ratio
protected boolean lctFlag; // local color table flag
protected boolean interlace; // interlace flag
protected int lctSize; // local color table size
protected int ix, iy, iw, ih; // current image rectangle
protected int lrx, lry, lrw, lrh;
protected DixieMap image; // current frame
protected DixieMap lastPixmap; // previous frame
protected byte[] block = new byte[256]; // current data block
protected int blockSize = 0; // block size last graphic control extension info
protected int dispose = 0; // 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev
protected int lastDispose = 0;
protected boolean transparency = false; // use transparent color
protected int delay = 0; // delay in milliseconds
protected int transIndex; // transparent color index
// LZW decoder working arrays
protected short[] prefix;
protected byte[] suffix;
protected byte[] pixelStack;
protected byte[] pixels;
protected Vector<GifFrame> frames; // frames read from current file
protected int frameCount;
private static class DixieMap extends Pixmap {
DixieMap(int w, int h, Format f) {
super(w, h, f);
}
DixieMap(int[] data, int w, int h, Format f) {
super(w, h, f);
int x, y;
for(y = 0; y < h; y++) {
for(x = 0; x < w; x++) {
int pxl_ARGB8888 = data[x + y * w];
int pxl_RGBA8888 =
((pxl_ARGB8888 >> 24) & 0x000000ff) | ((pxl_ARGB8888 << 8) & 0xffffff00);
// convert ARGB8888 > RGBA8888
drawPixel(x, y, pxl_RGBA8888);
}
}
}
void getPixels(int[] pixels, int offset, int stride, int x, int y, int width, int height) {
java.nio.ByteBuffer bb = getPixels();
int k, l;
for(k = y; k < y + height; k++) {
int _offset = offset;
for(l = x; l < x + width; l++) {
int pxl = bb.getInt(4 * (l + k * width));
// convert RGBA8888 > ARGB8888
pixels[_offset++] = ((pxl >> 8) & 0x00ffffff) | ((pxl << 24) & 0xff000000);
}
offset += stride;
}
}
}
private static class GifFrame {
public GifFrame(DixieMap im, int del) {
image = im;
delay = del;
}
public DixieMap image;
public int delay;
}
/**
* Gets display duration for specified frame.
*
* @param n
* int index of frame
* @return delay in milliseconds
*/
public int getDelay(int n) {
delay = -1;
if ((n >= 0) && (n < frameCount)) {
delay = frames.elementAt(n).delay;
}
return delay;
}
/**
* Gets the number of frames read from file.
*
* @return frame count
*/
public int getFrameCount() {
return frameCount;
}
/**
* Gets the first (or only) image read.
*
* @return BufferedPixmap containing first frame, or null if none.
*/
public Pixmap getPixmap() {
return getFrame(0);
}
/**
* Gets the "Netscape" iteration count, if any. A count of 0 means repeat indefinitely.
*
* @return iteration count if one was specified, else 1.
*/
public int getLoopCount() {
return loopCount;
}
/**
* Creates new frame image from current data (and previous frames as specified by their disposition codes).
*/
protected void setPixels() {
// expose destination image's pixels as int array
int[] dest = new int[width * height];
// fill in starting image contents based on last image's dispose code
if (lastDispose > 0) {
if (lastDispose == 3) {
// use image before last
int n = frameCount - 2;
if (n > 0) {
lastPixmap = getFrame(n - 1);
} else {
lastPixmap = null;
}
}
if (lastPixmap != null) {
lastPixmap.getPixels(dest, 0, width, 0, 0, width, height);
// copy pixels
if (lastDispose == 2) {
// fill last image rect area with background color
int c = 0;
if (!transparency) {
c = lastBgColor;
}
for (int i = 0; i < lrh; i++) {
int n1 = (lry + i) * width + lrx;
int n2 = n1 + lrw;
for (int k = n1; k < n2; k++) {
dest[k] = c;
}
}
}
}
}
// copy each source line to the appropriate place in the destination
int pass = 1;
int inc = 8;
int iline = 0;
for (int i = 0; i < ih; i++) {
int line = i;
if (interlace) {
if (iline >= ih) {
pass++;
switch (pass) {
case 2:
iline = 4;
break;
case 3:
iline = 2;
inc = 4;
break;
case 4:
iline = 1;
inc = 2;
break;
default:
break;
}
}
line = iline;
iline += inc;
}
line += iy;
if (line < height) {
int k = line * width;
int dx = k + ix; // start of line in dest
int dlim = dx + iw; // end of dest line
if ((k + width) < dlim) {
dlim = k + width; // past dest edge
}
int sx = i * iw; // start of line in source
while (dx < dlim) {
// map color and insert in destination
int index = ((int) pixels[sx++]) & 0xff;
int c = act[index];
if (c != 0) {
dest[dx] = c;
}
dx++;
}
}
}
image = new DixieMap(dest, width, height, Pixmap.Format.RGBA8888);
//Pixmap.createPixmap(dest, width, height, Config.ARGB_4444);
}
/**
* Gets the image contents of frame n.
*
* @return BufferedPixmap representation of frame, or null if n is invalid.
*/
public DixieMap getFrame(int n) {
if (frameCount <= 0)
return null;
n = n % frameCount;
return frames.elementAt(n).image;
}
/**
* Reads GIF image from stream
*
* @param is
* containing GIF file.
* @return read status code (0 = no errors)
*/
public int read(InputStream is) {
init();
if (is != null) {
in = is;
readHeader();
if (!err()) {
readContents();
if (frameCount < 0) {
status = STATUS_FORMAT_ERROR;
}
}
} else {
status = STATUS_OPEN_ERROR;
}
try {
is.close();
} catch (Exception e) {
}
return status;
}
/**
* Decodes LZW image data into pixel array. Adapted from John Cristy's BitmapMagick.
*/
protected void decodeBitmapData() {
int nullCode = -1;
int npix = iw * ih;
int available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, data_size, first, top, bi, pi;
if ((pixels == null) || (pixels.length < npix)) {
pixels = new byte[npix]; // allocate new pixel array
}
if (prefix == null) {
prefix = new short[MAX_STACK_SIZE];
}
if (suffix == null) {
suffix = new byte[MAX_STACK_SIZE];
}
if (pixelStack == null) {
pixelStack = new byte[MAX_STACK_SIZE + 1];
}
// Initialize GIF data stream decoder.
data_size = read();
clear = 1 << data_size;
end_of_information = clear + 1;
available = clear + 2;
old_code = nullCode;
code_size = data_size + 1;
code_mask = (1 << code_size) - 1;
for (code = 0; code < clear; code++) {
prefix[code] = 0; // XXX ArrayIndexOutOfBoundsException
suffix[code] = (byte) code;
}
// Decode GIF pixel stream.
datum = bits = count = first = top = pi = bi = 0;
for (i = 0; i < npix;) {
if (top == 0) {
if (bits < code_size) {
// Load bytes until there are enough bits for a code.
if (count == 0) {
// Read a new data block.
count = readBlock();
if (count <= 0) {
break;
}
bi = 0;
}
datum += (((int) block[bi]) & 0xff) << bits;
bits += 8;
bi++;
count--;
continue;
}
// Get the next code.
code = datum & code_mask;
datum >>= code_size;
bits -= code_size;
// Interpret the code
if ((code > available) || (code == end_of_information)) {
break;
}
if (code == clear) {
// Reset decoder.
code_size = data_size + 1;
code_mask = (1 << code_size) - 1;
available = clear + 2;
old_code = nullCode;
continue;
}
if (old_code == nullCode) {
pixelStack[top++] = suffix[code];
old_code = code;
first = code;
continue;
}
in_code = code;
if (code == available) {
pixelStack[top++] = (byte) first;
code = old_code;
}
while (code > clear) {
pixelStack[top++] = suffix[code];
code = prefix[code];
}
first = ((int) suffix[code]) & 0xff;
// Add a new string to the string table,
if (available >= MAX_STACK_SIZE) {
break;
}
pixelStack[top++] = (byte) first;
prefix[available] = (short) old_code;
suffix[available] = (byte) first;
available++;
if (((available & code_mask) == 0) && (available < MAX_STACK_SIZE)) {
code_size++;
code_mask += available;
}
old_code = in_code;
}
// Pop a pixel off the pixel stack.
top--;
pixels[pi++] = pixelStack[top];
i++;
}
for (i = pi; i < npix; i++) {
pixels[i] = 0; // clear missing pixels
}
}
/**
* Returns true if an error was encountered during reading/decoding
*/
protected boolean err() {
return status != STATUS_OK;
}
/**
* Initializes or re-initializes reader
*/
protected void init() {
status = STATUS_OK;
frameCount = 0;
frames = new Vector<>();
gct = null;
lct = null;
}
/**
* Reads a single byte from the input stream.
*/
protected int read() {
int curByte = 0;
try {
curByte = in.read();
} catch (Exception e) {
status = STATUS_FORMAT_ERROR;
}
return curByte;
}
/**
* Reads next variable length block from input.
*
* @return number of bytes stored in "buffer"
*/
protected int readBlock() {
blockSize = read();
int n = 0;
if (blockSize > 0) {
try {
int count = 0;
while (n < blockSize) {
count = in.read(block, n, blockSize - n);
if (count == -1) {
break;
}
n += count;
}
} catch (Exception e) {
e.printStackTrace();
}
if (n < blockSize) {
status = STATUS_FORMAT_ERROR;
}
}
return n;
}
/**
* Reads color table as 256 RGB integer values
*
* @param ncolors
* int number of colors to read
* @return int array containing 256 colors (packed ARGB with full alpha)
*/
protected int[] readColorTable(int ncolors) {
int nbytes = 3 * ncolors;
int[] tab = null;
byte[] c = new byte[nbytes];
int n = 0;
try {
n = in.read(c);
} catch (Exception e) {
e.printStackTrace();
}
if (n < nbytes) {
status = STATUS_FORMAT_ERROR;
} else {
tab = new int[256]; // max size to avoid bounds checks
int i = 0;
int j = 0;
while (i < ncolors) {
int r = ((int) c[j++]) & 0xff;
int g = ((int) c[j++]) & 0xff;
int b = ((int) c[j++]) & 0xff;
tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
}
}
return tab;
}
/**
* Main file parser. Reads GIF content blocks.
*/
protected void readContents() {
// read GIF file content blocks
boolean done = false;
while (!(done || err())) {
int code = read();
switch (code) {
case 0x2C: // image separator
readBitmap();
break;
case 0x21: // extension
code = read();
switch (code) {
case 0xf9: // graphics control extension
readGraphicControlExt();
break;
case 0xff: // application extension
readBlock();
StringBuilder app = new StringBuilder();
for (int i = 0; i < 11; i++) {
app.append((char) block[i]);
}
if (app.toString().equals("NETSCAPE2.0")) {
readNetscapeExt();
} else {
skip(); // don't care
}
break;
case 0xfe:// comment extension
skip();
break;
case 0x01:// plain text extension
skip();
break;
default: // uninteresting extension
skip();
}
break;
case 0x3b: // terminator
done = true;
break;
case 0x00: // bad byte, but keep going and see what happens break;
default:
status = STATUS_FORMAT_ERROR;
}
}
}
/**
* Reads Graphics Control Extension values
*/
protected void readGraphicControlExt() {
read(); // block size
int packed = read(); // packed fields
dispose = (packed & 0x1c) >> 2; // disposal method
if (dispose == 0) {
dispose = 1; // elect to keep old image if discretionary
}
transparency = (packed & 1) != 0;
delay = readShort() * 10; // delay in milliseconds
transIndex = read(); // transparent color index
read(); // block terminator
}
/**
* Reads GIF file header information.
*/
protected void readHeader() {
StringBuilder id = new StringBuilder();
for (int i = 0; i < 6; i++) {
id.append((char) read());
}
if (!id.toString().startsWith("GIF")) {
status = STATUS_FORMAT_ERROR;
return;
}
readLSD();
if (gctFlag && !err()) {
gct = readColorTable(gctSize);
bgColor = gct[bgIndex];
}
}
/**
* Reads next frame image
*/
protected void readBitmap() {
ix = readShort(); // (sub)image position & size
iy = readShort();
iw = readShort();
ih = readShort();
int packed = read();
lctFlag = (packed & 0x80) != 0; // 1 - local color table flag interlace
lctSize = (int) Math.pow(2, (packed & 0x07) + 1);
// 3 - sort flag
// 4-5 - reserved lctSize = 2 << (packed & 7); // 6-8 - local color
// table size
interlace = (packed & 0x40) != 0;
if (lctFlag) {
lct = readColorTable(lctSize); // read table
act = lct; // make local table active
} else {
act = gct; // make global table active
if (bgIndex == transIndex) {
bgColor = 0;
}
}
int save = 0;
if (transparency) {
save = act[transIndex];
act[transIndex] = 0; // set transparent color if specified
}
if (act == null) {
status = STATUS_FORMAT_ERROR; // no color table defined
}
if (err()) {
return;
}
decodeBitmapData(); // decode pixel data
skip();
if (err()) {
return;
}
frameCount++;
// create new image to receive frame data
image = new DixieMap(width, height, Pixmap.Format.RGBA8888);
setPixels(); // transfer pixel data to image
frames.addElement(new GifFrame(image, delay)); // add image to frame
// list
if (transparency) {
act[transIndex] = save;
}
resetFrame();
}
/**
* Reads Logical Screen Descriptor
*/
protected void readLSD() {
// logical screen size
width = readShort();
height = readShort();
// packed fields
int packed = read();
gctFlag = (packed & 0x80) != 0; // 1 : global color table flag
// 2-4 : color resolution
// 5 : gct sort flag
gctSize = 2 << (packed & 7); // 6-8 : gct size
bgIndex = read(); // background color index
pixelAspect = read(); // pixel aspect ratio
}
/**
* Reads Netscape extenstion to obtain iteration count
*/
protected void readNetscapeExt() {
do {
readBlock();
if (block[0] == 1) {
// loop count sub-block
int b1 = ((int) block[1]) & 0xff;
int b2 = ((int) block[2]) & 0xff;
loopCount = (b2 << 8) | b1;
}
} while ((blockSize > 0) && !err());
}
/**
* Reads next 16-bit value, LSB first
*/
protected int readShort() {
// read 16-bit value, LSB first
return read() | (read() << 8);
}
/**
* Resets frame state for reading next image.
*/
protected void resetFrame() {
lastDispose = dispose;
lrx = ix;
lry = iy;
lrw = iw;
lrh = ih;
lastPixmap = image;
lastBgColor = bgColor;
dispose = 0;
transparency = false;
delay = 0;
lct = null;
}
/**
* Skips variable length blocks up to and including next zero length block.
*/
protected void skip() {
do {
readBlock();
} while ((blockSize > 0) && !err());
}
private Animation<TextureRegion> getAnimation(PlayMode playType) {
int nrFrames = getFrameCount();
Pixmap frame = getFrame(0);
int width = frame.getWidth();
int height = frame.getHeight();
int vzones = (int)Math.sqrt(nrFrames);
int hzones = vzones;
while(vzones * hzones < nrFrames) vzones++;
int v, h;
Pixmap target = new Pixmap(width * hzones, height * vzones, Pixmap.Format.RGBA8888);
for(h = 0; h < hzones; h++) {
for(v = 0; v < vzones; v++) {
int frameID = v + h * vzones;
if(frameID < nrFrames) {
frame = getFrame(frameID);
target.drawPixmap(frame, h * width, v * height);
}
}
}
Texture texture = new Texture(target);
Array<TextureRegion> texReg = new Array<>();
for(h = 0; h < hzones; h++) {
for(v = 0; v < vzones; v++) {
int frameID = v + h * vzones;
if(frameID < nrFrames) {
TextureRegion tr = new TextureRegion(texture, h * width, v * height, width, height);
texReg.add(tr);
}
}
}
float frameDuration = (float)getDelay(0);
frameDuration /= 1000; // convert milliseconds into seconds
return new Animation<>(frameDuration, texReg, playType);
}
public static Animation<TextureRegion> loadGIFAnimation(PlayMode playType, InputStream is) {
GifDecoder gdec = new GifDecoder();
gdec.read(is);
return gdec.getAnimation(playType);
}
}

View File

@@ -0,0 +1,179 @@
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.Application.ApplicationType;
import com.badlogic.gdx.Gdx;
import com.google.common.collect.ImmutableList;
import forge.adventure.libgdxgui.Forge;
import forge.gui.FThreads;
import forge.gui.GuiBase;
import forge.gui.download.GuiDownloadZipService;
import forge.gui.util.SOptionPane;
import forge.localinstance.properties.ForgeConstants;
import forge.adventure.libgdxgui.screens.SplashScreen;
import forge.util.FileUtil;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.List;
public class AssetsDownloader {
public static final boolean SHARE_DESKTOP_ASSETS = true; //change to false to test downloading separate assets for desktop version
private final static ImmutableList<String> downloadIgnoreExit = ImmutableList.of("Download", "Ignore", "Exit");
private final static ImmutableList<String> downloadExit = ImmutableList.of("Download", "Exit");
//if not sharing desktop assets, check whether assets are up to date
public static void checkForUpdates(final SplashScreen splashScreen) {
if (Gdx.app.getType() == ApplicationType.Desktop && SHARE_DESKTOP_ASSETS) { return; }
final boolean isSnapshots = Forge.CURRENT_VERSION.contains("SNAPSHOT");
final String snapsURL = "https://downloads.cardforge.org/dailysnapshots/";
final String releaseURL = "https://releases.cardforge.org/forge/forge-gui-android/";
final String versionText = isSnapshots ? snapsURL + "version.txt" : releaseURL + "version.txt";
splashScreen.getProgressBar().setDescription("Checking for updates...");
String message;
boolean connectedToInternet = Forge.getDeviceAdapter().isConnectedToInternet();
if (connectedToInternet) {
try {
URL versionUrl = new URL(versionText);
String version = FileUtil.readFileToString(versionUrl);
String filename = "forge-android-" + version + "-signed-aligned.apk";
String apkURL = isSnapshots ? snapsURL + filename : releaseURL + version + "/" + filename;
if (!StringUtils.isEmpty(version) && !Forge.CURRENT_VERSION.equals(version)) {
splashScreen.prepareForDialogs();
message = "A new version of Forge is available (" + version + ").\n" +
"You are currently on an older version (" + Forge.CURRENT_VERSION + ").\n\n" +
"Would you like to update to the new version now?";
if (!Forge.getDeviceAdapter().isConnectedToWifi()) {
message += " If so, you may want to connect to wifi first. The download is around 6.5MB.";
}
if (SOptionPane.showConfirmDialog(message, "New Version Available", "Update Now", "Update Later", true, true)) {
String apkFile = new GuiDownloadZipService("", "update", apkURL,
Forge.getDeviceAdapter().getDownloadsDir(), null, splashScreen.getProgressBar()).download(filename);
if (apkFile != null) {
/* FileUriExposedException was added on API 24, Forge now targets API 26 so Android 10 and above runs,
most user thinks Forge crashes but in reality, the method below just can't open the apk when Forge
exits silently to run the downloaded apk. Some devices allow the apk to run but most users are annoyed when
Forge didn't open the apk so I downgrade the check so it will run only on target devices without FileUriExposedException */
if (Forge.androidVersion < 24) {
Forge.getDeviceAdapter().openFile(apkFile);
Forge.exit(true);
return;
}
// API 24 and above needs manual apk installation unless we provide a FileProvider for FileUriExposedException
switch (SOptionPane.showOptionDialog("Download Successful. Go to your downloads folder and install " + filename +" to update Forge. Forge will now exit.", "", null, ImmutableList.of("Ok"))) {
default:
Forge.exit(true);
}
return;
}
SOptionPane.showMessageDialog("Could not download update. " +
"Press OK to proceed without update.", "Update Failed");
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
//see if assets need updating
File versionFile = new File(ForgeConstants.ASSETS_DIR + "version.txt");
if (!versionFile.exists()) {
try {
versionFile.createNewFile();
}
catch (IOException e) {
e.printStackTrace();
Forge.exit(true); //can't continue if this fails
return;
}
}
else if (Forge.CURRENT_VERSION.equals(FileUtil.readFileToString(versionFile)) && FSkin.getSkinDir() != null) {
return; //if version matches what had been previously saved and FSkin isn't requesting assets download, no need to download assets
}
splashScreen.prepareForDialogs(); //ensure colors set up for showing message dialogs
boolean canIgnoreDownload = FSkin.getAllSkins() != null; //don't allow ignoring download if resource files haven't been previously loaded
if (!connectedToInternet) {
message = "Updated resource files cannot be downloaded due to lack of internet connection.\n\n";
if (canIgnoreDownload) {
message += "You can continue without this download, but you may miss out on card fixes or experience other problems.";
}
else {
message += "You cannot start the app since you haven't previously downloaded these files.";
}
SOptionPane.showMessageDialog(message, "No Internet Connection");
if (!canIgnoreDownload) {
Forge.exit(true); //exit if can't ignore download
}
return;
}
//prompt user whether they wish to download the updated resource files
message = "There are updated resource files to download. " +
"This download is around 50MB, ";
if (Forge.getDeviceAdapter().isConnectedToWifi()) {
message += "which shouldn't take long if your wifi connection is good.";
}
else {
message += "so it's highly recommended that you connect to wifi first.";
}
final List<String> options;
message += "\n\n";
if (canIgnoreDownload) {
message += "If you choose to ignore this download, you may miss out on card fixes or experience other problems.";
options = downloadIgnoreExit;
} else {
message += "This download is mandatory to start the app since you haven't previously downloaded these files.";
options = downloadExit;
}
switch (SOptionPane.showOptionDialog(message, "", null, options)) {
case 1:
if (!canIgnoreDownload) {
Forge.exit(true); //exit if can't ignore download
}
return;
case 2:
Forge.exit(true);
return;
}
//allow deletion on Android 10 or if using app-specific directory
boolean allowDeletion = Forge.androidVersion < 30 || GuiBase.isUsingAppDirectory();
String assetURL = isSnapshots ? snapsURL + "assets.zip" : releaseURL + Forge.CURRENT_VERSION + "/" + "assets.zip";
new GuiDownloadZipService("", "resource files", assetURL,
ForgeConstants.ASSETS_DIR, ForgeConstants.RES_DIR, splashScreen.getProgressBar(), allowDeletion).downloadAndUnzip();
if (allowDeletion)
FSkinFont.deleteCachedFiles(); //delete cached font files in case any skin's .ttf file changed
//reload light version of skin after assets updated
FThreads.invokeInEdtAndWait(new Runnable() {
@Override
public void run() {
FSkinFont.updateAll(); //update all fonts used by splash screen
FSkin.loadLight(FSkin.getName(), splashScreen);
}
});
//save version string to file once assets finish downloading
//so they don't need to be re-downloaded until you upgrade again
FileUtil.writeFile(versionFile, Forge.CURRENT_VERSION);
//add restart after assets update
String msg = allowDeletion ? "Resource update finished..." : "Forge misses some files for deletion.\nIf you encounter issues, try deleting the Forge/res folder and/or deleting Forge/cache/fonts folder and try to download and update the assets.";
switch (SOptionPane.showOptionDialog(msg, "", null, ImmutableList.of("Restart"))) {
default:
Forge.restart(true);
}
}
}

View File

@@ -0,0 +1,391 @@
/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.PixmapIO;
import com.badlogic.gdx.graphics.g2d.BitmapFont.BitmapFontData;
import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph;
import com.badlogic.gdx.graphics.g2d.PixmapPacker.Page;
import com.badlogic.gdx.utils.Array;
import forge.util.TextUtil;
/**
* This file is 'borrowed' from gdx-tools in the libgdx source
*/
/** A utility to output BitmapFontData to a FNT file. This can be useful for caching the result from TrueTypeFont, for faster load
* times.
* <p>
* The font file format is from the AngelCodeFont BMFont tool.
* <p>
* Output is nearly identical to the FreeType settting in the Hiero tool {@Link com.badlogic.gdx.tools.hiero.Hiero}. BitmapFontWriter gives more flexibility, eg
* borders and shadows can be used. Hiero is able to avoid outputting the same glyph image more than once if multiple character
* codes have the exact same glyph.
* @author mattdesl AKA davedes */
public class BitmapFontWriter {
/** The output format. */
public enum OutputFormat {
/** AngelCodeFont text format */
Text,
/** AngelCodeFont XML format */
XML
}
/** The output format */
private static OutputFormat format = OutputFormat.Text;
/** Sets the AngelCodeFont output format for subsequent writes; can be text (for LibGDX) or XML (for other engines, like
* Pixi.js).
*
* @param fmt the output format to use */
public static void setOutputFormat (OutputFormat fmt) {
if (fmt == null) throw new NullPointerException("format cannot be null");
format = fmt;
}
/** Returns the currently used output format.
* @return the output format */
public static OutputFormat getOutputFormat () {
return format;
}
/** The Padding parameter for FontInfo. */
public static class Padding {
public int up, down, left, right;
public Padding () {
}
public Padding (int up, int down, int left, int right) {
this.up = up;
this.down = down;
this.left = left;
this.right = right;
}
}
/** The spacing parameter for FontInfo. */
public static class Spacing {
public int horizontal, vertical;
}
/** The font "info" line; everything except padding and override metrics are ignored by LibGDX's BitmapFont reader, it is otherwise just useful for
* clean and organized output. */
public static class FontInfo {
/** Face name */
public String face;
/** Font size (pt) */
public int size = 12;
/** Whether the font is bold */
public boolean bold;
/** Whether the font is italic */
public boolean italic;
/** The charset; or null/empty for default */
public String charset;
/** Whether the font uses unicode glyphs */
public boolean unicode = true;
/** Stretch for height; default to 100% */
public int stretchH = 100;
/** Whether smoothing is applied */
public boolean smooth = true;
/** Amount of anti-aliasing that was applied to the font */
public int aa = 2;
/** Padding that was applied to the font */
public Padding padding = new Padding();
/** Horizontal/vertical spacing that was applied to font */
public Spacing spacing = new Spacing();
public int outline = 0;
/** Override metrics */
public boolean hasOverrideMetrics;
public float ascent;
public float descent;
public float down;
public float capHeight;
public float lineHeight;
public float spaceXAdvance;
public float xHeight;
public FontInfo () {
}
public FontInfo (String face, int size) {
this.face = face;
this.size = size;
}
public void overrideMetrics (BitmapFontData data) {
hasOverrideMetrics = true;
ascent = data.ascent;
descent = data.descent;
down = data.down;
capHeight = data.capHeight;
lineHeight = data.lineHeight;
spaceXAdvance = data.spaceXadvance;
xHeight = data.xHeight;
}
}
private static String quote (Object params) {
return quote(params, false);
}
private static String quote (Object params, boolean spaceAfter) {
if (BitmapFontWriter.getOutputFormat() == OutputFormat.XML)
return "\"" + params.toString().trim() + "\"" + (spaceAfter ? " " : "");
else
return params.toString();
}
/** Writes the given BitmapFontData to a file, using the specified <tt>pageRefs</tt> strings as the image paths for each
* texture page. The glyphs in BitmapFontData have a "page" id, which references the index of the pageRef you specify here.
*
* The FontInfo parameter is useful for cleaner output; such as including a size and font face name hint. However, it can be
* null to use default values. LibGDX ignores most of the "info" line when reading back fonts, only padding is used. Padding
* also affects the size, location, and offset of the glyphs that are output.
*
* Likewise, the scaleW and scaleH are only for cleaner output. They are currently ignored by LibGDX's reader. For maximum
* compatibility with other BMFont tools, you should use the width and height of your texture pages (each page should be the
* same size).
*
* @param fontData the bitmap font
* @param pageRefs the references to each texture page image file, generally in the same folder as outFntFile
* @param outFntFile the font file to save to (typically ends with '.fnt')
* @param info the optional info for the file header; can be null
* @param scaleW the width of your texture pages
* @param scaleH the height of your texture pages */
public static void writeFont (BitmapFontData fontData, String[] pageRefs, FileHandle outFntFile, FontInfo info, int scaleW,
int scaleH) {
if (info == null) {
info = new FontInfo();
info.face = outFntFile.nameWithoutExtension();
}
int lineHeight = (int)fontData.lineHeight;
int pages = pageRefs.length;
int packed = 0;
int base = (int)((fontData.capHeight) + (fontData.flipped ? -fontData.ascent : fontData.ascent));
OutputFormat fmt = BitmapFontWriter.getOutputFormat();
boolean xml = fmt == OutputFormat.XML;
StringBuilder buf = new StringBuilder();
if (xml) {
buf.append("<font>\n");
}
String xmlOpen = xml ? "\t<" : "";
String xmlCloseSelf = xml ? "/>" : "";
String xmlTab = xml ? "\t" : "";
String xmlClose = xml ? ">" : "";
String xmlQuote = xml ? "\"" : "";
String alphaChnlParams = xml ? " alphaChnl=\"0\" redChnl=\"0\" greenChnl=\"0\" blueChnl=\"0\""
: " alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0";
// INFO LINE
buf.append(xmlOpen).append("info face=\"").append(info.face == null ? "" : TextUtil.fastReplace(info.face,"\"", "'"))
.append("\" size=").append(quote(info.size)).append(" bold=").append(quote(info.bold ? 1 : 0)).append(" italic=")
.append(quote(info.italic ? 1 : 0)).append(" charset=\"").append(info.charset == null ? "" : info.charset)
.append("\" unicode=").append(quote(info.unicode ? 1 : 0)).append(" stretchH=").append(quote(info.stretchH))
.append(" smooth=").append(quote(info.smooth ? 1 : 0)).append(" aa=").append(quote(info.aa)).append(" padding=")
.append(xmlQuote).append(info.padding.up).append(",").append(info.padding.right).append(",").append(info.padding.down)
.append(",").append(info.padding.left).append(xmlQuote).append(" spacing=").append(xmlQuote)
.append(info.spacing.horizontal).append(",").append(info.spacing.vertical).append(xmlQuote).append(xmlCloseSelf)
.append("\n");
// COMMON line
buf.append(xmlOpen).append("common lineHeight=").append(quote(lineHeight)).append(" base=").append(quote(base))
.append(" scaleW=").append(quote(scaleW)).append(" scaleH=").append(quote(scaleH)).append(" pages=").append(quote(pages))
.append(" packed=").append(quote(packed)).append(alphaChnlParams).append(xmlCloseSelf).append("\n");
if (xml) buf.append("\t<pages>\n");
// PAGES
for (int i = 0; i < pageRefs.length; i++) {
buf.append(xmlTab).append(xmlOpen).append("page id=").append(quote(i)).append(" file=\"").append(pageRefs[i])
.append("\"").append(xmlCloseSelf).append("\n");
}
if (xml) buf.append("\t</pages>\n");
// CHARS
Array<Glyph> glyphs = new Array<Glyph>(256);
for (int i = 0; i < fontData.glyphs.length; i++) {
if (fontData.glyphs[i] == null) continue;
for (int j = 0; j < fontData.glyphs[i].length; j++) {
if (fontData.glyphs[i][j] != null) {
glyphs.add(fontData.glyphs[i][j]);
}
}
}
buf.append(xmlOpen).append("chars count=").append(quote(glyphs.size)).append(xmlClose).append("\n");
int padLeft = 0, padRight = 0, padTop = 0, padX = 0, padY = 0;
if (info != null) {
padTop = info.padding.up;
padLeft = info.padding.left;
padRight = info.padding.right;
padX = padLeft + padRight;
padY = info.padding.up + info.padding.down;
}
// CHAR definitions
for (int i = 0; i < glyphs.size; i++) {
Glyph g = glyphs.get(i);
boolean empty = g.width == 0 || g.height == 0;
buf.append(xmlTab).append(xmlOpen).append("char id=").append(quote(String.format("%-6s", g.id), true)).append("x=")
.append(quote(String.format("%-5s", empty ? 0 : g.srcX), true)).append("y=")
.append(quote(String.format("%-5s", empty ? 0 : g.srcY), true)).append("width=")
.append(quote(String.format("%-5s", empty ? 0 : g.width), true)).append("height=")
.append(quote(String.format("%-5s", empty ? 0 : g.height), true)).append("xoffset=")
.append(quote(String.format("%-5s", g.xoffset - padLeft), true)).append("yoffset=")
.append(quote(String.format("%-5s", fontData.flipped ? g.yoffset + padTop : -(g.height + (g.yoffset + padTop))), true))
.append("xadvance=").append(quote(String.format("%-5s", g.xadvance), true)).append("page=")
.append(quote(String.format("%-5s", g.page), true)).append("chnl=").append(quote(0, true)).append(xmlCloseSelf)
.append("\n");
}
if (xml) buf.append("\t</chars>\n");
// KERNINGS
int kernCount = 0;
StringBuilder kernBuf = new StringBuilder();
for (int i = 0; i < glyphs.size; i++) {
for (int j = 0; j < glyphs.size; j++) {
Glyph first = glyphs.get(i);
Glyph second = glyphs.get(j);
int kern = first.getKerning((char)second.id);
if (kern != 0) {
kernCount++;
kernBuf.append(xmlTab).append(xmlOpen).append("kerning first=").append(quote(first.id)).append(" second=")
.append(quote(second.id)).append(" amount=").append(quote(kern, true)).append(xmlCloseSelf).append("\n");
}
}
}
// KERN info
buf.append(xmlOpen).append("kernings count=").append(quote(kernCount)).append(xmlClose).append("\n");
buf.append(kernBuf);
if (xml) {
buf.append("\t</kernings>\n");
}
// Override metrics
if (info.hasOverrideMetrics) {
if (xml) buf.append("\t<metrics>\n");
buf.append(xmlTab).append(xmlOpen)
.append("metrics ascent=").append(quote(info.ascent, true))
.append(" descent=").append(quote(info.descent, true))
.append(" down=").append(quote(info.down, true))
.append(" capHeight=").append(quote(info.capHeight, true))
.append(" lineHeight=").append(quote(info.lineHeight, true))
.append(" spaceXAdvance=").append(quote(info.spaceXAdvance, true))
.append(" xHeight=").append(quote(info.xHeight, true))
.append(xmlCloseSelf).append("\n");
if (xml) buf.append("\t</metrics>\n");
}
if (xml) {
buf.append("</font>");
}
String charset = info.charset;
if (charset != null && charset.length() == 0) charset = null;
outFntFile.writeString(buf.toString(), false, charset);
}
/** A utility method which writes the given font data to a file.
*
* The specified pixmaps are written to the parent directory of <tt>outFntFile</tt>, using that file's name without an
* extension for the PNG file name(s).
*
* The specified FontInfo is optional, and can be null.
*
* Typical usage looks like this:
*
* <pre>
* BitmapFontWriter.writeFont(myFontData, myFontPixmaps, Gdx.files.external(&quot;fonts/output.fnt&quot;), new FontInfo(&quot;Arial&quot;, 16));
* </pre>
*
* @param fontData the font data
* @param pages the pixmaps to write as PNGs
* @param outFntFile the output file for the font definition
* @param info the optional font info for the header file, can be null */
public static void writeFont (BitmapFontData fontData, Pixmap[] pages, FileHandle outFntFile, FontInfo info) {
String[] pageRefs = writePixmaps(pages, outFntFile.parent(), outFntFile.nameWithoutExtension());
// write the font data
writeFont(fontData, pageRefs, outFntFile, info, pages[0].getWidth(), pages[0].getHeight());
}
/** A utility method to write the given array of pixmaps to the given output directory, with the specified file name. If the
* pages array is of length 1, then the resulting file ref will look like: "fileName.png".
*
* If the pages array is greater than length 1, the resulting file refs will be appended with "_N", such as "fileName_0.png",
* "fileName_1.png", "fileName_2.png" etc.
*
* The returned string array can then be passed to the <tt>writeFont</tt> method.
*
* Note: None of the pixmaps will be disposed.
*
* @param pages the pages of pixmap data to write
* @param outputDir the output directory
* @param fileName the file names for the output images
* @return the array of string references to be used with <tt>writeFont</tt> */
public static String[] writePixmaps (Pixmap[] pages, FileHandle outputDir, String fileName) {
if (pages == null || pages.length == 0) throw new IllegalArgumentException("no pixmaps supplied to BitmapFontWriter.write");
String[] pageRefs = new String[pages.length];
for (int i = 0; i < pages.length; i++) {
String ref = pages.length == 1 ? (fileName + ".png") : (fileName + "_" + i + ".png");
// the ref for this image
pageRefs[i] = ref;
// write the PNG in that directory
PixmapIO.writePNG(outputDir.child(ref), pages[i]);
}
return pageRefs;
}
/** A convenience method to write pixmaps by page; typically returned from a PixmapPacker when used alongside
* FreeTypeFontGenerator.
*
* @param pages the pages containing the Pixmaps
* @param outputDir the output directory
* @param fileName the file name
* @return the file refs */
public static String[] writePixmaps (Array<Page> pages, FileHandle outputDir, String fileName) {
Pixmap[] pix = new Pixmap[pages.size];
for (int i = 0; i < pages.size; i++) {
pix[i] = pages.get(i).getPixmap();
}
return writePixmaps(pix, outputDir, fileName);
}
}

View File

@@ -0,0 +1,99 @@
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.math.Matrix4;
import forge.adventure.libgdxgui.Graphics;
import forge.gui.FThreads;
//Special graphics object for rendering to a texture
public abstract class FBufferedImage extends FImageComplex {
private final float width, height, opacity;
private FrameBuffer frameBuffer;
public FBufferedImage(float width0, float height0) {
this(width0, height0, 1);
}
public FBufferedImage(float width0, float height0, float opacity0) {
width = width0;
height = height0;
opacity = opacity0;
}
@Override
public float getWidth() {
return width;
}
@Override
public float getHeight() {
return height;
}
@Override
public int getRegionX() {
return 0;
}
@Override
public int getRegionY() {
return 0;
}
@Override
public Texture getTexture() {
if (frameBuffer == null) {
Gdx.gl.glDisable(GL20.GL_SCISSOR_TEST); //prevent buffered image being clipped
//render texture to frame buffer if needed
frameBuffer = new FrameBuffer(Format.RGBA8888, (int)width, (int)height, false);
frameBuffer.begin();
//frame graphics must be given a projection matrix
//so stuff is rendered properly to custom sized frame buffer
Graphics frameGraphics = new Graphics();
Matrix4 matrix = new Matrix4();
matrix.setToOrtho2D(0, 0, width, height);
frameGraphics.setProjectionMatrix(matrix);
frameGraphics.begin(width, height);
draw(frameGraphics, width, height);
frameGraphics.end();
frameBuffer.end();
frameGraphics.dispose();
Gdx.gl.glEnable(GL20.GL_SCISSOR_TEST);
}
return frameBuffer.getColorBufferTexture();
}
public void clear() {
final FrameBuffer fb = frameBuffer;
if (fb != null) {
frameBuffer = null;
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() {
fb.dispose(); //must be disposed on EDT thread
}
});
}
}
protected abstract void draw(Graphics g, float w, float h);
@Override
public void draw(Graphics g, float x, float y, float w, float h) {
if (opacity < 1) {
g.setAlphaComposite(opacity);
}
g.drawFlippedImage(getTexture(), x, y, w, h); //need to draw image flipped because of how FrameBuffer works
if (opacity < 1) {
g.resetAlphaComposite();
}
}
}

View File

@@ -0,0 +1,48 @@
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import forge.adventure.libgdxgui.Graphics;
//Special wrapper for a texture to be loaded later when it's needed
public class FDelayLoadImage extends FImageComplex {
private final String filename;
private Texture texture;
public FDelayLoadImage(String filename0) {
filename = filename0;
}
@Override
public float getWidth() {
return getTexture().getWidth();
}
@Override
public float getHeight() {
return getTexture().getHeight();
}
@Override
public Texture getTexture() {
if (texture == null) {
texture = new Texture(Gdx.files.absolute(filename));
}
return texture;
}
@Override
public int getRegionX() {
return 0;
}
@Override
public int getRegionY() {
return 0;
}
@Override
public void draw(Graphics g, float x, float y, float w, float h) {
g.drawImage(getTexture(), x, y, w, h);
}
}

View File

@@ -0,0 +1,10 @@
package forge.adventure.libgdxgui.assets;
import forge.adventure.libgdxgui.Graphics;
import forge.localinstance.skin.ISkinImage;
public interface FImage extends ISkinImage {
float getWidth();
float getHeight();
void draw(Graphics g, float x, float y, float w, float h);
}

View File

@@ -0,0 +1,9 @@
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.graphics.Texture;
public abstract class FImageComplex implements FImage {
public abstract Texture getTexture();
public abstract int getRegionX();
public abstract int getRegionY();
}

View File

@@ -0,0 +1,42 @@
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.properties.ForgePreferences;
import forge.localinstance.properties.ForgePreferences.FPref;
import forge.model.FModel;
import java.util.ArrayList;
import java.util.List;
public class FLanguage {
public static void changeLanguage(final String languageName) {
final ForgePreferences prefs = FModel.getPreferences();
if (languageName.equals(prefs.getPref(FPref.UI_LANGUAGE))) { return; }
//save language preference
prefs.setPref(FPref.UI_LANGUAGE, languageName);
prefs.save();
}
/**
* Gets the languages.
*
* @return the languages
*/
public static Iterable<String> getAllLanguages() {
final List<String> allLanguages = new ArrayList<>();
final FileHandle dir = Gdx.files.absolute(ForgeConstants.LANG_DIR);
for (FileHandle languageFile : dir.list()) {
String languageName = languageFile.name();
if (!languageName.endsWith(".properties")) { continue; }
allLanguages.add(languageName.replace(".properties", ""));
}
return allLanguages;
}
}

View File

@@ -0,0 +1,60 @@
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.graphics.Texture;
import forge.adventure.libgdxgui.Graphics;
public class FRotatedImage extends FImageComplex {
private final Texture texture;
private final int srcX, srcY, srcWidth, srcHeight;
private final boolean clockwise;
public FRotatedImage(Texture texture0, int srcX0, int srcY0, int srcWidth0, int srcHeight0, boolean clockwise0) {
texture = texture0;
srcX = srcX0;
srcY = srcY0;
srcWidth = srcWidth0;
srcHeight = srcHeight0;
clockwise = clockwise0;
}
@Override
public float getWidth() {
return srcHeight; //width and height are swapped since image rotated
}
@Override
public float getHeight() {
return srcWidth;
}
@Override
public Texture getTexture() {
return texture;
}
@Override
public int getRegionX() {
return srcX;
}
@Override
public int getRegionY() {
return srcY;
}
@Override
public void draw(Graphics g, float x, float y, float w, float h) {
float originX, originY, rotation;
if (clockwise) {
originX = x + w / 2;
originY = y + w / 2;
rotation = -90;
}
else {
originX = x + h / 2;
originY = y + h / 2;
rotation = 90;
}
g.drawRotatedImage(texture, x, y, h, w, originX, originY, srcX, srcY, srcWidth, srcHeight, rotation);
}
}

View File

@@ -0,0 +1,475 @@
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.Gdx;
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.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Array;
import forge.adventure.libgdxgui.Forge;
import forge.adventure.libgdxgui.assets.FSkinImage.SourceFile;
import forge.adventure.libgdxgui.card.CardFaceSymbols;
import forge.adventure.util.Config;
import forge.gui.FThreads;
import forge.gui.GuiBase;
import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.properties.ForgePreferences;
import forge.localinstance.properties.ForgePreferences.FPref;
import forge.localinstance.skin.FSkinProp;
import forge.model.FModel;
import forge.adventure.libgdxgui.screens.LoadingOverlay;
import forge.adventure.libgdxgui.screens.SplashScreen;
import forge.adventure.libgdxgui.toolbox.FProgressBar;
import forge.util.WordUtil;
import java.util.HashMap;
import java.util.Map;
public class FSkin {
private static final Map<FSkinProp, FSkinImage> images = new HashMap<>(512);
private static final Map<Integer, TextureRegion> avatars = new HashMap<>(150);
private static final Map<Integer, TextureRegion> sleeves = new HashMap<>(64);
private static final Map<Integer, TextureRegion> borders = new HashMap<>();
private static final Map<Integer, TextureRegion> deckbox = new HashMap<>();
private static Array<String> allSkins;
private static FileHandle preferredDir;
private static String preferredName;
private static boolean loaded = false;
public static Texture hdLogo = null;
public static void changeSkin(final String skinName) {
final ForgePreferences prefs = FModel.getPreferences();
if (skinName.equals(prefs.getPref(FPref.UI_SKIN))) { return; }
//save skin preference
prefs.setPref(FPref.UI_SKIN, skinName);
prefs.save();
//load skin
loaded = false; //reset this temporarily until end of loadFull()
final LoadingOverlay loader = new LoadingOverlay("Loading new theme...");
loader.show(); //show loading overlay then delay running remaining logic so UI can respond
FThreads.invokeInBackgroundThread(new Runnable() {
@Override
public void run() {
FThreads.invokeInEdtLater(new Runnable() {
@Override
public void run() {
loadLight(skinName, null);
loadFull(null);
loader.setCaption("Loading fonts...");
FThreads.invokeInBackgroundThread(new Runnable() {
@Override
public void run() {
FSkinFont.deleteCachedFiles(); //delete cached font files so font can be update for new skin
FSkinFont.updateAll();
//CardImageRenderer.forceStaticFieldUpdate();
FThreads.invokeInEdtLater(new Runnable() {
@Override
public void run() {
loader.hide();
}
});
}
});
}
});
}
});
}
/*
* Loads a "light" version of FSkin, just enough for the splash screen:
* skin name. Generates custom skin settings, fonts, and backgrounds.
*
*
* @param skinName
* the skin name
*/
public static void loadLight(String skinName, final SplashScreen splashScreen) {
preferredName = skinName.toLowerCase().replace(' ', '_');
//reset hd buttons/icons
Forge.hdbuttons = false;
Forge.hdstart = false;
preferredDir = Config.instance().getFile("skin");
FSkinTexture.BG_TEXTURE.load(); //load background texture early for splash screen
if (splashScreen != null) {
final FileHandle f = getSkinFile("bg_splash.png");
final FileHandle f2 = getSkinFile("bg_splash_hd.png"); //HD Splashscreen
final FileHandle f3 = getSkinFile("hd_logo.png");
if (!f.exists()) {
if (!skinName.equals("default")) {
FSkin.loadLight("default", splashScreen);
}
return;
}
try {
Texture txSplash = new Texture(f);
final int w = txSplash.getWidth();
final int h = txSplash.getHeight();
if (f2.exists()) {
Texture txSplashHD = new Texture(f2, true);
txSplashHD.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
splashScreen.setBackground(new TextureRegion(txSplashHD));
} else {
splashScreen.setBackground(new TextureRegion(txSplash, 0, 0, w, h - 100));
}
if (f3.exists()) {
Texture txOverlay = new Texture(f3, true);
txOverlay.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
hdLogo = txOverlay;
} else {
hdLogo = null;
}
Pixmap pxSplash = new Pixmap(f);
FProgressBar.BACK_COLOR = new Color(pxSplash.getPixel(25, h - 75));
FProgressBar.FORE_COLOR = new Color(pxSplash.getPixel(75, h - 75));
FProgressBar.SEL_BACK_COLOR = new Color(pxSplash.getPixel(25, h - 25));
FProgressBar.SEL_FORE_COLOR = new Color(pxSplash.getPixel(75, h - 25));
}
catch (final Exception e) {
e.printStackTrace();
}
loaded = true;
}
}
/**
* Loads two sprites: the default (which should be a complete
* collection of all symbols) and the preferred (which may be
* incomplete).
*
* Font must be present in the skin folder, and will not
* be replaced by default. The fonts are pre-derived
* in this method and saved in a HashMap for future access.
*
* Color swatches must be present in the preferred
* sprite, and will not be replaced by default.
*
* Background images must be present in skin folder,
* and will not be replaced by default.
*
* Icons, however, will be pulled from the two sprites. Obviously,
* preferred takes precedence over default, but if something is
* missing, the default picture is retrieved.
*/
public static void loadFull(final SplashScreen splashScreen) {
if (splashScreen != null) {
// Preferred skin name must be called via loadLight() method,
// which does some cleanup and init work.
if (FSkin.preferredName.isEmpty()) { FSkin.loadLight("default", splashScreen); }
}
avatars.clear();
sleeves.clear();
boolean textureFilter = Forge.isTextureFilteringEnabled();
final Map<String, Texture> textures = new HashMap<>();
// Grab and test various sprite files.
final FileHandle f1 = getDefaultSkinFile(SourceFile.ICONS.getFilename());
final FileHandle f2 = getSkinFile(SourceFile.ICONS.getFilename());
final FileHandle f3 = getDefaultSkinFile(SourceFile.FOILS.getFilename());
final FileHandle f4 = getDefaultSkinFile(ForgeConstants.SPRITE_AVATARS_FILE);
final FileHandle f5 = getSkinFile(ForgeConstants.SPRITE_AVATARS_FILE);
final FileHandle f6 = getDefaultSkinFile(SourceFile.OLD_FOILS.getFilename());
final FileHandle f7 = getSkinFile(ForgeConstants.SPRITE_MANAICONS_FILE);
final FileHandle f8 = getDefaultSkinFile(ForgeConstants.SPRITE_SLEEVES_FILE);
final FileHandle f9 = getDefaultSkinFile(ForgeConstants.SPRITE_SLEEVES2_FILE);
final FileHandle f10 = getDefaultSkinFile(ForgeConstants.SPRITE_BORDER_FILE);
final FileHandle f11 = getSkinFile(ForgeConstants.SPRITE_BUTTONS_FILE);
final FileHandle f12 = getSkinFile(ForgeConstants.SPRITE_START_FILE);
final FileHandle f13 = getDefaultSkinFile(ForgeConstants.SPRITE_DECKBOX_FILE);
try {
textures.put(f1.path(), new Texture(f1));
Pixmap preferredIcons = new Pixmap(f1);
if (f2.exists()) {
textures.put(f2.path(), new Texture(f2));
preferredIcons = new Pixmap(f2);
}
textures.put(f3.path(), new Texture(f3));
if (f6.exists()) {
textures.put(f6.path(), new Texture(f6));
}
else {
textures.put(f6.path(), textures.get(f3.path()));
}
if (f7.exists()){
Texture t = new Texture(f7, true);
//t.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
textures.put(f7.path(), t);
}
//hdbuttons
if (f11.exists()) {
if (GuiBase.isAndroid() && Forge.totalDeviceRAM <5000) {
Forge.hdbuttons = false;
} else {
Texture t = new Texture(f11, true);
t.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
textures.put(f11.path(), t);
Forge.hdbuttons = true;
}
} else { Forge.hdbuttons = false; } //how to refresh buttons when a theme don't have hd buttons?
if (f12.exists()) {
if (GuiBase.isAndroid() && Forge.totalDeviceRAM <5000) {
Forge.hdstart = false;
} else {
Texture t = new Texture(f12, true);
t.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
textures.put(f12.path(), t);
Forge.hdstart = true;
}
} else { Forge.hdstart = false; }
//update colors
for (final FSkinColor.Colors c : FSkinColor.Colors.values()) {
c.setColor(new Color(preferredIcons.getPixel(c.getX(), c.getY())));
}
//load images
for (FSkinImage image : FSkinImage.values()) {
if (GuiBase.isAndroid()) {
if (Forge.totalDeviceRAM>5000)
image.load(textures, preferredIcons);
else if (image.toString().equals("HDMULTI"))
image.load(textures, preferredIcons);
else if (!image.toString().startsWith("HD"))
image.load(textures, preferredIcons);
} else {
image.load(textures, preferredIcons);
}
}
for (FSkinTexture texture : FSkinTexture.values()) {
if (texture != FSkinTexture.BG_TEXTURE) {
texture.load();
}
}
//assemble avatar textures
int counter = 0;
int scount = 0;
Color pxTest;
Pixmap pxDefaultAvatars, pxPreferredAvatars, pxDefaultSleeves;
Texture txDefaultAvatars, txPreferredAvatars, txDefaultSleeves;
pxDefaultAvatars = new Pixmap(f4);
pxDefaultSleeves = new Pixmap(f8);
txDefaultAvatars = new Texture(f4, textureFilter);
if (textureFilter)
txDefaultAvatars.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
txDefaultSleeves = new Texture(f8, textureFilter);
if (textureFilter)
txDefaultSleeves.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
if (f5.exists()) {
pxPreferredAvatars = new Pixmap(f5);
txPreferredAvatars = new Texture(f5, textureFilter);
if (textureFilter)
txPreferredAvatars.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
final int pw = pxPreferredAvatars.getWidth();
final int ph = pxPreferredAvatars.getHeight();
for (int j = 0; j < ph; j += 100) {
for (int i = 0; i < pw; i += 100) {
if (i == 0 && j == 0) { continue; }
pxTest = new Color(pxPreferredAvatars.getPixel(i + 50, j + 50));
if (pxTest.a == 0) { continue; }
FSkin.avatars.put(counter++, new TextureRegion(txPreferredAvatars, i, j, 100, 100));
}
}
pxPreferredAvatars.dispose();
} else if (!FSkin.preferredName.isEmpty()){
//workaround bug crash fix if missing sprite avatar on preferred theme for quest tournament...
//i really don't know why it needs to populate the avatars twice.... needs investigation
final int pw = pxDefaultAvatars.getWidth();
final int ph = pxDefaultAvatars.getHeight();
for (int j = 0; j < ph; j += 100) {
for (int i = 0; i < pw; i += 100) {
if (i == 0 && j == 0) { continue; }
pxTest = new Color(pxDefaultAvatars.getPixel(i + 50, j + 50));
if (pxTest.a == 0) { continue; }
FSkin.avatars.put(counter++, new TextureRegion(txDefaultAvatars, i, j, 100, 100));
}
}
}
final int aw = pxDefaultAvatars.getWidth();
final int ah = pxDefaultAvatars.getHeight();
for (int j = 0; j < ah; j += 100) {
for (int i = 0; i < aw; i += 100) {
if (i == 0 && j == 0) { continue; }
pxTest = new Color(pxDefaultAvatars.getPixel(i + 50, j + 50));
if (pxTest.a == 0) { continue; }
FSkin.avatars.put(counter++, new TextureRegion(txDefaultAvatars, i, j, 100, 100));
}
}
final int sw = pxDefaultSleeves.getWidth();
final int sh = pxDefaultSleeves.getHeight();
for (int j = 0; j < sh; j += 500) {
for (int i = 0; i < sw; i += 360) {
pxTest = new Color(pxDefaultSleeves.getPixel(i + 180, j + 250));
if (pxTest.a == 0) { continue; }
FSkin.sleeves.put(scount++, new TextureRegion(txDefaultSleeves, i, j, 360, 500));
}
}
//re init second set of sleeves
pxDefaultSleeves = new Pixmap(f9);
txDefaultSleeves = new Texture(f9, textureFilter);
if (textureFilter)
txDefaultSleeves.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
final int sw2 = pxDefaultSleeves.getWidth();
final int sh2 = pxDefaultSleeves.getHeight();
for (int j = 0; j < sh2; j += 500) {
for (int i = 0; i < sw2; i += 360) {
pxTest = new Color(pxDefaultSleeves.getPixel(i + 180, j + 250));
if (pxTest.a == 0) { continue; }
FSkin.sleeves.put(scount++, new TextureRegion(txDefaultSleeves, i, j, 360, 500));
}
}
//borders
Texture bordersBW = new Texture(f10);
FSkin.borders.put(0, new TextureRegion(bordersBW, 2, 2, 672, 936));
FSkin.borders.put(1, new TextureRegion(bordersBW, 676, 2, 672, 936));
//deckboxes
Texture deckboxes = new Texture(f13, textureFilter);
if (textureFilter)
deckboxes.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
//gold bg
FSkin.deckbox.put(0, new TextureRegion(deckboxes, 2, 2, 488, 680));
//deck box for card art
FSkin.deckbox.put(1, new TextureRegion(deckboxes, 492, 2, 488, 680));
//generic deck box
FSkin.deckbox.put(2, new TextureRegion(deckboxes, 982, 2, 488, 680));
preferredIcons.dispose();
pxDefaultAvatars.dispose();
pxDefaultSleeves.dispose();
}
catch (final Exception e) {
System.err.println("FSkin$loadFull: Missing a sprite (default icons, "
+ "preferred icons, or foils.");
e.printStackTrace();
}
// Run through enums and load their coords.
FSkinColor.updateAll();
// Images loaded; can start UI init.
loaded = true;
if (splashScreen != null) {
CardFaceSymbols.loadImages();
}
}
/**
* Gets the name.
*
* @return Name of the current skin.
*/
public static String getName() {
return FSkin.preferredName;
}
/**
* Gets a FileHandle for a file within the directory where skin files should be stored
*/
public static FileHandle getSkinFile(String filename) {
return preferredDir.child(filename);
}
/**
* Gets a FileHandle for a file within the directory where the default skin files should be stored
*/
public static FileHandle getDefaultSkinFile(String filename) {
return Gdx.files.absolute(ForgeConstants.DEFAULT_SKINS_DIR + filename);
}
/**
* Gets a FileHandle for a file within the planechase cache directory
*/
public static FileHandle getCachePlanechaseFile(String filename) {
return Gdx.files.absolute(ForgeConstants.CACHE_PLANECHASE_PICS_DIR + filename);
}
public static FileHandle getSkinDir() {
return preferredDir;
}
/**
* Gets the skins.
*
* @return the skins
*/
public static Array<String> getSkinDirectoryNames() {
final Array<String> mySkins = new Array<>();
final FileHandle dir = Gdx.files.absolute(ForgeConstants.CACHE_SKINS_DIR);
for (FileHandle skinFile : dir.list()) {
String skinName = skinFile.name();
if (skinName.equalsIgnoreCase(".svn")) { continue; }
if (skinName.equalsIgnoreCase(".DS_Store")) { continue; }
mySkins.add(skinName);
}
return mySkins;
}
public static Iterable<String> getAllSkins() {
if (allSkins != null) {
allSkins.clear();
allSkins.add("Default"); //init default
final Array<String> skinDirectoryNames = getSkinDirectoryNames();
for (final String skinDirectoryName : skinDirectoryNames) {
allSkins.add(WordUtil.capitalize(skinDirectoryName.replace('_', ' ')));
}
allSkins.sort();
}
return allSkins;
}
public static Map<FSkinProp, FSkinImage> getImages() {
return images;
}
public static Map<Integer, TextureRegion> getAvatars() {
return avatars;
}
public static Map<Integer, TextureRegion> getSleeves() {
return sleeves;
}
public static Map<Integer, TextureRegion> getBorders() {
return borders;
}
public static Map<Integer, TextureRegion> getDeckbox() {
return deckbox;
}
public static boolean isLoaded() { return loaded; }
}

View File

@@ -0,0 +1,29 @@
package forge.adventure.libgdxgui.assets;
import forge.adventure.libgdxgui.Graphics;
public class FSkinBorder {
private final FSkinColor color;
private final float thickness;
public FSkinBorder(FSkinColor color0, float thickness0) {
color = color0;
thickness = thickness0;
}
public FSkinColor getColor() {
return color;
}
public float getThickness() {
return thickness;
}
public void draw(Graphics g, float x, float y, float w, float h) {
x -= thickness;
y -= thickness;
w += 2 * thickness;
h += 2 * thickness;
g.fillRect(color, x, y, w, h); //draw filled rectangle behind object
}
}

View File

@@ -0,0 +1,268 @@
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.graphics.Color;
import forge.localinstance.skin.FSkinProp;
import forge.adventure.libgdxgui.screens.match.TargetingOverlay;
import java.util.HashMap;
public class FSkinColor {
public enum Colors {
CLR_THEME (FSkinProp.CLR_THEME),
CLR_BORDERS (FSkinProp.CLR_BORDERS),
CLR_ZEBRA (FSkinProp.CLR_ZEBRA),
CLR_HOVER (FSkinProp.CLR_HOVER),
CLR_ACTIVE (FSkinProp.CLR_ACTIVE),
CLR_INACTIVE (FSkinProp.CLR_INACTIVE),
CLR_TEXT (FSkinProp.CLR_TEXT),
CLR_PHASE_INACTIVE_ENABLED (FSkinProp.CLR_PHASE_INACTIVE_ENABLED),
CLR_PHASE_INACTIVE_DISABLED (FSkinProp.CLR_PHASE_INACTIVE_DISABLED),
CLR_PHASE_ACTIVE_ENABLED (FSkinProp.CLR_PHASE_ACTIVE_ENABLED),
CLR_PHASE_ACTIVE_DISABLED (FSkinProp.CLR_PHASE_ACTIVE_DISABLED),
CLR_THEME2 (FSkinProp.CLR_THEME2),
CLR_OVERLAY (FSkinProp.CLR_OVERLAY),
CLR_COMBAT_TARGETING_ARROW (FSkinProp.CLR_COMBAT_TARGETING_ARROW),
CLR_NORMAL_TARGETING_ARROW (FSkinProp.CLR_NORMAL_TARGETING_ARROW),
CLR_PWATTK_TARGETING_ARROW (FSkinProp.CLR_PWATTK_TARGETING_ARROW);
private Color color;
private final int x, y;
private final FSkinProp skinProp;
Colors(final FSkinProp skinProp0) {
skinProp = skinProp0;
int[] coords = skinProp.getCoords();
x = coords[0];
y = coords[1];
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setColor(Color color0) {
color = color0;
}
public static Colors fromSkinProp(FSkinProp skinProp) {
for (final Colors c : Colors.values()) {
if (c.skinProp == skinProp) {
return c;
}
}
return null;
}
}
public static FSkinColor get(final Colors c0) {
return baseColors.get(c0);
}
public static FSkinColor getStandardColor(int r, int g, int b) {
return getStandardColor(fromRGB(r, g, b));
}
public static FSkinColor getStandardColor(final Color c0) {
return new FSkinColor(c0, NO_BRIGHTNESS_DELTA, NO_STEP, NO_STEP, NO_ALPHA);
}
private static final HashMap<Colors, FSkinColor> baseColors = new HashMap<>();
private static final HashMap<String, FSkinColor> derivedColors = new HashMap<>();
private static final int NO_BRIGHTNESS_DELTA = 0;
private static final int NO_STEP = -999; //needs to be large negative since small negative values are valid
private static final int NO_ALPHA = -1;
private final Colors baseColor;
private final int brightnessDelta;
private final int step;
private final int contrastStep;
private final float alpha;
protected Color color;
public Color getColor() { return color; }
private FSkinColor(Colors baseColor0) {
this(baseColor0, NO_BRIGHTNESS_DELTA, NO_STEP, NO_STEP, NO_ALPHA);
}
private FSkinColor(Colors baseColor0, int brightnessDelta0, int step0, int contrastStep0, float alpha0) {
baseColor = baseColor0;
brightnessDelta = brightnessDelta0;
step = step0;
contrastStep = contrastStep0;
alpha = alpha0;
updateColor();
}
private FSkinColor(Color color0, int brightnessDelta0, int step0, int contrastStep0, float alpha0) {
color = color0;
baseColor = null;
brightnessDelta = brightnessDelta0;
step = step0;
contrastStep = contrastStep0;
alpha = alpha0;
updateColor();
}
private FSkinColor getDerivedColor(int brightnessDelta0, int step0, int contrastStep0, float alpha0) {
if (baseColor == null) { //handle deriving from standard color
return new FSkinColor(color, brightnessDelta0, step0, contrastStep0, alpha0);
}
String key = baseColor.name() + "|" + brightnessDelta0 + "|" + step0 + "|" + contrastStep0 + "|" + alpha0;
FSkinColor derivedColor = derivedColors.get(key);
if (derivedColor == null) {
derivedColor = new FSkinColor(baseColor, brightnessDelta0, step0, contrastStep0, alpha0);
derivedColors.put(key, derivedColor);
}
return derivedColor;
}
public FSkinColor brighter() {
return getDerivedColor(brightnessDelta + 1, step, contrastStep, alpha);
}
public FSkinColor darker() {
return getDerivedColor(brightnessDelta - 1, step, contrastStep, alpha);
}
public FSkinColor stepColor(int step0) {
if (step != NO_STEP) {
step0 += step;
}
return getDerivedColor(brightnessDelta, step0, contrastStep, alpha);
}
public FSkinColor getContrastColor(int contrastStep0) {
if (contrastStep != NO_STEP) {
contrastStep0 += contrastStep;
}
return getDerivedColor(brightnessDelta, step, contrastStep0, alpha);
}
public FSkinColor getHighContrastColor() {
return getContrastColor(255);
}
public FSkinColor alphaColor(float alpha0) {
return getDerivedColor(brightnessDelta, step, contrastStep, alpha0);
}
protected void updateColor() {
if (baseColor != null) {
color = baseColor.color;
}
if (brightnessDelta != NO_BRIGHTNESS_DELTA) {
if (brightnessDelta < 0) {
for (int i = 0; i > brightnessDelta; i--) {
color = FSkinColor.stepColor(color, -20);
}
}
else {
for (int i = 0; i < brightnessDelta; i++) {
color = FSkinColor.stepColor(color, 20);
}
}
}
if (step != NO_STEP) {
color = FSkinColor.stepColor(color, step);
}
if (contrastStep != NO_STEP) {
color = FSkinColor.stepColor(color, FSkinColor.isColorBright(color) ? -contrastStep : contrastStep);
}
if (alpha != NO_ALPHA) {
color = FSkinColor.alphaColor(color, alpha);
}
}
/** Steps RGB components of a color up or down.
* Returns opaque (non-alpha) stepped color.
* Plus for lighter, minus for darker.
*
* @param clr0 {Color}
* @param step int
* @return {@link Color}
*/
public static Color stepColor(Color clr0, int step) {
float r = clr0.r * 255;
float g = clr0.g * 255;
float b = clr0.b * 255;
// Darker
if (step < 0) {
r = ((r + step > 0) ? r + step : 0);
g = ((g + step > 0) ? g + step : 0);
b = ((b + step > 0) ? b + step : 0);
}
else {
r = ((r + step < 255) ? r + step : 255);
g = ((g + step < 255) ? g + step : 255);
b = ((b + step < 255) ? b + step : 255);
}
return new Color(r / 255, g / 255, b / 255, clr0.a);
}
/**
* Returns RGB components of a color, with a new
* value for alpha. 0f = transparent, 1f = opaque.
*/
public static Color alphaColor(Color clr0, float alpha) {
return new Color(clr0.r, clr0.g, clr0.b, alpha);
}
/**
* see http://www.nbdtech.com/Blog/archive/2008/04/27/Calculating-the-Perceived-Brightness-of-a-Color.aspx
*/
public static boolean isColorBright(Color c) {
double v = Math.sqrt(
c.r * c.r * 0.241 +
c.g * c.g * 0.691 +
c.b * c.b * 0.068);
return v > 0.5;
}
public static Color getHighContrastColor(Color c) {
return isColorBright(c) ? Color.BLACK : Color.WHITE;
}
public static Color tintColor(Color source, Color tint, float alpha) {
float r = (tint.r - source.r) * alpha + source.r;
float g = (tint.g - source.g) * alpha + source.g;
float b = (tint.b - source.b) * alpha + source.b;
return new Color(r, g, b, 1f);
}
public static Color[] tintColors(Color source, Color[] tints, float alpha) {
Color[] tintedColors = new Color[tints.length];
for (int i = 0; i < tints.length; i++) {
tintedColors[i] = tintColor(source, tints[i], alpha);
}
return tintedColors;
}
public static Color fromRGB(int r, int g, int b) {
return new Color((float)r / 255f, (float)g / 255f, (float)b / 255f, 1f);
}
public static void updateAll() {
if (FSkinColor.baseColors.size() == 0) { //initialize base skin colors if needed
for (final Colors c : Colors.values()) {
FSkinColor.baseColors.put(c, new FSkinColor(c));
}
}
else { //update existing FSkinColors if baseColors already initialized
for (final FSkinColor c : FSkinColor.baseColors.values()) {
c.updateColor();
}
for (final FSkinColor c : FSkinColor.derivedColors.values()) {
c.updateColor();
}
}
TargetingOverlay.updateColors();
}
public float getAlpha() {
return color.a;
}
}

View File

@@ -0,0 +1,476 @@
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.Gdx;
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.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.BitmapFont.BitmapFontData;
import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph;
import com.badlogic.gdx.graphics.g2d.PixmapPacker;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter;
import com.badlogic.gdx.graphics.glutils.PixmapTextureData;
import com.badlogic.gdx.utils.Array;
import forge.adventure.libgdxgui.Forge;
import forge.adventure.libgdxgui.util.TextBounds;
import forge.adventure.libgdxgui.util.Utils;
import forge.gui.FThreads;
import forge.localinstance.properties.ForgeConstants;
import forge.util.FileUtil;
import forge.util.LineReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class FSkinFont {
private static final int MIN_FONT_SIZE = 8;
private static int MAX_FONT_SIZE = 72;
private static final int MAX_FONT_SIZE_LESS_GLYPHS = 72;
private static final int MAX_FONT_SIZE_MANY_GLYPHS = 36;
private static final String TTF_FILE = "font1.ttf";
private static final Map<Integer, FSkinFont> fonts = new HashMap<>();
private static final String commonCharacterSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm"
+ "nopqrstuvwxyz1234567890\"!?'.,;:()[]{}<>|/@\\^$-%+=#_&*\u2014"
+ "\u2022ÁÉÍÓÚáéíóúÀÈÌÒÙàèìòùÑñÄËÏÖÜäëïöüẞß¿¡";
private static final Map<String, String> langUniqueCharacterSet = new HashMap<>();
static {
FileUtil.ensureDirectoryExists(ForgeConstants.FONTS_DIR);
}
public static FSkinFont get(final int unscaledSize) {
return _get((int) Utils.scale(unscaledSize));
}
public static FSkinFont _get(final int scaledSize) {
FSkinFont skinFont = fonts.get(scaledSize);
if (skinFont == null) {
skinFont = new FSkinFont(scaledSize);
fonts.put(scaledSize, skinFont);
}
return skinFont;
}
public static FSkinFont forHeight(final float height) {
int size = MIN_FONT_SIZE + 1;
while (true) {
if (_get(size).getLineHeight() > height) {
return _get(size - 1);
}
size++;
}
}
//pre-load all supported font sizes
public static void preloadAll(String language) {
//todo:really check the language glyph is a lot
MAX_FONT_SIZE = (language.equals("zh-CN") || language.equals("ja-JP")) ? MAX_FONT_SIZE_MANY_GLYPHS : MAX_FONT_SIZE_LESS_GLYPHS;
for (int size = MIN_FONT_SIZE; size <= MAX_FONT_SIZE; size++) {
_get(size);
}
}
//delete all cached font files
public static void deleteCachedFiles() {
final FileHandle dir = Gdx.files.absolute(ForgeConstants.FONTS_DIR);
for (FileHandle fontFile : dir.list()) {
String name = fontFile.name();
if (name.endsWith(".fnt") || name.endsWith(".png")) {
fontFile.delete();
}
}
}
public static void updateAll() {
for (FSkinFont skinFont : fonts.values()) {
skinFont.updateFont();
}
}
private final int fontSize;
private final float scale;
private BitmapFont font;
private FSkinFont(int fontSize0) {
if (fontSize0 > MAX_FONT_SIZE) {
scale = (float)fontSize0 / MAX_FONT_SIZE;
}
else if (fontSize0 < MIN_FONT_SIZE) {
scale = (float)fontSize0 / MIN_FONT_SIZE;
}
else {
scale = 1;
}
fontSize = fontSize0;
updateFont();
}
static int indexOf (CharSequence text, char ch, int start) {
final int n = text.length();
for (; start < n; start++)
if (text.charAt(start) == ch) return start;
return n;
}
public int computeVisibleGlyphs (CharSequence str, int start, int end, float availableWidth) {
BitmapFontData data = font.getData();
int index = start;
float width = 0;
Glyph lastGlyph = null;
availableWidth /= data.scaleX;
for (; index < end; index++) {
char ch = str.charAt(index);
if (ch == '[' && data.markupEnabled) {
index++;
if (!(index < end && str.charAt(index) == '[')) { // non escaped '['
while (index < end && str.charAt(index) != ']')
index++;
continue;
}
}
Glyph g = data.getGlyph(ch);
if (g != null) {
if (lastGlyph != null) width += lastGlyph.getKerning(ch);
if ((width + g.xadvance) - availableWidth > 0.001f) break;
width += g.xadvance;
lastGlyph = g;
}
}
return index - start;
}
public boolean isBreakChar (char c) {
BitmapFontData data = font.getData();
if (data.breakChars == null) return false;
for (char br : data.breakChars)
if (c == br) return true;
return false;
}
static boolean isWhitespace (char c) {
switch (c) {
case '\n':
case '\r':
case '\t':
case ' ':
return true;
default:
return false;
}
}
// Expose methods from font that updates scale as needed
public TextBounds getBounds(CharSequence str) {
updateScale(); //must update scale before measuring text
return getBounds(str, 0, str.length());
}
public TextBounds getBounds(CharSequence str, int start, int end) {
BitmapFontData data = font.getData();
//int start = 0;
//int end = str.length();
int width = 0;
Glyph lastGlyph = null;
while (start < end) {
char ch = str.charAt(start++);
if (ch == '[' && data.markupEnabled) {
if (!(start < end && str.charAt(start) == '[')) { // non escaped '['
while (start < end && str.charAt(start) != ']')
start++;
start++;
continue;
}
start++;
}
lastGlyph = data.getGlyph(ch);
if (lastGlyph != null) {
width = lastGlyph.xadvance;
break;
}
}
while (start < end) {
char ch = str.charAt(start++);
if (ch == '[' && data.markupEnabled) {
if (!(start < end && str.charAt(start) == '[')) { // non escaped '['
while (start < end && str.charAt(start) != ']')
start++;
start++;
continue;
}
start++;
}
Glyph g = data.getGlyph(ch);
if (g != null) {
width += lastGlyph.getKerning(ch);
lastGlyph = g;
width += g.xadvance;
}
}
return new TextBounds(width * data.scaleX, data.capHeight);
}
public TextBounds getMultiLineBounds(CharSequence str) {
updateScale();
BitmapFontData data = font.getData();
int start = 0;
float maxWidth = 0;
int numLines = 0;
int length = str.length();
while (start < length) {
int lineEnd = indexOf(str, '\n', start);
float lineWidth = getBounds(str, start, lineEnd).width;
maxWidth = Math.max(maxWidth, lineWidth);
start = lineEnd + 1;
numLines++;
}
return new TextBounds(maxWidth, data.capHeight + (numLines - 1) * data.lineHeight);
}
public TextBounds getWrappedBounds(CharSequence str, float wrapWidth) {
updateScale();
BitmapFontData data = font.getData();
if (wrapWidth <= 0) wrapWidth = Integer.MAX_VALUE;
int start = 0;
int numLines = 0;
int length = str.length();
float maxWidth = 0;
while (start < length) {
int newLine = indexOf(str, '\n', start);
int lineEnd = start + computeVisibleGlyphs(str, start, newLine, wrapWidth);
int nextStart = lineEnd + 1;
if (lineEnd < newLine) {
// Find char to break on.
while (lineEnd > start) {
if (isWhitespace(str.charAt(lineEnd))) break;
if (isBreakChar(str.charAt(lineEnd - 1))) break;
lineEnd--;
}
if (lineEnd == start) {
if (nextStart > start + 1) nextStart--;
lineEnd = nextStart; // If no characters to break, show all.
} else {
nextStart = lineEnd;
// Eat whitespace at start of wrapped line.
while (nextStart < length) {
char c = str.charAt(nextStart);
if (!isWhitespace(c)) break;
nextStart++;
if (c == '\n') break; // Eat only the first wrapped newline.
}
// Eat whitespace at end of line.
while (lineEnd > start) {
if (!isWhitespace(str.charAt(lineEnd - 1))) break;
lineEnd--;
}
}
}
if (lineEnd > start) {
float lineWidth = getBounds(str, start, lineEnd).width;
maxWidth = Math.max(maxWidth, lineWidth);
}
start = nextStart;
numLines++;
}
return new TextBounds(maxWidth, data.capHeight + (numLines - 1) * data.lineHeight);
}
public float getAscent() {
updateScale();
return font.getAscent();
}
public float getCapHeight() {
updateScale();
return font.getCapHeight();
}
public float getLineHeight() {
updateScale();
return font.getLineHeight();
}
public void draw(Batch batch, String text, Color color, float x, float y, float w, boolean wrap, int horzAlignment) {
updateScale();
font.setColor(color);
font.draw(batch, text, x, y, w, horzAlignment, wrap);
}
//update scale of font if needed
private void updateScale() {
if (font.getScaleX() != scale) {
font.getData().setScale(scale);
}
}
public boolean canShrink() {
return fontSize > MIN_FONT_SIZE;
}
public FSkinFont shrink() {
return _get(fontSize - 1);
}
public String getCharacterSet(String langCode) {
if (langUniqueCharacterSet.containsKey(langCode)) {
return langUniqueCharacterSet.get(langCode);
}
StringBuilder characters = new StringBuilder(commonCharacterSet);
Set<Integer> characterSet = new HashSet<>();
for (int offset = 0; offset < commonCharacterSet.length();) {
final int codePoint = commonCharacterSet.codePointAt(offset);
characterSet.add(codePoint);
offset += Character.charCount(codePoint);
}
String[] translationFilePaths = { ForgeConstants.LANG_DIR + "cardnames-" + langCode + ".txt",
ForgeConstants.LANG_DIR + langCode + ".properties" };
for (int i = 0; i < translationFilePaths.length; i++) {
try (LineReader translationFile = new LineReader(new FileInputStream(translationFilePaths[i]),
StandardCharsets.UTF_8)) {
for (String fileLine : translationFile.readLines()) {
final int stringLength = fileLine.length();
for (int offset = 0; offset < stringLength;) {
final int codePoint = fileLine.codePointAt(offset);
if (!characterSet.contains(codePoint)) {
characterSet.add(codePoint);
characters.append(Character.toChars(codePoint));
}
offset += Character.charCount(codePoint);
}
}
translationFile.close();
} catch (IOException e) {
System.err.println("Error reading translation file: " + translationFilePaths[i]);
}
}
langUniqueCharacterSet.put(langCode, characters.toString());
return characters.toString();
}
private void updateFont() {
if (scale != 1) { //re-use font inside range if possible
if (fontSize > MAX_FONT_SIZE) {
font = _get(MAX_FONT_SIZE).font;
} else {
font = _get(MIN_FONT_SIZE).font;
}
return;
}
String fontName = "f" + fontSize;
if (Forge.locale.equals("zh-CN") || Forge.locale.equals("ja-JP")) {
fontName += Forge.locale;
}
FileHandle fontFile = Gdx.files.absolute(ForgeConstants.FONTS_DIR + fontName + ".fnt");
if (fontFile != null && fontFile.exists()) {
final BitmapFontData data = new BitmapFontData(fontFile, false);
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() { //font must be initialized on UI thread
font = new BitmapFont(data, (TextureRegion)null, true);
}
});
} else {
if (Forge.locale.equals("zh-CN") || Forge.locale.equals("ja-JP")) {
String ttfName = Forge.CJK_Font;
FileHandle ttfFile = Gdx.files.absolute(ForgeConstants.FONTS_DIR + ttfName + ".ttf");
if (ttfFile != null && ttfFile.exists()) {
generateFont(ttfFile, fontName, fontSize);
}
} else {
generateFont(FSkin.getSkinFile(TTF_FILE), fontName, fontSize);
}
}
}
private void generateFont(final FileHandle ttfFile, final String fontName, final int fontSize) {
if (!ttfFile.exists()) { return; }
final FreeTypeFontGenerator generator = new FreeTypeFontGenerator(ttfFile);
//approximate optimal page size
int pageSize;
if (fontSize >= 28) {
pageSize = 256;
}
else {
pageSize = 128;
}
final PixmapPacker packer = new PixmapPacker(pageSize, pageSize, Pixmap.Format.RGBA8888, 2, false);
final FreeTypeFontParameter parameter = new FreeTypeFontParameter();
parameter.characters = getCharacterSet(Forge.locale);
parameter.size = fontSize;
parameter.packer = packer;
final FreeTypeFontGenerator.FreeTypeBitmapFontData fontData = generator.generateData(parameter);
final Array<PixmapPacker.Page> pages = packer.getPages();
//finish generating font on UI thread
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() {
Array<TextureRegion> textureRegions = new Array<>();
for (int i = 0; i < pages.size; i++) {
PixmapPacker.Page p = pages.get(i);
Texture texture = new Texture(new PixmapTextureData(p.getPixmap(), p.getPixmap().getFormat(), false, false)) {
@Override
public void dispose() {
super.dispose();
getTextureData().consumePixmap().dispose();
}
};
texture.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest);
textureRegions.addAll(new TextureRegion(texture));
}
font = new BitmapFont(fontData, textureRegions, true);
//create .fnt and .png files for font
FileHandle pixmapDir = Gdx.files.absolute(ForgeConstants.FONTS_DIR);
if (pixmapDir != null) {
FileHandle fontFile = pixmapDir.child(fontName + ".fnt");
BitmapFontWriter.setOutputFormat(BitmapFontWriter.OutputFormat.Text);
String[] pageRefs = BitmapFontWriter.writePixmaps(packer.getPages(), pixmapDir, fontName);
BitmapFontWriter.writeFont(font.getData(), pageRefs, fontFile, new BitmapFontWriter.FontInfo(fontName, fontSize), 1, 1);
}
generator.dispose();
packer.dispose();
}
});
}
public static Iterable<String> getAllCJKFonts() {
final List<String> allCJKFonts = new ArrayList<>();
allCJKFonts.add("None");
final FileHandle dir = Gdx.files.absolute(ForgeConstants.FONTS_DIR);
for (FileHandle fontFile : dir.list()) {
String fontName = fontFile.name();
if (!fontName.endsWith(".ttf")) { continue; }
allCJKFonts.add(fontName.replace(".ttf", ""));
}
return allCJKFonts;
}
}

View File

@@ -0,0 +1,575 @@
package forge.adventure.libgdxgui.assets;
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.badlogic.gdx.graphics.g2d.TextureRegion;
import forge.adventure.libgdxgui.Forge;
import forge.adventure.libgdxgui.Graphics;
import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.skin.FSkinProp;
import forge.util.ImageUtil;
import java.util.Map;
/** Properties of various components that make up the skin.
* This interface allows all enums to be under the same roof.
* It also enforces a getter for coordinate locations in sprites. */
public enum FSkinImage implements FImage {
//Zones
HAND (FSkinProp.IMG_ZONE_HAND, SourceFile.ICONS),
HDHAND (FSkinProp.IMG_HDZONE_HAND, SourceFile.BUTTONS),
LIBRARY (FSkinProp.IMG_ZONE_LIBRARY, SourceFile.ICONS),
HDLIBRARY (FSkinProp.IMG_HDZONE_LIBRARY, SourceFile.BUTTONS),
EXILE (FSkinProp.IMG_ZONE_EXILE, SourceFile.ICONS),
HDEXILE (FSkinProp.IMG_HDZONE_EXILE, SourceFile.BUTTONS),
FLASHBACK (FSkinProp.IMG_ZONE_FLASHBACK, SourceFile.ICONS),
HDFLASHBACK (FSkinProp.IMG_HDZONE_FLASHBACK, SourceFile.BUTTONS),
GRAVEYARD (FSkinProp.IMG_ZONE_GRAVEYARD, SourceFile.ICONS),
HDGRAVEYARD (FSkinProp.IMG_HDZONE_GRAVEYARD, SourceFile.BUTTONS),
HDMANAPOOL (FSkinProp.IMG_HDZONE_MANAPOOL, SourceFile.BUTTONS),
POISON (FSkinProp.IMG_ZONE_POISON, SourceFile.ICONS),
//Mana symbols
MANA_COLORLESS (FSkinProp.IMG_MANA_COLORLESS, SourceFile.MANAICONS),
MANA_B (FSkinProp.IMG_MANA_B, SourceFile.MANAICONS),
MANA_R (FSkinProp.IMG_MANA_R, SourceFile.MANAICONS),
MANA_U (FSkinProp.IMG_MANA_U, SourceFile.MANAICONS),
MANA_G (FSkinProp.IMG_MANA_G, SourceFile.MANAICONS),
MANA_W (FSkinProp.IMG_MANA_W, SourceFile.MANAICONS),
MANA_2B (FSkinProp.IMG_MANA_2B, SourceFile.MANAICONS),
MANA_2G (FSkinProp.IMG_MANA_2G, SourceFile.MANAICONS),
MANA_2R (FSkinProp.IMG_MANA_2R, SourceFile.MANAICONS),
MANA_2U (FSkinProp.IMG_MANA_2U, SourceFile.MANAICONS),
MANA_2W (FSkinProp.IMG_MANA_2W, SourceFile.MANAICONS),
MANA_HYBRID_BG (FSkinProp.IMG_MANA_HYBRID_BG, SourceFile.MANAICONS),
MANA_HYBRID_BR (FSkinProp.IMG_MANA_HYBRID_BR, SourceFile.MANAICONS),
MANA_HYBRID_GU (FSkinProp.IMG_MANA_HYBRID_GU, SourceFile.MANAICONS),
MANA_HYBRID_GW (FSkinProp.IMG_MANA_HYBRID_GW, SourceFile.MANAICONS),
MANA_HYBRID_RG (FSkinProp.IMG_MANA_HYBRID_RG, SourceFile.MANAICONS),
MANA_HYBRID_RW (FSkinProp.IMG_MANA_HYBRID_RW, SourceFile.MANAICONS),
MANA_HYBRID_UB (FSkinProp.IMG_MANA_HYBRID_UB, SourceFile.MANAICONS),
MANA_HYBRID_UR (FSkinProp.IMG_MANA_HYBRID_UR, SourceFile.MANAICONS),
MANA_HYBRID_WB (FSkinProp.IMG_MANA_HYBRID_WB, SourceFile.MANAICONS),
MANA_HYBRID_WU (FSkinProp.IMG_MANA_HYBRID_WU, SourceFile.MANAICONS),
MANA_PHRYX_U (FSkinProp.IMG_MANA_PHRYX_U, SourceFile.MANAICONS),
MANA_PHRYX_W (FSkinProp.IMG_MANA_PHRYX_W, SourceFile.MANAICONS),
MANA_PHRYX_R (FSkinProp.IMG_MANA_PHRYX_R, SourceFile.MANAICONS),
MANA_PHRYX_G (FSkinProp.IMG_MANA_PHRYX_G, SourceFile.MANAICONS),
MANA_PHRYX_B (FSkinProp.IMG_MANA_PHRYX_B, SourceFile.MANAICONS),
MANA_SNOW (FSkinProp.IMG_MANA_SNOW, SourceFile.MANAICONS),
MANA_0 (FSkinProp.IMG_MANA_0, SourceFile.MANAICONS),
MANA_1 (FSkinProp.IMG_MANA_1, SourceFile.MANAICONS),
MANA_2 (FSkinProp.IMG_MANA_2, SourceFile.MANAICONS),
MANA_3 (FSkinProp.IMG_MANA_3, SourceFile.MANAICONS),
MANA_4 (FSkinProp.IMG_MANA_4, SourceFile.MANAICONS),
MANA_5 (FSkinProp.IMG_MANA_5, SourceFile.MANAICONS),
MANA_6 (FSkinProp.IMG_MANA_6, SourceFile.MANAICONS),
MANA_7 (FSkinProp.IMG_MANA_7, SourceFile.MANAICONS),
MANA_8 (FSkinProp.IMG_MANA_8, SourceFile.MANAICONS),
MANA_9 (FSkinProp.IMG_MANA_9, SourceFile.MANAICONS),
MANA_10 (FSkinProp.IMG_MANA_10, SourceFile.MANAICONS),
MANA_11 (FSkinProp.IMG_MANA_11, SourceFile.MANAICONS),
MANA_12 (FSkinProp.IMG_MANA_12, SourceFile.MANAICONS),
MANA_13 (FSkinProp.IMG_MANA_13, SourceFile.MANAICONS),
MANA_14 (FSkinProp.IMG_MANA_14, SourceFile.MANAICONS),
MANA_15 (FSkinProp.IMG_MANA_15, SourceFile.MANAICONS),
MANA_16 (FSkinProp.IMG_MANA_16, SourceFile.MANAICONS),
MANA_17 (FSkinProp.IMG_MANA_17, SourceFile.MANAICONS),
MANA_18 (FSkinProp.IMG_MANA_18, SourceFile.MANAICONS),
MANA_19 (FSkinProp.IMG_MANA_19, SourceFile.MANAICONS),
MANA_20 (FSkinProp.IMG_MANA_20, SourceFile.MANAICONS),
MANA_X (FSkinProp.IMG_MANA_X, SourceFile.MANAICONS),
MANA_Y (FSkinProp.IMG_MANA_Y, SourceFile.MANAICONS),
MANA_Z (FSkinProp.IMG_MANA_Z, SourceFile.MANAICONS),
//CMC ranges
CMC_LOW (FSkinProp.IMG_CMC_LOW, SourceFile.MANAICONS),
CMC_LOW_MID (FSkinProp.IMG_CMC_LOW_MID, SourceFile.MANAICONS),
CMC_MID_HIGH (FSkinProp.IMG_CMC_MID_HIGH, SourceFile.MANAICONS),
CMC_HIGH (FSkinProp.IMG_CMC_HIGH, SourceFile.MANAICONS),
//Gameplay
TAP (FSkinProp.IMG_TAP, SourceFile.MANAICONS),
UNTAP (FSkinProp.IMG_UNTAP, SourceFile.MANAICONS),
CHAOS (FSkinProp.IMG_CHAOS, SourceFile.ICONS),
SLASH (FSkinProp.IMG_SLASH, SourceFile.ICONS),
ATTACK (FSkinProp.IMG_ATTACK, SourceFile.ICONS),
DEFEND (FSkinProp.IMG_DEFEND, SourceFile.ICONS),
SUMMONSICK (FSkinProp.IMG_SUMMONSICK, SourceFile.ICONS),
PHASING (FSkinProp.IMG_PHASING, SourceFile.ICONS),
COSTRESERVED (FSkinProp.IMG_COSTRESERVED, SourceFile.ICONS),
COUNTERS1 (FSkinProp.IMG_COUNTERS1, SourceFile.ICONS),
COUNTERS2 (FSkinProp.IMG_COUNTERS2, SourceFile.ICONS),
COUNTERS3 (FSkinProp.IMG_COUNTERS3, SourceFile.ICONS),
COUNTERS_MULTI (FSkinProp.IMG_COUNTERS_MULTI, SourceFile.ICONS),
ENERGY (FSkinProp.IMG_ENERGY, SourceFile.ICONS),
//Dock Icons
SHORTCUTS (FSkinProp.ICO_SHORTCUTS, SourceFile.ICONS),
SETTINGS (FSkinProp.ICO_SETTINGS, SourceFile.ICONS),
ENDTURN (FSkinProp.ICO_ENDTURN, SourceFile.ICONS),
CONCEDE (FSkinProp.ICO_CONCEDE, SourceFile.ICONS),
REVERTLAYOUT (FSkinProp.ICO_REVERTLAYOUT, SourceFile.ICONS),
OPENLAYOUT (FSkinProp.ICO_OPENLAYOUT, SourceFile.ICONS),
SAVELAYOUT (FSkinProp.ICO_SAVELAYOUT, SourceFile.ICONS),
DECKLIST (FSkinProp.ICO_DECKLIST, SourceFile.ICONS),
ALPHASTRIKE (FSkinProp.ICO_ALPHASTRIKE, SourceFile.ICONS),
ARCSOFF (FSkinProp.ICO_ARCSOFF, SourceFile.ICONS),
ARCSON (FSkinProp.ICO_ARCSON, SourceFile.ICONS),
ARCSHOVER (FSkinProp.ICO_ARCSHOVER, SourceFile.ICONS),
//choice-search-misc
HDCHOICE (FSkinProp.ICO_HDCHOICE, SourceFile.BUTTONS),
HDSIDEBOARD (FSkinProp.ICO_HDSIDEBOARD, SourceFile.BUTTONS),
HDPREFERENCE (FSkinProp.ICO_HDPREFERENCE, SourceFile.BUTTONS),
HDIMPORT (FSkinProp.ICO_HDIMPORT, SourceFile.BUTTONS),
HDEXPORT (FSkinProp.ICO_HDEXPORT, SourceFile.BUTTONS),
HDYIELD (FSkinProp.ICO_HDYIELD, SourceFile.BUTTONS),
BLANK (FSkinProp.ICO_BLANK, SourceFile.ICONS),
//Achievement Trophies
COMMON_TROPHY (FSkinProp.IMG_COMMON_TROPHY, SourceFile.TROPHIES),
UNCOMMON_TROPHY (FSkinProp.IMG_UNCOMMON_TROPHY, SourceFile.TROPHIES),
RARE_TROPHY (FSkinProp.IMG_RARE_TROPHY, SourceFile.TROPHIES),
MYTHIC_TROPHY (FSkinProp.IMG_MYTHIC_TROPHY, SourceFile.TROPHIES),
SPECIAL_TROPHY (FSkinProp.IMG_SPECIAL_TROPHY, SourceFile.TROPHIES),
TROPHY_PLATE (FSkinProp.IMG_TROPHY_PLATE, SourceFile.TROPHIES),
TROPHY_CASE_TOP (FSkinProp.IMG_TROPHY_CASE_TOP, SourceFile.TROPHIES),
TROPHY_SHELF (FSkinProp.IMG_TROPHY_SHELF, SourceFile.TROPHIES),
//Planar Conquest Images
PLANE_MONITOR (FSkinProp.IMG_PLANE_MONITOR, SourceFile.PLANAR_CONQUEST),
AETHER_SHARD (FSkinProp.IMG_AETHER_SHARD, SourceFile.PLANAR_CONQUEST),
MULTIVERSE (FSkinProp.IMG_MULTIVERSE, SourceFile.PLANAR_CONQUEST),
SPELLBOOK (FSkinProp.IMG_SPELLBOOK, SourceFile.PLANAR_CONQUEST),
PW_BADGE_COMMON (FSkinProp.IMG_PW_BADGE_COMMON, SourceFile.PLANAR_CONQUEST),
PW_BADGE_UNCOMMON (FSkinProp.IMG_PW_BADGE_UNCOMMON, SourceFile.PLANAR_CONQUEST),
PW_BADGE_RARE (FSkinProp.IMG_PW_BADGE_RARE, SourceFile.PLANAR_CONQUEST),
PW_BADGE_MYTHIC (FSkinProp.IMG_PW_BADGE_MYTHIC, SourceFile.PLANAR_CONQUEST),
//Quest Icons
QUEST_ZEP (FSkinProp.ICO_QUEST_ZEP, SourceFile.ICONS),
QUEST_GEAR (FSkinProp.ICO_QUEST_GEAR, SourceFile.ICONS),
QUEST_GOLD (FSkinProp.ICO_QUEST_GOLD, SourceFile.ICONS),
QUEST_ELIXIR (FSkinProp.ICO_QUEST_ELIXIR, SourceFile.ICONS),
QUEST_BOOK (FSkinProp.ICO_QUEST_BOOK, SourceFile.ICONS),
QUEST_BOTTLES (FSkinProp.ICO_QUEST_BOTTLES, SourceFile.ICONS),
QUEST_BOX (FSkinProp.ICO_QUEST_BOX, SourceFile.ICONS),
QUEST_COIN (FSkinProp.ICO_QUEST_COIN, SourceFile.ICONS),
QUEST_CHARM (FSkinProp.ICO_QUEST_CHARM, SourceFile.ICONS),
QUEST_FOX (FSkinProp.ICO_QUEST_FOX, SourceFile.ICONS),
QUEST_LEAF (FSkinProp.ICO_QUEST_LEAF, SourceFile.ICONS),
QUEST_LIFE (FSkinProp.ICO_QUEST_LIFE, SourceFile.ICONS),
QUEST_COINSTACK (FSkinProp.ICO_QUEST_COINSTACK, SourceFile.ICONS),
QUEST_MAP (FSkinProp.ICO_QUEST_MAP, SourceFile.ICONS),
QUEST_NOTES (FSkinProp.ICO_QUEST_NOTES, SourceFile.ICONS),
QUEST_HEART (FSkinProp.ICO_QUEST_HEART, SourceFile.ICONS),
QUEST_BREW (FSkinProp.ICO_QUEST_BREW, SourceFile.ICONS),
QUEST_STAKES (FSkinProp.ICO_QUEST_STAKES, SourceFile.ICONS),
QUEST_MINUS (FSkinProp.ICO_QUEST_MINUS, SourceFile.ICONS),
QUEST_PLUS (FSkinProp.ICO_QUEST_PLUS, SourceFile.ICONS),
QUEST_PLUSPLUS (FSkinProp.ICO_QUEST_PLUSPLUS, SourceFile.ICONS),
QUEST_BIG_ELIXIR (FSkinProp.ICO_QUEST_BIG_ELIXIR, SourceFile.ICONS),
QUEST_BIG_BREW (FSkinProp.ICO_QUEST_BIG_BREW, SourceFile.ICONS),
QUEST_BIG_BM (FSkinProp.ICO_QUEST_BIG_BM, SourceFile.ICONS),
QUEST_BIG_STAKES (FSkinProp.ICO_QUEST_BIG_STAKES, SourceFile.ICONS),
QUEST_BIG_HOUSE (FSkinProp.ICO_QUEST_BIG_HOUSE, SourceFile.ICONS),
QUEST_BIG_COIN (FSkinProp.ICO_QUEST_BIG_COIN, SourceFile.ICONS),
QUEST_BIG_BOOK (FSkinProp.ICO_QUEST_BIG_BOOK, SourceFile.ICONS),
QUEST_BIG_MAP (FSkinProp.ICO_QUEST_BIG_MAP, SourceFile.ICONS),
QUEST_BIG_ZEP (FSkinProp.ICO_QUEST_BIG_ZEP, SourceFile.ICONS),
QUEST_BIG_CHARM (FSkinProp.ICO_QUEST_BIG_CHARM, SourceFile.ICONS),
QUEST_BIG_BOOTS (FSkinProp.ICO_QUEST_BIG_BOOTS, SourceFile.ICONS),
QUEST_BIG_SHIELD (FSkinProp.ICO_QUEST_BIG_SHIELD, SourceFile.ICONS),
QUEST_BIG_ARMOR (FSkinProp.ICO_QUEST_BIG_ARMOR, SourceFile.ICONS),
QUEST_BIG_AXE (FSkinProp.ICO_QUEST_BIG_AXE, SourceFile.ICONS),
QUEST_BIG_SWORD (FSkinProp.ICO_QUEST_BIG_SWORD, SourceFile.ICONS),
QUEST_BIG_BAG (FSkinProp.ICO_QUEST_BIG_BAG, SourceFile.ICONS),
//menu icon
MENU_GALAXY (FSkinProp.ICO_MENU_GALAXY, SourceFile.ICONS),
MENU_STATS (FSkinProp.ICO_MENU_STATS, SourceFile.ICONS),
MENU_PUZZLE (FSkinProp.ICO_MENU_PUZZLE, SourceFile.ICONS),
MENU_GAUNTLET (FSkinProp.ICO_MENU_GAUNTLET, SourceFile.ICONS),
MENU_SEALED (FSkinProp.ICO_MENU_SEALED, SourceFile.ICONS),
MENU_DRAFT (FSkinProp.ICO_MENU_DRAFT, SourceFile.ICONS),
MENU_CONSTRUCTED (FSkinProp.ICO_MENU_CONSTRUCTED, SourceFile.ICONS),
//Interface icons
QUESTION (FSkinProp.ICO_QUESTION, SourceFile.ICONS),
INFORMATION (FSkinProp.ICO_INFORMATION, SourceFile.ICONS),
WARNING (FSkinProp.ICO_WARNING, SourceFile.ICONS),
ERROR (FSkinProp.ICO_ERROR, SourceFile.ICONS),
DELETE (FSkinProp.ICO_DELETE, SourceFile.ICONS),
HDDELETE (FSkinProp.ICO_HDDELETE, SourceFile.BUTTONS),
DELETE_OVER (FSkinProp.ICO_DELETE_OVER, SourceFile.ICONS),
EDIT (FSkinProp.ICO_EDIT, SourceFile.ICONS),
HDEDIT (FSkinProp.ICO_HDEDIT, SourceFile.BUTTONS),
EDIT_OVER (FSkinProp.ICO_EDIT_OVER, SourceFile.ICONS),
OPEN (FSkinProp.ICO_OPEN, SourceFile.ICONS),
HDOPEN (FSkinProp.ICO_HDOPEN, SourceFile.BUTTONS),
MINUS (FSkinProp.ICO_MINUS, SourceFile.ICONS),
HDMINUS (FSkinProp.ICO_HDMINUS, SourceFile.BUTTONS),
NEW (FSkinProp.ICO_NEW, SourceFile.ICONS),
PLUS (FSkinProp.ICO_PLUS, SourceFile.ICONS),
HDPLUS (FSkinProp.ICO_HDPLUS, SourceFile.BUTTONS),
PRINT (FSkinProp.ICO_PRINT, SourceFile.ICONS),
SAVE (FSkinProp.ICO_SAVE, SourceFile.ICONS),
HDSAVE (FSkinProp.ICO_HDSAVE, SourceFile.BUTTONS),
SAVEAS (FSkinProp.ICO_SAVEAS, SourceFile.ICONS),
HDSAVEAS (FSkinProp.ICO_HDSAVEAS, SourceFile.BUTTONS),
CLOSE (FSkinProp.ICO_CLOSE, SourceFile.ICONS),
LIST (FSkinProp.ICO_LIST, SourceFile.ICONS),
CARD_IMAGE (FSkinProp.ICO_CARD_IMAGE, SourceFile.ICONS),
FOLDER (FSkinProp.ICO_FOLDER, SourceFile.ICONS),
HDFOLDER (FSkinProp.ICO_HDFOLDER, SourceFile.BUTTONS),
SEARCH (FSkinProp.ICO_SEARCH, SourceFile.ICONS),
HDSEARCH (FSkinProp.ICO_HDSEARCH, SourceFile.BUTTONS),
UNKNOWN (FSkinProp.ICO_UNKNOWN, SourceFile.ICONS),
LOGO (FSkinProp.ICO_LOGO, SourceFile.ICONS),
FLIPCARD (FSkinProp.ICO_FLIPCARD, SourceFile.ICONS),
HDFLIPCARD (FSkinProp.ICO_HDFLIPCARD, SourceFile.BUTTONS),
FAVICON (FSkinProp.ICO_FAVICON, SourceFile.ICONS),
LOCK (FSkinProp.ICO_LOCK, SourceFile.ICONS),
//Layout images
HANDLE (FSkinProp.IMG_HANDLE, SourceFile.ICONS),
CUR_L (FSkinProp.IMG_CUR_L, SourceFile.ICONS),
CUR_R (FSkinProp.IMG_CUR_R, SourceFile.ICONS),
CUR_T (FSkinProp.IMG_CUR_T, SourceFile.ICONS),
CUR_B (FSkinProp.IMG_CUR_B, SourceFile.ICONS),
CUR_TAB (FSkinProp.IMG_CUR_TAB, SourceFile.ICONS),
//Editor images
STAR_OUTLINE (FSkinProp.IMG_STAR_OUTLINE, SourceFile.ICONS),
HDSTAR_OUTLINE (FSkinProp.IMG_HDSTAR_OUTLINE, SourceFile.BUTTONS),
STAR_FILLED (FSkinProp.IMG_STAR_FILLED, SourceFile.ICONS),
HDSTAR_FILLED (FSkinProp.IMG_HDSTAR_FILLED, SourceFile.BUTTONS),
ARTIFACT (FSkinProp.IMG_ARTIFACT, SourceFile.MANAICONS),
CREATURE (FSkinProp.IMG_CREATURE, SourceFile.MANAICONS),
ENCHANTMENT (FSkinProp.IMG_ENCHANTMENT, SourceFile.MANAICONS),
INSTANT (FSkinProp.IMG_INSTANT, SourceFile.MANAICONS),
LAND (FSkinProp.IMG_LAND, SourceFile.MANAICONS),
LANDLOGO (FSkinProp.IMG_LANDLOGO, SourceFile.MANAICONS),
MULTI (FSkinProp.IMG_MULTI, SourceFile.ICONS),
HDMULTI (FSkinProp.IMG_HDMULTI, SourceFile.MANAICONS),
PLANESWALKER (FSkinProp.IMG_PLANESWALKER, SourceFile.MANAICONS),
PACK (FSkinProp.IMG_PACK, SourceFile.ICONS),
SORCERY (FSkinProp.IMG_SORCERY, SourceFile.MANAICONS),
COMMANDER (FSkinProp.IMG_COMMANDER, SourceFile.ICONS),
//Buttons
BTN_START_UP (FSkinProp.IMG_BTN_START_UP, SourceFile.ICONS),
BTN_START_OVER (FSkinProp.IMG_BTN_START_OVER, SourceFile.ICONS),
BTN_START_DOWN (FSkinProp.IMG_BTN_START_DOWN, SourceFile.ICONS),
BTN_UP_LEFT (FSkinProp.IMG_BTN_UP_LEFT, SourceFile.ICONS),
BTN_UP_CENTER (FSkinProp.IMG_BTN_UP_CENTER, SourceFile.ICONS),
BTN_UP_RIGHT (FSkinProp.IMG_BTN_UP_RIGHT, SourceFile.ICONS),
BTN_OVER_LEFT (FSkinProp.IMG_BTN_OVER_LEFT, SourceFile.ICONS),
BTN_OVER_CENTER (FSkinProp.IMG_BTN_OVER_CENTER, SourceFile.ICONS),
BTN_OVER_RIGHT (FSkinProp.IMG_BTN_OVER_RIGHT, SourceFile.ICONS),
BTN_DOWN_LEFT (FSkinProp.IMG_BTN_DOWN_LEFT, SourceFile.ICONS),
BTN_DOWN_CENTER (FSkinProp.IMG_BTN_DOWN_CENTER, SourceFile.ICONS),
BTN_DOWN_RIGHT (FSkinProp.IMG_BTN_DOWN_RIGHT, SourceFile.ICONS),
BTN_FOCUS_LEFT (FSkinProp.IMG_BTN_FOCUS_LEFT, SourceFile.ICONS),
BTN_FOCUS_CENTER (FSkinProp.IMG_BTN_FOCUS_CENTER, SourceFile.ICONS),
BTN_FOCUS_RIGHT (FSkinProp.IMG_BTN_FOCUS_RIGHT, SourceFile.ICONS),
BTN_TOGGLE_LEFT (FSkinProp.IMG_BTN_TOGGLE_LEFT, SourceFile.ICONS),
BTN_TOGGLE_CENTER (FSkinProp.IMG_BTN_TOGGLE_CENTER, SourceFile.ICONS),
BTN_TOGGLE_RIGHT (FSkinProp.IMG_BTN_TOGGLE_RIGHT, SourceFile.ICONS),
BTN_DISABLED_LEFT (FSkinProp.IMG_BTN_DISABLED_LEFT, SourceFile.ICONS),
BTN_DISABLED_CENTER (FSkinProp.IMG_BTN_DISABLED_CENTER, SourceFile.ICONS),
BTN_DISABLED_RIGHT (FSkinProp.IMG_BTN_DISABLED_RIGHT, SourceFile.ICONS),
//Hdbuttons
HDBTN_START_UP (FSkinProp.IMG_HDBTN_START_UP, SourceFile.BTNSTART),
HDBTN_START_OVER (FSkinProp.IMG_HDBTN_START_OVER, SourceFile.BTNSTART),
HDBTN_START_DOWN (FSkinProp.IMG_HDBTN_START_DOWN, SourceFile.BTNSTART),
HDBTN_UP_LEFT (FSkinProp.IMG_HDBTN_UP_LEFT, SourceFile.BUTTONS),
HDBTN_UP_CENTER (FSkinProp.IMG_HDBTN_UP_CENTER, SourceFile.BUTTONS),
HDBTN_UP_RIGHT (FSkinProp.IMG_HDBTN_UP_RIGHT, SourceFile.BUTTONS),
HDBTN_OVER_LEFT (FSkinProp.IMG_HDBTN_OVER_LEFT, SourceFile.BUTTONS),
HDBTN_OVER_CENTER (FSkinProp.IMG_HDBTN_OVER_CENTER, SourceFile.BUTTONS),
HDBTN_OVER_RIGHT (FSkinProp.IMG_HDBTN_OVER_RIGHT, SourceFile.BUTTONS),
HDBTN_DOWN_LEFT (FSkinProp.IMG_HDBTN_DOWN_LEFT, SourceFile.BUTTONS),
HDBTN_DOWN_CENTER (FSkinProp.IMG_HDBTN_DOWN_CENTER, SourceFile.BUTTONS),
HDBTN_DOWN_RIGHT (FSkinProp.IMG_HDBTN_DOWN_RIGHT, SourceFile.BUTTONS),
HDBTN_FOCUS_LEFT (FSkinProp.IMG_HDBTN_FOCUS_LEFT, SourceFile.BUTTONS),
HDBTN_FOCUS_CENTER (FSkinProp.IMG_HDBTN_FOCUS_CENTER, SourceFile.BUTTONS),
HDBTN_FOCUS_RIGHT (FSkinProp.IMG_HDBTN_FOCUS_RIGHT, SourceFile.BUTTONS),
HDBTN_TOGGLE_LEFT (FSkinProp.IMG_HDBTN_TOGGLE_LEFT, SourceFile.BUTTONS),
HDBTN_TOGGLE_CENTER (FSkinProp.IMG_HDBTN_TOGGLE_CENTER, SourceFile.BUTTONS),
HDBTN_TOGGLE_RIGHT (FSkinProp.IMG_HDBTN_TOGGLE_RIGHT, SourceFile.BUTTONS),
HDBTN_DISABLED_LEFT (FSkinProp.IMG_HDBTN_DISABLED_LEFT, SourceFile.BUTTONS),
HDBTN_DISABLED_CENTER (FSkinProp.IMG_HDBTN_DISABLED_CENTER, SourceFile.BUTTONS),
HDBTN_DISABLED_RIGHT (FSkinProp.IMG_HDBTN_DISABLED_RIGHT, SourceFile.BUTTONS),
//Foils
FOIL_01 (FSkinProp.FOIL_01, SourceFile.FOILS),
FOIL_02 (FSkinProp.FOIL_02, SourceFile.FOILS),
FOIL_03 (FSkinProp.FOIL_03, SourceFile.FOILS),
FOIL_04 (FSkinProp.FOIL_04, SourceFile.FOILS),
FOIL_05 (FSkinProp.FOIL_05, SourceFile.FOILS),
FOIL_06 (FSkinProp.FOIL_06, SourceFile.FOILS),
FOIL_07 (FSkinProp.FOIL_07, SourceFile.FOILS),
FOIL_08 (FSkinProp.FOIL_08, SourceFile.FOILS),
FOIL_09 (FSkinProp.FOIL_09, SourceFile.FOILS),
FOIL_10 (FSkinProp.FOIL_10, SourceFile.FOILS),
//Old Foils
FOIL_11 (FSkinProp.FOIL_11, SourceFile.OLD_FOILS),
FOIL_12 (FSkinProp.FOIL_12, SourceFile.OLD_FOILS),
FOIL_13 (FSkinProp.FOIL_13, SourceFile.OLD_FOILS),
FOIL_14 (FSkinProp.FOIL_14, SourceFile.OLD_FOILS),
FOIL_15 (FSkinProp.FOIL_15, SourceFile.OLD_FOILS),
FOIL_16 (FSkinProp.FOIL_16, SourceFile.OLD_FOILS),
FOIL_17 (FSkinProp.FOIL_17, SourceFile.OLD_FOILS),
FOIL_18 (FSkinProp.FOIL_18, SourceFile.OLD_FOILS),
FOIL_19 (FSkinProp.FOIL_19, SourceFile.OLD_FOILS),
FOIL_20 (FSkinProp.FOIL_20, SourceFile.OLD_FOILS),
//COMMANDER
IMG_ABILITY_COMMANDER (FSkinProp.IMG_ABILITY_COMMANDER, SourceFile.ABILITIES),
//ABILITY ICONS
IMG_ABILITY_DEATHTOUCH (FSkinProp.IMG_ABILITY_DEATHTOUCH, SourceFile.ABILITIES),
IMG_ABILITY_DEFENDER (FSkinProp.IMG_ABILITY_DEFENDER, SourceFile.ABILITIES),
IMG_ABILITY_DOUBLE_STRIKE (FSkinProp.IMG_ABILITY_DOUBLE_STRIKE, SourceFile.ABILITIES),
IMG_ABILITY_FIRST_STRIKE (FSkinProp.IMG_ABILITY_FIRST_STRIKE, SourceFile.ABILITIES),
IMG_ABILITY_FEAR (FSkinProp.IMG_ABILITY_FEAR, SourceFile.ABILITIES),
IMG_ABILITY_FLASH (FSkinProp.IMG_ABILITY_FLASH, SourceFile.ABILITIES),
IMG_ABILITY_FLYING (FSkinProp.IMG_ABILITY_FLYING, SourceFile.ABILITIES),
IMG_ABILITY_HASTE (FSkinProp.IMG_ABILITY_HASTE, SourceFile.ABILITIES),
IMG_ABILITY_HEXPROOF (FSkinProp.IMG_ABILITY_HEXPROOF, SourceFile.ABILITIES),
IMG_ABILITY_HORSEMANSHIP (FSkinProp.IMG_ABILITY_HORSEMANSHIP, SourceFile.ABILITIES),
IMG_ABILITY_INDESTRUCTIBLE (FSkinProp.IMG_ABILITY_INDESTRUCTIBLE, SourceFile.ABILITIES),
IMG_ABILITY_INTIMIDATE (FSkinProp.IMG_ABILITY_INTIMIDATE, SourceFile.ABILITIES),
IMG_ABILITY_LANDWALK (FSkinProp.IMG_ABILITY_LANDWALK, SourceFile.ABILITIES),
IMG_ABILITY_LIFELINK (FSkinProp.IMG_ABILITY_LIFELINK, SourceFile.ABILITIES),
IMG_ABILITY_MENACE (FSkinProp.IMG_ABILITY_MENACE, SourceFile.ABILITIES),
IMG_ABILITY_REACH (FSkinProp.IMG_ABILITY_REACH, SourceFile.ABILITIES),
IMG_ABILITY_SHADOW (FSkinProp.IMG_ABILITY_SHADOW, SourceFile.ABILITIES),
IMG_ABILITY_SHROUD (FSkinProp.IMG_ABILITY_SHROUD, SourceFile.ABILITIES),
IMG_ABILITY_TRAMPLE (FSkinProp.IMG_ABILITY_TRAMPLE, SourceFile.ABILITIES),
IMG_ABILITY_VIGILANCE (FSkinProp.IMG_ABILITY_VIGILANCE, SourceFile.ABILITIES),
//HEXPROOF FROM
IMG_ABILITY_HEXPROOF_R (FSkinProp.IMG_ABILITY_HEXPROOF_R, SourceFile.ABILITIES),
IMG_ABILITY_HEXPROOF_G (FSkinProp.IMG_ABILITY_HEXPROOF_G, SourceFile.ABILITIES),
IMG_ABILITY_HEXPROOF_B (FSkinProp.IMG_ABILITY_HEXPROOF_B, SourceFile.ABILITIES),
IMG_ABILITY_HEXPROOF_U (FSkinProp.IMG_ABILITY_HEXPROOF_U, SourceFile.ABILITIES),
IMG_ABILITY_HEXPROOF_W (FSkinProp.IMG_ABILITY_HEXPROOF_W, SourceFile.ABILITIES),
IMG_ABILITY_HEXPROOF_C (FSkinProp.IMG_ABILITY_HEXPROOF_C, SourceFile.ABILITIES),
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),
IMG_ABILITY_PROTECT_BU (FSkinProp.IMG_ABILITY_PROTECT_BU, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_BW (FSkinProp.IMG_ABILITY_PROTECT_BW, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_COLOREDSPELLS (FSkinProp.IMG_ABILITY_PROTECT_COLOREDSPELLS, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_G (FSkinProp.IMG_ABILITY_PROTECT_G, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_GB (FSkinProp.IMG_ABILITY_PROTECT_GB, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_GU (FSkinProp.IMG_ABILITY_PROTECT_GU, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_GW (FSkinProp.IMG_ABILITY_PROTECT_GW, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_GENERIC (FSkinProp.IMG_ABILITY_PROTECT_GENERIC, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_R (FSkinProp.IMG_ABILITY_PROTECT_R, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_RB (FSkinProp.IMG_ABILITY_PROTECT_RB, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_RG (FSkinProp.IMG_ABILITY_PROTECT_RG, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_RU (FSkinProp.IMG_ABILITY_PROTECT_RU, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_RW (FSkinProp.IMG_ABILITY_PROTECT_RW, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_U (FSkinProp.IMG_ABILITY_PROTECT_U, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_UW (FSkinProp.IMG_ABILITY_PROTECT_UW, SourceFile.ABILITIES),
IMG_ABILITY_PROTECT_W (FSkinProp.IMG_ABILITY_PROTECT_W, SourceFile.ABILITIES);
public enum SourceFile {
ICONS(ForgeConstants.SPRITE_ICONS_FILE),
FOILS(ForgeConstants.SPRITE_FOILS_FILE),
OLD_FOILS(ForgeConstants.SPRITE_OLD_FOILS_FILE),
TROPHIES(ForgeConstants.SPRITE_TROPHIES_FILE),
ABILITIES(ForgeConstants.SPRITE_ABILITY_FILE),
BORDERS(ForgeConstants.SPRITE_BORDER_FILE),
BUTTONS(ForgeConstants.SPRITE_BUTTONS_FILE),
BTNSTART(ForgeConstants.SPRITE_START_FILE),
MANAICONS(ForgeConstants.SPRITE_MANAICONS_FILE),
PLANAR_CONQUEST(ForgeConstants.SPRITE_PLANAR_CONQUEST_FILE);
private final String filename;
SourceFile(String filename0) {
filename = filename0;
}
public String getFilename() {
return filename;
}
}
private final int x, y, w, h;
private final SourceFile sourceFile;
private TextureRegion textureRegion;
FSkinImage(FSkinProp skinProp, SourceFile sourceFile0) {
int[] coords = skinProp.getCoords();
x = coords[0];
y = coords[1];
w = coords[2];
h = coords[3];
sourceFile = sourceFile0;
FSkin.getImages().put(skinProp, this);
}
public void load(Map<String, Texture> textures, Pixmap preferredIcons) {
String filename = sourceFile.getFilename();
FileHandle preferredFile = FSkin.getSkinFile(filename);
Texture texture = textures.get(preferredFile.path());
if (texture == null) {
if (preferredFile.exists()) {
try {
if (Forge.isTextureFilteringEnabled()){
texture = new Texture(preferredFile, true);
texture.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
} else {
texture = new Texture(preferredFile);
}
}
catch (final Exception e) {
System.err.println("Failed to load skin file: " + preferredFile);
e.printStackTrace();
}
}
}
if (texture != null) {
if (!(sourceFile == SourceFile.ICONS || sourceFile == SourceFile.MANAICONS)) { //just return region for preferred file if not icons file
textureRegion = new TextureRegion(texture, x, y, w, h);
return;
}
int fullWidth = texture.getWidth();
int fullHeight = texture.getHeight();
// Test if requested sub-image in inside bounds of preferred sprite.
// (Height and width of preferred sprite were set in loadFontAndImages.)
if (x + w <= fullWidth && y + h <= fullHeight) {
// Test if various points of requested sub-image are transparent.
// If any return true, image exists.
int x0 = 0, y0 = 0;
Color c;
// Center
x0 = (x + w / 2);
y0 = (y + h / 2);
c = new Color(preferredIcons.getPixel(x0, y0));
if (c.a != 0) {
textureRegion = new TextureRegion(texture, x, y, w, h);
return;
}
x0 += 2;
y0 += 2;
c = new Color(preferredIcons.getPixel(x0, y0));
if (c.a != 0) {
textureRegion = new TextureRegion(texture, x, y, w, h);
return;
}
x0 -= 4;
c = new Color(preferredIcons.getPixel(x0, y0));
if (c.a != 0) {
textureRegion = new TextureRegion(texture, x, y, w, h);
return;
}
y0 -= 4;
c = new Color(preferredIcons.getPixel(x0, y0));
if (c.a != 0) {
textureRegion = new TextureRegion(texture, x, y, w, h);
return;
}
x0 += 4;
c = new Color(preferredIcons.getPixel(x0, y0));
if (c.a != 0) {
textureRegion = new TextureRegion(texture, x, y, w, h);
return;
}
}
}
//use default file if can't use preferred file
FileHandle defaultFile = FSkin.getDefaultSkinFile(filename);
texture = textures.get(defaultFile.path());
if (texture == null) {
if (defaultFile.exists()) {
try {
if (Forge.isTextureFilteringEnabled()){
texture = new Texture(defaultFile, true);
texture.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
} else {
texture = new Texture(defaultFile);
}
}
catch (final Exception e) {
System.err.println("Failed to load skin file: " + defaultFile);
e.printStackTrace();
}
}
}
if (texture != null) {
textureRegion = new TextureRegion(texture, x, y, w, h);
}
}
@Override
public float getWidth() {
return w;
}
@Override
public float getHeight() {
return h;
}
public TextureRegion getTextureRegion() {
return textureRegion;
}
public float getNearestHQWidth(float baseWidth) {
return ImageUtil.getNearestHQSize(baseWidth, w);
}
public float getNearestHQHeight(float baseHeight) {
return ImageUtil.getNearestHQSize(baseHeight, h);
}
@Override
public void draw(Graphics g, float x, float y, float w, float h) {
g.drawImage(textureRegion, x, y, w, h);
}
}

View File

@@ -0,0 +1,190 @@
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureWrap;
import forge.adventure.libgdxgui.Graphics;
import forge.localinstance.properties.ForgeConstants;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public enum FSkinTexture implements FImage {
BG_TEXTURE(ForgeConstants.TEXTURE_BG_FILE, true, false),
BG_MATCH(ForgeConstants.MATCH_BG_FILE, false, false),
BG_SPACE(ForgeConstants.SPACE_BG_FILE, false, false),
BG_CHAOS_WHEEL(ForgeConstants.CHAOS_WHEEL_IMG_FILE, false, false),
Academy_at_Tolaria_West(ForgeConstants.BG_1, false, true),
Agyrem(ForgeConstants.BG_2, false, true),
Akoum(ForgeConstants.BG_3, false, true),
Aretopolis(ForgeConstants.BG_4, false, true),
Astral_Arena(ForgeConstants.BG_5, false, true),
Bant(ForgeConstants.BG_6, false, true),
Bloodhill_Bastion(ForgeConstants.BG_7, false, true),
Cliffside_Market(ForgeConstants.BG_8, false, true),
Edge_of_Malacol(ForgeConstants.BG_9, false, true),
Eloren_Wilds(ForgeConstants.BG_10, false, true),
Feeding_Grounds(ForgeConstants.BG_11, false, true),
Fields_of_Summer(ForgeConstants.BG_12, false, true),
Furnace_Layer(ForgeConstants.BG_13, false, true),
Gavony(ForgeConstants.BG_14, false, true),
Glen_Elendra(ForgeConstants.BG_15, false, true),
Glimmervoid_Basin(ForgeConstants.BG_16, false, true),
Goldmeadow(ForgeConstants.BG_17, false, true),
Grand_Ossuary(ForgeConstants.BG_18, false, true),
Grixis(ForgeConstants.BG_19, false, true),
Grove_of_the_Dreampods(ForgeConstants.BG_20, false, true),
Hedron_Fields_of_Agadeem(ForgeConstants.BG_21, false, true),
Immersturm(ForgeConstants.BG_22, false, true),
Isle_of_Vesuva(ForgeConstants.BG_23, false, true),
Izzet_Steam_Maze(ForgeConstants.BG_24, false, true),
Jund(ForgeConstants.BG_25, false, true),
Kessig(ForgeConstants.BG_26, false, true),
Kharasha_Foothills(ForgeConstants.BG_27, false, true),
Kilnspire_District(ForgeConstants.BG_28, false, true),
Krosa(ForgeConstants.BG_29, false, true),
Lair_of_the_Ashen_Idol(ForgeConstants.BG_30, false, true),
Lethe_Lake(ForgeConstants.BG_31, false, true),
Llanowar(ForgeConstants.BG_32, false, true),
Minamo(ForgeConstants.BG_33, false, true),
Mount_Keralia(ForgeConstants.BG_34, false, true),
Murasa(ForgeConstants.BG_35, false, true),
Naar_Isle(ForgeConstants.BG_36, false, true),
Naya(ForgeConstants.BG_37, false, true),
Nephalia(ForgeConstants.BG_38, false, true),
Norns_Dominion(ForgeConstants.BG_39, false, true),
Onakke_Catacomb(ForgeConstants.BG_40, false, true),
Orochi_Colony(ForgeConstants.BG_41, false, true),
Orzhova(ForgeConstants.BG_42, false, true),
Otaria(ForgeConstants.BG_43, false, true),
Panopticon(ForgeConstants.BG_44, false, true),
Pools_of_Becoming(ForgeConstants.BG_45, false, true),
Prahv(ForgeConstants.BG_46, false, true),
Quicksilver_Sea(ForgeConstants.BG_47, false, true),
Ravens_Run(ForgeConstants.BG_48, false, true),
Sanctum_of_Serra(ForgeConstants.BG_49, false, true),
Sea_of_Sand(ForgeConstants.BG_50, false, true),
Selesnya_Loft_Gardens(ForgeConstants.BG_51, false, true),
Shiv(ForgeConstants.BG_52, false, true),
Skybreen(ForgeConstants.BG_53, false, true),
Sokenzan(ForgeConstants.BG_54, false, true),
Stairs_to_Infinity(ForgeConstants.BG_55, false, true),
Stensia(ForgeConstants.BG_56, false, true),
Stronghold_Furnace(ForgeConstants.BG_57, false, true),
Takenuma(ForgeConstants.BG_58, false, true),
Tazeem(ForgeConstants.BG_59, false, true),
The_Aether_Flues(ForgeConstants.BG_60, false, true),
The_Dark_Barony(ForgeConstants.BG_61, false, true),
The_Eon_Fog(ForgeConstants.BG_62, false, true),
The_Fourth_Sphere(ForgeConstants.BG_63, false, true),
The_Great_Forest(ForgeConstants.BG_64, false, true),
The_Hippodrome(ForgeConstants.BG_65, false, true),
The_Maelstrom(ForgeConstants.BG_66, false, true),
The_Zephyr_Maze(ForgeConstants.BG_67, false, true),
Trail_of_the_MageRings(ForgeConstants.BG_68, false, true),
Truga_Jungle(ForgeConstants.BG_69, false, true),
Turri_Island(ForgeConstants.BG_70, false, true),
Undercity_Reaches(ForgeConstants.BG_71, false, true),
Velis_Vel(ForgeConstants.BG_72, false, true),
Windriddle_Palaces(ForgeConstants.BG_73, false, true),
Tember_City(ForgeConstants.BG_74, false, true),
Celestine_Reef(ForgeConstants.BG_75, false, true),
Horizon_Boughs(ForgeConstants.BG_76, false, true),
Mirrored_Depths(ForgeConstants.BG_77, false, true),
Talon_Gates(ForgeConstants.BG_78, false, true);
private final String filename;
private final boolean repeat;
private Texture texture;
private final boolean isPlane;
private static final List<String> PlanesValue;
FSkinTexture(String filename0, boolean repeat0, boolean isPlane0) {
filename = filename0;
repeat = repeat0;
isPlane = isPlane0;
}
static {
PlanesValue = new ArrayList<>();
for (FSkinTexture PlanesEnum : FSkinTexture.values()) {
PlanesValue.add(PlanesEnum.filename
.replace(".jpg", "")
.replace("'", "")
.replace("-", ""));
}
}
public static List<String> getValues() {
return Collections.unmodifiableList(PlanesValue);
}
public void load() {
FileHandle preferredFile = isPlane ? FSkin.getCachePlanechaseFile(filename) : FSkin.getSkinFile(filename);
if (preferredFile.exists()) {
try {
texture = new Texture(preferredFile);
}
catch (final Exception e) {
System.err.println("Failed to load skin file: " + preferredFile);
e.printStackTrace();
}
}
if (texture == null) {
//use default file if can't use preferred file
FileHandle defaultFile = FSkin.getDefaultSkinFile(filename);
if(isPlane) {
defaultFile = FSkin.getSkinFile(ForgeConstants.MATCH_BG_FILE);
if(!defaultFile.exists())
defaultFile = FSkin.getDefaultSkinFile(ForgeConstants.MATCH_BG_FILE);
}
if (defaultFile.exists()) {
try {
texture = new Texture(defaultFile);
}
catch (final Exception e) {
System.err.println("Failed to load skin file: " + defaultFile);
e.printStackTrace();
return;
}
}
else {
System.err.println("Failed to load skin file: " + defaultFile);
return;
}
}
if (repeat) {
texture.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);
}
}
@Override
public float getWidth() {
return texture.getWidth();
}
@Override
public float getHeight() {
return texture.getHeight();
}
@Override
public void draw(Graphics g, float x, float y, float w, float h) {
if (repeat) {
g.drawRepeatingImage(texture, x, y, w, h);
}
else {
g.drawImage(texture, x, y, w, h);
}
}
public void drawRotated(Graphics g, float x, float y, float w, float h, float rotation) {
g.drawRotatedImage(texture, x, y, w, h, x + w / 2, y + h / 2, rotation);
}
public void drawFlipped(Graphics g, float x, float y, float w, float h) {
g.drawFlippedImage(texture, x, y, w, h);
}
}

View File

@@ -0,0 +1,42 @@
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.graphics.Texture;
import forge.adventure.libgdxgui.Graphics;
public class FTextureImage extends FImageComplex {
private final Texture texture;
public FTextureImage(Texture texture0) {
texture = texture0;
}
@Override
public float getWidth() {
return texture.getWidth();
}
@Override
public float getHeight() {
return texture.getHeight();
}
@Override
public Texture getTexture() {
return texture;
}
@Override
public int getRegionX() {
return 0;
}
@Override
public int getRegionY() {
return 0;
}
@Override
public void draw(Graphics g, float x, float y, float w, float h) {
g.drawImage(texture, x, y, w, h);
}
}

View File

@@ -0,0 +1,43 @@
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import forge.adventure.libgdxgui.Graphics;
public class FTextureRegionImage extends FImageComplex {
private final TextureRegion textureRegion;
public FTextureRegionImage(TextureRegion textureRegion0) {
textureRegion = textureRegion0;
}
@Override
public float getWidth() {
return textureRegion.getRegionWidth();
}
@Override
public float getHeight() {
return textureRegion.getRegionHeight();
}
@Override
public Texture getTexture() {
return textureRegion.getTexture();
}
@Override
public int getRegionX() {
return textureRegion.getRegionX();
}
@Override
public int getRegionY() {
return textureRegion.getRegionY();
}
@Override
public void draw(Graphics g, float x, float y, float w, float h) {
g.drawImage(textureRegion, x, y, w, h);
}
}

View File

@@ -0,0 +1,337 @@
/*
* 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.adventure.libgdxgui.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.badlogic.gdx.graphics.g2d.TextureRegion;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import forge.adventure.libgdxgui.Forge;
import forge.ImageKeys;
import forge.adventure.libgdxgui.card.CardRenderer;
import forge.card.CardEdition;
import forge.deck.Deck;
import forge.game.card.CardView;
import forge.game.player.IHasIcon;
import forge.item.InventoryItem;
import forge.item.PaperCard;
import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.properties.ForgePreferences;
import forge.model.FModel;
import forge.util.ImageUtil;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* This class stores ALL card images in a cache with soft values. this means
* that the images may be collected when they are not needed any more, but will
* be kept as long as possible.
* <p/>
* The keys are the following:
* <ul>
* <li>Keys start with the file name, extension is skipped</li>
* <li>The key without suffix belongs to the unmodified image from the file</li>
* </ul>
*
* @author Forge
* @version $Id: ImageCache.java 24769 2014-02-09 13:56:04Z Hellfish $
*/
public class ImageCache {
// short prefixes to save memory
private static final Set<String> missingIconKeys = new HashSet<>();
private static LoadingCache<String, Texture> cache;
public static void initCache(int capacity) {
cache = CacheBuilder.newBuilder()
.maximumSize(capacity)
.expireAfterAccess(15, TimeUnit.MINUTES)
.removalListener(new RemovalListener<String, Texture>() {
@Override
public void onRemoval(RemovalNotification<String, Texture> removalNotification) {
if (removalNotification.wasEvicted()) {
if (removalNotification.getValue() != ImageCache.defaultImage)
removalNotification.getValue().dispose();
CardRenderer.clearcardArtCache();
}
}
})
.build(new ImageLoader());
System.out.println("Card Texture Cache Size: "+capacity);
}
private static final LoadingCache<String, Texture> otherCache = CacheBuilder.newBuilder().build(new OtherImageLoader());
public static final Texture defaultImage;
public static FImage BlackBorder = FSkinImage.IMG_BORDER_BLACK;
public static FImage WhiteBorder = FSkinImage.IMG_BORDER_WHITE;
private static final Map<String, Pair<String, Boolean>> imageBorder = new HashMap<>(1024);
private static boolean imageLoaded, delayLoadRequested;
public static void allowSingleLoad() {
imageLoaded = false; //reset at the beginning of each render
delayLoadRequested = false;
}
static {
Texture defImage = null;
try {
defImage = new Texture(Gdx.files.absolute(ForgeConstants.NO_CARD_FILE));
} catch (Exception ex) {
System.err.println("could not load default card image");
} finally {
defaultImage = (null == defImage) ? new Texture(10, 10, Format.RGBA8888) : defImage;
}
}
public static void clear() {
cache.invalidateAll();
cache.cleanUp();
missingIconKeys.clear();
}
public static void disposeTexture(){
CardRenderer.clearcardArtCache();
clear();
}
public static Texture getImage(InventoryItem ii) {
String imageKey = ii.getImageKey(false);
if (imageKey != null) {
if(imageKey.startsWith(ImageKeys.CARD_PREFIX) || imageKey.startsWith(ImageKeys.TOKEN_PREFIX))
return getImage(ii.getImageKey(false), true, false);
}
return getImage(ii.getImageKey(false), true, true);
}
/**
* retrieve an icon from the cache. returns the current skin's ICO_UNKNOWN if the icon image is not found
* in the cache and cannot be loaded from disk.
*/
public static FImage getIcon(IHasIcon ihi) {
String imageKey = ihi.getIconImageKey();
final Texture icon;
if (missingIconKeys.contains(imageKey) || (icon = getImage(ihi.getIconImageKey(), false, true)) == null) {
missingIconKeys.add(imageKey);
return FSkinImage.UNKNOWN;
}
return new FTextureImage(icon);
}
/**
* checks the card image exists from the disk.
*/
public static boolean imageKeyFileExists(String imageKey) {
if (StringUtils.isEmpty(imageKey))
return false;
if (imageKey.length() < 2)
return false;
final String prefix = imageKey.substring(0, 2);
if (prefix.equals(ImageKeys.CARD_PREFIX)) {
PaperCard paperCard = ImageUtil.getPaperCardFromImageKey(imageKey);
if (paperCard == null)
return false;
final boolean backFace = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX);
final String cardfilename = ImageUtil.getImageKey(paperCard, backFace, true);
if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".jpg").exists())
if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".png").exists())
return new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + TextUtil.fastReplace(cardfilename, ".full", ".fullborder") + ".jpg").exists();
} else if (prefix.equals(ImageKeys.TOKEN_PREFIX)) {
final String tokenfilename = imageKey.substring(2) + ".jpg";
return new File(ForgeConstants.CACHE_TOKEN_PICS_DIR, tokenfilename).exists();
}
return true;
}
/**
* This requests the original unscaled image from the cache for the given key.
* If the image does not exist then it can return a default image if desired.
* <p>
* If the requested image is not present in the cache then it attempts to load
* the image from file (slower) and then add it to the cache for fast future access.
* </p>
*/
public static Texture getImage(String imageKey, boolean useDefaultIfNotFound) {
return getImage(imageKey, useDefaultIfNotFound, false);
}
public static Texture getImage(String imageKey, boolean useDefaultIfNotFound, boolean useOtherCache) {
if (StringUtils.isEmpty(imageKey)) {
return null;
}
boolean altState = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX);
if (altState) {
imageKey = imageKey.substring(0, imageKey.length() - ImageKeys.BACKFACE_POSTFIX.length());
}
if (imageKey.startsWith(ImageKeys.CARD_PREFIX)) {
imageKey = ImageUtil.getImageKey(ImageUtil.getPaperCardFromImageKey(imageKey), altState, true);
if (StringUtils.isBlank(imageKey)) {
return defaultImage;
}
}
Texture image;
if (useDefaultIfNotFound) {
// Load from file and add to cache if not found in cache initially.
image = useOtherCache ? otherCache.getIfPresent(imageKey) : cache.getIfPresent(imageKey);
if (image != null) { return image; }
if (imageLoaded) { //prevent loading more than one image each render for performance
if (!delayLoadRequested) {
//ensure images continue to load even if no input is being received
delayLoadRequested = true;
Gdx.graphics.requestRendering();
}
return null;
}
imageLoaded = true;
}
try { image = useOtherCache ? otherCache.get(imageKey) : cache.get(imageKey); }
catch (final Exception ex) {
image = null;
}
// No image file exists for the given key so optionally associate with
// a default "not available" image and add to cache for given key.
if (image == null) {
if (useDefaultIfNotFound) {
image = defaultImage;
if (useOtherCache)
otherCache.put(imageKey, defaultImage);
else
cache.put(imageKey, defaultImage);
if (imageBorder.get(image.toString()) == null)
imageBorder.put(image.toString(), Pair.of(Color.valueOf("#171717").toString(), false)); //black border
}
}
return image;
}
public static void preloadCache(Iterable<String> keys) {
if (FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DISABLE_CARD_IMAGES))
return;
for (String imageKey : keys){
if(getImage(imageKey, false) == null)
System.err.println("could not load card image:"+imageKey);
}
}
public static void preloadCache(Deck deck) {
if (FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DISABLE_CARD_IMAGES))
return;
if(deck == null||!Forge.enablePreloadExtendedArt)
return;
if (deck.getAllCardsInASinglePool().toFlatList().size() <= 100) {
for (PaperCard p : deck.getAllCardsInASinglePool().toFlatList()) {
if (getImage(p.getImageKey(false),false) == null)
System.err.println("could not load card image:"+ p);
}
}
}
public static TextureRegion croppedBorderImage(Texture image) {
if (!image.toString().contains(".fullborder."))
return new TextureRegion(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)/2f);
int ry = Math.round((image.getHeight() - rh)/2f)-2;
return new TextureRegion(image, rx, ry, rw, rh);
}
public static Color borderColor(Texture t) {
if (t == null)
return Color.valueOf("#171717");
return Color.valueOf(imageBorder.get(t.toString()).getLeft());
}
public static int getFSkinBorders(CardView c) {
if (c == null)
return 0;
CardView.CardStateView state = c.getCurrentState();
CardEdition ed = FModel.getMagicDb().getEditions().get(state.getSetCode());
// TODO: Treatment for silver here
if (ed != null && ed.getBorderColor() == CardEdition.BorderColor.WHITE && state.getFoilIndex() == 0)
return 1;
return 0;
}
public static boolean isBorderlessCardArt(Texture t) {
return ImageLoader.isBorderless(t);
}
public static void updateBorders(String textureString, Pair<String, Boolean> colorPair){
imageBorder.put(textureString, colorPair);
}
public static FImage getBorder(String textureString) {
if (imageBorder.get(textureString) == null)
return BlackBorder;
return imageBorder.get(textureString).getRight() ? WhiteBorder : BlackBorder;
}
public static FImage getBorderImage(String textureString, boolean canshow) {
if (!canshow)
return BlackBorder;
return getBorder(textureString);
}
public static FImage getBorderImage(String textureString) {
return getBorder(textureString);
}
public static Color getTint(CardView c, Texture t) {
if (c == null)
return borderColor(t);
if (c.isFaceDown())
return Color.valueOf("#171717");
CardView.CardStateView state = c.getCurrentState();
if (state.getColors().isColorless()) { //Moonlace -> target spell or permanent becomes colorless.
if (state.hasDevoid()) //devoid is colorless at all zones so return its corresponding border color...
return borderColor(t);
return Color.valueOf("#A0A6A4");
}
else if (state.getColors().isMonoColor()) {
if (state.getColors().hasBlack())
return Color.valueOf("#48494a");
else if (state.getColors().hasBlue())
return Color.valueOf("#62b5f8");
else if (state.getColors().hasRed())
return Color.valueOf("#f6532d");
else if (state.getColors().hasGreen())
return Color.valueOf("#66cb35");
else if (state.getColors().hasWhite())
return Color.valueOf("#EEEBE1");
}
else if (state.getColors().isMulticolor())
return Color.valueOf("#F9E084");
return borderColor(t);
}
}

View File

@@ -0,0 +1,163 @@
package forge.adventure.libgdxgui.assets;
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.badlogic.gdx.graphics.TextureData;
import com.badlogic.gdx.graphics.glutils.PixmapTextureData;
import com.google.common.cache.CacheLoader;
import forge.adventure.libgdxgui.Forge;
import forge.ImageKeys;
import forge.gui.FThreads;
import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.properties.ForgePreferences;
import forge.model.FModel;
import forge.util.FileUtil;
import forge.util.TextUtil;
import org.apache.commons.lang3.tuple.Pair;
import java.io.File;
import java.util.List;
import static forge.adventure.libgdxgui.assets.ImageCache.croppedBorderImage;
final class ImageLoader extends CacheLoader<String, Texture> {
private static final List<String> borderlessCardlistKey = FileUtil.readFile(ForgeConstants.BORDERLESS_CARD_LIST_FILE);
Texture n;
@Override
public Texture load(String key) {
if (FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DISABLE_CARD_IMAGES))
return null;
boolean extendedArt = isBorderless(key) && Forge.enableUIMask.equals("Full");
boolean textureFilter = Forge.isTextureFilteringEnabled();
File file = ImageKeys.getImageFile(key);
if (file != null) {
FileHandle fh = new FileHandle(file);
try {
Texture t = new Texture(fh, textureFilter);
//update
ImageCache.updateBorders(t.toString(), extendedArt ? Pair.of(Color.valueOf("#171717").toString(), false): isCloserToWhite(getpixelColor(t)));
if (textureFilter)
t.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
if (extendedArt)
return generateTexture(fh, t, textureFilter);
return t;
}
catch (Exception ex) {
Forge.log("Could not read image file " + fh.path() + "\nException:\n" + ex);
return null;
}
}
return null;
}
public Texture generateTexture(FileHandle fh, Texture t, boolean textureFilter) {
if (t == null || fh == null)
return t;
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() {
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);
n = new Texture(textureData);
if (textureFilter)
n.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
pImage.dispose();
pMask.dispose();
}
});
return n;
}
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);
}
}
}
}
public boolean isBorderless(String imagekey) {
if(borderlessCardlistKey.isEmpty())
return false;
if (imagekey.length() > 7) {
if ((!imagekey.substring(0, 7).contains("MPS_KLD"))&&(imagekey.substring(0, 4).contains("MPS_"))) //MPS_ sets except MPD_KLD
return true;
}
return borderlessCardlistKey.contains(TextUtil.fastReplace(imagekey,".full",".fullborder"));
}
public static boolean isBorderless(Texture t) {
if(borderlessCardlistKey.isEmpty())
return false;
//generated texture/pixmap?
if (t.toString().contains("com.badlogic.gdx.graphics.Texture@"))
return true;
for (String key : borderlessCardlistKey) {
if (t.toString().contains(key))
return true;
}
return false;
}
public static String getpixelColor(Texture i) {
if (!i.getTextureData().isPrepared()) {
i.getTextureData().prepare(); //prepare texture
}
//get pixmap from texture data
Pixmap pixmap = i.getTextureData().consumePixmap();
//get pixel color from x,y texture coordinate based on the image fullborder or not
Color color = new Color(pixmap.getPixel(croppedBorderImage(i).getRegionX()+1, croppedBorderImage(i).getRegionY()+1));
pixmap.dispose();
return color.toString();
}
public static Pair<String, Boolean> isCloserToWhite(String c){
if (c == null || c == "")
return Pair.of(Color.valueOf("#171717").toString(), false);
int c_r = Integer.parseInt(c.substring(0,2),16);
int c_g = Integer.parseInt(c.substring(2,4),16);
int c_b = Integer.parseInt(c.substring(4,6),16);
int brightness = ((c_r * 299) + (c_g * 587) + (c_b * 114)) / 1000;
return Pair.of(c,brightness > 155);
}
}

View File

@@ -0,0 +1,34 @@
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.google.common.cache.CacheLoader;
import forge.adventure.libgdxgui.Forge;
import forge.ImageKeys;
import java.io.File;
final class OtherImageLoader extends CacheLoader<String, Texture> {
@Override
public Texture load(String key) {
File file = ImageKeys.getImageFile(key);
if (file != null) {
FileHandle fh = new FileHandle(file);
try {
if (Forge.isTextureFilteringEnabled()) {
Texture t = new Texture(fh, true);
t.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
return t;
} else {
return new Texture(fh);
}
}
catch (Exception ex) {
Forge.log("Could not read image file " + fh.path() + "\n\nException:\n" + ex);
return null;
}
}
return null;
}
}

View File

@@ -0,0 +1,649 @@
package forge.adventure.libgdxgui.assets;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.Align;
import forge.adventure.libgdxgui.Forge;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.libgdxgui.card.CardFaceSymbols;
import forge.localinstance.properties.ForgePreferences;
import forge.localinstance.properties.ForgePreferences.FPref;
import forge.model.FModel;
import forge.adventure.libgdxgui.util.TextBounds;
import java.text.BreakIterator;
import java.util.*;
//Encodes text for drawing with symbols and reminder text
public class TextRenderer {
private static final Map<String, FSkinImage> symbolLookup = new HashMap<>(64);
static {
symbolLookup.put("C", FSkinImage.MANA_COLORLESS);
symbolLookup.put("W", FSkinImage.MANA_W);
symbolLookup.put("U", FSkinImage.MANA_U);
symbolLookup.put("B", FSkinImage.MANA_B);
symbolLookup.put("R", FSkinImage.MANA_R);
symbolLookup.put("G", FSkinImage.MANA_G);
symbolLookup.put("W/U", FSkinImage.MANA_HYBRID_WU);
symbolLookup.put("U/B", FSkinImage.MANA_HYBRID_UB);
symbolLookup.put("B/R", FSkinImage.MANA_HYBRID_BR);
symbolLookup.put("R/G", FSkinImage.MANA_HYBRID_RG);
symbolLookup.put("G/W", FSkinImage.MANA_HYBRID_GW);
symbolLookup.put("W/B", FSkinImage.MANA_HYBRID_WB);
symbolLookup.put("U/R", FSkinImage.MANA_HYBRID_UR);
symbolLookup.put("B/G", FSkinImage.MANA_HYBRID_BG);
symbolLookup.put("R/W", FSkinImage.MANA_HYBRID_RW);
symbolLookup.put("G/U", FSkinImage.MANA_HYBRID_GU);
symbolLookup.put("2/W", FSkinImage.MANA_2W);
symbolLookup.put("2/U", FSkinImage.MANA_2U);
symbolLookup.put("2/B", FSkinImage.MANA_2B);
symbolLookup.put("2/R", FSkinImage.MANA_2R);
symbolLookup.put("2/G", FSkinImage.MANA_2G);
symbolLookup.put("P/W", FSkinImage.MANA_PHRYX_W);
symbolLookup.put("P/U", FSkinImage.MANA_PHRYX_U);
symbolLookup.put("P/B", FSkinImage.MANA_PHRYX_B);
symbolLookup.put("P/R", FSkinImage.MANA_PHRYX_R);
symbolLookup.put("P/G", FSkinImage.MANA_PHRYX_G);
symbolLookup.put("W/P", FSkinImage.MANA_PHRYX_W);
symbolLookup.put("U/P", FSkinImage.MANA_PHRYX_U);
symbolLookup.put("B/P", FSkinImage.MANA_PHRYX_B);
symbolLookup.put("R/P", FSkinImage.MANA_PHRYX_R);
symbolLookup.put("G/P", FSkinImage.MANA_PHRYX_G);
for (int i = 0; i <= 20; i++) {
symbolLookup.put(String.valueOf(i), FSkinImage.valueOf("MANA_" + i));
}
symbolLookup.put("X", FSkinImage.MANA_X);
symbolLookup.put("Y", FSkinImage.MANA_Y);
symbolLookup.put("Z", FSkinImage.MANA_Z);
symbolLookup.put("CHAOS", FSkinImage.CHAOS);
symbolLookup.put("Q", FSkinImage.UNTAP);
symbolLookup.put("S", FSkinImage.MANA_SNOW);
symbolLookup.put("T", FSkinImage.TAP);
symbolLookup.put("E", FSkinImage.ENERGY);
symbolLookup.put("AE", FSkinImage.AETHER_SHARD);
symbolLookup.put("PW", FSkinImage.PW_BADGE_COMMON);
symbolLookup.put("CR", FSkinImage.QUEST_COINSTACK);
}
public static String startColor(Color color) {
return "<clr " + Color.rgba8888(color) + ">";
}
public static String endColor() {
return "</clr>";
}
private final boolean parseReminderText;
private String fullText = "";
private float width, height, totalHeight;
private FSkinFont baseFont, font;
private boolean wrap, needClip;
private final List<Piece> pieces = new ArrayList<>();
private final List<Float> lineWidths = new ArrayList<>();
private final BreakIterator boundary = BreakIterator.getLineInstance(new Locale(Forge.locale));
public TextRenderer() {
this(false);
}
public TextRenderer(boolean parseReminderText0) {
parseReminderText = parseReminderText0;
}
//break text in pieces
private void updatePieces(FSkinFont font0) {
pieces.clear();
lineWidths.clear();
font = font0;
needClip = false;
if (fullText == null || fullText.isEmpty()) { return; }
totalHeight = font.getCapHeight();
if (totalHeight > height) {
//immediately try one font size smaller if no room for anything
if (font.canShrink()) {
updatePieces(font.shrink());
return;
}
needClip = true;
}
boundary.setText(fullText);
ForgePreferences prefs = FModel.getPreferences();
boolean hideReminderText = prefs != null && prefs.getPrefBoolean(FPref.UI_HIDE_REMINDER_TEXT);
char ch;
float x = 0;
float y = 0;
float pieceWidth = 0;
float lineHeight = font.getLineHeight();
int nextSpaceIdx = boundary.first();
int lastSpaceIdx = -1;
int lineNum = 0;
StringBuilder text = new StringBuilder();
int inSymbolCount = 0;
int consecutiveSymbols = 0;
int inKeywordCount = 0;
boolean atReminderTextEnd = false;
int inReminderTextCount = 0;
Color colorOverride = null;
for (int i = 0; i < fullText.length(); i++) {
atReminderTextEnd = false;
if (i == nextSpaceIdx) {
lastSpaceIdx = text.length();
nextSpaceIdx = boundary.next();
}
ch = fullText.charAt(i);
switch (ch) {
case '\r':
continue; //skip '\r' character
case '\n':
if (inSymbolCount > 0) {
inSymbolCount = 0;
text.insert(0, '{'); //if not a symbol, render as text
}
lineWidths.add(x + pieceWidth);
if (text.length() > 0) {
addPiece(new TextPiece(text.toString(), colorOverride, inReminderTextCount > 0), lineNum, x, y, pieceWidth, lineHeight);
pieceWidth = 0;
text.setLength(0);
consecutiveSymbols = 0;
}
lastSpaceIdx = -1;
x = 0;
y += lineHeight;
totalHeight += lineHeight;
lineNum++;
if (totalHeight > height) {
//try next font size down if out of space
if (font.canShrink()) {
updatePieces(font.shrink());
return;
}
needClip = true;
}
continue; //skip new line character
case '{':
if (inSymbolCount == 0 && text.length() > 0) { //add current text if just entering symbol
addPiece(new TextPiece(text.toString(), colorOverride,inReminderTextCount > 0), lineNum, x, y, pieceWidth, lineHeight);
x += pieceWidth;
pieceWidth = 0;
text.setLength(0);
lastSpaceIdx = -1;
consecutiveSymbols = 0;
}
inSymbolCount++;
continue; //skip '{' character
case '}':
if (inSymbolCount > 0) {
inSymbolCount--;
if (text.length() > 0) {
FSkinImage symbol = symbolLookup.get(text.toString());
if (symbol != null) {
pieceWidth = lineHeight * CardFaceSymbols.FONT_SIZE_FACTOR;
if (x + pieceWidth > width) {
if (wrap) {
y += lineHeight;
totalHeight += lineHeight;
lineNum++;
if (totalHeight > height) {
//try next font size down if out of space
if (font.canShrink()) {
updatePieces(font.shrink());
return;
}
needClip = true;
}
if (consecutiveSymbols == 0) {
lineWidths.add(x);
x = 0;
}
else { //make previous consecutive symbols wrap too if needed
x = 0;
int startSymbolIdx = pieces.size() - consecutiveSymbols;
lineWidths.add(pieces.get(startSymbolIdx).x);
for (int j = startSymbolIdx; j < pieces.size(); j++) {
Piece piece = pieces.get(j);
piece.x = x;
piece.y += lineHeight;
piece.lineNum++;
x += piece.w;
}
}
}
else if (font.canShrink()) {
//try next font size down if out of space
updatePieces(font.shrink());
return;
}
else {
needClip = true;
}
}
addPiece(new SymbolPiece(symbol, inReminderTextCount > 0), lineNum, x, y - font.getAscent() + (lineHeight - pieceWidth) / 2, pieceWidth, pieceWidth);
x += pieceWidth;
pieceWidth = 0;
text.setLength(0);
lastSpaceIdx = -1;
consecutiveSymbols++;
continue; //skip '}' character
}
}
if (!hideReminderText || inReminderTextCount == 0) {
text.insert(0, '{'); //if not a symbol, render as text
if (lastSpaceIdx >= 0) {
lastSpaceIdx++;
}
}
}
break;
case '<':
if (inSymbolCount > 0) {
inSymbolCount = 0;
text.insert(0, '{'); //if not a symbol, render as text
if (lastSpaceIdx >= 0) {
lastSpaceIdx++;
}
}
if (inKeywordCount == 0 && text.length() > 0) { //add current text if starting a keyword
addPiece(new TextPiece(text.toString(), colorOverride,false), lineNum, x, y, pieceWidth, lineHeight);
x += pieceWidth;
pieceWidth = 0;
text.setLength(0);
lastSpaceIdx = -1;
consecutiveSymbols = 0;
}
inKeywordCount++;
break;
case '>':
if (inSymbolCount > 0) {
inSymbolCount = 0;
text.insert(0, '{'); //if not a symbol, render as text
if (lastSpaceIdx >= 0) {
lastSpaceIdx++;
}
}
if (inKeywordCount > 0) {
inKeywordCount--;
if (inKeywordCount == 0 && text.length() > 0) {
String keyword, value;
text.deleteCharAt(0); //trim leading '<'
if (text.charAt(0) == '/') {
keyword = text.substring(1);
value = null;
}
else {
int idx = text.indexOf(" ");
if (idx != -1) {
keyword = text.substring(0, idx);
value = text.substring(idx + 1);
}
else {
keyword = text.toString();
value = null;
}
}
boolean validKeyword = true;
switch (keyword) {
case "clr":
colorOverride = value != null ? new Color(Integer.parseInt(value)) : null;
break;
default:
validKeyword = false;
break;
}
if (validKeyword) {
text.setLength(0);
lastSpaceIdx = -1;
continue; //skip '>' character
}
}
}
break;
case '(':
if (inSymbolCount > 0) {
inSymbolCount = 0;
text.insert(0, '{'); //if not a symbol, render as text
if (lastSpaceIdx >= 0) {
lastSpaceIdx++;
}
}
if (parseReminderText) {
if (inReminderTextCount == 0 && text.length() > 0) { //add current text if just entering reminder text
addPiece(new TextPiece(text.toString(), colorOverride,false), lineNum, x, y, pieceWidth, lineHeight);
x += pieceWidth;
pieceWidth = 0;
text.setLength(0);
lastSpaceIdx = -1;
consecutiveSymbols = 0;
}
inReminderTextCount++;
}
break;
case ')':
if (inSymbolCount > 0) {
inSymbolCount = 0;
text.insert(0, '{'); //if not a symbol, render as text
if (lastSpaceIdx >= 0) {
lastSpaceIdx++;
}
}
if (inReminderTextCount > 0) {
inReminderTextCount--;
if (inReminderTextCount == 0) {
atReminderTextEnd = true;
}
}
break;
case ' ':
if (inKeywordCount == 0 && inSymbolCount > 0) {
inSymbolCount = 0;
text.insert(0, '{'); //if not a symbol, render as text
if (lastSpaceIdx >= 0) {
lastSpaceIdx++;
}
}
break;
}
if (hideReminderText && (inReminderTextCount > 0 || atReminderTextEnd)) {
continue;
}
text.append(ch);
if (inSymbolCount == 0 && inKeywordCount == 0) {
pieceWidth = font.getBounds(text).width;
if (x + pieceWidth > width) { //wrap or shrink if needed
if (wrap && (lastSpaceIdx >= 0 || consecutiveSymbols > 0)) {
if (lastSpaceIdx < 0) {
//no space between symbols and end of line, wrap those symbols along with text
x = 0;
int startSymbolIdx = pieces.size() - consecutiveSymbols;
lineWidths.add(pieces.get(startSymbolIdx).x);
for (int j = startSymbolIdx; j < pieces.size(); j++) {
Piece piece = pieces.get(j);
piece.x = x;
piece.y += lineHeight;
piece.lineNum++;
x += piece.w;
}
}
else {
int endIdx = lastSpaceIdx;
if (lastSpaceIdx > 0 && text.charAt(lastSpaceIdx - 1) == ' ') {
endIdx = lastSpaceIdx - 1;
}
String currentLineText = text.substring(0, endIdx);
if (!currentLineText.isEmpty()) {
pieceWidth = font.getBounds(currentLineText).width;
addPiece(new TextPiece(currentLineText, colorOverride,inReminderTextCount > 0 || atReminderTextEnd), lineNum, x, y, pieceWidth, lineHeight);
consecutiveSymbols = 0;
}
else {
pieceWidth = 0;
}
lineWidths.add(x + pieceWidth);
text.delete(0, lastSpaceIdx);
x = 0;
}
lastSpaceIdx = -1;
pieceWidth = text.length() == 0 ? 0 : font.getBounds(text).width;
y += lineHeight;
totalHeight += lineHeight;
lineNum++;
if (totalHeight > height) {
//try next font size down if out of space
if (font.canShrink()) {
updatePieces(font.shrink());
return;
}
needClip = true;
}
}
else if (x > 0 && pieceWidth <= width) {
//if current piece starting past beginning of line and no spaces found,
//wrap current piece being built up along with part of previous pieces as needed
int lastPieceIdx;
for (lastPieceIdx = pieces.size() - 1; lastPieceIdx >= 0; lastPieceIdx--) {
Piece lastPiece = pieces.get(lastPieceIdx);
if (lastPiece.lineNum < lineNum) {
lastPieceIdx = pieces.size() - 1; //don't re-wrap anything if reached previous line
break;
}
if (lastPiece instanceof TextPiece) {
TextPiece textPiece = (TextPiece)lastPiece;
int index = textPiece.text.lastIndexOf(' ');
if (index != -1) {
if (index == 0) {
textPiece.text = textPiece.text.substring(1);
textPiece.w = font.getBounds(textPiece.text).width;
lastPieceIdx--;
}
else if (index == textPiece.text.length() - 1) {
textPiece.text = textPiece.text.substring(0, textPiece.text.length() - 1);
textPiece.w = font.getBounds(textPiece.text).width;
}
else {
TextPiece splitPiece = new TextPiece(textPiece.text.substring(index + 1), textPiece.colorOverride, textPiece.inReminderText);
textPiece.text = textPiece.text.substring(0, index);
textPiece.w = font.getBounds(textPiece.text).width;
splitPiece.x = textPiece.x + textPiece.w;
splitPiece.y = textPiece.y;
splitPiece.w = font.getBounds(splitPiece.text).width;
splitPiece.h = textPiece.h;
}
break;
}
}
}
if (lastPieceIdx >= 0) {
Piece lastPiece = pieces.get(lastPieceIdx);
lineWidths.add(lastPiece.x + lastPiece.w);
x = 0;
for (int j = lastPieceIdx + 1; j < pieces.size(); j++) {
Piece piece = pieces.get(j);
piece.x = x;
piece.y += lineHeight;
piece.lineNum++;
x += piece.w;
}
y += lineHeight;
totalHeight += lineHeight;
lineNum++;
if (totalHeight > height) {
//try next font size down if out of space
if (font.canShrink()) {
updatePieces(font.shrink());
return;
}
needClip = true;
}
} else {
if (font.canShrink()) {
//try next font size down if out of space
updatePieces(font.shrink());
return;
}
needClip = true;
}
}
else {
if (font.canShrink()) {
//try next font size down if out of space
updatePieces(font.shrink());
return;
}
needClip = true;
}
}
if (atReminderTextEnd && text.length() > 0) { //ensure final piece of reminder text added right away
addPiece(new TextPiece(text.toString(), colorOverride, true), lineNum, x, y, pieceWidth, lineHeight);
x += pieceWidth;
pieceWidth = 0;
text.setLength(0);
lastSpaceIdx = -1;
consecutiveSymbols = 0;
}
}
}
lineWidths.add(x + pieceWidth);
if (text.length() > 0) {
addPiece(new TextPiece(text.toString(), colorOverride, inReminderTextCount > 0), lineNum, x, y, pieceWidth, lineHeight);
consecutiveSymbols = 0;
}
}
private void addPiece(Piece piece, int lineNum, float x, float y, float w, float h) {
piece.lineNum = lineNum;
piece.x = x;
piece.y = y;
piece.w = w;
piece.h = h;
pieces.add(piece);
}
private void setProps(String text, FSkinFont skinFont, float w, float h, boolean wrap0) {
boolean needUpdate = false;
if (!fullText.equals(text)) {
fullText = text;
needUpdate = true;
}
if (skinFont != baseFont) {
baseFont = skinFont;
needUpdate = true;
}
if (width != w) {
width = w;
needUpdate = true;
}
if (height != h) {
height = h;
needUpdate = true;
}
if (wrap != wrap0) {
wrap = wrap0;
needUpdate = true;
}
if (needUpdate) {
updatePieces(baseFont);
}
}
private TextBounds getCurrentBounds() {
float maxWidth = 0;
for (Float lineWidth : lineWidths) {
if (lineWidth > maxWidth) {
maxWidth = lineWidth;
}
}
TextBounds bounds = new TextBounds();
bounds.width = maxWidth;
bounds.height = totalHeight;
return bounds;
}
public TextBounds getBounds(String text, FSkinFont skinFont) {
setProps(text, skinFont, Float.MAX_VALUE, Float.MAX_VALUE, false);
return getCurrentBounds();
}
public TextBounds getWrappedBounds(String text, FSkinFont skinFont, float maxWidth) {
setProps(text, skinFont, maxWidth, Float.MAX_VALUE, true);
return getCurrentBounds();
}
public void drawText(Graphics g, String text, FSkinFont skinFont, FSkinColor skinColor, float x, float y, float w, float h, float visibleStartY, float visibleHeight, boolean wrap0, int horzAlignment, boolean centerVertically) {
drawText(g, text, skinFont, skinColor.getColor(), x, y, w, h, visibleStartY, visibleHeight, wrap0, horzAlignment, centerVertically);
}
public void drawText(Graphics g, String text, FSkinFont skinFont, Color color, float x, float y, float w, float h, float visibleStartY, float visibleHeight, boolean wrap0, int horzAlignment, boolean centerVertically) {
setProps(text, skinFont, w, h, wrap0);
if (needClip) { //prevent text flowing outside region if couldn't shrink it to fit
g.startClip(x, y, w, h);
}
if (height > totalHeight && centerVertically) {
y += (height - totalHeight) / 2;
}
float[] alignmentOffsets = new float[lineWidths.size()];
for (int i = 0; i < lineWidths.size(); i++) {
switch (horzAlignment) {
case Align.left:
alignmentOffsets[i] = 0;
break;
case Align.center:
alignmentOffsets[i] = Math.max((width - lineWidths.get(i)) / 2, 0);
break;
case Align.right:
alignmentOffsets[i] = Math.max(width - lineWidths.get(i), 0);
break;
}
}
visibleStartY -= y; //subtract y to make calculation quicker
float visibleEndY = visibleStartY + visibleHeight;
for (Piece piece : pieces) {
if (piece.y + piece.h < visibleStartY) {
continue;
}
if (piece.y >= visibleEndY) {
break;
}
piece.draw(g, color, x + alignmentOffsets[piece.lineNum], y);
}
if (needClip) {
g.endClip();
}
}
private abstract class Piece {
protected static final float ALPHA_COMPOSITE = 0.5f;
protected final boolean inReminderText;
protected float x, y, w, h;
protected int lineNum;
protected Piece(boolean inReminderText0) {
inReminderText = inReminderText0;
}
public abstract void draw(Graphics g, Color color, float offsetX, float offsetY);
}
private class TextPiece extends Piece {
private String text;
private final Color colorOverride;
private TextPiece(String text0, Color colorOverride0, boolean inReminderText0) {
super(inReminderText0);
text = text0;
colorOverride = colorOverride0;
}
@Override
public void draw(Graphics g, Color color, float offsetX, float offsetY) {
if (colorOverride != null) {
color = colorOverride;
}
else if (inReminderText) {
color = FSkinColor.alphaColor(color, ALPHA_COMPOSITE);
}
g.drawText(text, font, color, x + offsetX, y + offsetY, w, h, false, Align.left, false);
}
}
private class SymbolPiece extends Piece {
private final FSkinImage image;
private SymbolPiece(FSkinImage image0, boolean inReminderText0) {
super(inReminderText0);
image = image0;
}
@Override
public void draw(Graphics g, Color color, float offsetX, float offsetY) {
if (inReminderText) {
g.setAlphaComposite(ALPHA_COMPOSITE);
}
g.drawImage(image, x + offsetX, y + offsetY, w, h);
if (inReminderText) {
g.resetAlphaComposite();
}
}
}
}

View File

@@ -0,0 +1,60 @@
package forge.adventure.libgdxgui.card;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.libgdxgui.assets.FImage;
import forge.adventure.libgdxgui.assets.ImageCache;
import forge.item.PaperCard;
public class CardAvatarImage implements FImage {
private final String imageKey;
private FImage image;
public CardAvatarImage(PaperCard card0) {
this(card0.getImageKey(false));
}
public CardAvatarImage(String imageKey0) {
imageKey = imageKey0;
}
@Override
public float getWidth() {
return getHeight(); //image will be drawn at its height
}
@Override
public float getHeight() {
if (image != null) {
return image.getHeight();
}
return ImageCache.defaultImage.getHeight() * CardRenderer.CARD_ART_HEIGHT_PERCENTAGE;
}
@Override
public void draw(Graphics g, float x, float y, float w, float h) {
//force to get the avatar since the the cardartcache & loadingcache is always cleared on screen change or the battle bar will display black
image = CardRenderer.getCardArt(imageKey, false, false, false);
if (image == null) {
return; //can't draw anything if can't be loaded yet
}
//draw scaled image into clipped region so it fills box while maintain aspect ratio
g.startClip(x, y, w, h);
float aspectRatio = w / h;
float imageAspectRatio = image.getWidth() / image.getHeight();
if (imageAspectRatio > aspectRatio) {
float w0 = w * imageAspectRatio / aspectRatio;
x -= (w0 - w) / 2;
w = w0;
}
else {
float h0 = h * aspectRatio / imageAspectRatio;
y -= (h0 - h) / 2;
h = h0;
}
image.draw(g, x, y, w, h);
g.endClip();
}
}

View File

@@ -0,0 +1,248 @@
/*
* 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.adventure.libgdxgui.card;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.libgdxgui.assets.FSkinImage;
import forge.card.ColorSet;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
import forge.gui.error.BugReporter;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
public class CardFaceSymbols {
public static final float FONT_SIZE_FACTOR = 0.85f;
private static final Map<String, FSkinImage> MANA_IMAGES = new HashMap<>(128);
public static void loadImages() {
for (int i = 0; i <= 20; i++) {
MANA_IMAGES.put(String.valueOf(i), FSkinImage.valueOf("MANA_" + i));
}
MANA_IMAGES.put("X", FSkinImage.MANA_X);
MANA_IMAGES.put("Y", FSkinImage.MANA_Y);
MANA_IMAGES.put("Z", FSkinImage.MANA_Z);
MANA_IMAGES.put("C", FSkinImage.MANA_COLORLESS);
MANA_IMAGES.put("B", FSkinImage.MANA_B);
MANA_IMAGES.put("BG", FSkinImage.MANA_HYBRID_BG);
MANA_IMAGES.put("BR", FSkinImage.MANA_HYBRID_BR);
MANA_IMAGES.put("G", FSkinImage.MANA_G);
MANA_IMAGES.put("GU", FSkinImage.MANA_HYBRID_GU);
MANA_IMAGES.put("GW", FSkinImage.MANA_HYBRID_GW);
MANA_IMAGES.put("R", FSkinImage.MANA_R);
MANA_IMAGES.put("RG", FSkinImage.MANA_HYBRID_RG);
MANA_IMAGES.put("RW", FSkinImage.MANA_HYBRID_RW);
MANA_IMAGES.put("U", FSkinImage.MANA_U);
MANA_IMAGES.put("UB", FSkinImage.MANA_HYBRID_UB);
MANA_IMAGES.put("UR", FSkinImage.MANA_HYBRID_UR);
MANA_IMAGES.put("W", FSkinImage.MANA_W);
MANA_IMAGES.put("WB", FSkinImage.MANA_HYBRID_WB);
MANA_IMAGES.put("WU", FSkinImage.MANA_HYBRID_WU);
MANA_IMAGES.put("PW", FSkinImage.MANA_PHRYX_W);
MANA_IMAGES.put("PR", FSkinImage.MANA_PHRYX_R);
MANA_IMAGES.put("PU", FSkinImage.MANA_PHRYX_U);
MANA_IMAGES.put("PB", FSkinImage.MANA_PHRYX_B);
MANA_IMAGES.put("PG", FSkinImage.MANA_PHRYX_G);
MANA_IMAGES.put("2W", FSkinImage.MANA_2W);
MANA_IMAGES.put("2U", FSkinImage.MANA_2U);
MANA_IMAGES.put("2R", FSkinImage.MANA_2R);
MANA_IMAGES.put("2G", FSkinImage.MANA_2G);
MANA_IMAGES.put("2B", FSkinImage.MANA_2B);
MANA_IMAGES.put("S", FSkinImage.MANA_SNOW);
MANA_IMAGES.put("T", FSkinImage.TAP);
MANA_IMAGES.put("E", FSkinImage.ENERGY);
MANA_IMAGES.put("slash", FSkinImage.SLASH);
MANA_IMAGES.put("attack", FSkinImage.ATTACK);
MANA_IMAGES.put("defend", FSkinImage.DEFEND);
MANA_IMAGES.put("summonsick", FSkinImage.SUMMONSICK);
MANA_IMAGES.put("phasing", FSkinImage.PHASING);
MANA_IMAGES.put("sacrifice", FSkinImage.COSTRESERVED);
MANA_IMAGES.put("counters1", FSkinImage.COUNTERS1);
MANA_IMAGES.put("counters2", FSkinImage.COUNTERS2);
MANA_IMAGES.put("counters3", FSkinImage.COUNTERS3);
MANA_IMAGES.put("countersMulti", FSkinImage.COUNTERS_MULTI);
MANA_IMAGES.put("foil01", FSkinImage.FOIL_01);
MANA_IMAGES.put("foil02", FSkinImage.FOIL_02);
MANA_IMAGES.put("foil03", FSkinImage.FOIL_03);
MANA_IMAGES.put("foil04", FSkinImage.FOIL_04);
MANA_IMAGES.put("foil05", FSkinImage.FOIL_05);
MANA_IMAGES.put("foil06", FSkinImage.FOIL_06);
MANA_IMAGES.put("foil07", FSkinImage.FOIL_07);
MANA_IMAGES.put("foil08", FSkinImage.FOIL_08);
MANA_IMAGES.put("foil09", FSkinImage.FOIL_09);
MANA_IMAGES.put("foil10", FSkinImage.FOIL_10);
MANA_IMAGES.put("foil11", FSkinImage.FOIL_11);
MANA_IMAGES.put("foil12", FSkinImage.FOIL_12);
MANA_IMAGES.put("foil13", FSkinImage.FOIL_13);
MANA_IMAGES.put("foil14", FSkinImage.FOIL_14);
MANA_IMAGES.put("foil15", FSkinImage.FOIL_15);
MANA_IMAGES.put("foil16", FSkinImage.FOIL_16);
MANA_IMAGES.put("foil17", FSkinImage.FOIL_17);
MANA_IMAGES.put("foil18", FSkinImage.FOIL_18);
MANA_IMAGES.put("foil19", FSkinImage.FOIL_19);
MANA_IMAGES.put("foil20", FSkinImage.FOIL_20);
MANA_IMAGES.put("commander", FSkinImage.IMG_ABILITY_COMMANDER);
MANA_IMAGES.put("deathtouch", FSkinImage.IMG_ABILITY_DEATHTOUCH);
MANA_IMAGES.put("defender", FSkinImage.IMG_ABILITY_DEFENDER);
MANA_IMAGES.put("doublestrike", FSkinImage.IMG_ABILITY_DOUBLE_STRIKE);
MANA_IMAGES.put("firststrike", FSkinImage.IMG_ABILITY_FIRST_STRIKE);
MANA_IMAGES.put("fear", FSkinImage.IMG_ABILITY_FEAR);
MANA_IMAGES.put("flash", FSkinImage.IMG_ABILITY_FLASH);
MANA_IMAGES.put("flying", FSkinImage.IMG_ABILITY_FLYING);
MANA_IMAGES.put("haste", FSkinImage.IMG_ABILITY_HASTE);
MANA_IMAGES.put("hexproof", FSkinImage.IMG_ABILITY_HEXPROOF);
MANA_IMAGES.put("horsemanship", FSkinImage.IMG_ABILITY_HORSEMANSHIP);
MANA_IMAGES.put("indestructible", FSkinImage.IMG_ABILITY_INDESTRUCTIBLE);
MANA_IMAGES.put("intimidate", FSkinImage.IMG_ABILITY_INTIMIDATE);
MANA_IMAGES.put("landwalk", FSkinImage.IMG_ABILITY_LANDWALK);
MANA_IMAGES.put("lifelink", FSkinImage.IMG_ABILITY_LIFELINK);
MANA_IMAGES.put("menace", FSkinImage.IMG_ABILITY_MENACE);
MANA_IMAGES.put("reach", FSkinImage.IMG_ABILITY_REACH);
MANA_IMAGES.put("shadow", FSkinImage.IMG_ABILITY_SHADOW);
MANA_IMAGES.put("shroud", FSkinImage.IMG_ABILITY_SHROUD);
MANA_IMAGES.put("trample", FSkinImage.IMG_ABILITY_TRAMPLE);
MANA_IMAGES.put("vigilance", FSkinImage.IMG_ABILITY_VIGILANCE);
//hexproof from
MANA_IMAGES.put("hexproofR", FSkinImage.IMG_ABILITY_HEXPROOF_R);
MANA_IMAGES.put("hexproofG", FSkinImage.IMG_ABILITY_HEXPROOF_G);
MANA_IMAGES.put("hexproofB", FSkinImage.IMG_ABILITY_HEXPROOF_B);
MANA_IMAGES.put("hexproofU", FSkinImage.IMG_ABILITY_HEXPROOF_U);
MANA_IMAGES.put("hexproofW", FSkinImage.IMG_ABILITY_HEXPROOF_W);
MANA_IMAGES.put("hexproofC", FSkinImage.IMG_ABILITY_HEXPROOF_C);
MANA_IMAGES.put("hexproofUB", FSkinImage.IMG_ABILITY_HEXPROOF_UB);
//token icon
MANA_IMAGES.put("token", FSkinImage.IMG_ABILITY_TOKEN);
//protection from
MANA_IMAGES.put("protectAll", FSkinImage.IMG_ABILITY_PROTECT_ALL);
MANA_IMAGES.put("protectB", FSkinImage.IMG_ABILITY_PROTECT_B);
MANA_IMAGES.put("protectBU", FSkinImage.IMG_ABILITY_PROTECT_BU);
MANA_IMAGES.put("protectBW", FSkinImage.IMG_ABILITY_PROTECT_BW);
MANA_IMAGES.put("protectColoredSpells", FSkinImage.IMG_ABILITY_PROTECT_COLOREDSPELLS);
MANA_IMAGES.put("protectG", FSkinImage.IMG_ABILITY_PROTECT_G);
MANA_IMAGES.put("protectGB", FSkinImage.IMG_ABILITY_PROTECT_GB);
MANA_IMAGES.put("protectGU", FSkinImage.IMG_ABILITY_PROTECT_GU);
MANA_IMAGES.put("protectGW", FSkinImage.IMG_ABILITY_PROTECT_GW);
MANA_IMAGES.put("protectGeneric", FSkinImage.IMG_ABILITY_PROTECT_GENERIC);
MANA_IMAGES.put("protectR", FSkinImage.IMG_ABILITY_PROTECT_R);
MANA_IMAGES.put("protectRB", FSkinImage.IMG_ABILITY_PROTECT_RB);
MANA_IMAGES.put("protectRG", FSkinImage.IMG_ABILITY_PROTECT_RG);
MANA_IMAGES.put("protectRU", FSkinImage.IMG_ABILITY_PROTECT_RU);
MANA_IMAGES.put("protectRW", FSkinImage.IMG_ABILITY_PROTECT_RW);
MANA_IMAGES.put("protectU", FSkinImage.IMG_ABILITY_PROTECT_U);
MANA_IMAGES.put("protectUW", FSkinImage.IMG_ABILITY_PROTECT_UW);
MANA_IMAGES.put("protectW", FSkinImage.IMG_ABILITY_PROTECT_W);
}
public static void drawManaCost(Graphics g, ManaCost manaCost, float x, float y, final float imageSize) {
if (manaCost.isNoCost()) {
return;
}
final int genericManaCost = manaCost.getGenericCost();
final boolean hasGeneric = (genericManaCost > 0) || manaCost.isPureGeneric();
final float dx = imageSize;
if (hasGeneric) {
for (final ManaCostShard s : manaCost) { //render X shards before generic
if (s == ManaCostShard.X) {
drawSymbol(s.getImageKey(), g, x, y, imageSize, imageSize);
x += dx;
}
}
final String sGeneric = Integer.toString(genericManaCost);
drawSymbol(sGeneric, g, x, y, imageSize, imageSize);
x += dx;
for (final ManaCostShard s : manaCost) { //render non-X shards after generic
if (s != ManaCostShard.X) {
drawSymbol(s.getImageKey(), g, x, y, imageSize, imageSize);
x += dx;
}
}
}
else { //if no generic, just render shards in order
for (final ManaCostShard s : manaCost) {
drawSymbol(s.getImageKey(), g, x, y, imageSize, imageSize);
x += dx;
}
}
}
public static void drawColorSet(Graphics g, ColorSet colorSet, float x, float y, final float imageSize) {
drawColorSet(g, colorSet, x, y, imageSize, false);
}
public static void drawColorSet(Graphics g, ColorSet colorSet, float x, float y, final float imageSize, boolean vertical) {
final float dx = imageSize;
for (final ManaCostShard s : colorSet.getOrderedShards()) {
drawSymbol(s.getImageKey(), g, x, y, imageSize, imageSize);
if (!vertical)
x += dx;
else
y += dx;
}
}
public static void drawOther(final Graphics g, String s, float x, final float y, final float w, final float h, boolean rotate) {
if (s.length() == 0) {
return;
}
final float dx = w;
StringTokenizer tok = new StringTokenizer(s, " ");
while (tok.hasMoreTokens()) {
String symbol = tok.nextToken();
FSkinImage image = MANA_IMAGES.get(symbol);
if (image == null) {
BugReporter.reportBug("Symbol not recognized \"" + symbol + "\" in string: " + s);
continue;
}
if(rotate) {
g.drawRotatedImage(image.getTextureRegion(), x, y, w, h, x+w /2, y+h /2,90);
}
else
g.drawImage(image, x, y, w, h);
x += dx;
}
}
public static void drawSymbol(final String imageName, final Graphics g, final float x, final float y, final float w, final float h) {
g.drawImage(MANA_IMAGES.get(imageName), x, y, w, h);
}
public static float getWidth(final ManaCost manaCost, float imageSize) {
return manaCost.getGlyphCount() * imageSize;
}
public static float getWidth(final ColorSet colorSet, float imageSize) {
return Math.max(colorSet.countColors(), 1) * imageSize;
}
}

View File

@@ -0,0 +1,64 @@
package forge.adventure.libgdxgui.card;
import com.badlogic.gdx.graphics.Texture;
import forge.adventure.libgdxgui.Forge;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.libgdxgui.assets.FImage;
import forge.adventure.libgdxgui.assets.ImageCache;
import forge.adventure.libgdxgui.card.CardRenderer.CardStackPosition;
import forge.game.card.CardView;
import forge.item.PaperCard;
import forge.adventure.libgdxgui.toolbox.FCardPanel;
public class CardImage implements FImage {
private final PaperCard card;
private Texture image;
public CardImage(PaperCard card0) {
card = card0;
}
@Override
public float getWidth() {
if (image != null) {
return image.getWidth();
}
return ImageCache.defaultImage.getWidth();
}
@Override
public float getHeight() {
return getWidth() * FCardPanel.ASPECT_RATIO;
}
@Override
public void draw(Graphics g, float x, float y, float w, float h) {
if (image == null) { //attempt to retrieve card image if needed
image = ImageCache.getImage(card);
if (image == null) {
if (!Forge.enableUIMask.equals("Off")) //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
}
}
if (image == ImageCache.defaultImage) {
CardImageRenderer.drawCardImage(g, CardView.getCardForUi(card), false, x, y, w, h, CardStackPosition.Top);
}
else {
if (Forge.enableUIMask.equals("Full")) {
if (ImageCache.isBorderlessCardArt(image))
g.drawImage(image, x, y, w, h);
else {
float radius = (h - w)/8;
g.drawborderImage(ImageCache.borderColor(image), x, y, w, h);
g.drawImage(ImageCache.croppedBorderImage(image), x+radius/2.2f, y+radius/2, w*0.96f, h*0.96f);
}
} else if (Forge.enableUIMask.equals("Crop")) {
g.drawImage(ImageCache.croppedBorderImage(image), x, y, w, h);
} else
g.drawImage(image, x, y, w, h);
}
}
}

View File

@@ -0,0 +1,587 @@
package forge.adventure.libgdxgui.card;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.utils.Align;
import com.google.common.collect.ImmutableList;
import forge.adventure.libgdxgui.Forge;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.libgdxgui.assets.*;
import forge.card.CardEdition;
import forge.card.CardRarity;
import forge.adventure.libgdxgui.card.CardRenderer.CardStackPosition;
import forge.card.mana.ManaCost;
import forge.game.GameView;
import forge.game.card.CardView;
import forge.game.card.CardView.CardStateView;
import forge.game.zone.ZoneType;
import forge.gui.card.CardDetailUtil;
import forge.gui.card.CardDetailUtil.DetailColors;
import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.properties.ForgePreferences;
import forge.model.FModel;
import forge.adventure.libgdxgui.screens.FScreen;
import forge.adventure.libgdxgui.screens.match.MatchController;
import forge.util.CardTranslation;
import forge.adventure.libgdxgui.util.Utils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import static forge.adventure.libgdxgui.card.CardRenderer.CROP_MULTIPLIER;
import static forge.adventure.libgdxgui.card.CardRenderer.isModernFrame;
public class CardImageRenderer {
private static final float BASE_IMAGE_WIDTH = 360;
private static final float BASE_IMAGE_HEIGHT = 504;
private static float MANA_SYMBOL_SIZE, PT_BOX_WIDTH, HEADER_PADDING, BORDER_THICKNESS;
private static FSkinFont NAME_FONT, TYPE_FONT, TEXT_FONT, PT_FONT;
private static float prevImageWidth, prevImageHeight;
private static final float BLACK_BORDER_THICKNESS_RATIO = 0.021f;
public static void forceStaticFieldUpdate() {
//force static fields to be updated the next time a card image is rendered
prevImageWidth = 0;
prevImageHeight = 0;
forgeArt.clear();
}
private static void updateStaticFields(float w, float h) {
if (w == prevImageWidth && h == prevImageHeight) {
//for performance sake, only update static fields if card image size is different than previous rendered card
return;
}
float ratio = Math.min(w / BASE_IMAGE_WIDTH, h / BASE_IMAGE_HEIGHT);
MANA_SYMBOL_SIZE = 20 * ratio;
PT_BOX_WIDTH = 56 * ratio;
HEADER_PADDING = 5 * ratio;
NAME_FONT = FSkinFont.forHeight(MANA_SYMBOL_SIZE);
TYPE_FONT = FSkinFont.forHeight(MANA_SYMBOL_SIZE * 0.9f);
TEXT_FONT = FSkinFont.forHeight(MANA_SYMBOL_SIZE * 0.95f);
PT_FONT = NAME_FONT;
BORDER_THICKNESS = Math.max(1.5f * ratio, 1f); //don't let border go below 1
prevImageWidth = w;
prevImageHeight = h;
}
public static void drawFaceDownCard(CardView card, Graphics g, float x, float y, float w, float h) {
//try to draw the card sleeves first
if (FSkin.getSleeves().get(card.getOwner()) != null)
g.drawImage(FSkin.getSleeves().get(card.getOwner()), x, y, w, h);
else
drawArt(g, x, y, w, h);
}
public static void drawCardImage(Graphics g, CardView card, boolean altState, float x, float y, float w, float h, CardStackPosition pos) {
updateStaticFields(w, h);
float blackBorderThickness = w * BLACK_BORDER_THICKNESS_RATIO;
g.fillRect(Color.BLACK, x, y, w, h);
x += blackBorderThickness;
y += blackBorderThickness;
w -= 2 * blackBorderThickness;
h -= 2 * blackBorderThickness;
final CardStateView state = card.getState(altState);
final boolean canShow = MatchController.instance.mayView(card);
if (!canShow) {
drawFaceDownCard(card, g, x, y, w, h);
return;
}
//determine colors for borders
final List<DetailColors> borderColors;
final boolean isFaceDown = card.isFaceDown();
if (isFaceDown) {
borderColors = ImmutableList.of(DetailColors.FACE_DOWN);
}
else {
borderColors = CardDetailUtil.getBorderColors(state, canShow);
}
Color[] colors = fillColorBackground(g, borderColors, x, y, w, h);
float artInset = blackBorderThickness * 0.5f;
float outerBorderThickness = 2 * blackBorderThickness - artInset;
x += outerBorderThickness;
y += outerBorderThickness;
w -= 2 * outerBorderThickness;
float headerHeight = Math.max(MANA_SYMBOL_SIZE + 2 * HEADER_PADDING, 2 * NAME_FONT.getCapHeight()) + 2;
//draw header containing name and mana cost
Color[] headerColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.NAME_BOX_TINT);
drawHeader(g, card, state, headerColors, 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;
float artWidth = w - 2 * artInset;
float artHeight = artWidth / CardRenderer.CARD_ART_RATIO;
float typeBoxHeight = 2 * TYPE_FONT.getCapHeight();
float ptBoxHeight = 0;
float textBoxHeight = h - headerHeight - artHeight - typeBoxHeight - outerBorderThickness - artInset;
if (state.isCreature() || state.isPlaneswalker() || state.getType().hasSubtype("Vehicle")) {
//if P/T box needed, make room for it
ptBoxHeight = 2 * PT_FONT.getCapHeight();
textBoxHeight -= ptBoxHeight;
}
else {
textBoxHeight -= 2 * artInset;
}
float minTextBoxHeight = 2 * headerHeight;
if (textBoxHeight < minTextBoxHeight) {
if (textBoxHeight < minTextBoxHeight) {
artHeight -= (minTextBoxHeight - textBoxHeight); //subtract from art height if text box not big enough otherwise
textBoxHeight = minTextBoxHeight;
if (artHeight < 0) {
textBoxHeight += artHeight;
artHeight = 0;
}
}
}
//draw art box with Forge icon
if (artHeight > 0) {
drawArt(g, x + artInset, y, artWidth, artHeight);
y += artHeight;
}
//draw type line
drawTypeLine(g, card, state, canShow, headerColors, x, y, w, typeBoxHeight);
y += typeBoxHeight;
//draw text box
Color[] textBoxColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.TEXT_BOX_TINT);
drawTextBox(g, card, state, textBoxColors, x + artInset, y, w - 2 * artInset, textBoxHeight, onTop);
y += textBoxHeight;
//draw P/T box
if (onTop && ptBoxHeight > 0) {
//only needed if on top since otherwise P/T will be hidden
Color[] ptColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.PT_BOX_TINT);
drawPtBox(g, card, state, ptColors, x, y - 2 * artInset, w, ptBoxHeight);
}
}
private static void drawHeader(Graphics g, CardView card, CardStateView state, Color[] colors, float x, float y, float w, float h) {
fillColorBackground(g, colors, x, y, w, h);
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
float padding = h / 8;
//draw mana cost for card
float manaCostWidth = 0;
ManaCost mainManaCost = state.getManaCost();
if (card.isSplitCard() && card.getAlternateState() != null) {
//handle rendering both parts of split card
mainManaCost = card.getLeftSplitState().getManaCost();
ManaCost otherManaCost = card.getAlternateState().getManaCost();
manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING;
CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth, y + (h - MANA_SYMBOL_SIZE) / 2, MANA_SYMBOL_SIZE);
//draw "//" between two parts of mana cost
manaCostWidth += NAME_FONT.getBounds("//").width + HEADER_PADDING;
g.drawText("//", NAME_FONT, Color.BLACK, x + w - manaCostWidth, y, w, h, false, Align.left, true);
}
manaCostWidth += CardFaceSymbols.getWidth(mainManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING;
CardFaceSymbols.drawManaCost(g, mainManaCost, x + w - manaCostWidth, y + (h - MANA_SYMBOL_SIZE) / 2, MANA_SYMBOL_SIZE);
//draw name for card
x += padding;
w -= 2 * padding;
g.drawText(CardTranslation.getTranslatedName(state.getName()), NAME_FONT, Color.BLACK, x, y, w - manaCostWidth - padding, h, false, Align.left, true);
}
public static final FBufferedImage forgeArt;
static {
final float logoWidth = FSkinImage.LOGO.getWidth();
final float logoHeight = FSkinImage.LOGO.getHeight();
float h = logoHeight * 1.1f;
float w = h * CardRenderer.CARD_ART_RATIO;
forgeArt = new FBufferedImage(w, h) {
@Override
protected void draw(Graphics g, float w, float h) {
g.drawImage(FSkinTexture.BG_TEXTURE, 0, 0, w, h);
g.fillRect(FScreen.TEXTURE_OVERLAY_COLOR, 0, 0, w, h);
g.drawImage(FSkinImage.LOGO, (w - logoWidth) / 2, (h - logoHeight) / 2, logoWidth, logoHeight);
}
};
}
private static void drawArt(Graphics g, float x, float y, float w, float h) {
g.drawImage(forgeArt, x, y, w, h);
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
}
private static void drawTypeLine(Graphics g, CardView card, CardStateView state, boolean canShow, Color[] colors, float x, float y, float w, float h) {
fillColorBackground(g, colors, x, y, w, h);
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
float padding = h / 8;
//draw square icon for rarity
float iconSize = h * 0.55f;
float iconPadding = (h - iconSize) / 2;
w -= iconSize + iconPadding * 2;
g.fillRect(CardRenderer.getRarityColor(state.getRarity()), x + w + iconPadding, y + (h - iconSize) / 2, iconSize, iconSize);
//draw type
x += padding;
g.drawText(CardDetailUtil.formatCardType(state, canShow), TYPE_FONT, Color.BLACK, x, y, w, h, false, Align.left, true);
}
//use text renderer to handle mana symbols and reminder text
private static final TextRenderer cardTextRenderer = new TextRenderer(true);
private static void drawTextBox(Graphics g, CardView card, CardStateView state, Color[] colors, float x, float y, float w, float h, boolean onTop) {
fillColorBackground(g, colors, x, y, w, h);
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
if (!onTop) { return; } //remaining rendering only needed if card on top
if (state.isBasicLand()) {
//draw icons for basic lands
FSkinImage image;
switch (state.getName().replaceFirst("^Snow-Covered ", "")) {
case "Plains":
image = FSkinImage.MANA_W;
break;
case "Island":
image = FSkinImage.MANA_U;
break;
case "Swamp":
image = FSkinImage.MANA_B;
break;
case "Mountain":
image = FSkinImage.MANA_R;
break;
case "Forest":
image = FSkinImage.MANA_G;
break;
default:
image = FSkinImage.MANA_COLORLESS;
break;
}
float iconSize = h * 0.75f;
g.drawImage(image, x + (w - iconSize) / 2, y + (h - iconSize) / 2, iconSize, iconSize);
}
else {
boolean needTranslation = true;
if (card.isToken()) {
if (card.getCloneOrigin() == null)
needTranslation = false;
}
final String text = !card.isSplitCard() ?
card.getText(state, needTranslation ? CardTranslation.getTranslationTexts(state.getName(), "") : null) :
card.getText(state, needTranslation ? CardTranslation.getTranslationTexts(card.getLeftSplitState().getName(), card.getRightSplitState().getName()) : null );
if (StringUtils.isEmpty(text)) { return; }
float padding = TEXT_FONT.getCapHeight() * 0.75f;
x += padding;
y += padding;
w -= 2 * padding;
h -= 2 * padding;
cardTextRenderer.drawText(g, text, TEXT_FONT, Color.BLACK, x, y, w, h, y, h, true, Align.left, true);
}
}
private static void drawPtBox(Graphics g, CardView card, CardStateView state, Color[] colors, float x, float y, float w, float h) {
List<String> pieces = new ArrayList<>();
if (state.isCreature()) {
pieces.add(String.valueOf(state.getPower()));
pieces.add("/");
pieces.add(String.valueOf(state.getToughness()));
}
else if (state.isPlaneswalker()) {
pieces.add(String.valueOf(state.getLoyalty()));
}
else if (state.getType().hasSubtype("Vehicle")) {
// TODO Invert color box for Vehicles?
pieces.add("[");
pieces.add(String.valueOf(state.getPower()));
pieces.add("/");
pieces.add(String.valueOf(state.getToughness()));
pieces.add("]");
}
else { return; }
float padding = Math.round(PT_FONT.getCapHeight() / 4);
float totalPieceWidth = -padding;
float[] pieceWidths = new float[pieces.size()];
for (int i = 0; i < pieces.size(); i++) {
float pieceWidth = PT_FONT.getBounds(pieces.get(i)).width + padding;
pieceWidths[i] = pieceWidth;
totalPieceWidth += pieceWidth;
}
float boxHeight = PT_FONT.getCapHeight() + PT_FONT.getAscent() + 3 * padding;
float boxWidth = Math.max(PT_BOX_WIDTH, totalPieceWidth + 2 * padding);
x += w - boxWidth;
y += h - boxHeight;
w = boxWidth;
h = boxHeight;
fillColorBackground(g, colors, x, y, w, h);
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
x += (boxWidth - totalPieceWidth) / 2;
for (int i = 0; i < pieces.size(); i++) {
g.drawText(pieces.get(i), PT_FONT, Color.BLACK, x, y, w, h, false, Align.left, true);
x += pieceWidths[i];
}
}
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 canshow = MatchController.instance.mayView(card);
Texture image = null;
try {
image = ImageCache.getImage(card.getState(altState).getImageKey(), true);
} catch (Exception ex) {
//System.err.println(card.toString()+" : " +ex.getMessage());
//TODO: don't know why this is needed, needs further investigation...
if (!card.hasAlternateState()) {
altState = false;
image = ImageCache.getImage(card.getState(altState).getImageKey(), true);
}
}
FImage sleeves = MatchController.getPlayerSleeve(card.getOwner());
if (image == null) { //draw details if can't draw zoom
drawDetails(g, card, gameView, altState, x, y, w, h);
return;
}
if(card.isToken() && card.getCurrentState().getType().hasSubtype("Effect")
&& FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DISABLE_IMAGES_EFFECT_CARDS)){
drawDetails(g, card, gameView, altState, x, y, w, h);
return;
}
if (image == ImageCache.defaultImage) { //support drawing card image manually if card image not found
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);
float croppedArea = isModernFrame(card) ? CROP_MULTIPLIER : 0.97f;
float minusxy = isModernFrame(card) ? 0.0f : 0.13f*radius;
if (card.getCurrentState().getSetCode().equals("LEA")||card.getCurrentState().getSetCode().equals("LEB")) {
croppedArea = 0.975f;
minusxy = 0.135f*radius;
}
if (rotatePlane && (card.getCurrentState().isPhenomenon() || card.getCurrentState().isPlane())) {
if (Forge.enableUIMask.equals("Full")){
if (ImageCache.isBorderlessCardArt(image))
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90);
else {
g.drawRotatedImage(FSkin.getBorders().get(0), new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90);
g.drawRotatedImage(ImageCache.croppedBorderImage(image), new_x+radius/2-minusxy, new_y+radius/2-minusxy, new_w*croppedArea, new_h*croppedArea, (new_x+radius/2-minusxy) + (new_w*croppedArea) / 2, (new_y+radius/2-minusxy) + (new_h*croppedArea) / 2, -90);
}
} else if (Forge.enableUIMask.equals("Crop")) {
g.drawRotatedImage(ImageCache.croppedBorderImage(image), new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 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() && canshow) {
boolean isAftermath = card.getText().contains("Aftermath") || card.getAlternateState().getOracleText().contains("Aftermath");
if (Forge.enableUIMask.equals("Full")) {
if (ImageCache.isBorderlessCardArt(image))
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.drawRotatedImage(FSkin.getBorders().get(ImageCache.getFSkinBorders(card)), new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, isAftermath ? 90 : -90);
g.drawRotatedImage(ImageCache.croppedBorderImage(image), new_x + radius / 2-minusxy, new_y + radius / 2-minusxy, new_w * croppedArea, new_h * croppedArea, (new_x + radius / 2-minusxy) + (new_w * croppedArea) / 2, (new_y + radius / 2-minusxy) + (new_h * croppedArea) / 2, isAftermath ? 90 : -90);
}
} else if (Forge.enableUIMask.equals("Crop")) {
g.drawRotatedImage(ImageCache.croppedBorderImage(image), new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 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 {
if (Forge.enableUIMask.equals("Full") && canshow) {
if (ImageCache.isBorderlessCardArt(image))
g.drawImage(image, x, y, w, h);
else {
g.drawImage(ImageCache.getBorderImage(image.toString()), ImageCache.borderColor(image), x, y, w, h);
g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f-minusxy, y + radius / 2-minusxy, w * croppedArea, h * croppedArea);
}
} else if (Forge.enableUIMask.equals("Crop") && canshow) {
g.drawImage(ImageCache.croppedBorderImage(image), x, y, w, h);
} else {
if (canshow)
g.drawImage(image, x, y, w, h);
else // sleeve
g.drawImage(sleeves, x, y, w, h);
}
}
}
CardRenderer.drawFoilEffect(g, card, x, y, w, h, isCurrentCard && canshow && image != ImageCache.defaultImage);
}
public static void drawDetails(Graphics g, CardView card, GameView gameView, boolean altState, float x, float y, float w, float h) {
updateStaticFields(w, h);
float blackBorderThickness = w * BLACK_BORDER_THICKNESS_RATIO;
g.fillRect(Color.BLACK, x, y, w, h);
x += blackBorderThickness;
y += blackBorderThickness;
w -= 2 * blackBorderThickness;
h -= 2 * blackBorderThickness;
final CardStateView state = card.getState(altState);
final boolean canShow = MatchController.instance.mayView(card);
//determine colors for borders
final List<DetailColors> borderColors;
final boolean isFaceDown = card.isFaceDown();
if (isFaceDown) {
borderColors = ImmutableList.of(DetailColors.FACE_DOWN);
}
else {
borderColors = CardDetailUtil.getBorderColors(state, canShow);
}
Color[] colors = fillColorBackground(g, borderColors, x, y, w, h);
Color idForeColor = FSkinColor.getHighContrastColor(colors[0]);
float outerBorderThickness = 2 * blackBorderThickness;
x += outerBorderThickness;
y += outerBorderThickness;
w -= 2 * outerBorderThickness;
float cardNameBoxHeight = Math.max(MANA_SYMBOL_SIZE + 2 * HEADER_PADDING, 2 * NAME_FONT.getCapHeight()) + 2 * TYPE_FONT.getCapHeight() + 2;
//draw name/type box
Color[] nameBoxColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.NAME_BOX_TINT);
drawDetailsNameBox(g, card, state, canShow, nameBoxColors, x, y, w, cardNameBoxHeight);
float innerBorderThickness = outerBorderThickness / 2;
float ptBoxHeight = 2 * PT_FONT.getCapHeight();
float textBoxHeight = h - cardNameBoxHeight - ptBoxHeight - outerBorderThickness - 3 * innerBorderThickness;
y += cardNameBoxHeight + innerBorderThickness;
Color[] textBoxColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.TEXT_BOX_TINT);
drawDetailsTextBox(g, state, gameView, canShow, textBoxColors, x, y, w, textBoxHeight);
y += textBoxHeight + innerBorderThickness;
Color[] ptColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.PT_BOX_TINT);
drawDetailsIdAndPtBox(g, card, state, canShow, idForeColor, ptColors, x, y, w, ptBoxHeight);
}
public static Color[] fillColorBackground(Graphics g, List<DetailColors> backColors, float x, float y, float w, float h) {
Color[] colors = new Color[backColors.size()];
for (int i = 0; i < colors.length; i++) {
DetailColors dc = backColors.get(i);
colors[i] = FSkinColor.fromRGB(dc.r, dc.g, dc.b);
}
fillColorBackground(g, colors, x, y, w, h);
return colors;
}
public static void fillColorBackground(Graphics g, Color[] colors, float x, float y, float w, float h) {
switch (colors.length) {
case 1:
g.fillRect(colors[0], x, y, w, h);
break;
case 2:
g.fillGradientRect(colors[0], colors[1], false, x, y, w, h);
break;
case 3:
float halfWidth = w / 2;
g.fillGradientRect(colors[0], colors[1], false, x, y, halfWidth, h);
g.fillGradientRect(colors[1], colors[2], false, x + halfWidth, y, halfWidth, h);
break;
}
}
private static void drawDetailsNameBox(Graphics g, CardView card, CardStateView state, boolean canShow, Color[] colors, float x, float y, float w, float h) {
fillColorBackground(g, colors, x, y, w, h);
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
float padding = h / 8;
//make sure name/mana cost row height is tall enough for both
h = Math.max(MANA_SYMBOL_SIZE + 2 * HEADER_PADDING, 2 * NAME_FONT.getCapHeight());
//draw mana cost for card
float manaCostWidth = 0;
if (canShow) {
ManaCost mainManaCost = state.getManaCost();
if (card.isSplitCard() && card.hasAlternateState() && !card.isFaceDown() && card.getZone() != ZoneType.Stack) { //only display current state's mana cost when on stack
//handle rendering both parts of split card
mainManaCost = card.getLeftSplitState().getManaCost();
ManaCost otherManaCost = card.getAlternateState().getManaCost();
manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING;
CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth, y + (h - MANA_SYMBOL_SIZE) / 2, MANA_SYMBOL_SIZE);
//draw "//" between two parts of mana cost
manaCostWidth += NAME_FONT.getBounds("//").width + HEADER_PADDING;
g.drawText("//", NAME_FONT, Color.BLACK, x + w - manaCostWidth, y, w, h, false, Align.left, true);
}
manaCostWidth += CardFaceSymbols.getWidth(mainManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING;
CardFaceSymbols.drawManaCost(g, mainManaCost, x + w - manaCostWidth, y + (h - MANA_SYMBOL_SIZE) / 2, MANA_SYMBOL_SIZE);
}
//draw name for card
x += padding;
w -= 2 * padding;
g.drawText(CardDetailUtil.formatCardName(card, canShow, state == card.getAlternateState()), NAME_FONT, Color.BLACK, x, y, w - manaCostWidth - padding, h, false, Align.left, true);
//draw type and set label for card
y += h;
h = 2 * TYPE_FONT.getCapHeight();
String set = state.getSetCode();
CardRarity rarity = state.getRarity();
if (!canShow) {
set = CardEdition.UNKNOWN.getCode();
rarity = CardRarity.Unknown;
}
if (!StringUtils.isEmpty(set)) {
float setWidth = CardRenderer.getSetWidth(TYPE_FONT, set);
CardRenderer.drawSetLabel(g, TYPE_FONT, set, rarity, x + w + padding - setWidth - HEADER_PADDING + CardRenderer.SET_BOX_MARGIN, y + CardRenderer.SET_BOX_MARGIN, setWidth, h - CardRenderer.SET_BOX_MARGIN);
w -= setWidth; //reduce available width for type
}
g.drawText(CardDetailUtil.formatCardType(state, canShow), TYPE_FONT, Color.BLACK, x, y, w, h, false, Align.left, true);
}
private static void drawDetailsTextBox(Graphics g, CardStateView state, GameView gameView, boolean canShow, Color[] colors, float x, float y, float w, float h) {
fillColorBackground(g, colors, x, y, w, h);
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
float padX = TEXT_FONT.getCapHeight() / 2;
float padY = padX + Utils.scale(2); //add a little more vertical padding
x += padX;
y += padY;
w -= 2 * padX;
h -= 2 * padY;
cardTextRenderer.drawText(g, CardDetailUtil.composeCardText(state, gameView, canShow), TEXT_FONT, Color.BLACK, x, y, w, h, y, h, true, Align.left, false);
}
private static void drawDetailsIdAndPtBox(Graphics g, CardView card, CardStateView state, boolean canShow, Color idForeColor, Color[] colors, float x, float y, float w, float h) {
float idWidth = 0;
if (canShow) {
String idText = CardDetailUtil.formatCardId(state);
g.drawText(idText, TYPE_FONT, idForeColor, x, y + TYPE_FONT.getCapHeight() / 2, w, h, false, Align.left, false);
idWidth = TYPE_FONT.getBounds(idText).width;
}
String ptText = CardDetailUtil.formatPowerToughness(state, canShow);
if (StringUtils.isEmpty(ptText)) { return; }
float padding = PT_FONT.getCapHeight() / 2;
float boxWidth = Math.min(PT_FONT.getBounds(ptText).width + 2 * padding,
w - idWidth - padding); //prevent box overlapping ID
x += w - boxWidth;
w = boxWidth;
fillColorBackground(g, colors, x, y, w, h);
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);
}
}

View File

@@ -0,0 +1,51 @@
package forge.adventure.libgdxgui.card;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Align;
import forge.item.PaperCard;
import forge.adventure.libgdxgui.toolbox.FChoiceList;
import forge.adventure.libgdxgui.toolbox.FLabel;
public class CardListPreview extends FLabel {
public static final float CARD_PREVIEW_RATIO = 0.5f;
private final FChoiceList<PaperCard> list;
public CardListPreview(FChoiceList<PaperCard> list0) {
super(new FLabel.Builder().iconScaleFactor(1).insets(new Vector2(0, 0))
.iconInBackground(true).align(Align.center));
list = list0;
}
@Override
public boolean tap(float x, float y, int count) {
return zoom();
}
@Override
public boolean longPress(float x, float y) {
return zoom();
}
private boolean zoom() {
int index = list.getSelectedIndex();
if (index == -1) { return false; }
CardZoom.show(list.extractListData(), index, list);
return true;
}
@Override
public boolean fling(float velocityX, float velocityY) {
if (Math.abs(velocityX) > Math.abs(velocityY)) {
int selectedIndex = list.getSelectedIndex();
if (velocityX > 0) {
if (selectedIndex > 0) {
list.setSelectedIndex(selectedIndex - 1);
}
}
else if (selectedIndex < list.getCount() - 1) {
list.setSelectedIndex(selectedIndex + 1);
}
return true;
}
return false;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,384 @@
package forge.adventure.libgdxgui.card;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.utils.Align;
import forge.adventure.libgdxgui.Forge;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.libgdxgui.assets.FSkinImage;
import forge.adventure.libgdxgui.screens.match.MatchController;
import forge.adventure.libgdxgui.toolbox.FCardPanel;
import forge.adventure.libgdxgui.toolbox.FDialog;
import forge.adventure.libgdxgui.toolbox.FOverlay;
import forge.adventure.libgdxgui.util.Utils;
import forge.deck.ArchetypeDeckGenerator;
import forge.deck.CardThemedDeckGenerator;
import forge.deck.CommanderDeckGenerator;
import forge.deck.DeckProxy;
import forge.game.GameView;
import forge.game.card.CardView;
import forge.gamemodes.planarconquest.ConquestCommander;
import forge.item.IPaperCard;
import forge.item.InventoryItem;
import forge.localinstance.properties.ForgePreferences;
import forge.localinstance.properties.ForgePreferences.FPref;
import forge.model.FModel;
import forge.util.Localizer;
import forge.util.collect.FCollectionView;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
public class CardZoom extends FOverlay {
private static final float REQ_AMOUNT = Utils.AVG_FINGER_WIDTH;
private static final CardZoom cardZoom = new CardZoom();
private static final ForgePreferences prefs = FModel.getPreferences();
private static List<?> items;
private static int currentIndex, initialIndex;
private static CardView currentCard, prevCard, nextCard;
private static boolean zoomMode = true;
private static boolean oneCardView = prefs.getPrefBoolean(FPref.UI_SINGLE_CARD_ZOOM);
private float totalZoomAmount;
private static ActivateHandler activateHandler;
private static String currentActivateAction;
private static Rectangle flipIconBounds;
private static Rectangle mutateIconBounds;
private static boolean showAltState;
private static boolean showBackSide = false;
private static boolean showMerged = false;
public static void show(Object item) {
show(item, false);
}
public static void show(Object item, boolean showbackside) {
List<Object> items0 = new ArrayList<>();
items0.add(item);
showBackSide = showbackside; //reverse the displayed zoomed card for the choice list
show(items0, 0, null);
}
public static void show(FCollectionView<?> items0, int currentIndex0, ActivateHandler activateHandler0) {
show((List<?>)items0, currentIndex0, activateHandler0);
}
public static void show(final List<?> items0, int currentIndex0, ActivateHandler activateHandler0) {
items = items0;
activateHandler = activateHandler0;
currentIndex = currentIndex0;
initialIndex = currentIndex0;
currentCard = getCardView(items.get(currentIndex));
prevCard = currentIndex > 0 ? getCardView(items.get(currentIndex - 1)) : null;
nextCard = currentIndex < items.size() - 1 ? getCardView(items.get(currentIndex + 1)) : null;
onCardChanged();
cardZoom.show();
}
public static boolean isOpen() {
return cardZoom.isVisible();
}
public static void hideZoom() {
cardZoom.hide();
}
private CardZoom() {
}
@Override
public void setVisible(boolean visible0) {
if (this.isVisible() == visible0) { return; }
super.setVisible(visible0);
//update selected index when hidden if current index is different than initial index
if (!visible0 && activateHandler != null && currentIndex != initialIndex) {
activateHandler.setSelectedIndex(currentIndex);
}
}
private static void incrementCard(int dir) {
if (dir > 0) {
if (currentIndex == items.size() - 1) { return; }
currentIndex++;
prevCard = currentCard;
currentCard = nextCard;
nextCard = currentIndex < items.size() - 1 ? getCardView(items.get(currentIndex + 1)) : null;
}
else {
if (currentIndex == 0) { return; }
currentIndex--;
nextCard = currentCard;
currentCard = prevCard;
prevCard = currentIndex > 0 ? getCardView(items.get(currentIndex - 1)) : null;
}
onCardChanged();
}
private static void onCardChanged() {
mutateIconBounds = null;
if (activateHandler != null) {
currentActivateAction = activateHandler.getActivateAction(currentIndex);
}
if (MatchController.instance.mayFlip(currentCard)) {
flipIconBounds = new Rectangle();
} else {
flipIconBounds = null;
}
if (currentCard != null) {
if (currentCard.getMergedCardsCollection() != null )
if (currentCard.getMergedCardsCollection().size() > 0)
mutateIconBounds = new Rectangle();
}
showAltState = false;
}
private static CardView getCardView(Object item) {
if (item instanceof Entry) {
item = ((Entry<?, ?>)item).getKey();
}
if (item instanceof CardView) {
return (CardView)item;
}
if (item instanceof DeckProxy) {
if (item instanceof CardThemedDeckGenerator){
return CardView.getCardForUi(((CardThemedDeckGenerator)item).getPaperCard());
}else if (item instanceof CommanderDeckGenerator){
return CardView.getCardForUi(((CommanderDeckGenerator)item).getPaperCard());
}else if (item instanceof ArchetypeDeckGenerator){
return CardView.getCardForUi(((ArchetypeDeckGenerator)item).getPaperCard());
}else{
DeckProxy deck = ((DeckProxy)item);
return new CardView(-1, null, deck.getName(), null, deck.getImageKey(false));
}
}
if (item instanceof IPaperCard) {
return CardView.getCardForUi((IPaperCard)item);
}
if (item instanceof ConquestCommander) {
return CardView.getCardForUi(((ConquestCommander)item).getCard());
}
if (item instanceof InventoryItem) {
InventoryItem ii = (InventoryItem)item;
return new CardView(-1, null, ii.getName(), null, ii.getImageKey(false));
}
return new CardView(-1, null, item.toString());
}
@Override
public boolean tap(float x, float y, int count) {
if (mutateIconBounds != null && mutateIconBounds.contains(x, y)) {
if(showMerged) {
showMerged = false;
} else {
showMerged = true;
show(currentCard.getMergedCardsCollection(), 0, null);
}
return true;
}
if (flipIconBounds != null && flipIconBounds.contains(x, y)) {
if (currentCard.isFaceDown() && currentCard.getBackup() != null) {
if (currentCard.getBackup().hasBackSide()) {
show(currentCard.getBackup());
return true;
}
}
if (!showBackSide)
showAltState = !showAltState;
else
showBackSide = !showBackSide;
return true;
}
hide();
showBackSide = false;
showAltState = false;
showMerged = false;
return true;
}
@Override
public boolean fling(float velocityX, float velocityY) {
if (Math.abs(velocityX) > Math.abs(velocityY)) {
incrementCard(velocityX > 0 ? -1 : 1);
showBackSide = false;
showAltState = false;
return true;
}
if (velocityY > 0) {
zoomMode = !zoomMode;
showBackSide = false;
showAltState = false;
return true;
}
if (currentActivateAction != null && activateHandler != null) {
hide();
showBackSide = false;
showAltState = false;
activateHandler.activate(currentIndex);
return true;
}
return false;
}
private void setOneCardView(boolean oneCardView0) {
if (oneCardView == oneCardView0 || Forge.isLandscapeMode()) { return; } //don't allow changing this when in landscape mode
oneCardView = oneCardView0;
prefs.setPref(FPref.UI_SINGLE_CARD_ZOOM, oneCardView0);
prefs.save();
}
@Override
public boolean zoom(float x, float y, float amount) {
totalZoomAmount += amount;
if (totalZoomAmount >= REQ_AMOUNT) {
setOneCardView(true);
totalZoomAmount = 0;
}
else if (totalZoomAmount <= -REQ_AMOUNT) {
setOneCardView(false);
totalZoomAmount = 0;
}
return true;
}
@Override
public boolean longPress(float x, float y) {
setOneCardView(!oneCardView);
return true;
}
@Override
public void drawOverlay(Graphics g) {
final GameView gameView = MatchController.instance.getGameView();
float w = getWidth();
float h = getHeight();
float messageHeight = FDialog.MSG_HEIGHT;
float AspectRatioMultiplier;
switch (Forge.extrawide) {
case "default":
AspectRatioMultiplier = 3; //good for tablets with 16:10 or similar
break;
case "wide":
AspectRatioMultiplier = 2.5f;
break;
case "extrawide":
AspectRatioMultiplier = 2; //good for tall phones with 21:9 or similar
break;
default:
AspectRatioMultiplier = 3;
break;
}
float maxCardHeight = h - AspectRatioMultiplier * messageHeight; //maxheight of currently zoomed card
float cardWidth, cardHeight, y;
if (oneCardView && !Forge.isLandscapeMode()) {
cardWidth = w;
cardHeight = FCardPanel.ASPECT_RATIO * cardWidth;
boolean rotateSplit = FModel.getPreferences().getPrefBoolean(FPref.UI_ROTATE_SPLIT_CARDS);
if (currentCard.isSplitCard() && rotateSplit) {
// card will be rotated. Make sure that the height does not exceed the width of the view
if (cardHeight > Gdx.graphics.getWidth())
{
cardHeight = Gdx.graphics.getWidth();
cardWidth = cardHeight / FCardPanel.ASPECT_RATIO;
}
}
}
else {
cardWidth = w * 0.5f;
cardHeight = FCardPanel.ASPECT_RATIO * cardWidth;
float maxSideCardHeight = maxCardHeight * 5 / 7;
if (cardHeight > maxSideCardHeight) { //prevent card overlapping message bars
cardHeight = maxSideCardHeight;
cardWidth = cardHeight / FCardPanel.ASPECT_RATIO;
}
y = (h - cardHeight) / 2;
if (prevCard != null) {
CardImageRenderer.drawZoom(g, prevCard, gameView, false, 0, y, cardWidth, cardHeight, getWidth(), getHeight(), false);
}
if (nextCard != null) {
CardImageRenderer.drawZoom(g, nextCard, gameView, false, w - cardWidth, y, cardWidth, cardHeight, getWidth(), getHeight(), false);
}
cardWidth = w * 0.7f;
cardHeight = FCardPanel.ASPECT_RATIO * cardWidth;
}
if (cardHeight > maxCardHeight) { //prevent card overlapping message bars
cardHeight = maxCardHeight;
cardWidth = cardHeight / FCardPanel.ASPECT_RATIO;
}
float x = (w - cardWidth) / 2;
y = (h - cardHeight) / 2;
if (zoomMode) {
CardImageRenderer.drawZoom(g, currentCard, gameView, showBackSide? showBackSide : showAltState, x, y, cardWidth, cardHeight, getWidth(), getHeight(), true);
} else {
CardImageRenderer.drawDetails(g, currentCard, gameView, showBackSide? showBackSide : showAltState, x, y, cardWidth, cardHeight);
}
if (!showMerged) {
if (mutateIconBounds != null) {
float oldAlpha = g.getfloatAlphaComposite();
try {
g.setAlphaComposite(0.6f);
drawIconBounds(g, mutateIconBounds, Forge.hdbuttons ? FSkinImage.HDLIBRARY : FSkinImage.LIBRARY, x, y, cardWidth, cardHeight);
g.setAlphaComposite(oldAlpha);
} catch (Exception e) {
mutateIconBounds = null;
g.setAlphaComposite(oldAlpha);
}
} else if (flipIconBounds != null) {
drawIconBounds(g, flipIconBounds, Forge.hdbuttons ? FSkinImage.HDFLIPCARD : FSkinImage.FLIPCARD, x, y, cardWidth, cardHeight);
}
} else if (flipIconBounds != null) {
drawIconBounds(g, flipIconBounds, Forge.hdbuttons ? FSkinImage.HDFLIPCARD : FSkinImage.FLIPCARD, x, y, cardWidth, cardHeight);
}
if (currentActivateAction != null) {
g.fillRect(FDialog.MSG_BACK_COLOR, 0, 0, w, messageHeight);
g.drawText(Localizer.getInstance().getMessage("lblSwipeUpTo").replace("%s", currentActivateAction), FDialog.MSG_FONT, FDialog.MSG_FORE_COLOR, 0, 0, w, messageHeight, false, Align.center, true);
}
g.fillRect(FDialog.MSG_BACK_COLOR, 0, h - messageHeight, w, messageHeight);
g.drawText(zoomMode ? Localizer.getInstance().getMessage("lblSwipeDownDetailView") : Localizer.getInstance().getMessage("lblSwipeDownPictureView"), FDialog.MSG_FONT, FDialog.MSG_FORE_COLOR, 0, h - messageHeight, w, messageHeight, false, Align.center, true);
interrupt(false);
}
private void drawIconBounds(Graphics g, Rectangle iconBounds, FSkinImage skinImage, float x, float y, float cardWidth, float cardHeight) {
float imageWidth = cardWidth / 2;
float imageHeight = imageWidth * skinImage.getHeight() / skinImage.getWidth();
iconBounds.set(x + (cardWidth - imageWidth) / 2, y + (cardHeight - imageHeight) / 2, imageWidth, imageHeight);
g.drawImage(skinImage, iconBounds.x, iconBounds.y, iconBounds.width, iconBounds.height);
}
@Override
protected void doLayout(float width, float height) {
}
public interface ActivateHandler {
String getActivateAction(int index);
void setSelectedIndex(int index);
void activate(int index);
}
public void interrupt(boolean resume) {
if (MatchController.instance.hasLocalPlayers())
return;
if(resume && MatchController.instance.isGamePaused()) {
MatchController.instance.resumeMatch();
return;
}
if(!MatchController.instance.isGamePaused())
MatchController.instance.pauseMatch();
}
}

View File

@@ -0,0 +1,38 @@
package forge.adventure.libgdxgui.card;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.libgdxgui.assets.FImage;
import forge.adventure.libgdxgui.assets.FSkinImage;
import forge.card.ColorSet;
public class ColorSetImage implements FImage {
private final ColorSet colorSet;
private final int shardCount;
public ColorSetImage(ColorSet colorSet0) {
colorSet = colorSet0;
shardCount = colorSet.getOrderedShards().length;
}
@Override
public float getWidth() {
return FSkinImage.MANA_W.getWidth() * shardCount;
}
@Override
public float getHeight() {
return FSkinImage.MANA_W.getHeight();
}
@Override
public void draw(Graphics g, float x, float y, float w, float h) {
float imageSize = w / shardCount;
if (imageSize > h) {
imageSize = h;
float w0 = imageSize * shardCount;
x += (w - w0) / 2;
w = w0;
}
CardFaceSymbols.drawColorSet(g, colorSet, x, y, imageSize);
}
}

View File

@@ -0,0 +1,135 @@
package forge.adventure.libgdxgui.card;
import com.google.common.collect.ImmutableList;
import forge.adventure.libgdxgui.Forge;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.libgdxgui.assets.FImage;
import forge.adventure.libgdxgui.assets.FSkinFont;
import forge.adventure.libgdxgui.assets.FSkinImage;
import forge.adventure.libgdxgui.screens.FScreen;
import forge.adventure.libgdxgui.screens.TabPageScreen;
import forge.adventure.libgdxgui.toolbox.FChoiceList;
import forge.adventure.libgdxgui.toolbox.FEvent;
import forge.adventure.libgdxgui.toolbox.FEvent.FEventHandler;
import forge.adventure.libgdxgui.toolbox.FOptionPane;
import forge.adventure.libgdxgui.toolbox.FTextField;
import forge.game.GameEntityView;
import forge.game.card.CardView;
import forge.util.Callback;
import forge.util.Localizer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class GameEntityPicker extends TabPageScreen<GameEntityPicker> {
private final FOptionPane optionPane;
public GameEntityPicker(String title, Collection<? extends GameEntityView> choiceList, Collection<CardView> revealList, String revealListCaption, FImage revealListImage, boolean isOptional, final Callback<GameEntityView> callback) {
super(new PickerTab[] {
new PickerTab(choiceList, Localizer.getInstance().getMessage("lblChoices"), Forge.hdbuttons ? FSkinImage.HDCHOICE : FSkinImage.DECKLIST, 1),
new PickerTab(revealList, revealListCaption, revealListImage, 0)
}, false);
setHeight(FOptionPane.getMaxDisplayObjHeight());
optionPane = new FOptionPane(null, null, title, null, this,
isOptional ? ImmutableList.of(Localizer.getInstance().getMessage("lblOK"), Localizer.getInstance().getMessage("lblCancel")) : ImmutableList.of(Localizer.getInstance().getMessage("lblOK")), 0, new Callback<Integer>() {
@Override
public void run(Integer result) {
if (result == 0) {
callback.run(((PickerTab)tabPages[0]).list.getSelectedItem());
}
else {
callback.run(null);
}
}
}) {
@Override
protected boolean padAboveAndBelow() {
return false; //allow list to go straight up against buttons
}
};
}
public void show() {
optionPane.show();
}
@Override
protected boolean canActivateTabPage() {
return true; //always allow activating tab pages while this is open
}
@Override
public FScreen getLandscapeBackdropScreen() {
return null;
}
private static class PickerTab extends TabPage<GameEntityPicker> {
private final FTextField txtSearch;
private final FChoiceList<GameEntityView> list;
private PickerTab(final Collection<? extends GameEntityView> items, String caption0, FImage icon0, final int maxChoices) {
super(caption0 + " (" + items.size() + ")", icon0);
txtSearch = add(new FTextField());
txtSearch.setFont(FSkinFont.get(12));
txtSearch.setGhostText(Localizer.getInstance().getMessage("lblSearch"));
txtSearch.setChangedHandler(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
String pattern = txtSearch.getText().toLowerCase();
list.clearSelection();
if (pattern.isEmpty()) {
list.setListData(items);
}
else {
List<GameEntityView> filteredList = new ArrayList<>();
for (GameEntityView option : items) {
if (option.toString().toLowerCase().contains(pattern)) {
filteredList.add(option);
}
}
list.setListData(filteredList);
}
if (!list.isEmpty() && maxChoices > 0) {
list.addSelectedIndex(0);
}
list.setScrollTop(0);
}
});
list = add(new FChoiceList<GameEntityView>(items, maxChoices, maxChoices) {
@Override
protected void onItemActivate(Integer index, GameEntityView value) {
if (maxChoices > 0) {
parentScreen.optionPane.setResult(0);
}
}
@Override
public void drawOverlay(Graphics g) {
//don't draw border
}
});
if (maxChoices > 0) {
list.addSelectedIndex(0);
}
}
@Override
protected void onActivate() {
if (parentScreen.optionPane != null) {
parentScreen.optionPane.setButtonEnabled(0, list.getMaxChoices() > 0);
}
}
@Override
protected void doLayout(float width, float height) {
float padding = txtSearch.getHeight() * 0.25f;
float y = padding;
txtSearch.setBounds(0, y, width, txtSearch.getHeight());
y += txtSearch.getHeight() + padding;
list.setBounds(0, y, width, height - y);
}
}
}

View File

@@ -0,0 +1,424 @@
/*
* 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.adventure.libgdxgui.deck;
import com.badlogic.gdx.utils.Align;
import com.google.common.collect.Iterables;
import forge.StaticData;
import forge.adventure.libgdxgui.Forge;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.libgdxgui.assets.FSkinFont;
import forge.adventure.libgdxgui.assets.FSkinImage;
import forge.adventure.libgdxgui.card.CardRenderer;
import forge.adventure.libgdxgui.card.CardRenderer.CardStackPosition;
import forge.adventure.libgdxgui.card.CardZoom;
import forge.adventure.libgdxgui.toolbox.*;
import forge.adventure.libgdxgui.toolbox.FEvent.FEventHandler;
import forge.adventure.libgdxgui.util.Utils;
import forge.card.CardEdition;
import forge.card.CardRules;
import forge.card.mana.ManaCostShard;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckgenUtil;
import forge.item.PaperCard;
import forge.model.FModel;
import forge.util.Callback;
import forge.util.Localizer;
import java.text.NumberFormat;
import java.util.Map;
import java.util.Map.Entry;
public class AddBasicLandsDialog extends FDialog {
private static final float ADD_BTN_SIZE = Utils.AVG_FINGER_HEIGHT * 0.75f;
private static final float LAND_PANEL_PADDING = Utils.scale(3);
private final Deck currentDeck;
private final Callback<CardPool> callback;
private final FLabel lblLandSet = add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblLandSet") + ":").font(FSkinFont.get(12)).textColor(FLabel.INLINE_LABEL_COLOR).build());
private final FComboBox<CardEdition> cbLandSet = add(new FComboBox<>(Iterables.filter(StaticData.instance().getEditions(), CardEdition.Predicates.hasBasicLands)));
private final FScrollPane scroller = add(new FScrollPane() {
@Override
protected ScrollBounds layoutAndGetScrollBounds(float visibleWidth, float visibleHeight) {
float padding = FOptionPane.PADDING;
float x = padding;
float totalWidth = Forge.isLandscapeMode() ? visibleWidth : 2 * visibleWidth - ADD_BTN_SIZE;
float panelWidth = (totalWidth - 6 * padding) / 5;
pnlPlains.setBounds(x, 0, panelWidth, visibleHeight);
x += panelWidth + padding;
pnlIsland.setBounds(x, 0, panelWidth, visibleHeight);
x += panelWidth + padding;
pnlSwamp.setBounds(x, 0, panelWidth, visibleHeight);
x += panelWidth + padding;
pnlMountain.setBounds(x, 0, panelWidth, visibleHeight);
x += panelWidth + padding;
pnlForest.setBounds(x, 0, panelWidth, visibleHeight);
return new ScrollBounds(totalWidth, visibleHeight);
}
});
private final LandPanel pnlPlains = scroller.add(new LandPanel("Plains"));
private final LandPanel pnlIsland = scroller.add(new LandPanel("Island"));
private final LandPanel pnlSwamp = scroller.add(new LandPanel("Swamp"));
private final LandPanel pnlMountain = scroller.add(new LandPanel("Mountain"));
private final LandPanel pnlForest = scroller.add(new LandPanel("Forest"));
private final FTextArea lblDeckInfo = add(new FTextArea(true) {
@Override
public boolean tap(float x, float y, int count) {
if (count == 2) {
Map<ManaCostShard, Integer> suggestionMap = DeckgenUtil.suggestBasicLandCount(currentDeck);
pnlPlains.count = suggestionMap.get(ManaCostShard.WHITE);
pnlIsland.count = suggestionMap.get(ManaCostShard.BLUE);
pnlSwamp.count = suggestionMap.get(ManaCostShard.BLACK);
pnlMountain.count = suggestionMap.get(ManaCostShard.RED);
pnlForest.count = suggestionMap.get(ManaCostShard.GREEN);
pnlPlains.lblCount.setText(String.valueOf(pnlPlains.count));
pnlIsland.lblCount.setText(String.valueOf(pnlIsland.count));
pnlSwamp.lblCount.setText(String.valueOf(pnlSwamp.count));
pnlMountain.lblCount.setText(String.valueOf(pnlMountain.count));
pnlForest.lblCount.setText(String.valueOf(pnlForest.count));
updateDeckInfoLabel();
}
return true;
}
});
private int nonLandCount, oldLandCount;
private CardEdition landSet;
public AddBasicLandsDialog(Deck deck, CardEdition defaultLandSet, final Callback<CardPool> callback0) {
super(Localizer.getInstance().getMessage("lblAddBasicLandsAutoSuggest").replace("%s", deck.getName()), 2);
callback = callback0;
currentDeck = deck;
lblDeckInfo.setAlignment(Align.center);
lblDeckInfo.setFont(FSkinFont.get(12));
cbLandSet.setFont(lblLandSet.getFont());
cbLandSet.setChangedHandler(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
landSet = cbLandSet.getSelectedItem();
pnlPlains.refreshArtChoices();
pnlIsland.refreshArtChoices();
pnlSwamp.refreshArtChoices();
pnlMountain.refreshArtChoices();
pnlForest.refreshArtChoices();
}
});
cbLandSet.setSelectedItem(defaultLandSet);
initButton(0, Localizer.getInstance().getMessage("lblOK"), new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
CardPool landsToAdd = new CardPool();
pnlPlains.addToCardPool(landsToAdd);
pnlIsland.addToCardPool(landsToAdd);
pnlSwamp.addToCardPool(landsToAdd);
pnlMountain.addToCardPool(landsToAdd);
pnlForest.addToCardPool(landsToAdd);
hide();
if (landsToAdd.countAll() > 0) {
callback.run(landsToAdd);
}
}
});
initButton(1, Localizer.getInstance().getMessage("lblCancel"), new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
hide();
}
});
//initialize land counts based on current deck contents
int halfCountW = 0; //track half shard count for each color to add to symbol count only if a full symbol is also found
int halfCountU = 0;
int halfCountB = 0;
int halfCountR = 0;
int halfCountG = 0;
for (Entry<PaperCard, Integer> entry : deck.getMain()) {
CardRules cardRules = entry.getKey().getRules();
int count = entry.getValue();
if (cardRules.getType().isLand()) {
oldLandCount += count;
}
else {
nonLandCount += count;
for (ManaCostShard shard : cardRules.getManaCost()) {
boolean isMonoColor = shard.isMonoColor();
if (shard.isWhite()) {
if (isMonoColor) {
pnlPlains.symbolCount += count;
continue;
}
halfCountW += count;
}
if (shard.isBlue()) {
if (isMonoColor) {
pnlIsland.symbolCount += count;
continue;
}
halfCountU += count;
}
if (shard.isBlack()) {
if (isMonoColor) {
pnlSwamp.symbolCount += count;
continue;
}
halfCountB += count;
}
if (shard.isRed()) {
if (isMonoColor) {
pnlMountain.symbolCount += count;
continue;
}
halfCountR += count;
}
if (shard.isGreen()) {
if (isMonoColor) {
pnlForest.symbolCount += count;
continue;
}
halfCountG += count;
}
}
}
//only account for half shards if full shards exist for a given color
if (pnlPlains.symbolCount > 0 && halfCountW > 0) {
pnlPlains.symbolCount += halfCountW * 0.5;
}
if (pnlIsland.symbolCount > 0 && halfCountU > 0) {
pnlIsland.symbolCount += halfCountU * 0.5;
}
if (pnlSwamp.symbolCount > 0 && halfCountB > 0) {
pnlSwamp.symbolCount += halfCountB * 0.5;
}
if (pnlMountain.symbolCount > 0 && halfCountR > 0) {
pnlMountain.symbolCount += halfCountR * 0.5;
}
if (pnlForest.symbolCount > 0 && halfCountG > 0) {
pnlForest.symbolCount += halfCountG * 0.5;
}
}
updateDeckInfoLabel();
}
@Override
protected float layoutAndGetHeight(float width, float maxHeight) {
float padding = FOptionPane.PADDING;
float x = padding;
float y = padding;
float w = width - 2 * padding;
//layout land set combo box
float comboBoxHeight = cbLandSet.getHeight();
lblLandSet.setBounds(x, y, lblLandSet.getAutoSizeBounds().width, comboBoxHeight);
cbLandSet.setBounds(x + lblLandSet.getWidth(), y, w - lblLandSet.getWidth(), comboBoxHeight);
//layout card panel scroller
y += comboBoxHeight + padding;
float panelExtraHeight = pnlPlains.cbLandArt.getHeight() + ADD_BTN_SIZE + 2 * LAND_PANEL_PADDING;
float panelWidth;
if (Forge.isLandscapeMode()) {
panelWidth = (width - 6 * padding) / 5;
}
else {
panelWidth = (2 * width - ADD_BTN_SIZE - 6 * padding) / 5;
}
float panelHeight = panelWidth * FCardPanel.ASPECT_RATIO + panelExtraHeight;
scroller.setBounds(0, y, width, panelHeight);
//adjust scroll based on prevalent colors in deck
if (pnlMountain.symbolCount + pnlForest.symbolCount > pnlPlains.symbolCount + pnlIsland.symbolCount) {
scroller.scrollToRight();
}
else {
scroller.scrollToLeft();
}
//layout info label
y += panelHeight + padding;
lblDeckInfo.setBounds(x, y, w, lblDeckInfo.getPreferredHeight(w));
return y + lblDeckInfo.getHeight() + padding;
}
private void updateDeckInfoLabel() {
NumberFormat integer = NumberFormat.getIntegerInstance();
NumberFormat percent = NumberFormat.getPercentInstance();
int newLandCount = pnlPlains.count + pnlIsland.count + pnlSwamp.count + pnlMountain.count + pnlForest.count;
double totalSymbolCount = pnlPlains.symbolCount + pnlIsland.symbolCount + pnlSwamp.symbolCount + pnlMountain.symbolCount + pnlForest.symbolCount;
if (totalSymbolCount == 0) {
totalSymbolCount = 1; //prevent divide by 0 error
}
int newTotalCount = nonLandCount + oldLandCount + newLandCount;
lblDeckInfo.setText(
String.format(Localizer.getInstance().getMessage("lblNonLandCount"), nonLandCount) + " + " +
String.format(Localizer.getInstance().getMessage("lblOldLandCount"), oldLandCount) + " + " +
String.format(Localizer.getInstance().getMessage("lblNewLandCount"), newLandCount) + " = " +
String.format(Localizer.getInstance().getMessage("lblNewTotalCount"), newTotalCount) + "\n" +
"{W} " + integer.format(pnlPlains.symbolCount) + " (" + percent.format(pnlPlains.symbolCount / totalSymbolCount) + ") | " +
"{U} " + integer.format(pnlIsland.symbolCount) + " (" + percent.format(pnlIsland.symbolCount / totalSymbolCount) + ") | " +
"{B} " + integer.format(pnlSwamp.symbolCount) + " (" + percent.format(pnlSwamp.symbolCount / totalSymbolCount) + ") | " +
"{R} " + integer.format(pnlMountain.symbolCount) + " (" + percent.format(pnlMountain.symbolCount / totalSymbolCount) + ") | " +
"{G} " + integer.format(pnlForest.symbolCount) + " (" + percent.format(pnlForest.symbolCount / totalSymbolCount) + ")");
}
private class LandPanel extends FContainer {
private final LandCardPanel cardPanel;
private final FLabel lblCount, btnSubtract, btnAdd;
private final FComboBox<String> cbLandArt;
private final String cardName;
private PaperCard card;
private int count, maxCount;
private double symbolCount;
private LandPanel(String cardName0) {
cardName = cardName0;
cardPanel = add(new LandCardPanel());
cbLandArt = add(new FComboBox<>());
cbLandArt.setFont(cbLandSet.getFont());
cbLandArt.setChangedHandler(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
int artIndex = cbLandArt.getSelectedIndex();
if (artIndex < 0) { return; }
card = generateCard(artIndex); //generate card for display
}
});
lblCount = add(new FLabel.Builder().text("0").font(FSkinFont.get(18)).align(Align.center).build());
btnSubtract = add(new FLabel.ButtonBuilder().icon(Forge.hdbuttons ? FSkinImage.HDMINUS : FSkinImage.MINUS).command(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
if (count > 0) {
count--;
lblCount.setText(String.valueOf(count));
updateDeckInfoLabel();
}
}
}).build());
btnAdd = add(new FLabel.ButtonBuilder().icon(Forge.hdbuttons ? FSkinImage.HDPLUS : FSkinImage.PLUS).command(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
if (maxCount == 0 || count < maxCount) {
count++;
lblCount.setText(String.valueOf(count));
updateDeckInfoLabel();
}
}
}).build());
}
private void addToCardPool(CardPool pool) {
if (count == 0) { return; }
int artIndex = cbLandArt.getSelectedIndex();
if (artIndex < 0) { return; }
if (artIndex > 0 && card != null) {
pool.add(card, count); //simplify things if art index specified
}
else {
for (int i = 0; i < count; i++) {
pool.add(generateCard(artIndex));
}
}
}
private PaperCard generateCard(int artIndex) {
PaperCard c = FModel.getMagicDb().getCommonCards().getCard(cardName, landSet.getCode(), artIndex);
if (c == null) {
//if can't find land for this set, fall back to Zendikar lands
c = FModel.getMagicDb().getCommonCards().getCard(cardName, "ZEN");
}
return c;
}
private void refreshArtChoices() {
cbLandArt.removeAllItems();
if (landSet == null) { return; }
int artChoiceCount = FModel.getMagicDb().getCommonCards().getArtCount(cardName, landSet.getCode());
cbLandArt.addItem(Localizer.getInstance().getMessage("lblAssortedArt"));
for (int i = 1; i <= artChoiceCount; i++) {
cbLandArt.addItem(Localizer.getInstance().getMessage("lblCardArtN", String.valueOf(i)));
}
}
@Override
protected void doLayout(float width, float height) {
float y = height - ADD_BTN_SIZE;
float buttonWidth = ADD_BTN_SIZE;
float labelWidth = width - 2 * ADD_BTN_SIZE;
float minLabelWidth = lblCount.getFont().getBounds("0").width + 2 * lblCount.getInsets().x;
if (labelWidth < minLabelWidth) { //ensure count label has enough room for display a single digit count at normal font size
labelWidth = minLabelWidth;
buttonWidth = (width - labelWidth) / 2;
}
btnSubtract.setBounds(0, y, buttonWidth, ADD_BTN_SIZE);
lblCount.setBounds(buttonWidth, y, labelWidth, ADD_BTN_SIZE);
btnAdd.setBounds(width - buttonWidth, y, buttonWidth, ADD_BTN_SIZE);
y -= cbLandArt.getHeight() + LAND_PANEL_PADDING;
cbLandArt.setBounds(0, y, width, cbLandArt.getHeight());
float cardPanelHeight = y - LAND_PANEL_PADDING;
float cardPanelWidth = cardPanelHeight / FCardPanel.ASPECT_RATIO;
cardPanel.setBounds((width - cardPanelWidth) / 2, 0, cardPanelWidth, cardPanelHeight);
}
private class LandCardPanel extends FDisplayObject {
private LandCardPanel() {
}
@Override
public boolean tap(float x, float y, int count) {
if (card == null) { return false; }
CardZoom.show(card);
return true;
}
@Override
public boolean longPress(float x, float y) {
if (card == null) { return false; }
CardZoom.show(card);
return true;
}
@Override
public void draw(Graphics g) {
if (card == null) { return; }
CardRenderer.drawCard(g, card, 0, 0, getWidth(), getHeight(), CardStackPosition.Top);
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,184 @@
/*
* 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.adventure.libgdxgui.deck;
import com.google.common.collect.ImmutableList;
import forge.adventure.libgdxgui.Forge;
import forge.adventure.libgdxgui.Graphics;
import forge.deck.Deck;
import forge.deck.DeckImportController;
import forge.deck.DeckRecognizer;
import forge.deck.DeckRecognizer.TokenType;
import forge.gui.FThreads;
import forge.gui.util.SOptionPane;
import forge.adventure.libgdxgui.toolbox.*;
import forge.adventure.libgdxgui.toolbox.FEvent.FEventHandler;
import forge.util.Callback;
import forge.util.Localizer;
import java.util.List;
public class FDeckImportDialog extends FDialog {
private final Callback<Deck> callback;
private final FTextArea txtInput = add(new FTextArea(true));
private final FCheckBox newEditionCheck = add(new FCheckBox(Localizer.getInstance().getMessage("lblImportLatestVersionCard"), true));
private final FCheckBox dateTimeCheck = add(new FCheckBox(Localizer.getInstance().getMessage("lblUseOnlySetsReleasedBefore"), false));
/*setting onlyCoreExpCheck to false allow the copied cards to pass the check of deck contents
forge-core\src\main\java\forge\deck\Deck.javaDeck.java starting @ Line 320 which is called by
forge-gui-mobile\src\forge\deck\FDeckEditor.java starting @ Line 373
(as of latest commit: 8e6655e3ee67688cff66b422d4722c58392eaa7e)
*/
private final FCheckBox onlyCoreExpCheck = add(new FCheckBox(Localizer.getInstance().getMessage("lblUseOnlyCoreAndExpansionSets"), false));
private final FComboBox<String> monthDropdown = add(new FComboBox<>()); //don't need wrappers since skin can't change while this dialog is open
private final FComboBox<Integer> yearDropdown = add(new FComboBox<>());
private final boolean showOptions;
private final DeckImportController controller;
private final static ImmutableList<String> importOrCancel = ImmutableList.of(Localizer.getInstance().getMessage("lblImport"), Localizer.getInstance().getMessage("lblCancel"));
public FDeckImportDialog(final boolean replacingDeck, final Callback<Deck> callback0) {
super(Localizer.getInstance().getMessage("lblImportFromClipboard"), 2);
callback = callback0;
controller = new DeckImportController(replacingDeck, newEditionCheck, dateTimeCheck, onlyCoreExpCheck, monthDropdown, yearDropdown);
txtInput.setText(Forge.getClipboard().getContents()); //just pull import directly off the clipboard
initButton(0, Localizer.getInstance().getMessage("lblImport"), new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
FThreads.invokeInBackgroundThread(new Runnable() {
@Override
public void run() {
List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText()); //ensure deck updated based on any changes to options
//if there are any unknown cards, let user know this and give them the option to cancel
StringBuilder sb = new StringBuilder();
for (DeckRecognizer.Token token : tokens) {
if (token.getType() == TokenType.UnknownCard) {
if (sb.length() > 0) {
sb.append("\n");
}
sb.append(token.getNumber()).append(" ").append(token.getText());
}
}
if (sb.length() > 0) {
if (SOptionPane.showOptionDialog(Localizer.getInstance().getMessage("lblFollowingCardsCannotBeImported") + "\n\n" + sb, Localizer.getInstance().getMessage("lblImportRemainingCards"), SOptionPane.INFORMATION_ICON, importOrCancel) == 1) {
return;
}
}
final Deck deck = controller.accept(); //must accept in background thread in case a dialog is shown
if (deck == null) { return; }
FThreads.invokeInEdtLater(new Runnable() {
@Override
public void run() {
hide();
callback.run(deck);
}
});
}
});
}
});
initButton(1, Localizer.getInstance().getMessage("lblCancel"), new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
hide();
}
});
List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText());
//ensure at least one known card found on clipboard
for (DeckRecognizer.Token token : tokens) {
if (token.getType() == TokenType.KnownCard) {
showOptions = true;
dateTimeCheck.setCommand(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
updateDropDownEnabled();
}
});
updateDropDownEnabled();
return;
}
}
showOptions = false;
setButtonEnabled(0, false);
txtInput.setText(Localizer.getInstance().getMessage("lblNoKnownCardsOnClipboard"));
}
private void updateDropDownEnabled() {
boolean enabled = dateTimeCheck.isSelected();
monthDropdown.setEnabled(enabled);
yearDropdown.setEnabled(enabled);
}
@Override
public void drawOverlay(Graphics g) {
super.drawOverlay(g);
if (showOptions) {
float y = txtInput.getTop() - FOptionPane.PADDING;
g.drawLine(BORDER_THICKNESS, BORDER_COLOR, 0, y, getWidth(), y);
}
}
@Override
protected float layoutAndGetHeight(float width, float maxHeight) {
float padding = FOptionPane.PADDING;
float x = padding;
float y = padding;
float w = width - 2 * padding;
float h;
if (showOptions) {
h = monthDropdown.getHeight();
float fieldPadding = padding / 2;
newEditionCheck.setBounds(x, y, w, h);
y += h + fieldPadding;
dateTimeCheck.setBounds(x, y, w, h);
y += h + fieldPadding;
float dropDownWidth = (w - fieldPadding) / 2;
monthDropdown.setBounds(x, y, dropDownWidth, h);
yearDropdown.setBounds(x + dropDownWidth + fieldPadding, y, dropDownWidth, h);
y += h + fieldPadding;
onlyCoreExpCheck.setBounds(x, y, w, h);
y += h + 2 * padding;
}
h = txtInput.getPreferredHeight(w);
float maxTextBoxHeight = maxHeight - y - padding;
if (h > maxTextBoxHeight) {
h = maxTextBoxHeight;
}
txtInput.setBounds(x, y, w, h);
y += h + padding;
if (showOptions) {
h = newEditionCheck.getHeight();
}
return y;
}
}

View File

@@ -0,0 +1,175 @@
package forge.adventure.libgdxgui.deck;
import forge.adventure.libgdxgui.Forge;
import forge.adventure.libgdxgui.assets.*;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckSection;
import forge.item.PaperCard;
import forge.adventure.libgdxgui.itemmanager.CardManager;
import forge.itemmanager.ItemManagerConfig;
import forge.adventure.libgdxgui.itemmanager.filters.ItemFilter;
import forge.adventure.libgdxgui.menu.FMenuItem;
import forge.adventure.libgdxgui.menu.FPopupMenu;
import forge.adventure.libgdxgui.screens.FScreen;
import forge.adventure.libgdxgui.screens.match.MatchController;
import forge.adventure.libgdxgui.toolbox.FEvent;
import forge.adventure.libgdxgui.toolbox.FEvent.FEventHandler;
import forge.adventure.libgdxgui.toolbox.FOptionPane;
import forge.util.Localizer;
import java.util.Map.Entry;
public class FDeckViewer extends FScreen {
private static FDeckViewer deckViewer;
private static final FPopupMenu menu = new FPopupMenu() {
@Override
protected void buildMenu() {
Deck deck = deckViewer.deck;
for (Entry<DeckSection, CardPool> entry : deck) {
final DeckSection section = entry.getKey();
final CardPool pool = entry.getValue();
int count = pool.countAll();
if (count == 0) { continue; }
final String captionPrefix;
final FImage icon;
switch (section) {
default:
case Main:
captionPrefix = Localizer.getInstance().getMessage("ttMain");
icon = FDeckEditor.MAIN_DECK_ICON;
break;
case Sideboard:
captionPrefix = Localizer.getInstance().getMessage("lblSideboard");
icon = FDeckEditor.SIDEBOARD_ICON;
break;
case Commander:
captionPrefix = Localizer.getInstance().getMessage("lblCommander");
icon = FSkinImage.COMMANDER;
break;
case Avatar:
captionPrefix = Localizer.getInstance().getMessage("lblAvatar");
icon = new FTextureRegionImage(FSkin.getAvatars().get(0));
break;
case Planes:
captionPrefix = Localizer.getInstance().getMessage("lblPlanes");
icon = FSkinImage.CHAOS;
break;
case Schemes:
captionPrefix = Localizer.getInstance().getMessage("lblSchemes");
icon = FSkinImage.POISON;
break;
}
FMenuItem item = new FMenuItem(captionPrefix + " (" + count + ")", icon, new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
deckViewer.setCurrentSection(section);
}
});
if (section == deckViewer.currentSection) {
item.setSelected(true);
}
addItem(item);
}
addItem(new FMenuItem(Localizer.getInstance().getMessage("btnCopyToClipboard"), Forge.hdbuttons ? FSkinImage.HDEXPORT : FSkinImage.BLANK, new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
copyDeckToClipboard(deckViewer.deck);
}
}));
}
};
public static void copyDeckToClipboard(Deck deck) {
final String nl = System.getProperty("line.separator");
final StringBuilder deckList = new StringBuilder();
String dName = deck.getName();
//fix copying a commander netdeck then importing it again...
if (dName.startsWith("[Commander")||dName.contains("Commander"))
dName = "";
deckList.append(dName == null ? "" : dName + nl + nl);
for (DeckSection s : DeckSection.values()){
CardPool cp = deck.get(s);
if (cp == null || cp.isEmpty()) {
continue;
}
deckList.append(s.toString()).append(": ");
deckList.append(nl);
for (final Entry<PaperCard, Integer> ev : cp) {
deckList.append(ev.getValue()).append(" ").append(ev.getKey()).append(nl);
}
deckList.append(nl);
}
Forge.getClipboard().setContents(deckList.toString());
FOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblDeckListCopiedClipboard", deck.getName()));
}
private final Deck deck;
private final CardManager cardManager;
private DeckSection currentSection;
public static void show(final Deck deck0) {
show(deck0, false);
}
public static void show(final Deck deck0, boolean noPreload) {
if (deck0 == null) { return; }
if (!noPreload){
/*preload deck to cache*/
ImageCache.preloadCache(deck0);
}
deckViewer = new FDeckViewer(deck0);
deckViewer.setRotate180(MatchController.getView() != null && MatchController.getView().isTopHumanPlayerActive());
Forge.openScreen(deckViewer);
}
private FDeckViewer(Deck deck0) {
super(new MenuHeader(deck0.getName(), menu) {
@Override
protected boolean displaySidebarForLandscapeMode() {
return false;
}
});
deck = deck0;
cardManager = new CardManager(false);
cardManager.setPool(deck.getMain());
currentSection = DeckSection.Main;
updateCaption();
add(cardManager);
cardManager.setup(ItemManagerConfig.DECK_VIEWER);
}
private void setCurrentSection(DeckSection currentSection0) {
if (currentSection == currentSection0) { return; }
currentSection = currentSection0;
cardManager.setPool(deck.get(currentSection));
updateCaption();
}
private void updateCaption() {
cardManager.setCaption(currentSection.name());
}
@Override
protected void doLayout(float startY, float width, float height) {
float x = 0;
if (Forge.isLandscapeMode()) { //add some horizontal padding in landscape mode
x = ItemFilter.PADDING;
width -= 2 * x;
}
cardManager.setBounds(x, startY, width, height - startY);
}
@Override
public FScreen getLandscapeBackdropScreen() {
return null; //never use backdrop for editor
}
}

View File

@@ -0,0 +1,211 @@
package forge.adventure.libgdxgui.deck;
import forge.adventure.libgdxgui.assets.FImage;
import forge.deck.CardPool;
import forge.item.PaperCard;
import forge.adventure.libgdxgui.itemmanager.CardManager;
import forge.adventure.libgdxgui.itemmanager.ItemManager.ContextMenuBuilder;
import forge.itemmanager.ItemManagerConfig;
import forge.adventure.libgdxgui.menu.FDropDownMenu;
import forge.adventure.libgdxgui.menu.FMenuItem;
import forge.adventure.libgdxgui.screens.FScreen;
import forge.adventure.libgdxgui.screens.TabPageScreen;
import forge.adventure.libgdxgui.toolbox.FDialog;
import forge.adventure.libgdxgui.toolbox.FEvent;
import forge.adventure.libgdxgui.toolbox.FEvent.FEventHandler;
import forge.adventure.libgdxgui.toolbox.GuiChoose;
import forge.util.Callback;
import forge.util.Localizer;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
public class FSideboardDialog extends FDialog {
private final SideboardTabs tabs;
private final Callback<List<PaperCard>> callback;
public FSideboardDialog(CardPool sideboard, CardPool main, final Callback<List<PaperCard>> callback0, String message) {
super(String.format(Localizer.getInstance().getMessage("lblUpdateMainFromSideboard"), message), 1);
callback = callback0;
tabs = add(new SideboardTabs(sideboard, main));
initButton(0, Localizer.getInstance().getMessage("lblOK"), new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
hide();
}
});
if (sideboard.isEmpty()) { //show main deck by default if sideboard is empty
tabs.setSelectedPage(tabs.getMainDeckPage());
}
}
@Override
public void setVisible(boolean visible0) {
super.setVisible(visible0);
if (!visible0) { //do callback when hidden to ensure you don't get stuck if Back pressed
callback.run(tabs.getMainDeckPage().cardManager.getPool().toFlatList());
}
}
@Override
protected float layoutAndGetHeight(float width, float maxHeight) {
tabs.setBounds(0, 0, width, maxHeight);
return maxHeight;
}
private static class SideboardTabs extends TabPageScreen<SideboardTabs> {
private SideboardTabs(CardPool sideboard, CardPool main) {
super(new TabPageBase[] {
new SideboardPage(sideboard),
new MainDeckPage(main)
}, false);
((SideboardPage)tabPages[0]).parent = this;
((MainDeckPage)tabPages[1]).parent = this;
}
private SideboardPage getSideboardPage() {
return ((SideboardPage)tabPages[0]);
}
private MainDeckPage getMainDeckPage() {
return ((MainDeckPage)tabPages[1]);
}
@Override
protected boolean canActivateTabPage() {
return true; //always allow activating tab pages while this is open
}
@Override
public FScreen getLandscapeBackdropScreen() {
return null;
}
private static abstract class TabPageBase extends TabPage<SideboardTabs> {
protected SideboardTabs parent;
protected final CardManager cardManager = add(new CardManager(false));
protected TabPageBase(CardPool cardPool, FImage icon0) {
super("", icon0);
cardManager.setItemActivateHandler(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
onCardActivated(cardManager.getSelectedItem());
}
});
cardManager.setContextMenuBuilder(new ContextMenuBuilder<PaperCard>() {
@Override
public void buildMenu(final FDropDownMenu menu, final PaperCard card) {
TabPageBase.this.buildMenu(menu, card);
}
});
cardManager.setup(ItemManagerConfig.SIDEBOARD);
cardManager.setPool(new CardPool(cardPool)); //create copy of card pool to avoid modifying the original card pool
updateCaption();
}
protected void addCard(PaperCard card, int qty) {
cardManager.addItem(card, qty);
updateCaption();
}
protected void removeCard(PaperCard card, int qty) {
cardManager.removeItem(card, qty);
updateCaption();
}
protected void addItem(FDropDownMenu menu, final String verb, String dest, FImage icon, final Callback<Integer> callback) {
String label = verb;
if (!StringUtils.isEmpty(dest)) {
label += " " + dest;
}
menu.addItem(new FMenuItem(label, icon, new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
PaperCard card = cardManager.getSelectedItem();
int max = cardManager.getItemCount(card);
if (max == 1) {
callback.run(max);
}
else {
GuiChoose.getInteger(card + " - " + verb + " " + Localizer.getInstance().getMessage("lblHowMany"), 1, max, 20, callback);
}
}
}));
}
protected abstract void updateCaption();
protected abstract void onCardActivated(PaperCard card);
protected abstract void buildMenu(final FDropDownMenu menu, final PaperCard card);
@Override
protected void doLayout(float width, float height) {
cardManager.setBounds(0, 0, width, height);
}
}
private static class SideboardPage extends TabPageBase {
protected SideboardPage(CardPool cardPool) {
super(cardPool, FDeckEditor.SIDEBOARD_ICON);
cardManager.setCaption(Localizer.getInstance().getMessage("lblSideboard"));
}
@Override
protected void updateCaption() {
caption = Localizer.getInstance().getMessage("lblSideboard") + " (" + cardManager.getPool().countAll() + ")";
}
@Override
protected void onCardActivated(PaperCard card) {
removeCard(card, 1);
parent.getMainDeckPage().addCard(card, 1);
}
@Override
protected void buildMenu(FDropDownMenu menu, final PaperCard card) {
addItem(menu, Localizer.getInstance().getMessage("lblMove"), Localizer.getInstance().getMessage("lblToMainDeck"), FDeckEditor.MAIN_DECK_ICON, new Callback<Integer>() {
@Override
public void run(Integer result) {
if (result == null || result <= 0) { return; }
removeCard(card, result);
parent.getMainDeckPage().addCard(card, result);
}
});
}
}
private static class MainDeckPage extends TabPageBase {
protected MainDeckPage(CardPool cardPool) {
super(cardPool, FDeckEditor.MAIN_DECK_ICON);
cardManager.setCaption(Localizer.getInstance().getMessage("ttMain"));
}
@Override
protected void updateCaption() {
caption = Localizer.getInstance().getMessage("ttMain") + " (" + cardManager.getPool().countAll() + ")";
}
@Override
protected void onCardActivated(PaperCard card) {
removeCard(card, 1);
parent.getSideboardPage().addCard(card, 1);
}
@Override
protected void buildMenu(FDropDownMenu menu, final PaperCard card) {
addItem(menu, Localizer.getInstance().getMessage("lblMove"), Localizer.getInstance().getMessage("lbltosideboard"), FDeckEditor.SIDEBOARD_ICON, new Callback<Integer>() {
@Override
public void run(Integer result) {
if (result == null || result <= 0) { return; }
removeCard(card, result);
parent.getSideboardPage().addCard(card, result);
}
});
}
}
}
}

View File

@@ -0,0 +1,111 @@
package forge.adventure.libgdxgui.deck;
import forge.adventure.libgdxgui.Forge;
import forge.deck.CardPool;
import forge.item.PaperCard;
import forge.adventure.libgdxgui.itemmanager.CardManager;
import forge.itemmanager.ItemManagerConfig;
import forge.model.FModel;
import forge.adventure.libgdxgui.screens.FScreen;
import forge.adventure.libgdxgui.toolbox.FButton;
import forge.adventure.libgdxgui.toolbox.FEvent;
import forge.adventure.libgdxgui.toolbox.FEvent.FEventHandler;
import forge.util.Aggregates;
import forge.util.Localizer;
import forge.adventure.libgdxgui.util.Utils;
public class FVanguardChooser extends FScreen {
public static final float PADDING = Utils.scale(5);
private static final CardPool allHumanAvatars = new CardPool();
private static final CardPool allAiAvatars = new CardPool();
private static final CardPool nonRandomHumanAvatars = new CardPool();
private static final CardPool nonRandomAiAvatars = new CardPool();
static {
for (PaperCard c : FModel.getMagicDb().getVariantCards().getAllCards()) {
if (c.getRules().getType().isVanguard()) {
allHumanAvatars.add(c);
if (!c.getRules().getAiHints().getRemRandomDecks()) {
nonRandomHumanAvatars.add(c);
}
if (!c.getRules().getAiHints().getRemAIDecks()) {
allAiAvatars.add(c);
if (!c.getRules().getAiHints().getRemRandomDecks()) {
nonRandomAiAvatars.add(c);
}
}
}
}
}
private final CardManager lstVanguards = add(new CardManager(true));
private final FButton btnRandom = add(new FButton(Localizer.getInstance().getMessage("lblRandomVanguard")));
private boolean isAi;
public FVanguardChooser(boolean isAi0, FEventHandler selectionChangedHandler) {
super("");
isAi = isAi0;
lstVanguards.setItemActivateHandler(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
Forge.back();
}
});
btnRandom.setCommand(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
selectRandom();
Forge.back();
}
});
lstVanguards.setup(ItemManagerConfig.VANGUARDS);
lstVanguards.setPool(isAi ? allAiAvatars : allHumanAvatars, true);
lstVanguards.setSelectionChangedHandler(selectionChangedHandler);
selectRandom();
}
private void selectRandom() {
if (lstVanguards.getItemCount() == 0) { return; }
if (isAi) {
lstVanguards.setSelectedItem(Aggregates.random(nonRandomAiAvatars).getKey());
}
else {
lstVanguards.setSelectedItem(Aggregates.random(nonRandomHumanAvatars).getKey());
}
}
public void setIsAi(boolean isAi0) {
if (isAi == isAi0) { return; }
isAi = isAi0;
PaperCard lastSelection = lstVanguards.getSelectedItem();
lstVanguards.setPool(isAi ? allAiAvatars : allHumanAvatars, true);
if (lastSelection != null) {
lstVanguards.setSelectedItem(lastSelection);
}
if (lstVanguards.getSelectedIndex() == -1) {
selectRandom();
}
}
public CardManager getLstVanguards() {
return lstVanguards;
}
@Override
protected void doLayout(float startY, float width, float height) {
float x = PADDING;
float y = startY;
width -= 2 * x;
float buttonHeight = Utils.AVG_FINGER_HEIGHT;
lstVanguards.setBounds(x, y, width, height - y - buttonHeight - 2 * PADDING); //leave room for buttons at bottom
y += lstVanguards.getHeight() + PADDING;
btnRandom.setBounds(x, y, width, buttonHeight);
}
}

View File

@@ -0,0 +1,158 @@
package forge.adventure.libgdxgui.error;
import com.badlogic.gdx.utils.Align;
import forge.adventure.libgdxgui.Forge;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.libgdxgui.assets.FSkinColor;
import forge.adventure.libgdxgui.assets.FSkinFont;
import forge.adventure.libgdxgui.screens.FScreen;
import forge.adventure.libgdxgui.toolbox.FButton;
import forge.adventure.libgdxgui.toolbox.FEvent;
import forge.adventure.libgdxgui.toolbox.FScrollPane;
import forge.adventure.libgdxgui.toolbox.FTextArea;
import forge.adventure.libgdxgui.util.TextBounds;
import forge.adventure.libgdxgui.util.Utils;
import forge.gui.error.BugReporter;
import forge.util.Callback;
public class BugReportDialog extends FScreen { //use screen rather than dialog so screen with bug isn't rendered
private static final float PADDING = Utils.scale(5);
private static final float BUTTON_HEIGHT = Utils.AVG_FINGER_HEIGHT * 0.75f;
private static boolean isOpen;
public static void show(String title, String text, boolean showExitAppBtn) {
if (isOpen || Forge.getCurrentScreen() == null) { return; } //don't allow showing if Forge not finished initializing yet
isOpen = true;
Forge.openScreen(new BugReportDialog(title, text, showExitAppBtn));
}
private final FTextArea lblHeader = add(new FTextArea(false, "Report Bug"));
private final TemplateView tvDetails;
private final FButton btnReport = add(new FButton(BugReporter.REPORT));
private final FButton btnSave = add(new FButton(BugReporter.SAVE));
private final FButton btnDiscard = add(new FButton(BugReporter.DISCARD));
private final FButton btnExit = add(new FButton(BugReporter.EXIT));
private BugReportDialog(String title, String text0, boolean showExitAppBtn) {
super(title);
lblHeader.setFont(FSkinFont.get(12));
tvDetails = add(new TemplateView(text0));
btnReport.setCommand(new FEvent.FEventHandler() {
@Override
public void handleEvent(FEvent e) {
BugReporter.sendSentry();
Forge.back();
}
});
btnSave.setCommand(new FEvent.FEventHandler() {
@Override
public void handleEvent(FEvent e) {
BugReporter.saveToFile(tvDetails.text);
}
});
btnDiscard.setCommand(new FEvent.FEventHandler() {
@Override
public void handleEvent(FEvent e) {
Forge.back();
}
});
if (showExitAppBtn) {
btnExit.setCommand(new FEvent.FEventHandler() {
@Override
public void handleEvent(FEvent e) {
Forge.exit(true);
}
});
}
else {
btnExit.setVisible(false);
}
}
@Override
public FScreen getLandscapeBackdropScreen() {
return null;
}
@Override
public void onClose(Callback<Boolean> canCloseCallback) {
super.onClose(canCloseCallback);
isOpen = false;
}
@Override
protected void doLayout(float startY, float width, float height) {
float x = PADDING;
float y = startY + PADDING;
float w = width - 2 * PADDING;
lblHeader.setBounds(x, y, w, lblHeader.getPreferredHeight(w));
y += lblHeader.getHeight() + PADDING;
float buttonWidth, totalButtonHeight;
float buttonHeight = BUTTON_HEIGHT;
boolean landscapeMode = Forge.isLandscapeMode();
if (landscapeMode) {
buttonWidth = (w - 3 * PADDING) / 4;
totalButtonHeight = buttonHeight;
}
else {
buttonWidth = (w - PADDING) / 2;
totalButtonHeight = 2 * buttonHeight + PADDING;
}
tvDetails.setBounds(x, y, w, height - totalButtonHeight - 2 * PADDING - y);
y += tvDetails.getHeight() + PADDING;
btnReport.setBounds(x, y, buttonWidth, buttonHeight);
btnSave.setBounds(x + buttonWidth + PADDING, y, buttonWidth, buttonHeight);
if (landscapeMode) {
x += 2 * (buttonWidth + PADDING);
}
else {
y += buttonHeight + PADDING;
}
if (btnExit.isVisible()) {
btnDiscard.setBounds(x, y, buttonWidth, buttonHeight);
btnExit.setBounds(x + buttonWidth + PADDING, y, buttonWidth, buttonHeight);
}
else {
btnDiscard.setBounds(x, y, 2 * buttonWidth + PADDING, buttonHeight);
}
}
private static class TemplateView extends FScrollPane {
private static final FSkinFont FONT = FSkinFont.get(11);
private static final FSkinColor BACK_COLOR = FSkinColor.get(FSkinColor.Colors.CLR_ZEBRA);
private static final FSkinColor FORE_COLOR = FSkinColor.get(FSkinColor.Colors.CLR_TEXT);
private static final FSkinColor BORDER_COLOR = FSkinColor.get(FSkinColor.Colors.CLR_BORDERS);
private static final float PADDING = Utils.scale(3);
private final String text;
private TemplateView(String text0) {
text = text0;
setHeight(Forge.getScreenHeight() / 3);
}
@Override
protected ScrollBounds layoutAndGetScrollBounds(float visibleWidth, float visibleHeight) {
TextBounds bounds = FONT.getMultiLineBounds(text);
return new ScrollBounds(bounds.width + 2 * PADDING, bounds.height + 2 * PADDING +
FONT.getLineHeight() - FONT.getCapHeight()); //account for height below baseline of final line);
}
@Override
public void drawBackground(Graphics g) {
g.fillRect(BACK_COLOR, 0, 0, getWidth(), getHeight());
g.drawText(text, FONT, FORE_COLOR, PADDING - getScrollLeft(), PADDING - getScrollTop(), getScrollWidth() - 2 * PADDING, getScrollHeight() - 2 * PADDING, false, Align.left, false);
}
@Override
public void drawOverlay(Graphics g) {
super.drawOverlay(g);
g.drawRect(1, BORDER_COLOR, 0, 0, getWidth(), getHeight());
}
}
}

View File

@@ -0,0 +1,92 @@
package forge.adventure.libgdxgui.itemmanager;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.libgdxgui.assets.FSkinColor;
import forge.adventure.libgdxgui.assets.FSkinFont;
import forge.adventure.libgdxgui.card.CardRenderer;
import forge.adventure.libgdxgui.card.CardZoom;
import forge.item.PaperCard;
import forge.adventure.libgdxgui.itemmanager.filters.*;
import forge.adventure.libgdxgui.toolbox.FList;
import forge.adventure.libgdxgui.toolbox.FList.CompactModeHandler;
import java.util.Map.Entry;
/**
* ItemManager for cards
*/
public class CardManager extends ItemManager<PaperCard> {
public CardManager(boolean wantUnique0) {
super(PaperCard.class, wantUnique0);
}
@Override
protected void addDefaultFilters() {
addDefaultFilters(this);
}
@Override
protected TextSearchFilter<PaperCard> createSearchFilter() {
return createSearchFilter(this);
}
@Override
protected AdvancedSearchFilter<PaperCard> createAdvancedSearchFilter() {
return createAdvancedSearchFilter(this);
}
protected void onCardLongPress(int index, Entry<PaperCard, Integer> value, float x, float y) {
CardZoom.show(model.getOrderedList(), index, CardManager.this);
}
/* Static overrides shared with SpellShopManager*/
public static void addDefaultFilters(final ItemManager<? super PaperCard> itemManager) {
itemManager.addFilter(new CardColorFilter(itemManager));
itemManager.addFilter(new CardFormatFilter(itemManager));
itemManager.addFilter(new CardTypeFilter(itemManager));
}
public static TextSearchFilter<PaperCard> createSearchFilter(final ItemManager<? super PaperCard> itemManager) {
return new CardSearchFilter(itemManager);
}
public static AdvancedSearchFilter<PaperCard> createAdvancedSearchFilter(final ItemManager<? super PaperCard> itemManager) {
return new AdvancedSearchFilter<>(itemManager);
}
@Override
public ItemRenderer getListItemRenderer(final CompactModeHandler compactModeHandler) {
return new ItemRenderer() {
@Override
public float getItemHeight() {
return CardRenderer.getCardListItemHeight(compactModeHandler.isCompactMode());
}
@Override
public void drawValue(Graphics g, Entry<PaperCard, Integer> value, FSkinFont font, FSkinColor foreColor, FSkinColor backColor, boolean pressed, float x, float y, float w, float h) {
CardRenderer.drawCardListItem(g, font, foreColor, value.getKey(), isInfinite() ? 0 : value.getValue(), getItemSuffix(value), x, y, w, h, compactModeHandler.isCompactMode());
}
@Override
public boolean tap(Integer index, Entry<PaperCard, Integer> value, float x, float y, int count) {
return CardRenderer.cardListItemTap(model.getOrderedList(), index, CardManager.this, x, y, count, compactModeHandler.isCompactMode());
}
@Override
public boolean longPress(Integer index, Entry<PaperCard, Integer> value, float x, float y) {
if (CardRenderer.cardListItemTap(model.getOrderedList(), index, CardManager.this, x, y, 1, compactModeHandler.isCompactMode())) {
return true; //avoid calling onCardLongPress if user long presses on card art
}
onCardLongPress(index, value, x, y);
return true;
}
@Override
public boolean allowPressEffect(FList<Entry<PaperCard, Integer>> list, float x, float y) {
//only allow press effect if right of card art
return x > CardRenderer.getCardListItemHeight(compactModeHandler.isCompactMode()) * CardRenderer.CARD_ART_RATIO;
}
};
}
}

View File

@@ -0,0 +1,181 @@
package forge.adventure.libgdxgui.itemmanager;
import com.badlogic.gdx.utils.Align;
import forge.adventure.libgdxgui.Forge;
import forge.adventure.libgdxgui.Graphics;
import forge.adventure.libgdxgui.assets.FSkinColor;
import forge.adventure.libgdxgui.assets.FSkinFont;
import forge.adventure.libgdxgui.assets.FSkinImage;
import forge.adventure.libgdxgui.card.CardFaceSymbols;
import forge.adventure.libgdxgui.card.CardRenderer;
import forge.adventure.libgdxgui.deck.FDeckViewer;
import forge.adventure.libgdxgui.itemmanager.filters.AdvancedSearchFilter;
import forge.adventure.libgdxgui.itemmanager.filters.DeckColorFilter;
import forge.adventure.libgdxgui.itemmanager.filters.DeckFormatFilter;
import forge.adventure.libgdxgui.itemmanager.filters.TextSearchFilter;
import forge.adventure.libgdxgui.toolbox.FList;
import forge.adventure.libgdxgui.toolbox.FList.CompactModeHandler;
import forge.adventure.libgdxgui.util.Utils;
import forge.card.ColorSet;
import forge.deck.DeckProxy;
import forge.deck.io.DeckPreferences;
import forge.game.GameType;
import forge.game.IHasGameType;
import forge.itemmanager.ItemManagerConfig;
import forge.util.Localizer;
import java.util.Map.Entry;
/**
* ItemManager for decks
*/
public final class DeckManager extends ItemManager<DeckProxy> implements IHasGameType {
private final GameType gameType;
/**
* Creates deck list for selected decks for quick deleting, editing, and
* basic info. "selectable" and "editable" assumed true.
*
* @param gt
*/
public DeckManager(final GameType gt) {
super(DeckProxy.class, true);
gameType = gt;
setCaption(Localizer.getInstance().getMessage("lblDecks"));
}
public GameType getGameType() {
return gameType;
}
@Override
public void setup(ItemManagerConfig config0) {
boolean wasStringOnly = (getConfig() == ItemManagerConfig.STRING_ONLY);
boolean isStringOnly = (config0 == ItemManagerConfig.STRING_ONLY);
super.setup(config0, null);
if (isStringOnly != wasStringOnly) {
restoreDefaultFilters();
}
}
@Override
protected void addDefaultFilters() {
if (getConfig() == ItemManagerConfig.STRING_ONLY) { return; }
addFilter(new DeckColorFilter(this));
addFilter(new DeckFormatFilter(this));
}
@Override
protected TextSearchFilter<DeckProxy> createSearchFilter() {
return new TextSearchFilter<>(this);
}
@Override
protected AdvancedSearchFilter<DeckProxy> createAdvancedSearchFilter() {
return new AdvancedSearchFilter<>(this);
}
@Override
protected boolean allowSortChange() {
return false;
}
private static final float IMAGE_SIZE = CardRenderer.MANA_SYMBOL_SIZE;
@Override
public ItemRenderer getListItemRenderer(final CompactModeHandler compactModeHandler) {
return new ItemRenderer() {
@Override
public float getItemHeight() {
if (DeckManager.this.getConfig().getCols().size() == 1) {
//if just string column, use normal list item height
return Utils.AVG_FINGER_HEIGHT;
}
return CardRenderer.getCardListItemHeight(compactModeHandler.isCompactMode()); //use same height for decks as for cards
}
@Override
public boolean tap(Integer index, Entry<DeckProxy, Integer> value, float x, float y, int count) {
float bottomRight = IMAGE_SIZE + 2 * FList.PADDING;
if (x <= bottomRight && y <= bottomRight) {
DeckPreferences prefs = DeckPreferences.getPrefs(value.getKey());
prefs.setStarCount((prefs.getStarCount() + 1) % 2); //TODO: consider supporting more than 1 star
return true;
}
return false;
}
@Override
public boolean longPress(Integer index, Entry<DeckProxy, Integer> value, float x, float y) {
FDeckViewer.show(value.getKey().getDeck());
return true;
}
@Override
public void drawValue(Graphics g, Entry<DeckProxy, Integer> value, FSkinFont font, FSkinColor foreColor, FSkinColor backColor, boolean pressed, float x, float y, float w, float h) {
DeckProxy deck = value.getKey();
if (DeckManager.this.getConfig().getCols().size() == 1) {
//if just string column, just draw deck string value
g.drawText(deck.toString(), font, foreColor, x, y, w, h, false, Align.left, true);
return;
}
//draw favorite, name, path and color on first line
if (Forge.hdbuttons)
g.drawImage(DeckPreferences.getPrefs(deck).getStarCount() > 0 ?
FSkinImage.HDSTAR_FILLED : FSkinImage.HDSTAR_OUTLINE, x, y, IMAGE_SIZE, IMAGE_SIZE);
else
g.drawImage(DeckPreferences.getPrefs(deck).getStarCount() > 0 ? FSkinImage.STAR_FILLED : FSkinImage.STAR_OUTLINE, x, y, IMAGE_SIZE, IMAGE_SIZE);
x += IMAGE_SIZE + FList.PADDING;
ColorSet deckColor = deck.getColor();
float availableNameWidth = w - CardFaceSymbols.getWidth(deckColor, IMAGE_SIZE) - IMAGE_SIZE - 2 * FList.PADDING;
String name = deck.getName();
if (!deck.getPath().isEmpty()) { //render path after name if needed
name += " (" + deck.getPath().substring(1) + ")";
}
g.drawText(name, font, foreColor, x, y, availableNameWidth, IMAGE_SIZE, false, Align.left, true);
x += availableNameWidth + FList.PADDING;
CardFaceSymbols.drawColorSet(g, deckColor, x, y, IMAGE_SIZE);
if (compactModeHandler.isCompactMode()) {
return; //skip second line if compact mode
}
//draw formats, main/side, and set/highest rarity on second line
font = FSkinFont.get(12);
float lineHeight = font.getLineHeight();
x = FList.PADDING;
y += IMAGE_SIZE + FList.PADDING + CardRenderer.SET_BOX_MARGIN;
String set = deck.getEdition().getCode();
float setWidth = CardRenderer.getSetWidth(font, set);
float availableFormatWidth = w - setWidth + CardRenderer.SET_BOX_MARGIN;
int mainSize = deck.getMainSize();
if (mainSize < 0) {
mainSize = 0; //show main as 0 if empty
}
int sideSize = deck.getSideSize();
if (sideSize < 0) {
sideSize = 0; //show sideboard as 0 if empty
}
g.drawText(deck.getFormatsString() + " (" + mainSize + " / " + sideSize + ")", font, foreColor, x, y, availableFormatWidth, lineHeight, false, Align.left, true);
x += availableFormatWidth + CardRenderer.SET_BOX_MARGIN;
y -= CardRenderer.SET_BOX_MARGIN;
CardRenderer.drawSetLabel(g, font, set, deck.getHighestRarity(), x, y, setWidth, lineHeight + 2 * CardRenderer.SET_BOX_MARGIN);
}
@Override
public boolean allowPressEffect(FList<Entry<DeckProxy, Integer>> list, float x, float y) {
return true;
}
};
}
}

Some files were not shown because too many files have changed in this diff Show More