diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 00000000000..356eb4516b8
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,6 @@
+deploy:
+ image: maven:3.6-jdk-8
+ script:
+ - 'mvn -U -B clean -P windows-linux install'
+ only:
+ - master
diff --git a/forge-adventure/fallback_skin/bg_splash.png b/forge-adventure/fallback_skin/bg_splash.png
new file mode 100644
index 00000000000..66249347632
Binary files /dev/null and b/forge-adventure/fallback_skin/bg_splash.png differ
diff --git a/forge-adventure/fallback_skin/bg_texture.jpg b/forge-adventure/fallback_skin/bg_texture.jpg
new file mode 100644
index 00000000000..7c925524eb3
Binary files /dev/null and b/forge-adventure/fallback_skin/bg_texture.jpg differ
diff --git a/forge-adventure/fallback_skin/font1.ttf b/forge-adventure/fallback_skin/font1.ttf
new file mode 100644
index 00000000000..4b4ecc66671
Binary files /dev/null and b/forge-adventure/fallback_skin/font1.ttf differ
diff --git a/forge-adventure/libs/gdx-backend-lwjgl-natives.jar b/forge-adventure/libs/gdx-backend-lwjgl-natives.jar
new file mode 100644
index 00000000000..c9dc62392d6
Binary files /dev/null and b/forge-adventure/libs/gdx-backend-lwjgl-natives.jar differ
diff --git a/forge-adventure/libs/gdx-backend-lwjgl-sources.jar b/forge-adventure/libs/gdx-backend-lwjgl-sources.jar
new file mode 100644
index 00000000000..29349a77cf7
Binary files /dev/null and b/forge-adventure/libs/gdx-backend-lwjgl-sources.jar differ
diff --git a/forge-adventure/libs/gdx-backend-lwjgl.jar b/forge-adventure/libs/gdx-backend-lwjgl.jar
new file mode 100644
index 00000000000..a4806235c90
Binary files /dev/null and b/forge-adventure/libs/gdx-backend-lwjgl.jar differ
diff --git a/forge-adventure/libs/gdx-freetype-natives.jar b/forge-adventure/libs/gdx-freetype-natives.jar
new file mode 100644
index 00000000000..0ef42d6f42a
Binary files /dev/null and b/forge-adventure/libs/gdx-freetype-natives.jar differ
diff --git a/forge-adventure/libs/gdx-natives.jar b/forge-adventure/libs/gdx-natives.jar
new file mode 100644
index 00000000000..76fba30327a
Binary files /dev/null and b/forge-adventure/libs/gdx-natives.jar differ
diff --git a/forge-adventure/pom.xml b/forge-adventure/pom.xml
new file mode 100644
index 00000000000..534932584a0
--- /dev/null
+++ b/forge-adventure/pom.xml
@@ -0,0 +1,233 @@
+
+
+
+ forge
+ forge
+ 1.6.46-SNAPSHOT
+
+ 4.0.0
+
+ forge-adventure
+ jar
+ Forge Adventure
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+
+ src
+
+
+ ${project.basedir}
+
+ **/*.vert
+ **/*.frag
+
+
+
+
+
+ maven-compiler-plugin
+
+ 1.8
+ 1.8
+
+
+
+
+ com.akathist.maven.plugins.launch4j
+ launch4j-maven-plugin
+ 1.7.25
+
+
+ l4j-adv
+ package
+
+ launch4j
+
+
+ gui
+ ${project.build.directory}/forge-adventure.exe
+ ${project.build.finalName}-jar-with-dependencies.jar
+ true
+ forge
+ src/main/config/forge-adventure.ico
+
+ forge.adventure.Main
+ false
+ anything
+
+
+ 1.8.0
+ 4096
+
+ -Dfile.encoding=UTF-8
+
+
+
+
+ 1.0.0.0
+
+
+ 1.0.0.0
+
+ Forge
+ Forge
+
+ 1.0.0.0
+
+
+ 1.0.0.0
+
+ forge-adventure
+ forge-adventure
+ forge-adventure.exe
+
+
+
+
+
+
+
+ com.google.code.maven-replacer-plugin
+ replacer
+ 1.5.2
+
+
+ package
+
+ replace
+
+
+
+
+ ${basedir}/${configSourceDirectory}
+ forge-adventure.sh, forge-adventure.command
+ ${project.build.directory}
+ .
+ false
+
+
+ $project.build.finalName$
+ ${project.build.finalName}-jar-with-dependencies.jar
+
+
+
+
+
+
+ maven-assembly-plugin
+
+ false
+
+ jar-with-dependencies
+
+
+
+ forge.adventure.Main
+ true
+
+
+
+
+
+ make-assembly
+
+ package
+
+
+ single
+
+
+
+
+
+
+
+
+ com.github.jetopto1
+ cling
+ 1.0.0
+
+
+ com.badlogicgames.gdx
+ gdx
+ 1.10.0
+ compile
+
+
+ com.badlogicgames.gdx
+ gdx-platform
+ 1.10.0
+ natives-desktop
+
+
+ com.badlogicgames.gdx
+ gdx-freetype
+ 1.10.0
+
+
+ com.badlogicgames.gdx
+ gdx-backend-lwjgl3
+ 1.10.0
+ compile
+
+
+ com.badlogicgames.gdx
+ gdx-freetype-platform
+ 1.10.0
+ natives-desktop
+
+
+ forge
+ forge-core
+ ${project.version}
+
+
+ forge
+ forge-game
+ ${project.version}
+
+
+ forge
+ forge-ai
+ ${project.version}
+
+
+ forge
+ forge-gui
+ ${project.version}
+
+
+ forge
+ forge-gui-mobile
+ ${project.version}
+
+
+ com.github.raeleus.TenPatch
+ tenpatch
+ 5.2.0
+ compile
+
+
+ org.jetbrains
+ annotations
+ 22.0.0
+ compile
+
+
+ forge
+ forge-gui-mobile
+ 1.6.46-SNAPSHOT
+ compile
+
+
+
+
+ 8
+ 8
+
+
+
\ No newline at end of file
diff --git a/forge-adventure/sentry.properties b/forge-adventure/sentry.properties
new file mode 100644
index 00000000000..683ff25e08a
--- /dev/null
+++ b/forge-adventure/sentry.properties
@@ -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
\ No newline at end of file
diff --git a/forge-adventure/shaders/grayscale.frag b/forge-adventure/shaders/grayscale.frag
new file mode 100644
index 00000000000..aa5fd3ef8ec
--- /dev/null
+++ b/forge-adventure/shaders/grayscale.frag
@@ -0,0 +1,15 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+varying vec4 v_color;
+varying vec2 v_texCoords;
+uniform sampler2D u_texture;
+uniform float u_grayness;
+
+void main() {
+ vec4 c = v_color * texture2D(u_texture, v_texCoords);
+ float grey = dot( c.rgb, vec3(0.22, 0.707, 0.071) );
+ vec3 blendedColor = mix(c.rgb, vec3(grey), u_grayness);
+ gl_FragColor = vec4(blendedColor.rgb, c.a);
+}
\ No newline at end of file
diff --git a/forge-adventure/shaders/grayscale.vert b/forge-adventure/shaders/grayscale.vert
new file mode 100644
index 00000000000..17d96ca8dde
--- /dev/null
+++ b/forge-adventure/shaders/grayscale.vert
@@ -0,0 +1,14 @@
+attribute vec4 a_position;
+attribute vec4 a_color;
+attribute vec2 a_texCoord0;
+
+uniform mat4 u_projTrans;
+
+varying vec4 v_color;
+varying vec2 v_texCoords;
+
+void main() {
+ v_color = a_color;
+ v_texCoords = a_texCoord0;
+ gl_Position = u_projTrans * a_position;
+}
\ No newline at end of file
diff --git a/forge-adventure/shaders/outline.frag b/forge-adventure/shaders/outline.frag
new file mode 100644
index 00000000000..738d23c1f71
--- /dev/null
+++ b/forge-adventure/shaders/outline.frag
@@ -0,0 +1,40 @@
+#ifdef GL_ES
+precision mediump float;
+precision mediump int;
+#endif
+
+uniform sampler2D u_texture;
+uniform vec2 u_viewportInverse;
+uniform vec3 u_color;
+uniform float u_offset;
+uniform float u_step;
+
+varying vec4 v_color;
+varying vec2 v_texCoord;
+
+#define ALPHA_VALUE_BORDER 0.5
+
+void main() {
+ vec2 T = v_texCoord.xy;
+
+ float alpha = 0.0;
+ bool allin = true;
+ for( float ix = -u_offset; ix < u_offset; ix += u_step )
+ {
+ for( float iy = -u_offset; iy < u_offset; iy += u_step )
+ {
+ float newAlpha = texture2D(u_texture, T + vec2(ix, iy) * u_viewportInverse).a;
+ allin = allin && newAlpha > ALPHA_VALUE_BORDER;
+ if (newAlpha > ALPHA_VALUE_BORDER && newAlpha >= alpha)
+ {
+ alpha = newAlpha;
+ }
+ }
+ }
+ if (allin)
+ {
+ alpha = 0.0;
+ }
+
+ gl_FragColor = vec4(u_color,alpha);
+}
\ No newline at end of file
diff --git a/forge-adventure/shaders/outline.vert b/forge-adventure/shaders/outline.vert
new file mode 100644
index 00000000000..1b6e438116d
--- /dev/null
+++ b/forge-adventure/shaders/outline.vert
@@ -0,0 +1,16 @@
+uniform mat4 u_projTrans;
+
+attribute vec4 a_position;
+attribute vec2 a_texCoord0;
+attribute vec4 a_color;
+
+varying vec4 v_color;
+varying vec2 v_texCoord;
+
+uniform vec2 u_viewportInverse;
+
+void main() {
+ gl_Position = u_projTrans * a_position;
+ v_texCoord = a_texCoord0;
+ v_color = a_color;
+}
\ No newline at end of file
diff --git a/forge-adventure/shaders/underwater.frag b/forge-adventure/shaders/underwater.frag
new file mode 100644
index 00000000000..974a1e8ae92
--- /dev/null
+++ b/forge-adventure/shaders/underwater.frag
@@ -0,0 +1,23 @@
+#ifdef GL_ES
+#define PRECISION mediump
+precision PRECISION float;
+precision PRECISION int;
+#else
+#define PRECISION
+#endif
+
+varying vec2 v_texCoords;
+uniform sampler2D u_texture;
+uniform float u_amount;
+uniform float u_speed;
+uniform float u_time;
+
+void main () {
+ vec2 uv = v_texCoords;
+
+ uv.y += (cos((uv.y + (u_time * 0.04 * u_speed)) * 45.0) * 0.0019 * u_amount) + (cos((uv.y + (u_time * 0.1 * u_speed)) * 10.0) * 0.002 * u_amount);
+
+ uv.x += (sin((uv.y + (u_time * 0.07 * u_speed)) * 15.0) * 0.0029 * u_amount) + (sin((uv.y + (u_time * 0.1 * u_speed)) * 15.0) * 0.002 * u_amount);
+
+ gl_FragColor = texture2D(u_texture, uv);
+}
\ No newline at end of file
diff --git a/forge-adventure/shaders/warp.frag b/forge-adventure/shaders/warp.frag
new file mode 100644
index 00000000000..f8a7022fa2c
--- /dev/null
+++ b/forge-adventure/shaders/warp.frag
@@ -0,0 +1,57 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+varying vec2 v_texCoords;
+uniform sampler2D u_texture;
+
+uniform float u_time;
+uniform float u_speed;
+uniform float u_amount;
+uniform vec2 u_viewport;
+uniform vec2 u_position;
+
+float random2d(vec2 n) {
+ return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
+}
+
+float randomRange (in vec2 seed, in float min, in float max) {
+ return min + random2d(seed) * (max - min);
+}
+
+float insideRange(float v, float bottom, float top) {
+ return step(bottom, v) - step(top, v);
+}
+
+void main()
+{
+ float time = floor(u_time * u_speed * 60.0);
+
+ vec3 outCol = texture2D(u_texture, v_texCoords).rgb;
+
+ float maxOffset = u_amount/2.0;
+ for (float i = 0.0; i < 2.0; i += 1.0) {
+ float sliceY = random2d(vec2(time, 2345.0 + float(i)));
+ float sliceH = random2d(vec2(time, 9035.0 + float(i))) * 0.25;
+ float hOffset = randomRange(vec2(time, 9625.0 + float(i)), -maxOffset, maxOffset);
+ vec2 uvOff = v_texCoords;
+ uvOff.x += hOffset;
+ if (insideRange(v_texCoords.y, sliceY, fract(sliceY+sliceH)) == 1.0){
+ outCol = texture2D(u_texture, uvOff).rgb;
+ }
+ }
+
+ float maxColOffset = u_amount / 6.0;
+ float rnd = random2d(vec2(time , 9545.0));
+ vec2 colOffset = vec2(randomRange(vec2(time , 9545.0), -maxColOffset, maxColOffset),
+ randomRange(vec2(time , 7205.0), -maxColOffset, maxColOffset));
+ if (rnd < 0.33) {
+ outCol.r = texture2D(u_texture, v_texCoords + colOffset).r;
+ } else if (rnd < 0.66) {
+ outCol.g = texture2D(u_texture, v_texCoords + colOffset).g;
+ } else {
+ outCol.b = texture2D(u_texture, v_texCoords + colOffset).b;
+ }
+
+ gl_FragColor = vec4(outCol, 1.0);
+}
\ No newline at end of file
diff --git a/forge-adventure/src/main/config/forge-adventure.command b/forge-adventure/src/main/config/forge-adventure.command
new file mode 100644
index 00000000000..1e3165f2ed0
--- /dev/null
+++ b/forge-adventure/src/main/config/forge-adventure.command
@@ -0,0 +1,3 @@
+#!/bin/sh
+cd $(dirname "${0}")
+java -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$
diff --git a/forge-adventure/src/main/config/forge-adventure.ico b/forge-adventure/src/main/config/forge-adventure.ico
new file mode 100644
index 00000000000..e2a08b382d8
Binary files /dev/null and b/forge-adventure/src/main/config/forge-adventure.ico differ
diff --git a/forge-adventure/src/main/config/forge-adventure.sh b/forge-adventure/src/main/config/forge-adventure.sh
new file mode 100644
index 00000000000..1e3165f2ed0
--- /dev/null
+++ b/forge-adventure/src/main/config/forge-adventure.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+cd $(dirname "${0}")
+java -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$
diff --git a/forge-adventure/src/main/config/forge.ico b/forge-adventure/src/main/config/forge.ico
new file mode 100644
index 00000000000..ab79e69fd4b
Binary files /dev/null and b/forge-adventure/src/main/config/forge.ico differ
diff --git a/forge-adventure/src/main/java/forge/adventure/AdventureApplicationAdapter.java b/forge-adventure/src/main/java/forge/adventure/AdventureApplicationAdapter.java
new file mode 100644
index 00000000000..24ef1836c55
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/AdventureApplicationAdapter.java
@@ -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.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 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;
+ }
+
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/DesktopAdapter.java b/forge-adventure/src/main/java/forge/adventure/DesktopAdapter.java
new file mode 100644
index 00000000000..679506e99c2
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/DesktopAdapter.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/forge-adventure/src/main/java/forge/adventure/Main.java b/forge-adventure/src/main/java/forge/adventure/Main.java
new file mode 100644
index 00000000000..edf0ddd0466
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/Main.java
@@ -0,0 +1,300 @@
+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.Forge;
+import forge.FrameRate;
+import forge.GuiMobile;
+import forge.adventure.scene.SettingsScene;
+import forge.adventure.util.Config;
+import forge.assets.AssetsDownloader;
+import forge.assets.FSkin;
+import forge.assets.FSkinFont;
+import forge.assets.ImageCache;
+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.screens.FScreen;
+import forge.screens.SplashScreen;
+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 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,Config.instance().getFile("skin"));
+
+ 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 borderlessCardlistkeys = FileUtil.readFile(ForgeConstants.BORDERLESS_CARD_LIST_FILE);
+ if (borderlessCardlistkeys.isEmpty())
+ return;
+ List 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);
+
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/character/CharacterSprite.java b/forge-adventure/src/main/java/forge/adventure/character/CharacterSprite.java
new file mode 100644
index 00000000000..96b9d48feda
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/character/CharacterSprite.java
@@ -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>> animations = new HashMap<>();
+ float timer;
+ private Animation 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> dirs = new HashMap<>();
+ for (AnimationDirections dir : AnimationDirections.values()) {
+
+ Array 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> 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 FlipAnimation(Animation anim) {
+ TextureRegion[] texReg = anim.getKeyFrames();
+ Array 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> 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
+ }
+
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/character/EnemySprite.java b/forge-adventure/src/main/java/forge/adventure/character/EnemySprite.java
new file mode 100644
index 00000000000..a27c074d9f6
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/character/EnemySprite.java
@@ -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 getRewards() {
+ Array ret=new Array();
+ 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;
+ }
+}
+
diff --git a/forge-adventure/src/main/java/forge/adventure/character/EntryActor.java b/forge-adventure/src/main/java/forge/adventure/character/EntryActor.java
new file mode 100644
index 00000000000..8ae5574f6b7
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/character/EntryActor.java
@@ -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;
+ }
+}
+
diff --git a/forge-adventure/src/main/java/forge/adventure/character/MapActor.java b/forge-adventure/src/main/java/forge/adventure/character/MapActor.java
new file mode 100644
index 00000000000..5625cd337b9
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/character/MapActor.java
@@ -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();
+
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/character/OnCollide.java b/forge-adventure/src/main/java/forge/adventure/character/OnCollide.java
new file mode 100644
index 00000000000..bed104c8e13
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/character/OnCollide.java
@@ -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();
+ }
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/character/PlayerSprite.java b/forge-adventure/src/main/java/forge/adventure/character/PlayerSprite.java
new file mode 100644
index 00000000000..744654a115d
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/character/PlayerSprite.java
@@ -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);
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/character/ShopActor.java b/forge-adventure/src/main/java/forge/adventure/character/ShopActor.java
new file mode 100644
index 00000000000..d092fbcf571
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/character/ShopActor.java
@@ -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 rewardData;
+
+ public ShopActor(MapStage stage, int id, Array 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;
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/character/TextureSprite.java b/forge-adventure/src/main/java/forge/adventure/character/TextureSprite.java
new file mode 100644
index 00000000000..b64de572025
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/character/TextureSprite.java
@@ -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());
+ }
+
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/data/BiomeData.java b/forge-adventure/src/main/java/forge/adventure/data/BiomeData.java
new file mode 100644
index 00000000000..7b659cf1b11
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/data/BiomeData.java
@@ -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 enemies;
+ public List pointsOfInterest;
+
+ private ArrayList enemyList;
+ private ArrayList pointOfInterestList;
+
+ public Color GetColor() {
+ return Color.valueOf(color);
+ }
+
+ public ArrayList getEnemyList() {
+ if (enemyList == null) {
+ enemyList = new ArrayList();
+ 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 getPointsOfInterest() {
+ if (pointOfInterestList == null) {
+ pointOfInterestList = new ArrayList();
+ if(pointsOfInterest==null)
+ return pointOfInterestList;
+ Array 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;
+ }
+}
\ No newline at end of file
diff --git a/forge-adventure/src/main/java/forge/adventure/data/BiomeSpriteData.java b/forge-adventure/src/main/java/forge/adventure/data/BiomeSpriteData.java
new file mode 100644
index 00000000000..6c997ecc09c
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/data/BiomeSpriteData.java
@@ -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;
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/data/BiomeTerrainData.java b/forge-adventure/src/main/java/forge/adventure/data/BiomeTerrainData.java
new file mode 100644
index 00000000000..1ccaee59ce8
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/data/BiomeTerrainData.java
@@ -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;
+
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/data/ConfigData.java b/forge-adventure/src/main/java/forge/adventure/data/ConfigData.java
new file mode 100644
index 00000000000..e64ad0e63fa
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/data/ConfigData.java
@@ -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;
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/data/DifficultyData.java b/forge-adventure/src/main/java/forge/adventure/data/DifficultyData.java
new file mode 100644
index 00000000000..e1d8f3d23b4
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/data/DifficultyData.java
@@ -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;
+
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/data/EnemyData.java b/forge-adventure/src/main/java/forge/adventure/data/EnemyData.java
new file mode 100644
index 00000000000..c8d486e6d16
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/data/EnemyData.java
@@ -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 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 getRaces() {
+ if (instance == null)
+ instance = read();
+ Array ret = new Array<>();
+ for (HeroData hero : instance.heroes) {
+ ret.add(hero.name);
+ }
+ return ret;
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/data/PointOfInterestData.java b/forge-adventure/src/main/java/forge/adventure/data/PointOfInterestData.java
new file mode 100644
index 00000000000..ecf96696b74
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/data/PointOfInterestData.java
@@ -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 pointOfInterestList;
+ public static Array 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;
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/data/RewardData.java b/forge-adventure/src/main/java/forge/adventure/data/RewardData.java
new file mode 100644
index 00000000000..8d32b75d0f1
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/data/RewardData.java
@@ -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 allCards;
+ private static Iterable allEnemyCards;
+ public Array generate(boolean isForEnemy)
+ {
+ return generate(isForEnemy,null);
+ }
+ public Array generate(boolean isForEnemy,Iterable 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 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 generateAllCards(Iterable dataList, boolean isForEnemy)
+ {
+
+ return rewardsToCards(generateAll(dataList, isForEnemy));
+ }
+ static public Iterable generateAll(Iterable dataList, boolean isForEnemy)
+ {
+ Array ret=new Array();
+ for (RewardData data:dataList)
+ ret.addAll(data.generate(isForEnemy));
+ return ret;
+ }
+ static public List rewardsToCards(Iterable dataList)
+ {
+ ArrayList ret=new ArrayList();
+ for (Reward data:dataList)
+ {
+ ret.add(data.getCard());
+ }
+ return ret;
+ }
+
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/data/SettingData.java b/forge-adventure/src/main/java/forge/adventure/data/SettingData.java
new file mode 100644
index 00000000000..6c80280c2fd
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/data/SettingData.java
@@ -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;
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/data/ShopData.java b/forge-adventure/src/main/java/forge/adventure/data/ShopData.java
new file mode 100644
index 00000000000..4b9b455306d
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/data/ShopData.java
@@ -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 rewards;
+
+
+
+
+
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/data/UIData.java b/forge-adventure/src/main/java/forge/adventure/data/UIData.java
new file mode 100644
index 00000000000..2a95ded452a
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/data/UIData.java
@@ -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> elements;
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/data/WorldData.java b/forge-adventure/src/main/java/forge/adventure/data/WorldData.java
new file mode 100644
index 00000000000..58a082ac7c3
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/data/WorldData.java
@@ -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 allEnemies;
+ public int width;
+ public int height;
+ public float playerStartPosX;
+ public float playerStartPosY;
+ public float noiseZoomBiome;
+ public int tileSize;
+ public List biomesNames;
+ public BiomeData roadTileset;
+ public String biomesSprites;
+ public float maxRoadDistance;
+ private BiomeSprites sprites;
+ private List biomes;
+ private static Array shopList;
+
+
+ public static Array 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 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 GetBiomes() {
+ if (biomes == null) {
+ biomes = new ArrayList();
+ Json json = new Json();
+ for (String name : biomesNames) {
+ biomes.add(json.fromJson(BiomeData.class, Config.instance().getFile(name)));
+ }
+ }
+ return biomes;
+ }
+
+
+}
\ No newline at end of file
diff --git a/forge-adventure/src/main/java/forge/adventure/editor/DocumentChangeListener.java b/forge-adventure/src/main/java/forge/adventure/editor/DocumentChangeListener.java
new file mode 100644
index 00000000000..eec1a325b5c
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/editor/DocumentChangeListener.java
@@ -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();
+ }
+
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/editor/EditorMainWindow.java b/forge-adventure/src/main/java/forge/adventure/editor/EditorMainWindow.java
new file mode 100644
index 00000000000..229ba565f8b
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/editor/EditorMainWindow.java
@@ -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);
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/editor/EnemyEdit.java b/forge-adventure/src/main/java/forge/adventure/editor/EnemyEdit.java
new file mode 100644
index 00000000000..9c6043afec6
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/editor/EnemyEdit.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/forge-adventure/src/main/java/forge/adventure/editor/EnemyEditor.java b/forge-adventure/src/main/java/forge/adventure/editor/EnemyEditor.java
new file mode 100644
index 00000000000..2666f31f656
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/editor/EnemyEditor.java
@@ -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 model = new DefaultListModel<>();
+ JList 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 allEnemies=new Array<>();
+ for(int i=0;i 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;ifind());
+
+ 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();
+ }
+ }
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/editor/Main.java b/forge-adventure/src/main/java/forge/adventure/editor/Main.java
new file mode 100644
index 00000000000..51762965c8a
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/editor/Main.java
@@ -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();
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/editor/RewardEdit.java b/forge-adventure/src/main/java/forge/adventure/editor/RewardEdit.java
new file mode 100644
index 00000000000..7a4353a59fb
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/editor/RewardEdit.java
@@ -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;
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/editor/RewardsEditor.java b/forge-adventure/src/main/java/forge/adventure/editor/RewardsEditor.java
new file mode 100644
index 00000000000..bbacb573200
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/editor/RewardsEditor.java
@@ -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 model = new DefaultListModel<>();
+ JList 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> images=new HashMap<>();
+ public HashMap> 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 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 imageList= images.get(images.keySet().iterator().next());
+ if(imageList.isEmpty())
+ return null;
+ return imageList.get(0);
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/editor/SwingAtlasPreview.java b/forge-adventure/src/main/java/forge/adventure/editor/SwingAtlasPreview.java
new file mode 100644
index 00000000000..c67f5646601
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/editor/SwingAtlasPreview.java
@@ -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> element : labels) {
+ element.getKey().setIcon(element.getValue().get(counter % element.getValue().size()));
+ }
+ }
+ });
+ }
+ int counter=0;
+ List>> 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> 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();
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/editor/TextListEdit.java b/forge-adventure/src/main/java/forge/adventure/editor/TextListEdit.java
new file mode 100644
index 00000000000..b2a32447bc9
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/editor/TextListEdit.java
@@ -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;ii+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 getColOverrides(ItemManagerConfig config) {
+ Map colOverrides = new HashMap<>();
+ switch (config) {
+ case QUEST_EDITOR_POOL:
+ ItemColumn.addColOverride(config, colOverrides, ColumnDef.NEW, FModel.getQuest().getCards().getFnNewCompare(), FModel.getQuest().getCards().getFnNewGet());
+ break;
+ case QUEST_DECK_EDITOR:
+ ItemColumn.addColOverride(config, colOverrides, ColumnDef.NEW, FModel.getQuest().getCards().getFnNewCompare(), FModel.getQuest().getCards().getFnNewGet());
+ ItemColumn.addColOverride(config, colOverrides, ColumnDef.DECKS, QuestSpellShop.fnDeckCompare, QuestSpellShop.fnDeckGet);
+ break;
+ default:
+ colOverrides = null; //shouldn't happen
+ break;
+ }
+ return colOverrides;
+ }
+
+ public void refresh() {
+ for(TabPage page:tabPages)
+ {
+ if(page instanceof CardManagerPage)
+ ((CardManagerPage)page).refresh();
+ }
+ }
+ }
+
+ AdventureDeckEditor screen;
+ Stage stage;
+
+ public DeckEditScene() {
+
+ }
+
+ @Override
+ public void dispose() {
+ if (stage != null)
+ stage.dispose();
+ }
+
+
+
+ @Override
+ public void enter() {
+ QuestData data = new QuestData("", 0, QuestMode.Classic, null, false, "", DeckConstructionRules.Commander);
+ FModel.getQuest().load(data);
+
+
+ FModel.getQuest().getCards().getCardpool().clear();
+
+
+
+ for (PaperCard card : AdventurePlayer.current().getCards())
+ FModel.getQuest().getCards().addSingleCard(card, 1);
+
+
+ Deck deck = AdventurePlayer.current().getDeck();
+ getScreen();
+ screen.getEditorType().getController().setDeck(deck);
+ screen.refresh();
+
+
+
+
+
+ super.enter();
+
+ }
+
+
+ @Override
+ public FScreen getScreen() {
+ return screen==null?screen = new AdventureDeckEditor(false):screen;
+ }
+
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/scene/DuelScene.java b/forge-adventure/src/main/java/forge/adventure/scene/DuelScene.java
new file mode 100644
index 00000000000..581fb7f61a4
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/scene/DuelScene.java
@@ -0,0 +1,130 @@
+package forge.adventure.scene;
+
+import forge.LobbyPlayer;
+import forge.adventure.AdventureApplicationAdapter;
+import forge.adventure.character.EnemySprite;
+import forge.adventure.character.PlayerSprite;
+import forge.assets.FSkin;
+import forge.screens.FScreen;
+import forge.screens.match.MatchController;
+import forge.adventure.util.Current;
+import forge.adventure.util.Config;
+import forge.adventure.world.AdventurePlayer;
+import forge.deck.Deck;
+import forge.game.GameRules;
+import forge.game.GameType;
+import forge.game.player.Player;
+import forge.game.player.RegisteredPlayer;
+import forge.gamemodes.match.HostedMatch;
+import forge.gui.interfaces.IGuiGame;
+import forge.player.GamePlayerUtil;
+import forge.player.PlayerControllerHuman;
+import forge.trackable.TrackableCollection;
+
+import java.util.*;
+
+/**
+ * DuelScene
+ * Forge screen scene that contains the duel screen
+ */
+public class DuelScene extends ForgeScene {
+
+ //GameLobby lobby;
+ HostedMatch hostedMatch;
+ EnemySprite enemy;
+ PlayerSprite player;
+ RegisteredPlayer humanPlayer;
+ public DuelScene() {
+
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+
+ public void GameEnd() {
+ Scene last= AdventureApplicationAdapter.instance.switchToLast();
+
+ if(last instanceof HudScene)
+ {
+ ((HudScene)last).stage.setWinner(humanPlayer == hostedMatch.getGame().getMatch().getWinner());
+ }
+
+ }
+
+ @Override
+ public void enter() {
+ Set appliedVariants = new HashSet<>();
+ appliedVariants.add(GameType.Constructed);
+
+ List players = new ArrayList<>();
+ Deck playerDeck=(Deck)AdventurePlayer.current().getDeck().copyTo("PlayerDeckCopy");
+ int missingCards= Config.instance().getConfigData().minDeckSize-playerDeck.getMain().countAll();
+ if(missingCards>0)
+ playerDeck.getMain().add("Wastes",missingCards);
+ humanPlayer = RegisteredPlayer.forVariants(2, appliedVariants,playerDeck, null, false, null, null);
+ LobbyPlayer playerObject = GamePlayerUtil.getGuiPlayer();
+ FSkin.getAvatars().put(90001, Current.player().avatar());
+ playerObject.setAvatarIndex(90001);
+ humanPlayer.setPlayer(playerObject);
+ humanPlayer.setStartingLife(Current.player().getLife());
+ Current.setLatestDeck(enemy.getData().generateDeck());
+ RegisteredPlayer aiPlayer = RegisteredPlayer.forVariants(2, appliedVariants, Current.latestDeck(), null, false, null, null);
+ LobbyPlayer enemyPlayer = GamePlayerUtil.createAiPlayer();
+
+ FSkin.getAvatars().put(90000, this.enemy.getAvatar());
+ enemyPlayer.setAvatarIndex(90000);
+
+ enemyPlayer.setName(this.enemy.getData().name);
+ aiPlayer.setPlayer(enemyPlayer);
+ aiPlayer.setStartingLife(Math.round((float)enemy.getData().life*Current.player().getDifficulty().enemyLifeFactor));
+
+ players.add(humanPlayer);
+ players.add(aiPlayer);
+
+ final Map guiMap = new HashMap<>();
+ guiMap.put(humanPlayer, MatchController.instance);
+
+ hostedMatch = MatchController.hostMatch();
+
+
+ GameRules rules = new GameRules(GameType.Constructed);
+ rules.setPlayForAnte(false);
+ rules.setMatchAnteRarity(true);
+ rules.setGamesPerMatch(1);
+ rules.setManaBurn(false);
+
+ hostedMatch.setEndGameHook(() -> GameEnd());
+ hostedMatch.startMatch(rules, appliedVariants, players, guiMap);
+
+ MatchController.instance.setGameView(hostedMatch.getGameView());
+
+
+ for (final Player p : hostedMatch.getGame().getPlayers()) {
+ if (p.getController() instanceof PlayerControllerHuman) {
+ final PlayerControllerHuman humanController = (PlayerControllerHuman) p.getController();
+ humanController.setGui(MatchController.instance);
+ MatchController.instance.setOriginalGameController(p.getView(), humanController);
+ MatchController.instance.openView(new TrackableCollection<>(p.getView()));
+ }
+ }
+ super.enter();
+ }
+
+ @Override
+ public FScreen getScreen() {
+ return MatchController.getView();
+ }
+
+
+
+ public void setEnemy(EnemySprite data) {
+ this.enemy = data;
+ }
+
+ public void setPlayer(PlayerSprite sprite) {
+ this.player = sprite;
+ }
+}
+
diff --git a/forge-adventure/src/main/java/forge/adventure/scene/ForgeInput.java b/forge-adventure/src/main/java/forge/adventure/scene/ForgeInput.java
new file mode 100644
index 00000000000..b5d66aade45
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/scene/ForgeInput.java
@@ -0,0 +1,306 @@
+package forge.adventure.scene;
+
+import com.badlogic.gdx.Input;
+import forge.Forge;
+import forge.screens.match.MatchController;
+import forge.toolbox.FContainer;
+import forge.toolbox.FDisplayObject;
+import forge.toolbox.FGestureAdapter;
+import forge.toolbox.FOverlay;
+import forge.util.Utils;
+import forge.gui.error.BugReporter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * ForgeInput
+ * Handles input for forge screens
+ */
+public class ForgeInput extends FGestureAdapter {
+ private static final List potentialListeners = new ArrayList<>();
+ private static char lastKeyTyped;
+ private static boolean keyTyped, shiftKeyDown;
+ private final ForgeScene forgeScene;
+ private final Forge.KeyInputAdapter keyInputAdapter = null;
+ //mouseMoved and scrolled events for desktop version
+ private int mouseMovedX, mouseMovedY;
+
+ public ForgeInput(ForgeScene forgeScene) {
+ this.forgeScene=forgeScene;
+ }
+
+ @Override
+ public boolean keyDown(int keyCode) {
+ if (keyCode == Input.Keys.MENU) {
+ //showMenu();
+ return true;
+ }
+ if (keyCode == Input.Keys.SHIFT_LEFT || keyCode == Input.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 == Input.Keys.LEFT) {
+ touchDown(0, 0, 0, 0);
+ return fling(1000, 0);
+ }
+ if (keyCode == Input.Keys.RIGHT) {
+ touchDown(0, 0, 0, 0);
+ return fling(-1000, 0);
+ }
+ if (keyCode == Input.Keys.UP) {
+ touchDown(0, 0, 0, 0);
+ return fling(0, -1000);
+ }
+ if (keyCode == Input.Keys.DOWN) {
+ touchDown(0, 0, 0, 0);
+ return fling(0, 1000);
+ }
+ /*
+ if(keyCode == Input.Keys.BACK){
+ if (destroyThis)
+ deviceAdapter.exit();
+ else if(onHomeScreen() && isLandscapeMode())
+ back();
+ }
+ */
+ if (keyInputAdapter == null) {
+ if (Forge.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 = MatchController.getView();
+ 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 == Input.Keys.SHIFT_LEFT || keyCode == Input.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(forgeScene.getScreen())) {
+ overlay.buildTouchListeners(x, y, potentialListeners);
+ if (overlay.preventInputBehindOverlay()) {
+ return;
+ }
+ }
+ }
+ forgeScene.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 (Forge.KeyInputAdapter.isCtrlKeyDown()) { //zoom in or out based on amount
+ return zoom(mouseMovedX, mouseMovedY, -Utils.AVG_FINGER_WIDTH * amountY);
+ }
+
+ boolean handled;
+ if (Forge.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;
+ }
+}
\ No newline at end of file
diff --git a/forge-adventure/src/main/java/forge/adventure/scene/ForgeScene.java b/forge-adventure/src/main/java/forge/adventure/scene/ForgeScene.java
new file mode 100644
index 00000000000..487dd00eb2f
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/scene/ForgeScene.java
@@ -0,0 +1,105 @@
+package forge.adventure.scene;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.GL20;
+import forge.adventure.AdventureApplicationAdapter;
+import forge.Forge;
+import forge.Graphics;
+import forge.animation.ForgeAnimation;
+import forge.assets.ImageCache;
+import forge.screens.FScreen;
+import forge.toolbox.FDisplayObject;
+import forge.toolbox.FOverlay;
+import forge.gamemodes.match.LobbySlotType;
+import forge.interfaces.IUpdateable;
+
+import java.util.List;
+
+/**
+ * base class to render base forge screens like the deck editor and matches
+ */
+public abstract class ForgeScene extends Scene implements IUpdateable {
+
+ //GameLobby lobby;
+ Graphics localGraphics;
+ ForgeInput input=new ForgeInput(this);
+ @Override
+ public void dispose() {
+ }
+ @Override
+ public void render() {
+
+ Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); // Clear the screen.
+ if (getScreen() == null) {
+ return;
+ }
+
+
+ localGraphics.begin(AdventureApplicationAdapter.instance.getCurrentWidth(), AdventureApplicationAdapter.instance.getCurrentHeight());
+ getScreen().screenPos.setSize(AdventureApplicationAdapter.instance.getCurrentWidth(), AdventureApplicationAdapter.instance.getCurrentHeight());
+ if (getScreen().getRotate180()) {
+ localGraphics.startRotateTransform(AdventureApplicationAdapter.instance.getCurrentWidth() / 2f, AdventureApplicationAdapter.instance.getCurrentHeight() / 2f, 180);
+ }
+ getScreen().draw(localGraphics);
+ if (getScreen().getRotate180()) {
+ localGraphics.endTransform();
+ }
+ for (FOverlay overlay : FOverlay.getOverlays()) {
+ if (overlay.isVisibleOnScreen(getScreen())) {
+ overlay.screenPos.setSize(AdventureApplicationAdapter.instance.getCurrentWidth(), AdventureApplicationAdapter.instance.getCurrentHeight());
+ overlay.setSize(AdventureApplicationAdapter.instance.getCurrentWidth(), AdventureApplicationAdapter.instance.getCurrentHeight()); //update overlay sizes as they're rendered
+ if (overlay.getRotate180()) {
+ localGraphics.startRotateTransform(AdventureApplicationAdapter.instance.getCurrentHeight() / 2f, AdventureApplicationAdapter.instance.getCurrentHeight() / 2f, 180);
+ }
+ overlay.draw(localGraphics);
+ if (overlay.getRotate180()) {
+ localGraphics.endTransform();
+ }
+ }
+ }
+ localGraphics.end();
+
+ //Batch.end();
+ }
+ @Override
+ public void act(float delta) {
+
+ ImageCache.allowSingleLoad();
+ ForgeAnimation.advanceAll();
+ }
+
+
+ @Override
+ public void enter() {
+ if(getScreen()!=null)
+ getScreen().setSize(AdventureApplicationAdapter.instance.getCurrentWidth(), AdventureApplicationAdapter.instance.getCurrentHeight());
+
+ Forge.openScreen(getScreen());
+ Gdx.input.setInputProcessor(input);
+
+ }
+ public abstract FScreen getScreen();
+
+ public void buildTouchListeners(int x, int y, List potentialListeners) {
+ if(getScreen()!=null)
+ getScreen().buildTouchListeners(x, y, potentialListeners);
+ }
+
+ @Override
+ public void resLoaded() {
+ localGraphics = AdventureApplicationAdapter.instance.getGraphics();
+ }
+
+
+ @Override
+ public void update(boolean fullUpdate) {
+
+ }
+
+ @Override
+ public void update(int slot, LobbySlotType type) {
+
+ }
+
+
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/scene/GameScene.java b/forge-adventure/src/main/java/forge/adventure/scene/GameScene.java
new file mode 100644
index 00000000000..7e1dd138d06
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/scene/GameScene.java
@@ -0,0 +1,46 @@
+package forge.adventure.scene;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.GL20;
+import forge.adventure.stage.WorldStage;
+
+/**
+ * Game scene main over world scene
+ * does render the WorldStage and HUD
+ */
+public class GameScene extends HudScene {
+
+ public GameScene() {
+ super(WorldStage.getInstance());
+
+ }
+
+ @Override
+ public void dispose() {
+ stage.dispose();
+ }
+
+ @Override
+ public void act(float delta) {
+
+ stage.act(delta);
+
+
+ }
+ @Override
+ public void render() {
+
+
+ Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
+ stage.draw();
+ hud.draw();
+
+ }
+
+ @Override
+ public void resLoaded() {
+
+
+ }
+}
+
diff --git a/forge-adventure/src/main/java/forge/adventure/scene/HudScene.java b/forge-adventure/src/main/java/forge/adventure/scene/HudScene.java
new file mode 100644
index 00000000000..5a4142880a6
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/scene/HudScene.java
@@ -0,0 +1,119 @@
+package forge.adventure.scene;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.InputProcessor;
+import com.badlogic.gdx.graphics.GL20;
+import forge.adventure.stage.GameHUD;
+import forge.adventure.stage.GameStage;
+
+/**
+ * Hud base scene
+ */
+public class HudScene extends Scene implements InputProcessor {
+
+ GameHUD hud;
+ GameStage stage;
+
+ protected HudScene(GameStage s) {
+ stage = s;
+ hud = GameHUD.getInstance();
+ }
+
+ @Override
+ public boolean leave() {
+ stage.leave();
+ return true;
+ }
+
+ @Override
+ public void enter() {
+ Gdx.input.setInputProcessor(this);
+ stage.enter();
+ hud.enter();
+ }
+
+ @Override
+ public void dispose() {
+ stage.dispose();
+ }
+
+ @Override
+ public void act(float delta)
+ {
+ stage.act(delta);
+ hud.act(delta);
+ }
+ @Override
+ public void render() {
+
+ Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
+ stage.draw();
+ hud.draw();
+
+ }
+
+ @Override
+ public void resLoaded() {
+
+
+ }
+
+ @Override
+ public boolean keyDown(int keycode) {
+
+ if (hud.keyDown(keycode))
+ return true;
+ return stage.keyDown(keycode);
+ }
+
+ @Override
+ public boolean keyUp(int keycode) {
+
+ if (hud.keyUp(keycode))
+ return true;
+ return stage.keyUp(keycode);
+ }
+
+ @Override
+ public boolean keyTyped(char character) {
+
+ if (hud.keyTyped(character))
+ return true;
+ return stage.keyTyped(character);
+ }
+
+ @Override
+ public boolean touchDown(int screenX, int screenY, int pointer, int button) {
+ if (hud.touchDown(screenX, screenY, pointer, button))
+ return true;
+ return stage.touchDown(screenX, screenY, pointer, button);
+ }
+
+ @Override
+ public boolean touchUp(int screenX, int screenY, int pointer, int button) {
+ if (hud.touchUp(screenX, screenY, pointer, button))
+ return true;
+ return stage.touchUp(screenX, screenY, pointer, button);
+ }
+
+ @Override
+ public boolean touchDragged(int screenX, int screenY, int pointer) {
+ if (hud.touchDragged(screenX, screenY, pointer))
+ return true;
+ return stage.touchDragged(screenX, screenY, pointer);
+ }
+
+ @Override
+ public boolean mouseMoved(int screenX, int screenY) {
+ if (hud.mouseMoved(screenX, screenY))
+ return true;
+ return stage.mouseMoved(screenX, screenY);
+ }
+
+ @Override
+ public boolean scrolled(float amountX, float amountY) {
+ if (hud.scrolled(amountX, amountY))
+ return true;
+ return stage.scrolled(amountX, amountY);
+ }
+}
\ No newline at end of file
diff --git a/forge-adventure/src/main/java/forge/adventure/scene/InnScene.java b/forge-adventure/src/main/java/forge/adventure/scene/InnScene.java
new file mode 100644
index 00000000000..80913394f2b
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/scene/InnScene.java
@@ -0,0 +1,41 @@
+package forge.adventure.scene;
+
+import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
+import forge.adventure.AdventureApplicationAdapter;
+import forge.adventure.util.Current;
+
+/**
+ * Scene for the Inn in towns
+ *
+ */
+public class InnScene extends UIScene {
+
+ public InnScene()
+ {
+ super("ui/inn.json");
+ }
+
+ public void done()
+ {
+ AdventureApplicationAdapter.instance.switchToLast();
+ }
+ public void heal()
+ {
+ Current.player().heal();
+ }
+
+ @Override
+ public void act(float delta) {
+
+ stage.act(delta);
+ }
+ @Override
+ public void resLoaded() {
+ super.resLoaded();
+ ui.onButtonPress("done",()->done());
+ ui.onButtonPress("heal",()->heal());
+ TextButton doneButton = ui.findActor("done");
+ }
+
+
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/scene/NewGameScene.java b/forge-adventure/src/main/java/forge/adventure/scene/NewGameScene.java
new file mode 100644
index 00000000000..dd47490c989
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/scene/NewGameScene.java
@@ -0,0 +1,136 @@
+package forge.adventure.scene;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.scenes.scene2d.ui.Image;
+import com.badlogic.gdx.scenes.scene2d.ui.TextField;
+import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
+import com.badlogic.gdx.utils.Array;
+import forge.adventure.AdventureApplicationAdapter;
+import forge.adventure.data.DifficultyData;
+import forge.adventure.data.HeroListData;
+import forge.adventure.util.Config;
+import forge.adventure.util.Selector;
+import forge.adventure.world.WorldSave;
+import forge.deck.Deck;
+import forge.localinstance.properties.ForgePreferences;
+import forge.model.FModel;
+import forge.player.GamePlayerUtil;
+import forge.util.NameGenerator;
+
+import java.util.Random;
+
+/**
+ * NewGame scene that contains the character creation
+ */
+public class NewGameScene extends UIScene {
+ TextField selectedName;
+ Deck[] starterDeck;
+ private Image avatarImage;
+ private int avatarIndex = 0;
+ private Selector race;
+ private Selector deck;
+ private Selector gender;
+ private Selector difficulty;
+
+ public NewGameScene() {
+ super("ui/new_game.json");
+ }
+
+ public boolean start() {
+ FModel.getPreferences().setPref(ForgePreferences.FPref.UI_ENABLE_MUSIC, false);
+ WorldSave.generateNewWorld(selectedName.getText(),
+ gender.getCurrentIndex() == 0,
+ race.getCurrentIndex(),
+ avatarIndex,
+ deck.getCurrentIndex(),
+ Config.instance().getConfigData().difficulties[difficulty.getCurrentIndex()],0);
+ GamePlayerUtil.getGuiPlayer().setName(selectedName.getText());
+ //image = new Texture(img);
+
+ AdventureApplicationAdapter.instance.switchScene(SceneType.GameScene.instance);
+ return true;
+ }
+
+ public boolean back() {
+ AdventureApplicationAdapter.instance.switchScene(SceneType.StartScene.instance);
+ return true;
+ }
+
+ @Override
+ public void resLoaded() {
+ super.resLoaded();
+ selectedName = ui.findActor("nameField");
+ selectedName.setText(NameGenerator.getRandomName("Any", "Any", ""));
+ avatarImage = ui.findActor("avatarPreview");
+ gender = ui.findActor("gender");
+ gender.setTextList(new String[]{"Male", "Female"});
+ gender.addListener(event -> updateAvatar());
+ Random rand=new Random();
+
+ deck = ui.findActor("deck");
+
+ starterDeck = Config.instance().starterDecks();
+ Array stringList = new Array<>(starterDeck.length);
+ for (Deck deck : starterDeck)
+ stringList.add(deck.getName());
+
+ deck.setTextList(stringList);
+
+ race = ui.findActor("race");
+ race.addListener(event -> updateAvatar());
+ race.setTextList(HeroListData.getRaces());
+ difficulty = ui.findActor("difficulty");
+
+ Array diffList = new Array<>(starterDeck.length);
+ int i=0;
+ int startingDifficulty=0;
+ for (DifficultyData diff : Config.instance().getConfigData().difficulties)
+ {
+ if(diff.startingDifficulty)
+ startingDifficulty=i;
+ diffList.add(diff.name);
+ i++;
+ }
+ difficulty.setTextList(diffList);
+ difficulty.setCurrentIndex(startingDifficulty);
+ avatarIndex=rand.nextInt();
+ gender.setCurrentIndex(rand.nextInt());
+ deck.setCurrentIndex(rand.nextInt());
+ race.setCurrentIndex(rand.nextInt());
+ ui.onButtonPress("back", this::back);
+ ui.onButtonPress("start", this::start);
+ ui.onButtonPress("leftAvatar", this::leftAvatar);
+ ui.onButtonPress("rightAvatar", this::rightAvatar);
+
+ updateAvatar();
+ }
+
+ private void rightAvatar() {
+
+ avatarIndex++;
+ updateAvatar();
+ }
+
+ private void leftAvatar() {
+ avatarIndex--;
+ updateAvatar();
+ }
+
+ private boolean updateAvatar() {
+
+ avatarImage.setDrawable(new TextureRegionDrawable(HeroListData.getAvatar(race.getCurrentIndex(), gender.getCurrentIndex() != 0, avatarIndex)));
+ return false;
+ }
+
+ @Override
+ public void create() {
+
+ }
+
+ @Override
+ public void enter() {
+ Gdx.input.setInputProcessor(stage); //Start taking input from the ui
+
+
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/scene/RewardScene.java b/forge-adventure/src/main/java/forge/adventure/scene/RewardScene.java
new file mode 100644
index 00000000000..f38730eb716
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/scene/RewardScene.java
@@ -0,0 +1,280 @@
+package forge.adventure.scene;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.scenes.scene2d.Actor;
+import com.badlogic.gdx.scenes.scene2d.InputEvent;
+import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
+import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
+import com.badlogic.gdx.utils.Array;
+import forge.adventure.AdventureApplicationAdapter;
+import forge.adventure.character.ShopActor;
+import forge.assets.ImageCache;
+import forge.adventure.util.CardUtil;
+import forge.adventure.util.Current;
+import forge.adventure.util.Reward;
+import forge.adventure.util.RewardActor;
+import forge.adventure.world.AdventurePlayer;
+import forge.adventure.world.PointOfInterestChanges;
+import forge.adventure.world.WorldSave;
+
+/**
+ * Displays the rewards of a fight or a treasure
+ */
+public class RewardScene extends UIScene {
+
+ private TextButton doneButton;
+
+ public enum Type
+ {
+ Shop,
+ Loot
+
+ }
+ Type type;
+ Array generated =new Array<>();
+ static public final float CARD_WIDTH =550f;
+ static public final float CARD_HEIGHT =400f;
+ static public final float CARD_WIDTH_TO_HEIGHT =CARD_WIDTH/CARD_HEIGHT;
+ public RewardScene()
+ {
+ super("ui/items.json");
+ }
+
+ boolean doneClicked=false;
+ float flipCountDown=1.0f;
+ public boolean done()
+ {
+ if(doneClicked)
+ return true;
+
+ if(type==Type.Loot)
+ {
+
+ boolean wait=false;
+ for(Actor actor: new Array.ArrayIterator<>(generated))
+ {
+ if(!(actor instanceof RewardActor))
+ {
+ continue;
+ }
+ RewardActor reward=(RewardActor) actor;
+ AdventurePlayer.current().addReward(reward.getReward());
+ if(!reward.isFlipped())
+ {
+ wait = true;
+ reward.flip();
+ }
+ }
+ if(wait)
+ {
+ flipCountDown=3.0f;
+ doneClicked=true;
+ }
+ else
+ {
+ AdventureApplicationAdapter.instance.switchToLast();
+ }
+ }
+ else
+ {
+ AdventureApplicationAdapter.instance.switchToLast();
+ }
+ return true;
+ }
+ @Override
+ public void act(float delta) {
+
+ stage.act(delta);
+ ImageCache.allowSingleLoad();
+ if(doneClicked)
+ {
+ if(type==Type.Loot)
+ flipCountDown-=Gdx.graphics.getDeltaTime();
+ if(flipCountDown<=0)
+ {
+ AdventureApplicationAdapter.instance.switchToLast();
+ }
+ }
+ }
+ @Override
+ public void resLoaded() {
+ super.resLoaded();
+ ui.onButtonPress("done",()->done());
+ doneButton=ui.findActor("done");
+ }
+
+
+
+ public void loadRewards(Array newRewards, Type type, ShopActor shopActor)
+ {
+ this.type=type;
+ doneClicked=false;
+
+
+
+
+ for(Actor actor: new Array.ArrayIterator<>(generated))
+ {
+ actor.remove();
+ if(actor instanceof RewardActor)
+ {
+ ((RewardActor)actor).dispose();
+ }
+ }
+ generated.clear();
+
+
+ Actor card=ui.findActor("cards");
+
+ // card.setDrawable(new TextureRegionDrawable(new Texture(Res.CurrentRes.GetFile("ui/transition.png"))));
+
+ float targetWidth = card.getWidth();
+ float targetHeight = card.getHeight();
+ float xOff = card.getX();
+ float yOff = card.getY();
+
+ int numberOfRows=0;
+ float cardWidth=0;
+ float cardHeight=0;
+ float bestCardHeight=0;
+ int numberOfColumns=0;
+ float targetArea=targetHeight*targetWidth;
+ float oldCardArea=0;
+ float newArea=0;
+
+ switch (type) {
+ case Shop:
+ doneButton.setText("Return");
+ break;
+ case Loot:
+ doneButton.setText("Take all");
+ break;
+ }
+ for(int h=1;holdCardArea&&newArea<=targetArea&&rows*cardHeight(newRewards))
+ {
+ boolean skipCard=false;
+ if(type==Type.Shop)
+ {
+ if(shopActor.getMapStage().getChanges().wasCardBought(shopActor.getObjectID(),i))
+ {
+ skipCard=true;
+ }
+ }
+
+
+
+ int currentRow=(i/numberOfColumns);
+ float lastRowXAdjust=0;
+ if(currentRow==numberOfRows-1)
+ {
+ int lastRowCount=newRewards.size%numberOfColumns;
+ if(lastRowCount!=0)
+ lastRowXAdjust=((numberOfColumns*cardWidth)-(lastRowCount*cardWidth))/2;
+ }
+ RewardActor actor=new RewardActor(reward,type==Type.Loot);
+ actor.setBounds(lastRowXAdjust+xOff+cardWidth*(i%numberOfColumns)+spacing,yOff+cardHeight*currentRow+spacing,cardWidth-spacing*2,cardHeight-spacing*2);
+
+ if(type==Type.Shop)
+ {
+ if(currentRow!=((i+1)/numberOfColumns))
+ yOff+=doneButton.getHeight();
+
+ TextButton buyCardButton=new BuyButton(shopActor.getObjectID(),i,shopActor.getMapStage().getChanges(),actor,doneButton);
+
+ generated.add(buyCardButton);
+ if(!skipCard)
+ {
+ stage.addActor(buyCardButton);
+ }
+ }
+ generated.add(actor);
+ if(!skipCard)
+ {
+ stage.addActor(actor);
+ }
+ i++;
+ }
+ updateBuyButtons();
+
+ }
+
+ private void updateBuyButtons() {
+
+ for(Actor actor: new Array.ArrayIterator<>(generated))
+ {
+ if(actor instanceof BuyButton)
+ {
+ ((BuyButton)actor).update();
+ }
+ }
+ }
+
+ private class BuyButton extends TextButton {
+ private final int objectID;
+ private final int index;
+ private final PointOfInterestChanges changes;
+ RewardActor reward;
+ int price;
+ void update(){
+ setDisabled(WorldSave.getCurrentSave().getPlayer().getGold()< price);
+ }
+ public BuyButton(int id, int i, PointOfInterestChanges ch, RewardActor actor, TextButton style) {
+ super("",style.getStyle());
+ this.objectID = id;
+ this.index = i;
+ this.changes = ch;
+ reward=actor;
+ setHeight(style.getHeight());
+ setWidth(actor.getWidth());
+ setX(actor.getX());
+ setY(actor.getY()-getHeight());
+ price= CardUtil.getCardPrice(actor.getReward().getCard());
+ setText("Buy for "+price);
+ addListener(new ClickListener(){
+
+ @Override
+ public void clicked (InputEvent event, float x, float y) {
+ if(Current.player().getGold()>= price)
+ {
+ changes.buyCard(objectID,index);
+ Current.player().takeGold(price);
+ Current.player().addReward(reward.getReward());
+ setDisabled(true);
+ reward.flip();
+ remove();
+ updateBuyButtons();
+ }
+
+ }
+ });
+ }
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/scene/SaveLoadScene.java b/forge-adventure/src/main/java/forge/adventure/scene/SaveLoadScene.java
new file mode 100644
index 00000000000..1dd2d6b0284
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/scene/SaveLoadScene.java
@@ -0,0 +1,205 @@
+package forge.adventure.scene;
+
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.scenes.scene2d.InputEvent;
+import com.badlogic.gdx.scenes.scene2d.ui.*;
+import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
+import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
+import com.badlogic.gdx.utils.Align;
+import com.badlogic.gdx.utils.GdxRuntimeException;
+import com.badlogic.gdx.utils.IntMap;
+import forge.adventure.AdventureApplicationAdapter;
+import forge.adventure.util.Controls;
+import forge.adventure.world.WorldSave;
+import forge.adventure.world.WorldSaveHeader;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.zip.InflaterInputStream;
+
+/**
+ * Scene to load and save the game.
+ *
+ */
+public class SaveLoadScene extends UIScene {
+ private final IntMap buttons = new IntMap<>();
+ IntMap previews = new IntMap<>();
+ Color defColor;
+ Table layout;
+ boolean save = true;
+ Dialog dialog;
+ TextField textInput;
+ Label header;
+ int currentSlot = -3;
+ Image previewImage;
+ TextButton saveLoadButton;
+
+ public SaveLoadScene() {
+ super("ui/save_load.json");
+ }
+
+
+
+
+ private void addSaveSlot(String name, int i) {
+ layout.add(Controls.newLabel(name));
+ TextButton button = Controls.newTextButton("...");
+ button.addListener(new ClickListener() {
+ @Override
+ public void clicked(InputEvent event, float x, float y) {
+ try {
+ select(i);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ layout.add(button).expandX();
+ buttons.put(i, button);
+ layout.row();
+
+ }
+
+ public void back() {
+ AdventureApplicationAdapter.instance.switchToLast();
+ }
+
+ public boolean select(int slot) {
+ currentSlot = slot;
+
+ if (previews.containsKey(slot)) {
+ WorldSaveHeader header = previews.get(slot);
+ if (header.preview != null) {
+ previewImage.setDrawable(new TextureRegionDrawable(new Texture(header.preview)));
+ previewImage.layout();
+ }
+ }
+ for (IntMap.Entry butt : new IntMap.Entries (buttons)) {
+ butt.value.setColor(defColor);
+ }
+ if (buttons.containsKey(slot)) {
+ TextButton button = buttons.get(slot);
+ button.setColor(Color.RED);
+ }
+
+ return true;
+ }
+
+ public void loadSave() {
+ if (save) {
+ textInput.setText("Save Game " + currentSlot);
+ dialog.show(stage);
+ stage.setKeyboardFocus(textInput);
+ } else {
+ if(WorldSave.load(currentSlot))
+ AdventureApplicationAdapter.instance.switchScene(SceneType.GameScene.instance);
+ }
+ }
+
+ public boolean saveAbort() {
+
+ dialog.hide();
+ return true;
+ }
+
+ public void save() {
+ dialog.hide();
+ if( WorldSave.getCurrentSave().save(textInput.getText(), currentSlot))
+ {
+ updateFiles();
+ AdventureApplicationAdapter.instance.switchScene(SceneType.GameScene.instance);
+ }
+
+
+ }
+
+ private void updateFiles() {
+
+ File f = new File(WorldSave.getSaveDir());
+ f.mkdirs();
+ File[] names = f.listFiles();
+ if(names==null)
+ throw new RuntimeException("Can not find save directory");
+ previews.clear();
+ for (File name : names) {
+ if (WorldSave.isSafeFile(name.getName())) {
+ try {
+
+ try (FileInputStream fos = new FileInputStream(name.getAbsolutePath());
+ InflaterInputStream inf = new InflaterInputStream(fos);
+ ObjectInputStream oos = new ObjectInputStream(inf)) {
+
+
+ int slot=WorldSave.filenameToSlot(name.getName());
+ WorldSaveHeader header = (WorldSaveHeader) oos.readObject();
+ buttons.get(slot).setText(header.name);
+ previews.put(slot, header);
+ }
+
+ } catch (ClassNotFoundException | IOException | GdxRuntimeException e) {
+
+
+ }
+ }
+ }
+
+ }
+
+
+ public void setSaveGame(boolean save) {
+ if (save) {
+ header.setText("Save game");
+ saveLoadButton.setText("Save");
+ } else {
+ header.setText("Load game");
+ saveLoadButton.setText("Load");
+ }
+ this.save = save;
+ }
+
+ @Override
+ public void enter() {
+ select(-3);
+ updateFiles();
+ super.enter();
+ }
+
+ @Override
+ public void resLoaded() {
+ super.resLoaded();
+ layout = new Table();
+ layout.setFillParent(true);
+ stage.addActor(layout);
+ dialog = Controls.newDialog("Save");
+ textInput = Controls.newTextField("");
+ dialog.getButtonTable().add(Controls.newLabel("Name your new save file.")).colspan(2);
+ dialog.getButtonTable().row();
+ dialog.getButtonTable().add(Controls.newLabel("Name:")).align(Align.left);
+ dialog.getButtonTable().add(textInput).fillX().expandX();
+ dialog.getButtonTable().row();
+ dialog.getButtonTable().add(Controls.newTextButton("Save", () -> save())).align(Align.left);
+ dialog.getButtonTable().add(Controls.newTextButton("Abort", () -> saveAbort())).align(Align.left);
+
+ previewImage = ui.findActor("preview");
+ header = Controls.newLabel("Save");
+ header.setHeight(header.getHeight() * 2);
+ layout.add(header).colspan(2).align(Align.center);
+ layout.row();
+ addSaveSlot("Auto save", -2);
+ addSaveSlot("Quick save", -1);
+ for (int i = 1; i < 11; i++)
+ addSaveSlot("Slot:" + i, i);
+
+ saveLoadButton = ui.findActor("save");
+ ui.onButtonPress("save",()-> loadSave());
+ ui.onButtonPress("return",()-> back());
+ defColor = saveLoadButton.getColor();
+
+
+ ScrollPane scrollPane = ui.findActor("saveSlots");
+ scrollPane.setActor(layout);
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/scene/Scene.java b/forge-adventure/src/main/java/forge/adventure/scene/Scene.java
new file mode 100644
index 00000000000..4ed18ff3f1b
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/scene/Scene.java
@@ -0,0 +1,49 @@
+package forge.adventure.scene;
+
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
+import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
+import com.badlogic.gdx.utils.Disposable;
+import forge.adventure.util.Config;
+
+/**
+ * Base class for all rendered scenes
+ */
+public abstract class Scene implements Disposable {
+
+ public Scene() {
+
+ }
+
+ public static int GetIntendedWidth() {
+ return Config.instance().getConfigData().screenWidth;
+ }
+
+ public static int GetIntendedHeight() {
+ return Config.instance().getConfigData().screenHeight;
+ }
+
+ public abstract void act(float delta);
+ public abstract void render();
+
+ public void create() {
+
+ }
+
+ public Drawable DrawableImage(String path) {
+ return new TextureRegionDrawable(new Texture(Config.instance().getFile(path)));
+ }
+
+ public void resLoaded() {
+ }
+
+ public boolean leave() {
+ return true;
+ }
+
+ public void enter() {
+ }
+
+
+
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/scene/SceneType.java b/forge-adventure/src/main/java/forge/adventure/scene/SceneType.java
new file mode 100644
index 00000000000..3a4188cf35f
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/scene/SceneType.java
@@ -0,0 +1,23 @@
+package forge.adventure.scene;
+
+/**
+ * Enum of all scenes
+ */
+public enum SceneType {
+ StartScene(new forge.adventure.scene.StartScene()),
+ NewGameScene(new forge.adventure.scene.NewGameScene()),
+ SettingsScene(new forge.adventure.scene.SettingsScene()),
+ GameScene(new forge.adventure.scene.GameScene()),
+ DuelScene(new forge.adventure.scene.DuelScene()),
+ SaveLoadScene(new forge.adventure.scene.SaveLoadScene()),
+ DeckEditScene(new forge.adventure.scene.DeckEditScene()),
+ TileMapScene(new forge.adventure.scene.TileMapScene()),
+ RewardScene(new forge.adventure.scene.RewardScene()),
+ InnScene(new forge.adventure.scene.InnScene());
+
+
+ public final forge.adventure.scene.Scene instance;
+ SceneType(forge.adventure.scene.Scene scene) {
+ this.instance = scene;
+ }
+}
diff --git a/forge-adventure/src/main/java/forge/adventure/scene/SettingsScene.java b/forge-adventure/src/main/java/forge/adventure/scene/SettingsScene.java
new file mode 100644
index 00000000000..c133a71a5cc
--- /dev/null
+++ b/forge-adventure/src/main/java/forge/adventure/scene/SettingsScene.java
@@ -0,0 +1,194 @@
+package forge.adventure.scene;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.GL20;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.scenes.scene2d.Actor;
+import com.badlogic.gdx.scenes.scene2d.Stage;
+import com.badlogic.gdx.scenes.scene2d.ui.*;
+import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
+import com.badlogic.gdx.utils.Align;
+import forge.adventure.AdventureApplicationAdapter;
+import forge.adventure.util.Config;
+import forge.adventure.util.Controls;
+import forge.localinstance.properties.ForgePreferences;
+import forge.util.Localizer;
+
+import java.util.function.Function;
+
+/**
+ * Scene to handle settings of the base forge and adventure mode
+ */
+public class SettingsScene extends UIScene {
+
+
+ static public ForgePreferences Preference;
+ Stage stage;
+ Texture Background;
+ private Table settingGroup;
+
+ public SettingsScene() {
+ super("ui/settings.json");
+ }
+
+
+ @Override
+ public void dispose() {
+ if (stage != null)
+ stage.dispose();
+ }
+
+ public void renderAct(float delta) {
+
+
+ Gdx.gl.glClearColor(1, 0, 1, 1);
+ Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
+ stage.getBatch().begin();
+ stage.getBatch().disableBlending();
+ stage.getBatch().draw(Background, 0, 0, GetIntendedWidth(), GetIntendedHeight());
+ stage.getBatch().enableBlending();
+ stage.getBatch().end();
+ stage.act(delta);
+ stage.draw();
+
+ }
+
+ public boolean back() {
+ AdventureApplicationAdapter.instance.switchToLast();
+ return true;
+ }
+ private void addCheckBox(String name, ForgePreferences.FPref pref) {
+
+
+ CheckBox box = Controls.newCheckBox("");
+ box.setChecked(Preference.getPrefBoolean(pref));
+ box.addListener(new ChangeListener() {
+ @Override
+ public void changed(ChangeEvent event, Actor actor) {
+ Preference.setPref(pref, ((CheckBox) actor).isChecked());
+ Preference.save();
+ }
+ });
+
+ addLabel(name);
+ settingGroup.add(box).align(Align.right);
+ }
+ private void addSettingSlider(String name, ForgePreferences.FPref pref, int min,int max) {
+
+ Slider slide = Controls.newSlider(min,max, 1, false);
+ slide.setValue(Preference.getPrefInt(pref));
+ slide.addListener(new ChangeListener() {
+ @Override
+ public void changed(ChangeEvent event, Actor actor) {
+ Preference.setPref(pref, String.valueOf((int) ((Slider) actor).getValue()));
+ Preference.save();
+ }
+ });
+ addLabel(name);
+ settingGroup.add(slide).align(Align.right);
+ }
+ private void addSettingField(String name, boolean value, ChangeListener change) {
+
+ CheckBox box = Controls.newCheckBox("");
+ box.setChecked(value);
+ box.addListener(change);
+ addLabel(name);
+ settingGroup.add(box).align(Align.right);
+ }
+ private void addSettingField(String name, int value, ChangeListener change) {
+
+
+ TextField text = Controls.newTextField(String.valueOf(value));
+ text.setTextFieldFilter(new TextField.TextFieldFilter() {
+ @Override
+ public boolean acceptChar(TextField textField, char c) {
+ return Character.isDigit(c);
+ }
+ });
+ text.addListener(change);
+
+
+ addLabel(name);
+ settingGroup.add(text).align(Align.right);
+ }
+ void addLabel( String name)
+ {
+
+ Label label = new Label(name, Controls.GetSkin().get("white",Label.LabelStyle.class));
+
+ settingGroup.row().space(5);
+ settingGroup.add(label).align(Align.left).fillX();
+ }
+ @Override
+ public void resLoaded() {
+ super.resLoaded();
+ settingGroup = new Table();
+ if (Preference == null) {
+ Preference = new ForgePreferences();
+ }
+ Localizer localizer = Localizer.getInstance();
+
+ SelectBox plane = Controls.newComboBox(Config.instance().getAllAdventures(), Config.instance().getSettingData().plane, new Function