diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java
index 3af26c3358f..0d83d91aefe 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java
@@ -1626,7 +1626,7 @@ public static boolean hasAFogEffect(final Player defender, final Player ai, bool
for (final Card c : all) {
// check if card is at least available to be played
// further improvements might consider if AI has options to steal the spell by making it playable first
- if (c.getZone().getPlayer() != null && c.getZone().getPlayer() != defender && c.mayPlay(defender).isEmpty()) {
+ if (c.getZone() != null && c.getZone().getPlayer() != null && c.getZone().getPlayer() != defender && c.mayPlay(defender).isEmpty()) {
continue;
}
for (final SpellAbility sa : c.getSpellAbilities()) {
diff --git a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
index f35c873f368..2f507a41dda 100644
--- a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
+++ b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
@@ -26,6 +26,9 @@ public int evaluateCreature(final Card c) {
return evaluateCreature(c, true, true);
}
public int evaluateCreature(final Card c, final boolean considerPT, final boolean considerCMC) {
+ //Card shouldn't be null and AI shouldn't crash since this is just score
+ if (c == null)
+ return 0;
int value = 80;
if (!c.isToken()) {
value += addValue(20, "non-token"); // tokens should be worth less than actual cards
diff --git a/forge-core/pom.xml b/forge-core/pom.xml
index d96a8c7ead6..24740c37a5c 100644
--- a/forge-core/pom.xml
+++ b/forge-core/pom.xml
@@ -23,6 +23,22 @@
commons-lang3
3.17.0
+
+ org.apache.commons
+ commons-text
+ 1.12.0
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+
+ com.apptasticsoftware
+ rssreader
+ 3.8.2
+
diff --git a/forge-core/src/main/java/forge/util/RSSReader.java b/forge-core/src/main/java/forge/util/RSSReader.java
new file mode 100644
index 00000000000..2c60a1685d6
--- /dev/null
+++ b/forge-core/src/main/java/forge/util/RSSReader.java
@@ -0,0 +1,76 @@
+package forge.util;
+
+import com.apptasticsoftware.rssreader.Item;
+import com.apptasticsoftware.rssreader.RssReader;
+import org.apache.commons.text.StringEscapeUtils;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.time.ZonedDateTime;
+import java.util.Date;
+import java.util.List;
+
+public class RSSReader {
+ public static String getCommitLog(String commitsAtom, Date buildDateOriginal, Date maxDate) {
+ String message = "";
+ SimpleDateFormat simpleDate = TextUtil.getSimpleDate();
+ try {
+ RssReader reader = new RssReader();
+ URL url = new URL(commitsAtom);
+ InputStream inputStream = url.openStream();
+ List- items = reader.read(inputStream).toList();
+ StringBuilder logs = new StringBuilder();
+ int c = 0;
+ for (Item i : items) {
+ if (i.getTitle().isEmpty())
+ continue;
+ String title = TextUtil.stripNonValidXMLCharacters(i.getTitle().get());
+ if (title.contains("Merge"))
+ continue;
+ ZonedDateTime zonedDateTime = i.getPubDateZonedDateTime().isPresent() ? i.getPubDateZonedDateTime().get() : null;
+ if (zonedDateTime == null)
+ continue;
+ Date feedDate = Date.from(zonedDateTime.toInstant());
+ if (buildDateOriginal != null && feedDate.before(buildDateOriginal))
+ continue;
+ if (maxDate != null && feedDate.after(maxDate))
+ continue;
+ logs.append(simpleDate.format(feedDate)).append(" | ").append(StringEscapeUtils.unescapeXml(title).replace("\n", "").replace(" ", "")).append("\n\n");
+ if (c >= 15)
+ break;
+ c++;
+ }
+ if (logs.length() > 0)
+ message += ("\n\nLatest Changes:\n\n" + logs);
+ inputStream.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return message;
+ }
+ public static String getLatestReleaseTag(String releaseAtom) {
+ String tag = "";
+ try {
+ RssReader reader = new RssReader();
+ URL url = new URL(releaseAtom);
+ InputStream inputStream = url.openStream();
+ List
- items = reader.read(inputStream).toList();
+ for (Item i : items) {
+ if (i.getLink().isPresent()) {
+ try {
+ String val = i.getLink().get();
+ tag = val.substring(val.lastIndexOf("forge"));
+ break;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ inputStream.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return tag;
+ }
+}
diff --git a/forge-core/src/main/java/forge/util/TextUtil.java b/forge-core/src/main/java/forge/util/TextUtil.java
index c9388983c77..775cfdbec05 100644
--- a/forge-core/src/main/java/forge/util/TextUtil.java
+++ b/forge-core/src/main/java/forge/util/TextUtil.java
@@ -1,16 +1,23 @@
package forge.util;
+import java.io.File;
import java.text.DecimalFormat;
import java.text.Normalizer;
+import java.text.SimpleDateFormat;
+import java.time.OffsetDateTime;
import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.TimeZone;
import forge.item.IPaperCard;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.text.StringEscapeUtils;
import com.google.common.collect.ImmutableSortedMap;
@@ -21,6 +28,8 @@
*
*/
public class TextUtil {
+ private static final StringBuilder changes = new StringBuilder();
+ private static SimpleDateFormat simpleDate;
static ImmutableSortedMap romanMap = ImmutableSortedMap.naturalOrder()
.put(1000, "M").put(900, "CM")
@@ -313,8 +322,8 @@ public static String fastReplace( String str, String target, String replacement
return sb.toString();
}
//Convert to Mana String
- public static String toManaString(String ManaProduced){
- if (ManaProduced == "mana"|| ManaProduced.contains("Combo")|| ManaProduced.contains("Any"))
+ public static String toManaString(String ManaProduced) {
+ if ("mana".equals(ManaProduced) || ManaProduced.contains("Combo")|| ManaProduced.contains("Any"))
return "mana";//fix manamorphose stack description and probably others..
return "{"+TextUtil.fastReplace(ManaProduced," ","}{")+"}";
}
@@ -363,4 +372,83 @@ public static String moveArticleToEnd(String str) {
}
return str;
}
+ /*
+ * Strip non valid XML Characters
+ */
+ public static String stripNonValidXMLCharacters(String in) {
+ StringBuffer out = new StringBuffer();
+ char current;
+
+ if (in == null || ("".equals(in))) {
+ return "";
+ }
+ for (int i = 0; i < in.length(); i++) {
+ current = in.charAt(i);
+ if ((current == 0x9) || (current == 0xA) || (current == 0xD)
+ || ((current >= 0x20) && (current <= 0xD7FF))
+ || ((current >= 0xE000) && (current <= 0xFFFD))
+ || ((current >= 0x10000) && (current <= 0x10FFFF))) {
+ out.append(current);
+ }
+ }
+ return out.toString();
+ }
+ public static SimpleDateFormat getSimpleDate() {
+ if (simpleDate == null)
+ simpleDate = new SimpleDateFormat("E, MMM dd, yyyy - hh:mm:ss a");
+ return simpleDate;
+ }
+ //format changelog
+ public static String getFormattedChangelog(File changelog, String defaultLog) {
+ if (!changelog.exists())
+ return defaultLog;
+ if (changes == null || changes.toString().isEmpty()) {
+ try {
+ Calendar calendar = Calendar.getInstance();
+ SimpleDateFormat original = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ SimpleDateFormat formatted = getSimpleDate();
+ String offset = " GMT " + OffsetDateTime.now().getOffset();
+ List toformat = FileUtil.readAllLines(changelog, false);
+ boolean skip = false;
+ int count = 0;
+ for (String line : toformat) {
+ if (line.isEmpty() || line.startsWith("#") || line.length() < 4)
+ continue;
+ if (line.contains("**Merge")) {
+ skip = true;
+ continue;
+ }
+ if (line.startsWith("[")) {
+ if (skip) {
+ skip = false;
+ continue;
+ }
+ count++;
+ String datestring = line.substring(line.lastIndexOf(" *")+1).replace("*", "");
+ try {
+ original.setTimeZone(TimeZone.getTimeZone("UTC"));
+ Date toDate = original.parse(datestring);
+ calendar.setTime(toDate);
+ formatted.setTimeZone(TimeZone.getDefault());
+ changes.append("\n(").append(formatted.format(calendar.getTime())).append(offset).append(")\n\n");
+ } catch (Exception e2) {
+ changes.append("\n(").append(datestring).append(")\n\n");
+ }
+ if (count > 20)
+ break;
+ } else {
+ if (skip)
+ continue;
+ if (line.startsWith(" * "))
+ changes.append("\n").append(StringEscapeUtils.unescapeXml(line));
+ else
+ changes.append(StringEscapeUtils.unescapeXml(line));
+ }
+ }
+ } catch (Exception e) {
+ return defaultLog;
+ }
+ }
+ return changes.toString();
+ }
}
diff --git a/forge-gui-android/assets/shaders/grayscale.frag b/forge-gui-android/assets/shaders/grayscale.frag
deleted file mode 100644
index 4d801c770a4..00000000000
--- a/forge-gui-android/assets/shaders/grayscale.frag
+++ /dev/null
@@ -1,16 +0,0 @@
-#ifdef GL_ES
-precision mediump float;
-#endif
-
-varying vec4 v_color;
-varying vec2 v_texCoords;
-uniform sampler2D u_texture;
-uniform float u_grayness;
-uniform float u_bias;
-
-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 = mix(vec4(0.0, 0.0, 0.0, 1.0), vec4(blendedColor.rgb, c.a), u_bias);
-}
\ No newline at end of file
diff --git a/forge-gui-android/assets/shaders/grayscale.vert b/forge-gui-android/assets/shaders/grayscale.vert
deleted file mode 100644
index 17d96ca8dde..00000000000
--- a/forge-gui-android/assets/shaders/grayscale.vert
+++ /dev/null
@@ -1,14 +0,0 @@
-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-gui-android/assets/shaders/outline.frag b/forge-gui-android/assets/shaders/outline.frag
deleted file mode 100644
index 738d23c1f71..00000000000
--- a/forge-gui-android/assets/shaders/outline.frag
+++ /dev/null
@@ -1,40 +0,0 @@
-#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-gui-android/assets/shaders/outline.vert b/forge-gui-android/assets/shaders/outline.vert
deleted file mode 100644
index 1b6e438116d..00000000000
--- a/forge-gui-android/assets/shaders/outline.vert
+++ /dev/null
@@ -1,16 +0,0 @@
-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-gui-android/assets/shaders/underwater.frag b/forge-gui-android/assets/shaders/underwater.frag
deleted file mode 100644
index 6287a862c9c..00000000000
--- a/forge-gui-android/assets/shaders/underwater.frag
+++ /dev/null
@@ -1,26 +0,0 @@
-#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;
-uniform float u_bias;
-
-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);
-
- vec4 texColor = texture2D(u_texture, uv);
-
- gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), texColor, u_bias);
-}
\ No newline at end of file
diff --git a/forge-gui-android/assets/shaders/warp.frag b/forge-gui-android/assets/shaders/warp.frag
deleted file mode 100644
index f8a7022fa2c..00000000000
--- a/forge-gui-android/assets/shaders/warp.frag
+++ /dev/null
@@ -1,57 +0,0 @@
-#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-gui-android/src/com/badlogic/gdx/backends/android/ForgeAndroidApplication.java b/forge-gui-android/src/com/badlogic/gdx/backends/android/ForgeAndroidApplication.java
index 4a38863d058..a1c08bab642 100644
--- a/forge-gui-android/src/com/badlogic/gdx/backends/android/ForgeAndroidApplication.java
+++ b/forge-gui-android/src/com/badlogic/gdx/backends/android/ForgeAndroidApplication.java
@@ -172,10 +172,10 @@ public void dispose () {
createWakeLock(config.useWakelock);
useImmersiveMode(this.useImmersiveMode);
- if (this.useImmersiveMode) {
+ /*if (this.useImmersiveMode) {
AndroidVisibilityListener vlistener = new AndroidVisibilityListener();
vlistener.createListener(this);
- }
+ }*/
// detect an already connected bluetooth keyboardAvailable
if (getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS) input.setKeyboardAvailable(true);
@@ -229,13 +229,13 @@ public void onWindowFocusChanged (boolean hasFocus) {
@Override
public void useImmersiveMode (boolean use) {
- if (!use) return;
+ /*if (!use) return;
View view = getWindow().getDecorView();
int code = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
- view.setSystemUiVisibility(code);
+ view.setSystemUiVisibility(code);*/
}
@Override
diff --git a/forge-gui-android/src/forge/app/AtomReader.java b/forge-gui-android/src/forge/app/AtomReader.java
new file mode 100644
index 00000000000..b48e6762f6d
--- /dev/null
+++ b/forge-gui-android/src/forge/app/AtomReader.java
@@ -0,0 +1,133 @@
+package forge.app;
+
+import android.util.Xml;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AtomReader {
+ private static final String ns = null;
+
+ public List parse(InputStream in) throws Exception {
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
+ parser.setInput(in, "UTF-8");
+ parser.nextTag();
+ return readFeed(parser);
+ } finally {
+ in.close();
+ }
+ }
+
+ private List readFeed(XmlPullParser parser) throws Exception {
+ List entries = new ArrayList<>();
+
+ parser.require(XmlPullParser.START_TAG, ns, "feed");
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ String name = parser.getName();
+ // Starts by looking for the entry tag.
+ if (name.equals("entry")) {
+ entries.add(readEntry(parser));
+ } else {
+ skip(parser);
+ }
+ }
+ return entries;
+ }
+
+ private void skip(XmlPullParser parser) throws Exception {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException();
+ }
+ int depth = 1;
+ while (depth != 0) {
+ switch (parser.next()) {
+ case XmlPullParser.END_TAG:
+ depth--;
+ break;
+ case XmlPullParser.START_TAG:
+ depth++;
+ break;
+ }
+ }
+ }
+
+ public static class Entry {
+ public final String title;
+ public final String updated;
+ public final String link;
+
+ private Entry(String title, String updated, String link) {
+ this.title = title;
+ this.updated = updated;
+ this.link = link;
+ }
+ }
+
+ private Entry readEntry(XmlPullParser parser) throws Exception {
+ parser.require(XmlPullParser.START_TAG, ns, "entry");
+ String title = null;
+ String updated = null;
+ String link = null;
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ String name = parser.getName();
+ if (name.equals("title")) {
+ title = readTitle(parser);
+ } else if (name.equals("updated")) {
+ updated = readUpdated(parser);
+ } else if (name.equals("link")) {
+ link = readLink(parser);
+ } else {
+ skip(parser);
+ }
+ }
+ return new Entry(title, updated, link);
+ }
+
+ private String readTitle(XmlPullParser parser) throws Exception {
+ parser.require(XmlPullParser.START_TAG, ns, "title");
+ String title = readText(parser);
+ parser.require(XmlPullParser.END_TAG, ns, "title");
+ return title;
+ }
+
+ private String readUpdated(XmlPullParser parser) throws Exception {
+ parser.require(XmlPullParser.START_TAG, ns, "updated");
+ String updated = readText(parser);
+ parser.require(XmlPullParser.END_TAG, ns, "updated");
+ return updated;
+ }
+
+ private String readLink(XmlPullParser parser) throws Exception {
+ String link = "";
+ parser.require(XmlPullParser.START_TAG, ns, "link");
+ String tag = parser.getName();
+ String relType = parser.getAttributeValue(null, "rel");
+ if (tag.equals("link")) {
+ if (relType.equals("alternate")) {
+ link = parser.getAttributeValue(null, "href");
+ parser.nextTag();
+ }
+ }
+ parser.require(XmlPullParser.END_TAG, ns, "link");
+ return link;
+ }
+
+ private String readText(XmlPullParser parser) throws Exception {
+ String result = "";
+ if (parser.next() == XmlPullParser.TEXT) {
+ result = parser.getText();
+ parser.nextTag();
+ }
+ return result;
+ }
+}
diff --git a/forge-gui-android/src/forge/app/GitLogs.java b/forge-gui-android/src/forge/app/GitLogs.java
new file mode 100644
index 00000000000..6d7b0e32dde
--- /dev/null
+++ b/forge-gui-android/src/forge/app/GitLogs.java
@@ -0,0 +1,73 @@
+package forge.app;
+
+import forge.util.TextUtil;
+import org.apache.commons.text.StringEscapeUtils;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+public class GitLogs {
+ public String getLatest(String commitsAtom, Date buildDateOriginal, Date maxDate) {
+ String message = "";
+ try {
+ URL url = new URL(commitsAtom);
+ InputStream inputStream = url.openStream();
+ List entries = new AtomReader().parse(inputStream);
+ StringBuilder logs = new StringBuilder();
+ SimpleDateFormat simpleDate = TextUtil.getSimpleDate();
+ SimpleDateFormat atomDate = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss");
+ int c = 0;
+ for (AtomReader.Entry entry : entries) {
+ if (entry.title == null)
+ continue;
+ String title = TextUtil.stripNonValidXMLCharacters(entry.title);
+ if (title.contains("Merge"))
+ continue;
+ if (entry.updated == null)
+ continue;
+ Date feedDate = atomDate.parse(entry.updated);
+ if (buildDateOriginal != null && feedDate.before(buildDateOriginal))
+ continue;
+ if (maxDate != null && feedDate.after(maxDate))
+ continue;
+ logs.append(simpleDate.format(feedDate)).append(" | ").append(StringEscapeUtils.unescapeXml(title).replace("\n", "").replace(" ", "")).append("\n\n");
+ if (c >= 15)
+ break;
+ c++;
+ }
+ if (logs.length() > 0)
+ message += ("\n\nLatest Changes:\n\n" + logs);
+ inputStream.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return message;
+ }
+
+ public String getLatestReleaseTag(String releaseAtom) {
+ String tag = "";
+ try {
+ URL url = new URL(releaseAtom);
+ InputStream inputStream = url.openStream();
+ List entries = new AtomReader().parse(inputStream);
+ for (AtomReader.Entry entry : entries) {
+ if (entry.link != null) {
+ try {
+ String val = entry.link;
+ tag = val.substring(val.lastIndexOf("forge"));
+ break;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ inputStream.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return tag;
+ }
+}
diff --git a/forge-gui-android/src/forge/app/Main.java b/forge-gui-android/src/forge/app/Main.java
index 1456dd14155..a405d43a124 100644
--- a/forge-gui-android/src/forge/app/Main.java
+++ b/forge-gui-android/src/forge/app/Main.java
@@ -71,6 +71,7 @@
import java.io.OutputStream;
import java.text.Normalizer;
import java.util.ArrayList;
+import java.util.Date;
public class Main extends ForgeAndroidApplication {
private AndroidAdapter Gadapter;
@@ -633,6 +634,16 @@ public String getVersionString() {
return versionString;
}
+ @Override
+ public String getLatestChanges(String commitsAtom, Date buildDateOriginal, Date maxDate) {
+ return new GitLogs().getLatest(commitsAtom, buildDateOriginal, maxDate);
+ }
+
+ @Override
+ public String getReleaseTag(String releaseAtom) {
+ return new GitLogs().getLatestReleaseTag(releaseAtom);
+ }
+
@Override
public boolean openFile(String filename) {
try {
diff --git a/forge-gui-desktop/filters/build.txt b/forge-gui-desktop/filters/build.txt
new file mode 100644
index 00000000000..aca99ef22d7
--- /dev/null
+++ b/forge-gui-desktop/filters/build.txt
@@ -0,0 +1 @@
+${timestamp}
\ No newline at end of file
diff --git a/forge-gui-desktop/filters/version.txt b/forge-gui-desktop/filters/version.txt
new file mode 100644
index 00000000000..fb02fab734c
--- /dev/null
+++ b/forge-gui-desktop/filters/version.txt
@@ -0,0 +1 @@
+${snapshot-version}
\ No newline at end of file
diff --git a/forge-gui-desktop/pom.xml b/forge-gui-desktop/pom.xml
index 85cf10bf48e..1aeb7a74877 100644
--- a/forge-gui-desktop/pom.xml
+++ b/forge-gui-desktop/pom.xml
@@ -15,11 +15,47 @@
0
0
0
+ ${maven.build.timestamp}
+ yyyy-MM-dd HH:mm:ss
--add-opens java.desktop/java.beans=ALL-UNNAMED --add-opens java.desktop/javax.swing.border=ALL-UNNAMED --add-opens java.desktop/javax.swing.event=ALL-UNNAMED --add-opens java.desktop/sun.swing=ALL-UNNAMED --add-opens java.desktop/java.awt.image=ALL-UNNAMED --add-opens java.desktop/java.awt.color=ALL-UNNAMED --add-opens java.desktop/sun.awt.image=ALL-UNNAMED --add-opens java.desktop/javax.swing=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED -Dio.netty.tryReflectionSetAccessible=true -Dfile.encoding=UTF-8
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.6.0
+
+
+ timestamp-property
+ initialize
+
+ timestamp-property
+
+
+
+ month.date
+ MM.dd
+
+
+
+ regex-property
+ initialize
+
+ regex-property
+
+
+
+ snapshot-version
+ ${revision}
+ -SNAPSHOT
+ -SNAPSHOT-${month.date}
+ false
+
+
+
+
com.akathist.maven.plugins.launch4j
launch4j-maven-plugin
@@ -75,22 +111,6 @@
-
- org.apache.maven.plugins
- maven-jar-plugin
- 3.3.0
-
-
-
- true
- true
-
-
- java.desktop/java.beans java.desktop/javax.swing.border java.desktop/javax.swing.event java.desktop/sun.swing java.desktop/java.awt.image java.desktop/java.awt.color java.desktop/sun.awt.image java.desktop/javax.swing java.desktop/java.awt java.base/java.util java.base/java.lang java.base/java.lang.reflect java.base/java.text java.desktop/java.awt.font java.base/jdk.internal.misc java.base/sun.nio.ch java.base/java.nio java.base/java.math java.base/java.util.concurrent java.base/java.net
-
-
-
-
com.google.code.maven-replacer-plugin
replacer
@@ -129,10 +149,10 @@
jar-with-dependencies
-
- true
-
+ ${project.name}
+ ${snapshot-version}
+ ${project.organization.name}
java.desktop/java.beans java.desktop/javax.swing.border java.desktop/javax.swing.event java.desktop/sun.swing java.desktop/java.awt.image java.desktop/java.awt.color java.desktop/sun.awt.image java.desktop/javax.swing java.desktop/java.awt java.base/java.util java.base/java.lang java.base/java.lang.reflect java.base/java.text java.desktop/java.awt.font java.base/jdk.internal.misc java.base/sun.nio.ch java.base/java.nio java.base/java.math java.base/java.util.concurrent java.base/java.net
forge.view.Main
@@ -222,6 +242,12 @@
+
+
+ filters
+ true
+
+
diff --git a/forge-gui-desktop/src/main/java/forge/control/FControl.java b/forge-gui-desktop/src/main/java/forge/control/FControl.java
index 6ebdbb2783b..620fcbdc08b 100644
--- a/forge-gui-desktop/src/main/java/forge/control/FControl.java
+++ b/forge-gui-desktop/src/main/java/forge/control/FControl.java
@@ -26,6 +26,7 @@
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
+import java.net.URL;
import java.util.Collections;
import java.util.List;
@@ -61,8 +62,10 @@
import forge.screens.deckeditor.CDeckEditorUI;
import forge.toolbox.FOptionPane;
import forge.toolbox.FSkin;
+import forge.util.FileUtil;
import forge.util.Localizer;
import forge.util.RestartUtil;
+import forge.util.TextUtil;
import forge.view.FFrame;
import forge.view.FView;
@@ -83,6 +86,7 @@ public enum FControl implements KeyEventDispatcher {
private boolean altKeyLastDown;
private CloseAction closeAction;
private final List currentMatches = Lists.newArrayList();
+ private String snapsVersion = "";
public enum CloseAction {
NONE,
@@ -210,6 +214,12 @@ public boolean exitForge() {
/** After view and model have been initialized, control can start.*/
public void initialize() {
+ //get version string
+ try {
+ URL url = new URL("https://downloads.cardforge.org/dailysnapshots/version.txt");
+ snapsVersion = FileUtil.readFileToString(url);
+
+ } catch (Exception e) {}
// Preloads skin components (using progress bar).
FSkin.loadFull(true);
@@ -237,7 +247,10 @@ public void initialize() {
System.err.printf("Error loading quest data (%s).. skipping for now..%n", questname);
}
}
-
+ // format release notes upon loading
+ try {
+ TextUtil.getFormattedChangelog(new File(FileUtil.pathCombine(System.getProperty("user.dir"), ForgeConstants.CHANGES_FILE_NO_RELEASE)),"");
+ } catch (Exception e){}
// Handles resizing in null layouts of layers in JLayeredPane as well as saving window layout
final FFrame window = Singletons.getView().getFrame();
window.addComponentListener(new ComponentAdapter() {
@@ -262,6 +275,12 @@ public void componentMoved(final ComponentEvent e) {
FView.SINGLETON_INSTANCE.setSplashProgessBarMessage(localizer.getMessage("lblOpeningMainWindow"));
SwingUtilities.invokeLater(() -> Singletons.getView().initialize());
}
+ public String compareVersion(String currentVersion) {
+ if (currentVersion.isEmpty() || snapsVersion.isEmpty()
+ || !currentVersion.contains("SNAPSHOT") || currentVersion.equalsIgnoreCase(snapsVersion))
+ return "";
+ return "NEW SNAPSHOT AVAILABLE!!!";
+ }
private void setGlobalKeyboardHandler() {
final KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java
index 34c027b6ee0..cff8d450da9 100644
--- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java
+++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java
@@ -15,6 +15,11 @@
import forge.gui.download.GuiDownloadSkins;
import forge.gui.error.BugReporter;
import forge.gui.framework.ICDoc;
+import forge.util.RSSReader;
+
+import java.util.concurrent.CompletableFuture;
+
+import static forge.localinstance.properties.ForgeConstants.GITHUB_COMMITS_URL_ATOM;
/**
* Controls the utilities submenu in the home UI.
@@ -27,7 +32,7 @@ public enum CSubmenuDownloaders implements ICDoc {
SINGLETON_INSTANCE;
private final UiCommand cmdLicensing = VSubmenuDownloaders.SINGLETON_INSTANCE::showLicensing;
- private final UiCommand cmdCheckForUpdates = () -> new AutoUpdater(false).attemptToUpdate();
+ private final UiCommand cmdCheckForUpdates = () -> new AutoUpdater(false).attemptToUpdate(CompletableFuture.supplyAsync(() -> RSSReader.getCommitLog(GITHUB_COMMITS_URL_ATOM, null, null)));
private final UiCommand cmdPicDownload = () -> new GuiDownloader(new GuiDownloadPicturesLQ()).show();
private final UiCommand cmdPicDownloadHQ = () -> new GuiDownloader(new GuiDownloadPicturesHQ()).show();
diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuReleaseNotes.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuReleaseNotes.java
index f4efe48833b..89497f98bd0 100644
--- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuReleaseNotes.java
+++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuReleaseNotes.java
@@ -20,7 +20,11 @@
import forge.gui.framework.ICDoc;
import forge.localinstance.properties.ForgeConstants;
+import forge.util.BuildInfo;
import forge.util.FileUtil;
+import forge.util.TextUtil;
+
+import java.io.File;
/**
* Controller for VSubmenuReleaseNotes submenu in the home UI.
@@ -80,7 +84,8 @@ private static String getReleaseNotes() {
String notes;
if (FileUtil.doesFileExist(filePath)) {
- notes = filePath + "\n\n" + FileUtil.readFileToString(filePath);
+ // get release notes
+ notes = BuildInfo.getVersionString() +" Changelog:\n\n" + TextUtil.getFormattedChangelog(new File(filePath), ForgeConstants.CHANGES_FILE_NO_RELEASE);
} else if (FileUtil.doesFileExist(filePathRelease)) {
notes = filePathRelease + "\n\n" + FileUtil.readFileToString(filePathRelease);
} else {
diff --git a/forge-gui-desktop/src/main/java/forge/view/FTitleBarBase.java b/forge-gui-desktop/src/main/java/forge/view/FTitleBarBase.java
index 017e9daf455..65457a699fb 100644
--- a/forge-gui-desktop/src/main/java/forge/view/FTitleBarBase.java
+++ b/forge-gui-desktop/src/main/java/forge/view/FTitleBarBase.java
@@ -12,17 +12,24 @@
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
+import java.util.concurrent.CompletableFuture;
import javax.swing.SpringLayout;
import javax.swing.SwingUtilities;
+import forge.control.FControl;
+import forge.download.AutoUpdater;
import forge.gui.framework.ILocalRepaint;
import forge.toolbox.FSkin;
import forge.toolbox.FSkin.Colors;
import forge.toolbox.FSkin.SkinColor;
import forge.toolbox.FSkin.SkinnedLabel;
import forge.toolbox.FSkin.SkinnedMenuBar;
+import forge.util.BuildInfo;
import forge.util.Localizer;
+import forge.util.RSSReader;
+
+import static forge.localinstance.properties.ForgeConstants.GITHUB_COMMITS_URL_ATOM;
@SuppressWarnings("serial")
public abstract class FTitleBarBase extends SkinnedMenuBar {
@@ -42,6 +49,7 @@ public abstract class FTitleBarBase extends SkinnedMenuBar {
protected final FullScreenButton btnFullScreen = new FullScreenButton();
protected final MaximizeButton btnMaximize = new MaximizeButton();
protected final CloseButton btnClose = new CloseButton();
+ protected final UpdaterButton btnUpdateShortcut = new UpdaterButton();
protected FTitleBarBase(ITitleBarOwner owner0) {
this.owner = owner0;
@@ -71,6 +79,11 @@ protected void addControls() {
add(btnLockTitleBar);
layout.putConstraint(SpringLayout.EAST, btnLockTitleBar, 0, SpringLayout.WEST, btnMinimize);
layout.putConstraint(SpringLayout.SOUTH, btnLockTitleBar, 0, SpringLayout.SOUTH, btnMinimize);
+
+ add(btnUpdateShortcut);
+ layout.putConstraint(SpringLayout.EAST, btnUpdateShortcut, 0, SpringLayout.WEST, btnMinimize);
+ layout.putConstraint(SpringLayout.SOUTH, btnUpdateShortcut, 0, SpringLayout.SOUTH, btnMinimize);
+
}
else {
int offset = owner instanceof FDialog && ((FDialog)owner).allowResize() ? 0 : -1;
@@ -408,4 +421,34 @@ public void paintComponent(Graphics g) {
g2d.drawLine(x2, y1, x1, y2);
}
}
+ public class UpdaterButton extends TitleBarButton {
+ final int MARQUEE_SPEED_DIV = 15;
+ final int REPAINT_WITHIN_MS = 25;
+ final String displayText = FControl.instance.compareVersion(BuildInfo.getVersionString());
+ private UpdaterButton() {
+ setToolTipText(Localizer.getInstance().getMessage("btnCheckForUpdates"));
+ setPreferredSize(new Dimension(160, 25));
+ setEnabled(!displayText.isEmpty());
+ }
+ @Override
+ protected void onClick() {
+ try {
+ new AutoUpdater(false).attemptToUpdate(CompletableFuture.supplyAsync(() -> RSSReader.getCommitLog(GITHUB_COMMITS_URL_ATOM, null, null)));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ @Override
+ public void paintComponent(Graphics g) {
+ g.translate((int)((System.currentTimeMillis() / MARQUEE_SPEED_DIV) % (getWidth() * 2)) - getWidth(), 0);
+ super.paintComponent(g);
+ int thickness = 2;
+ Graphics2D g2d = (Graphics2D) g;
+ FSkin.setGraphicsColor(g2d, foreColor);
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setStroke(new BasicStroke(thickness));
+ g2d.drawString(displayText, 0, 17);
+ repaint(REPAINT_WITHIN_MS);
+ }
+ }
}
diff --git a/forge-gui-ios/src/forge/ios/Main.java b/forge-gui-ios/src/forge/ios/Main.java
index fc452ca70ba..cbfc78b65bd 100644
--- a/forge-gui-ios/src/forge/ios/Main.java
+++ b/forge-gui-ios/src/forge/ios/Main.java
@@ -4,6 +4,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.Date;
import org.apache.commons.lang3.tuple.Pair;
import org.robovm.apple.foundation.NSAutoreleasePool;
@@ -78,6 +79,16 @@ public String getVersionString() {
return "0.0";
}
+ @Override
+ public String getLatestChanges(String commitsAtom, Date buildDateOriginal, Date maxDate) {
+ return "";
+ }
+
+ @Override
+ public String getReleaseTag(String releaseAtom) {
+ return "";
+ }
+
@Override
public boolean openFile(final String filename) {
return new IOSFiles().local(filename).exists();
diff --git a/forge-gui-mobile-dev/filters/build.txt b/forge-gui-mobile-dev/filters/build.txt
new file mode 100644
index 00000000000..aca99ef22d7
--- /dev/null
+++ b/forge-gui-mobile-dev/filters/build.txt
@@ -0,0 +1 @@
+${timestamp}
\ No newline at end of file
diff --git a/forge-gui-mobile-dev/filters/version.txt b/forge-gui-mobile-dev/filters/version.txt
new file mode 100644
index 00000000000..fb02fab734c
--- /dev/null
+++ b/forge-gui-mobile-dev/filters/version.txt
@@ -0,0 +1 @@
+${snapshot-version}
\ No newline at end of file
diff --git a/forge-gui-mobile-dev/pom.xml b/forge-gui-mobile-dev/pom.xml
index 1f69a13cb61..bf7c57c2260 100644
--- a/forge-gui-mobile-dev/pom.xml
+++ b/forge-gui-mobile-dev/pom.xml
@@ -18,25 +18,41 @@
src
-
-
- ${project.basedir}
-
- **/*.vert
- **/*.frag
- **/title_bg_lq.png
- **/title_bg_lq_portrait.png
- **/transition.png
- **/adv_bg_texture.jpg
- **/adv_bg_splash.png
- **/bg_splash.png
- **/bg_texture.jpg
- **/font1.ttf
- **/logo.gif
-
-
-
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.6.0
+
+
+ timestamp-property
+ initialize
+
+ timestamp-property
+
+
+
+ month.date
+ MM.dd
+
+
+
+ regex-property
+ initialize
+
+ regex-property
+
+
+
+ snapshot-version
+ ${revision}
+ -SNAPSHOT
+ -SNAPSHOT-${month.date}
+ false
+
+
+
+
maven-compiler-plugin
@@ -133,10 +149,10 @@
jar-with-dependencies
-
- true
-
+ ${project.name}
+ ${snapshot-version}
+ ${project.organization.name}
java.desktop/java.beans java.desktop/javax.swing.border java.desktop/javax.swing.event java.desktop/sun.swing java.desktop/java.awt.image java.desktop/java.awt.color java.desktop/sun.awt.image java.desktop/javax.swing java.desktop/java.awt java.base/java.util java.base/java.lang java.base/java.lang.reflect java.base/java.text java.desktop/java.awt.font java.base/jdk.internal.misc java.base/sun.nio.ch java.base/java.nio java.base/java.math java.base/java.util.concurrent java.base/java.net
forge.app.Main
splash/logo.gif
@@ -156,6 +172,26 @@
+
+
+ ${project.basedir}
+
+ **/title_bg_lq.png
+ **/title_bg_lq_portrait.png
+ **/transition.png
+ **/adv_bg_texture.jpg
+ **/adv_bg_splash.png
+ **/bg_splash.png
+ **/bg_texture.jpg
+ **/font1.ttf
+ **/logo.gif
+
+
+
+ filters
+ true
+
+
diff --git a/forge-gui-mobile-dev/shaders/grayscale.frag b/forge-gui-mobile-dev/shaders/grayscale.frag
deleted file mode 100644
index 4d801c770a4..00000000000
--- a/forge-gui-mobile-dev/shaders/grayscale.frag
+++ /dev/null
@@ -1,16 +0,0 @@
-#ifdef GL_ES
-precision mediump float;
-#endif
-
-varying vec4 v_color;
-varying vec2 v_texCoords;
-uniform sampler2D u_texture;
-uniform float u_grayness;
-uniform float u_bias;
-
-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 = mix(vec4(0.0, 0.0, 0.0, 1.0), vec4(blendedColor.rgb, c.a), u_bias);
-}
\ No newline at end of file
diff --git a/forge-gui-mobile-dev/shaders/grayscale.vert b/forge-gui-mobile-dev/shaders/grayscale.vert
deleted file mode 100644
index 17d96ca8dde..00000000000
--- a/forge-gui-mobile-dev/shaders/grayscale.vert
+++ /dev/null
@@ -1,14 +0,0 @@
-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-gui-mobile-dev/shaders/outline.frag b/forge-gui-mobile-dev/shaders/outline.frag
deleted file mode 100644
index 738d23c1f71..00000000000
--- a/forge-gui-mobile-dev/shaders/outline.frag
+++ /dev/null
@@ -1,40 +0,0 @@
-#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-gui-mobile-dev/shaders/outline.vert b/forge-gui-mobile-dev/shaders/outline.vert
deleted file mode 100644
index 1b6e438116d..00000000000
--- a/forge-gui-mobile-dev/shaders/outline.vert
+++ /dev/null
@@ -1,16 +0,0 @@
-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-gui-mobile-dev/shaders/underwater.frag b/forge-gui-mobile-dev/shaders/underwater.frag
deleted file mode 100644
index 6287a862c9c..00000000000
--- a/forge-gui-mobile-dev/shaders/underwater.frag
+++ /dev/null
@@ -1,26 +0,0 @@
-#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;
-uniform float u_bias;
-
-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);
-
- vec4 texColor = texture2D(u_texture, uv);
-
- gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), texColor, u_bias);
-}
\ No newline at end of file
diff --git a/forge-gui-mobile-dev/shaders/warp.frag b/forge-gui-mobile-dev/shaders/warp.frag
deleted file mode 100644
index f8a7022fa2c..00000000000
--- a/forge-gui-mobile-dev/shaders/warp.frag
+++ /dev/null
@@ -1,57 +0,0 @@
-#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-gui-mobile-dev/src/forge/app/GameLauncher.java b/forge-gui-mobile-dev/src/forge/app/GameLauncher.java
index 83d8560dffc..046c6c126ba 100644
--- a/forge-gui-mobile-dev/src/forge/app/GameLauncher.java
+++ b/forge-gui-mobile-dev/src/forge/app/GameLauncher.java
@@ -9,8 +9,6 @@
import com.badlogic.gdx.utils.SharedLibraryLoader;
import forge.Forge;
import forge.adventure.util.Config;
-import forge.assets.AssetsDownloader;
-import forge.util.FileUtil;
import org.lwjgl.system.Configuration;
import java.nio.file.Files;
@@ -28,16 +26,9 @@ public GameLauncher(final String versionString) {
if (!Files.exists(Paths.get(desktopModeAssetsDir + "res")))
desktopModeAssetsDir = "../forge-gui/";//try IDE run
- // Assets directory used when the game fully emulates smartphone/tablet mode (desktopMode = false), useful when debugging from IDE
- String assetsDir;
- if (!AssetsDownloader.SHARE_DESKTOP_ASSETS) {
- assetsDir = "testAssets/";
- FileUtil.ensureDirectoryExists(assetsDir);
- } else {
- assetsDir = "./";
- if (!Files.exists(Paths.get(assetsDir + "res")))
- assetsDir = "../forge-gui/";
- }
+ String assetsDir = "./";
+ if (!Files.exists(Paths.get(assetsDir + "res")))
+ assetsDir = "../forge-gui/";
// Place the file "switch_orientation.ini" to your assets folder to make the game switch to landscape orientation (unless desktopMode = true)
String switchOrientationFile = assetsDir + "switch_orientation.ini";
diff --git a/forge-gui-mobile-dev/src/forge/app/Main.java b/forge-gui-mobile-dev/src/forge/app/Main.java
index 040e0c28f8b..a8e9c424299 100644
--- a/forge-gui-mobile-dev/src/forge/app/Main.java
+++ b/forge-gui-mobile-dev/src/forge/app/Main.java
@@ -2,10 +2,7 @@
import com.badlogic.gdx.Gdx;
import forge.interfaces.IDeviceAdapter;
-import forge.util.BuildInfo;
-import forge.util.FileUtil;
-import forge.util.OperatingSystem;
-import forge.util.RestartUtil;
+import forge.util.*;
import org.apache.commons.lang3.tuple.Pair;
import javax.imageio.ImageIO;
@@ -17,6 +14,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.Date;
import java.util.Optional;
public class Main {
@@ -53,10 +51,28 @@ public String getVersionString() {
return versionString;
}
+ @Override
+ public String getLatestChanges(String commitsAtom, Date buildDateOriginal, Date max) {
+ return RSSReader.getCommitLog(commitsAtom, buildDateOriginal, max);
+ }
+
+ @Override
+ public String getReleaseTag(String releaseAtom) {
+ return RSSReader.getLatestReleaseTag(releaseAtom);
+ }
+
@Override
public boolean openFile(String filename) {
try {
- Desktop.getDesktop().open(new File(filename));
+ File installer = new File(filename);
+ if (installer.exists()) {
+ if (filename.endsWith(".jar")) {
+ installer.setExecutable(true, false);
+ Desktop.getDesktop().open(installer);
+ } else {
+ Desktop.getDesktop().open(installer.getParentFile());
+ }
+ }
return true;
} catch (IOException e) {
e.printStackTrace();
diff --git a/forge-gui-mobile/libs/gdx-natives.jar b/forge-gui-mobile/libs/gdx-natives.jar
new file mode 100644
index 00000000000..15c0b61ffe5
Binary files /dev/null and b/forge-gui-mobile/libs/gdx-natives.jar differ
diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java
index 4636aa2b07a..73502b18914 100644
--- a/forge-gui-mobile/src/forge/Forge.java
+++ b/forge-gui-mobile/src/forge/Forge.java
@@ -76,7 +76,7 @@ public class Forge implements ApplicationListener {
protected static ClosingScreen closingScreen;
protected static TransitionScreen transitionScreen;
public static KeyInputAdapter keyInputAdapter;
- private static boolean exited;
+ private static boolean exited, initialized;
public boolean needsUpdate = false;
public static boolean advStartup = false;
public static boolean safeToClose = true;
@@ -199,7 +199,8 @@ the app again (seems it doesnt dispose correctly...?!?)
} else {
skinName = "default"; //use default skin if preferences file doesn't exist yet
}
- FSkin.loadLight(skinName, splashScreen);
+ if (!initialized)
+ FSkin.loadLight(skinName, getSplashScreen());
textureFiltering = getForgePreferences().getPrefBoolean(FPref.UI_LIBGDX_TEXTURE_FILTERING);
showFPS = getForgePreferences().getPrefBoolean(FPref.UI_SHOW_FPS);
@@ -223,49 +224,45 @@ else if (getForgePreferences().getPref(FPref.UI_ENABLE_BORDER_MASKING).equals("f
if (totalDeviceRAM > 5000) //devices with more than 10GB RAM will have 600 Cache size, 400 Cache size for morethan 5GB RAM
cacheSize = totalDeviceRAM > 10000 ? 600 : 400;
}
- //init cache
- ImageCache.initCache(cacheSize);
+ if (!initialized) {
+ initialized = true;
+
+ Runnable runnable = () -> {
+ safeToClose = false;
+ ImageKeys.setIsLibGDXPort(GuiBase.getInterface().isLibgdxPort());
+ FModel.initialize(getSplashScreen().getProgressBar(), null);
+
+ getSplashScreen().getProgressBar().setDescription(getLocalizer().getMessage("lblLoadingFonts"));
+ FSkinFont.preloadAll(locale);
+
+ getSplashScreen().getProgressBar().setDescription(getLocalizer().getMessage("lblLoadingCardTranslations"));
+ CardTranslation.preloadTranslation(locale, ForgeConstants.LANG_DIR);
+
+ getSplashScreen().getProgressBar().setDescription(getLocalizer().getMessage("lblFinishingStartup"));
+
+ //add reminder to preload
+ if (enablePreloadExtendedArt) {
+ if (autoCache)
+ getSplashScreen().getProgressBar().setDescription(getLocalizer().getMessage("lblPreloadExtendedArt") + "\nDetected RAM: " + totalDeviceRAM + "MB. Cache size: " + cacheSize);
+ else
+ getSplashScreen().getProgressBar().setDescription(getLocalizer().getMessage("lblPreloadExtendedArt"));
+ } else {
+ if (autoCache)
+ getSplashScreen().getProgressBar().setDescription(getLocalizer().getMessage("lblFinishingStartup") + "\nDetected RAM: " + totalDeviceRAM + "MB. Cache size: " + cacheSize);
+ else
+ getSplashScreen().getProgressBar().setDescription(getLocalizer().getMessage("lblFinishingStartup"));
+ }
- //load model on background thread (using progress bar to report progress)
- FThreads.invokeInBackgroundThread(() -> {
+ Gdx.app.postRunnable(() -> {
+ afterDbLoaded();
+ /* call preloadExtendedArt here, if we put it above we will *
+ * get error: No OpenGL context found in the current thread. */
+ preloadExtendedArt();
+ });
+ };
//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
-
- safeToClose = false;
- ImageKeys.setIsLibGDXPort(GuiBase.getInterface().isLibgdxPort());
- FModel.initialize(splashScreen.getProgressBar(), null);
-
- splashScreen.getProgressBar().setDescription(getLocalizer().getMessage("lblLoadingFonts"));
- FSkinFont.preloadAll(locale);
-
- splashScreen.getProgressBar().setDescription(getLocalizer().getMessage("lblLoadingCardTranslations"));
- CardTranslation.preloadTranslation(locale, ForgeConstants.LANG_DIR);
-
- splashScreen.getProgressBar().setDescription(getLocalizer().getMessage("lblFinishingStartup"));
-
- //add reminder to preload
- if (enablePreloadExtendedArt) {
- if (autoCache)
- splashScreen.getProgressBar().setDescription(getLocalizer().getMessage("lblPreloadExtendedArt") + "\nDetected RAM: " + totalDeviceRAM + "MB. Cache size: " + cacheSize);
- else
- splashScreen.getProgressBar().setDescription(getLocalizer().getMessage("lblPreloadExtendedArt"));
- } else {
- if (autoCache)
- splashScreen.getProgressBar().setDescription(getLocalizer().getMessage("lblFinishingStartup") + "\nDetected RAM: " + totalDeviceRAM + "MB. Cache size: " + cacheSize);
- else
- splashScreen.getProgressBar().setDescription(getLocalizer().getMessage("lblFinishingStartup"));
- }
-
- Gdx.app.postRunnable(() -> {
- afterDbLoaded();
- /* call preloadExtendedArt here, if we put it above we will *
- * get error: No OpenGL context found in the current thread. */
- preloadExtendedArt();
- });
- });
+ FThreads.invokeInBackgroundThread(() -> AssetsDownloader.checkForUpdates(exited, runnable));
+ }
}
public static boolean hasGamepad() {
//Classic Mode Various Screen GUI are not yet supported, needs control mapping for each screens
@@ -282,6 +279,11 @@ public static InputProcessor getInputProcessor() {
public static Graphics getGraphics() {
return graphics;
}
+ public static SplashScreen getSplashScreen() {
+ if (splashScreen == null)
+ splashScreen = new SplashScreen();
+ return splashScreen;
+ }
public static Scene getCurrentScene() {
return currentScene;
@@ -447,10 +449,10 @@ public static void setCursor(TextureRegion textureRegion, String name) {
String path = "skin/cursor" + name + ".png";
Pixmap pm = new Pixmap(Config.instance().getFile(path));
- if (name == "0") {
+ if ("0".equals(name)) {
cursorA0 = Gdx.graphics.newCursor(pm, 0, 0);
setGdxCursor(cursorA0);
- } else if (name == "1") {
+ } else if ("1".equals(name)) {
cursorA1 = Gdx.graphics.newCursor(pm, 0, 0);
setGdxCursor(cursorA1);
} else {
@@ -461,20 +463,20 @@ public static void setCursor(TextureRegion textureRegion, String name) {
pm.dispose();
return;
}
- if (!FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ENABLE_MAGNIFIER) && name != "0")
+ if (!FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ENABLE_MAGNIFIER) && !"0".equals(name))
return; //don't change if it's disabled
- if (currentScreen != null && !currentScreen.toString().toLowerCase().contains("match") && name != "0")
+ if (currentScreen != null && !currentScreen.toString().toLowerCase().contains("match") && !"0".equals(name))
return; // cursor indicator should be during matches
if (textureRegion == null) {
return;
}
- if (cursor0 != null && name == "0") {
+ if (cursor0 != null && "0".equals(name)) {
setGdxCursor(cursor0);
return;
- } else if (cursor1 != null && name == "1") {
+ } else if (cursor1 != null && "1".equals(name)) {
setGdxCursor(cursor1);
return;
- } else if (cursor2 != null && name == "2") {
+ } else if (cursor2 != null && "2".equals(name)) {
setGdxCursor(cursor2);
return;
}
@@ -496,10 +498,10 @@ public static void setCursor(TextureRegion textureRegion, String name) {
textureRegion.getRegionWidth(), // The width of the area from the other Pixmap in pixels
textureRegion.getRegionHeight() // The height of the area from the other Pixmap in pixels
);
- if (name == "0") {
+ if ("0".equals(name)) {
cursor0 = Gdx.graphics.newCursor(pm, 0, 0);
setGdxCursor(cursor0);
- } else if (name == "1") {
+ } else if ("1".equals(name)) {
cursor1 = Gdx.graphics.newCursor(pm, 0, 0);
setGdxCursor(cursor1);
} else {
@@ -829,8 +831,10 @@ private static void setCurrentScreen(FScreen screen0) {
endKeyInput(); //end key input before switching screens
ForgeAnimation.endAll(); //end all active animations before switching screens
currentScreen = screen0;
- currentScreen.setSize(screenWidth, screenHeight);
- currentScreen.onActivate();
+ if (currentScreen != null) {
+ currentScreen.setSize(screenWidth, screenHeight);
+ currentScreen.onActivate();
+ }
} catch (Exception ex) {
graphics.end();
//check if sentry is enabled, if not it will call the gui interface but here we end the graphics so we only send it via sentry..
diff --git a/forge-gui-mobile/src/forge/Graphics.java b/forge-gui-mobile/src/forge/Graphics.java
index b7ca391d9bf..bc6b01fe8ec 100644
--- a/forge-gui-mobile/src/forge/Graphics.java
+++ b/forge-gui-mobile/src/forge/Graphics.java
@@ -43,10 +43,10 @@ public class Graphics {
private int failedClipCount;
private float alphaComposite = 1;
private int transformCount = 0;
- private final ShaderProgram shaderOutline = new ShaderProgram(Gdx.files.internal("shaders").child("outline.vert"), Gdx.files.internal("shaders").child("outline.frag"));
- private final ShaderProgram shaderGrayscale = new ShaderProgram(Gdx.files.internal("shaders").child("grayscale.vert"), Gdx.files.internal("shaders").child("grayscale.frag"));
- private final ShaderProgram shaderWarp = new ShaderProgram(Gdx.files.internal("shaders").child("grayscale.vert"), Gdx.files.internal("shaders").child("warp.frag"));
- private final ShaderProgram shaderUnderwater = new ShaderProgram(Gdx.files.internal("shaders").child("grayscale.vert"), Gdx.files.internal("shaders").child("underwater.frag"));
+ private final ShaderProgram shaderOutline = new ShaderProgram(Shaders.outlineVert, Shaders.outlineFrag);
+ private final ShaderProgram shaderGrayscale = new ShaderProgram(Shaders.grayscaleVert, Shaders.grayscaleFrag);
+ private final ShaderProgram shaderWarp = new ShaderProgram(Shaders.grayscaleVert, Shaders.warpFrag);
+ private final ShaderProgram shaderUnderwater = new ShaderProgram(Shaders.grayscaleVert, Shaders.underwaterFrag);
private final ShaderProgram shaderNightDay = new ShaderProgram(Shaders.vertexShaderDayNight, Shaders.fragmentShaderDayNight);
private final ShaderProgram shaderPixelate = new ShaderProgram(Shaders.vertPixelateShader, Shaders.fragPixelateShader);
private final ShaderProgram shaderRipple = new ShaderProgram(Shaders.vertPixelateShader, Shaders.fragRipple);
diff --git a/forge-gui-mobile/src/forge/Shaders.java b/forge-gui-mobile/src/forge/Shaders.java
index beeb90d3396..784517459b9 100644
--- a/forge-gui-mobile/src/forge/Shaders.java
+++ b/forge-gui-mobile/src/forge/Shaders.java
@@ -1986,4 +1986,174 @@ public class Shaders {
" luv.yz = (luv.yz) + (v_color.yz);\n" +
" gl_FragColor = vec4(sRGB(clamp(luv2rgb(luv), 0.0, 1.0)), v_color.a * tgt.a);\n" +
"}";
+ // moved shaders from dirs to here
+ public static String warpFrag = "#ifdef GL_ES\n" +
+ "precision mediump float;\n" +
+ "#endif\n" +
+ "\n" +
+ "varying vec2 v_texCoords;\n" +
+ "uniform sampler2D u_texture;\n" +
+ "\n" +
+ "uniform float u_time;\n" +
+ "uniform float u_speed;\n" +
+ "uniform float u_amount;\n" +
+ "uniform vec2 u_viewport;\n" +
+ "uniform vec2 u_position;\n" +
+ "\n" +
+ "float random2d(vec2 n) {\n" +
+ " return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);\n" +
+ "}\n" +
+ "\n" +
+ "float randomRange (in vec2 seed, in float min, in float max) {\n" +
+ " return min + random2d(seed) * (max - min);\n" +
+ "}\n" +
+ "\n" +
+ "float insideRange(float v, float bottom, float top) {\n" +
+ " return step(bottom, v) - step(top, v);\n" +
+ "}\n" +
+ "\n" +
+ "void main()\n" +
+ "{\n" +
+ " float time = floor(u_time * u_speed * 60.0);\n" +
+ "\n" +
+ " vec3 outCol = texture2D(u_texture, v_texCoords).rgb;\n" +
+ "\n" +
+ " float maxOffset = u_amount/2.0;\n" +
+ " for (float i = 0.0; i < 2.0; i += 1.0) {\n" +
+ " float sliceY = random2d(vec2(time, 2345.0 + float(i)));\n" +
+ " float sliceH = random2d(vec2(time, 9035.0 + float(i))) * 0.25;\n" +
+ " float hOffset = randomRange(vec2(time, 9625.0 + float(i)), -maxOffset, maxOffset);\n" +
+ " vec2 uvOff = v_texCoords;\n" +
+ " uvOff.x += hOffset;\n" +
+ " if (insideRange(v_texCoords.y, sliceY, fract(sliceY+sliceH)) == 1.0){\n" +
+ " outCol = texture2D(u_texture, uvOff).rgb;\n" +
+ " }\n" +
+ " }\n" +
+ "\n" +
+ " float maxColOffset = u_amount / 6.0;\n" +
+ " float rnd = random2d(vec2(time , 9545.0));\n" +
+ " vec2 colOffset = vec2(randomRange(vec2(time , 9545.0), -maxColOffset, maxColOffset),\n" +
+ " randomRange(vec2(time , 7205.0), -maxColOffset, maxColOffset));\n" +
+ " if (rnd < 0.33) {\n" +
+ " outCol.r = texture2D(u_texture, v_texCoords + colOffset).r;\n" +
+ " } else if (rnd < 0.66) {\n" +
+ " outCol.g = texture2D(u_texture, v_texCoords + colOffset).g;\n" +
+ " } else {\n" +
+ " outCol.b = texture2D(u_texture, v_texCoords + colOffset).b;\n" +
+ " }\n" +
+ "\n" +
+ " gl_FragColor = vec4(outCol, 1.0);\n" +
+ "}";
+ public static String underwaterFrag = "#ifdef GL_ES\n" +
+ "#define PRECISION mediump\n" +
+ "precision PRECISION float;\n" +
+ "precision PRECISION int;\n" +
+ "#else\n" +
+ "#define PRECISION\n" +
+ "#endif\n" +
+ "\n" +
+ "varying vec2 v_texCoords;\n" +
+ "uniform sampler2D u_texture;\n" +
+ "uniform float u_amount;\n" +
+ "uniform float u_speed;\n" +
+ "uniform float u_time;\n" +
+ "uniform float u_bias;\n" +
+ "\n" +
+ "void main () {\n" +
+ " vec2 uv = v_texCoords;\n" +
+ "\n" +
+ " 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);\n" +
+ "\n" +
+ " 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);\n" +
+ "\n" +
+ "\tvec4 texColor = texture2D(u_texture, uv);\n" +
+ "\n" +
+ " gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), texColor, u_bias);\n" +
+ "}";
+ public static String outlineFrag = "#ifdef GL_ES\n" +
+ "precision mediump float;\n" +
+ "precision mediump int;\n" +
+ "#endif\n" +
+ "\n" +
+ "uniform sampler2D u_texture;\n" +
+ "uniform vec2 u_viewportInverse;\n" +
+ "uniform vec3 u_color;\n" +
+ "uniform float u_offset;\n" +
+ "uniform float u_step;\n" +
+ "\n" +
+ "varying vec4 v_color;\n" +
+ "varying vec2 v_texCoord;\n" +
+ "\n" +
+ "#define ALPHA_VALUE_BORDER 0.5\n" +
+ "\n" +
+ "void main() {\n" +
+ " vec2 T = v_texCoord.xy;\n" +
+ "\n" +
+ " float alpha = 0.0;\n" +
+ " bool allin = true;\n" +
+ " for( float ix = -u_offset; ix < u_offset; ix += u_step )\n" +
+ " {\n" +
+ " for( float iy = -u_offset; iy < u_offset; iy += u_step )\n" +
+ " {\n" +
+ " float newAlpha = texture2D(u_texture, T + vec2(ix, iy) * u_viewportInverse).a;\n" +
+ " allin = allin && newAlpha > ALPHA_VALUE_BORDER;\n" +
+ " if (newAlpha > ALPHA_VALUE_BORDER && newAlpha >= alpha)\n" +
+ " {\n" +
+ " alpha = newAlpha;\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ " if (allin)\n" +
+ " {\n" +
+ " alpha = 0.0;\n" +
+ " }\n" +
+ "\n" +
+ " gl_FragColor = vec4(u_color,alpha);\n" +
+ "}";
+ public static String outlineVert = "uniform mat4 u_projTrans;\n" +
+ "\n" +
+ "attribute vec4 a_position;\n" +
+ "attribute vec2 a_texCoord0;\n" +
+ "attribute vec4 a_color;\n" +
+ "\n" +
+ "varying vec4 v_color;\n" +
+ "varying vec2 v_texCoord;\n" +
+ "\n" +
+ "uniform vec2 u_viewportInverse;\n" +
+ "\n" +
+ "void main() {\n" +
+ " gl_Position = u_projTrans * a_position;\n" +
+ " v_texCoord = a_texCoord0;\n" +
+ " v_color = a_color;\n" +
+ "}";
+ public static String grayscaleFrag = "#ifdef GL_ES\n" +
+ "precision mediump float;\n" +
+ "#endif\n" +
+ "\n" +
+ "varying vec4 v_color;\n" +
+ "varying vec2 v_texCoords;\n" +
+ "uniform sampler2D u_texture;\n" +
+ "uniform float u_grayness;\n" +
+ "uniform float u_bias;\n" +
+ "\n" +
+ "void main() {\n" +
+ " vec4 c = v_color * texture2D(u_texture, v_texCoords);\n" +
+ " float grey = dot( c.rgb, vec3(0.22, 0.707, 0.071) );\n" +
+ " vec3 blendedColor = mix(c.rgb, vec3(grey), u_grayness);\n" +
+ " gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), vec4(blendedColor.rgb, c.a), u_bias);\n" +
+ "}";
+ public static String grayscaleVert = "attribute vec4 a_position;\n" +
+ "attribute vec4 a_color;\n" +
+ "attribute vec2 a_texCoord0;\n" +
+ "\n" +
+ "uniform mat4 u_projTrans;\n" +
+ "\n" +
+ "varying vec4 v_color;\n" +
+ "varying vec2 v_texCoords;\n" +
+ "\n" +
+ "void main() {\n" +
+ " v_color = a_color;\n" +
+ " v_texCoords = a_texCoord0;\n" +
+ " gl_Position = u_projTrans * a_position;\n" +
+ "}";
}
diff --git a/forge-gui-mobile/src/forge/assets/AssetsDownloader.java b/forge-gui-mobile/src/forge/assets/AssetsDownloader.java
index 104840e8f4d..a7b00d4ac43 100644
--- a/forge-gui-mobile/src/forge/assets/AssetsDownloader.java
+++ b/forge-gui-mobile/src/forge/assets/AssetsDownloader.java
@@ -1,15 +1,18 @@
package forge.assets;
-import java.io.File;
import java.io.IOException;
import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
import java.util.List;
+import java.util.TimeZone;
import com.badlogic.gdx.files.FileHandle;
import forge.gui.GuiBase;
+import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
-import com.badlogic.gdx.Application.ApplicationType;
import com.badlogic.gdx.Gdx;
import com.google.common.collect.ImmutableList;
@@ -18,26 +21,39 @@
import forge.gui.download.GuiDownloadZipService;
import forge.gui.util.SOptionPane;
import forge.localinstance.properties.ForgeConstants;
-import forge.screens.SplashScreen;
import forge.util.FileUtil;
-public class AssetsDownloader {
- public static final boolean SHARE_DESKTOP_ASSETS = true; //change to false to test downloading separate assets for desktop version
+import static forge.localinstance.properties.ForgeConstants.GITHUB_COMMITS_URL_ATOM;
+import static forge.localinstance.properties.ForgeConstants.GITHUB_RELEASES_URL_ATOM;
+public class AssetsDownloader {
private final static ImmutableList downloadIgnoreExit = ImmutableList.of("Download", "Ignore", "Exit");
private final static ImmutableList downloadExit = ImmutableList.of("Download", "Exit");
//if not sharing desktop assets, check whether assets are up to date
- public static void checkForUpdates(final SplashScreen splashScreen) {
- if (Gdx.app.getType() == ApplicationType.Desktop && SHARE_DESKTOP_ASSETS) { return; }
-
+ public static void checkForUpdates(boolean exited, Runnable runnable) {
+ if (exited)
+ return;
final String versionString = Forge.getDeviceAdapter().getVersionString();
+ Forge.getSplashScreen().getProgressBar().setDescription("Checking for updates...");
+ if (versionString.contains("GIT")) {
+ if (!GuiBase.isAndroid()) {
+ run(runnable);
+ return;
+ }
+ }
+ //currently for desktop/mobile-dev release on github
+ final String releaseTag = Forge.getDeviceAdapter().getReleaseTag(GITHUB_RELEASES_URL_ATOM);
+ final String packageSize = GuiBase.isAndroid() ? "160MB" : "270MB";
+ final String apkSize = "12MB";
+
final boolean isSnapshots = versionString.contains("SNAPSHOT");
final String snapsURL = "https://downloads.cardforge.org/dailysnapshots/";
final String releaseURL = "https://releases.cardforge.org/forge/forge-gui-android/";
final String versionText = isSnapshots ? snapsURL + "version.txt" : releaseURL + "version.txt";
-
- splashScreen.getProgressBar().setDescription("Checking for updates...");
+ FileHandle assetsDir = Gdx.files.absolute(ForgeConstants.ASSETS_DIR);
+ FileHandle resDir = Gdx.files.absolute(ForgeConstants.RES_DIR);
+ boolean mandatory = false;
String message;
boolean connectedToInternet = Forge.getDeviceAdapter().isConnectedToInternet();
@@ -45,80 +61,171 @@ public static void checkForUpdates(final SplashScreen splashScreen) {
try {
URL versionUrl = new URL(versionText);
String version = FileUtil.readFileToString(versionUrl);
- String filename = "forge-android-" + version + "-signed-aligned.apk";
- String apkURL = isSnapshots ? snapsURL + filename : releaseURL + version + "/" + filename;
+ String filename = "";
+ String installerURL = "";
+ if (GuiBase.isAndroid()) {
+ filename = "forge-android-" + version + "-signed-aligned.apk";
+ installerURL = isSnapshots ? snapsURL + filename : releaseURL + version + "/" + filename;
+ } else {
+ //current release on github is tar.bz2, update this to jar installer in the future...
+ filename = isSnapshots ? "forge-installer-" + version + ".jar" : releaseTag.replace("forge-", "forge-gui-desktop-") + ".tar.bz2";
+ String releaseBZ2URL = "https://github.com/Card-Forge/forge/releases/download/" + releaseTag + "/" + filename;
+ String snapsBZ2URL = "https://downloads.cardforge.org/dailysnapshots/";
+ installerURL = isSnapshots ? snapsBZ2URL : releaseBZ2URL;
+ }
+ //TODO build version
+ /*String buildver = "";
+ SimpleDateFormat DateFor = TextUtil.getSimpleDate();
+ Calendar calendar = Calendar.getInstance();
+ Date buildDateOriginal = null;
+ try {
+ FileHandle build = Gdx.files.classpath("build.txt");
+ if (build.exists()) {
+ SimpleDateFormat original = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ original.setTimeZone(TimeZone.getTimeZone("UTC"));
+ Date buildDate = original.parse(build.readString());
+ buildDateOriginal = original.parse(build.readString());
+ calendar.setTime(buildDate);
+ DateFor.setTimeZone(TimeZone.getDefault());
+ buildver = "\nForge Build: " + DateFor.format(calendar.getTime());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }*/
+
if (!StringUtils.isEmpty(version) && !versionString.equals(version)) {
- splashScreen.prepareForDialogs();
+ Forge.getSplashScreen().prepareForDialogs();
message = "A new version of Forge is available (" + version + ").\n" +
"You are currently on an older version (" + versionString + ").\n\n" +
"Would you like to update to the new version now?";
if (!Forge.getDeviceAdapter().isConnectedToWifi()) {
- message += " If so, you may want to connect to wifi first. The download is around 12MB.";
+ message += " If so, you may want to connect to wifi first. The download is around " + (GuiBase.isAndroid() ? apkSize : packageSize) + ".";
+ }
+ if (!GuiBase.isAndroid()) {
+ message += Forge.getDeviceAdapter().getLatestChanges(GITHUB_COMMITS_URL_ATOM, null, null);
}
- if (SOptionPane.showConfirmDialog(message, "New Version Available", "Update Now", "Update Later", true, true)) {
- String apkFile = new GuiDownloadZipService("", "update", apkURL,
- Forge.getDeviceAdapter().getDownloadsDir(), null, splashScreen.getProgressBar()).download(filename);
- if (apkFile != null) {
- Forge.getDeviceAdapter().openFile(apkFile);
+ //failed to grab latest github tag
+ if (!isSnapshots && releaseTag.isEmpty()) {
+ if (!GuiBase.isAndroid())
+ run(runnable);
+ } else if (SOptionPane.showConfirmDialog(message, "New Version Available", "Update Now", "Update Later", true, true)) {
+ String installer = new GuiDownloadZipService("", "update", installerURL,
+ Forge.getDeviceAdapter().getDownloadsDir(), null, Forge.getSplashScreen().getProgressBar()).download(filename);
+ if (installer != null) {
+ Forge.getDeviceAdapter().openFile(installer);
Forge.isMobileAdventureMode = Forge.advStartup;
Forge.exitAnimation(false);
return;
}
- SOptionPane.showOptionDialog("Could not download update. " +
- "Press OK to proceed without update.", "Update Failed", null, ImmutableList.of("Ok"));
+ switch (SOptionPane.showOptionDialog("Could not download update. " +
+ "Press OK to proceed without update.", "Update Failed", null, ImmutableList.of("Ok"))) {
+ default:
+ if (!GuiBase.isAndroid()) {
+ run(runnable);
+ return;
+ }
+ break;
+ }
}
+ } else {
+ if (!GuiBase.isAndroid())
+ run(runnable);
}
- }
- catch (Exception e) {
+ } catch (Exception e) {
e.printStackTrace();
+ if (!GuiBase.isAndroid()) {
+ run(runnable);
+ return;
+ }
}
+ } else {
+ if (!GuiBase.isAndroid()) {
+ run(runnable);
+ return;
+ }
+ }
+ // non android don't have seperate package to check
+ if (!GuiBase.isAndroid()) {
+ run(runnable);
+ return;
}
+ // Android assets fallback
+ String build = "";
+ String log = "";
//see if assets need updating
- if (GuiBase.isAndroid()) {
- FileHandle resDir = Gdx.files.absolute(ForgeConstants.RES_DIR);
- FileHandle assetsDir = Gdx.files.absolute(ForgeConstants.ASSETS_DIR);
- FileHandle advBG = Gdx.files.absolute(ForgeConstants.DEFAULT_SKINS_DIR).child(ForgeConstants.ADV_TEXTURE_BG_FILE);
- if (!advBG.exists()) {
- FileHandle deleteVersion = assetsDir.child("version.txt");
- if (deleteVersion.exists())
- deleteVersion.delete();
- FileHandle deleteBuild = resDir.child("build.txt");
- if (deleteBuild.exists())
- deleteBuild.delete();
- }
+ FileHandle advBG = Gdx.files.absolute(ForgeConstants.DEFAULT_SKINS_DIR).child(ForgeConstants.ADV_TEXTURE_BG_FILE);
+ if (!advBG.exists()) {
+ FileHandle deleteVersion = assetsDir.child("version.txt");
+ if (deleteVersion.exists())
+ deleteVersion.delete();
+ FileHandle deleteBuild = resDir.child("build.txt");
+ if (deleteBuild.exists())
+ deleteBuild.delete();
}
- File versionFile = new File(ForgeConstants.ASSETS_DIR + "version.txt");
+
+ FileHandle versionFile = assetsDir.child("version.txt");
if (!versionFile.exists()) {
try {
- versionFile.createNewFile();
- }
- catch (IOException e) {
+ versionFile.file().createNewFile();
+ } catch (IOException e) {
e.printStackTrace();
+ Forge.isMobileAdventureMode = Forge.advStartup;
Forge.exitAnimation(false); //can't continue if this fails
return;
}
- }
- else if (versionString.equals(FileUtil.readFileToString(versionFile)) && FSkin.getSkinDir() != null) {
+ } else if (versionString.equals(FileUtil.readFileToString(versionFile.file())) && FSkin.getSkinDir() != null) {
+ run(runnable);
return; //if version matches what had been previously saved and FSkin isn't requesting assets download, no need to download assets
}
- splashScreen.prepareForDialogs(); //ensure colors set up for showing message dialogs
+ FileHandle f = Gdx.files.classpath("build.txt");
+ FileHandle t = resDir.child("build.txt");
+ if (f.exists() && t.exists()) {
+ String buildString = f.readString();
+ String target = t.readString();
+ try {
+ Date buildDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(buildString);
+ Date targetDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(target);
+ // if res folder has same build date then continue loading assets
+ if (buildDate.equals(targetDate) && versionString.equals(FileUtil.readFileToString(versionFile.file()))) {
+ run(runnable);
+ return;
+ }
+ mandatory = true;
+ //format to local date
+ SimpleDateFormat original = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ original.setTimeZone(TimeZone.getTimeZone("UTC"));
+ targetDate = original.parse(target);
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(targetDate);
+ SimpleDateFormat simpleDate = TextUtil.getSimpleDate();
+ simpleDate.setTimeZone(TimeZone.getDefault());
+ build += "Installed resources date: " + simpleDate.format(calendar.getTime()) + "\n\n";
+ log = Forge.getDeviceAdapter().getLatestChanges(GITHUB_COMMITS_URL_ATOM, null, null);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
- boolean canIgnoreDownload = FSkin.getAllSkins() != null; //don't allow ignoring download if resource files haven't been previously loaded
+ Forge.getSplashScreen().prepareForDialogs(); //ensure colors set up for showing message dialogs
+
+ boolean canIgnoreDownload = resDir.exists() && FSkin.getAllSkins() != null && !FileUtil.readFileToString(versionFile.file()).isEmpty(); //don't allow ignoring download if resource files haven't been previously loaded
+ if (mandatory && connectedToInternet)
+ canIgnoreDownload = false;
if (!connectedToInternet) {
message = "Updated resource files cannot be downloaded due to lack of internet connection.\n\n";
if (canIgnoreDownload) {
message += "You can continue without this download, but you may miss out on card fixes or experience other problems.";
- }
- else {
+ } else {
message += "You cannot start the app since you haven't previously downloaded these files.";
}
switch (SOptionPane.showOptionDialog(message, "No Internet Connection", null, ImmutableList.of("Ok"))) {
default: {
if (!canIgnoreDownload) {
+ Forge.isMobileAdventureMode = Forge.advStartup;
Forge.exitAnimation(false); //exit if can't ignore download
}
}
@@ -128,11 +235,10 @@ else if (versionString.equals(FileUtil.readFileToString(versionFile)) && FSkin.g
//prompt user whether they wish to download the updated resource files
message = "There are updated resource files to download. " +
- "This download is around 50MB, ";
+ "This download is around " + packageSize + ", ";
if (Forge.getDeviceAdapter().isConnectedToWifi()) {
message += "which shouldn't take long if your wifi connection is good.";
- }
- else {
+ } else {
message += "so it's highly recommended that you connect to wifi first.";
}
final List options;
@@ -145,13 +251,18 @@ else if (versionString.equals(FileUtil.readFileToString(versionFile)) && FSkin.g
options = downloadExit;
}
- switch (SOptionPane.showOptionDialog(message, "", null, options)) {
+ switch (SOptionPane.showOptionDialog(message + build + log, "", null, options)) {
case 1:
if (!canIgnoreDownload) {
+ Forge.isMobileAdventureMode = Forge.advStartup;
Forge.exitAnimation(false); //exit if can't ignore download
+ return;
+ } else {
+ run(runnable);
+ return;
}
- return;
case 2:
+ Forge.isMobileAdventureMode = Forge.advStartup;
Forge.exitAnimation(false);
return;
}
@@ -160,7 +271,7 @@ else if (versionString.equals(FileUtil.readFileToString(versionFile)) && FSkin.g
boolean allowDeletion = Forge.androidVersion < 30 || GuiBase.isUsingAppDirectory();
String assetURL = isSnapshots ? snapsURL + "assets.zip" : releaseURL + versionString + "/" + "assets.zip";
new GuiDownloadZipService("", "resource files", assetURL,
- ForgeConstants.ASSETS_DIR, ForgeConstants.RES_DIR, splashScreen.getProgressBar(), allowDeletion).downloadAndUnzip();
+ ForgeConstants.ASSETS_DIR, ForgeConstants.RES_DIR, Forge.getSplashScreen().getProgressBar(), allowDeletion).downloadAndUnzip();
if (allowDeletion)
FSkinFont.deleteCachedFiles(); //delete cached font files in case any skin's .ttf file changed
@@ -168,18 +279,38 @@ else if (versionString.equals(FileUtil.readFileToString(versionFile)) && FSkin.g
//reload light version of skin after assets updated
FThreads.invokeInEdtAndWait(() -> {
FSkinFont.updateAll(); //update all fonts used by splash screen
- FSkin.loadLight(FSkin.getName(), splashScreen);
+ FSkin.loadLight(FSkin.getName(), Forge.getSplashScreen());
});
//save version string to file once assets finish downloading
//so they don't need to be re-downloaded until you upgrade again
- FileUtil.writeFile(versionFile, versionString);
+ if (connectedToInternet) {
+ if (versionFile.exists())
+ FileUtil.writeFile(versionFile.file(), versionString);
+ }
+ //final check if temp.zip exists then extraction is not complete...
+ FileHandle check = assetsDir.child("temp.zip");
+ if (check.exists()) {
+ if (versionFile.exists())
+ versionFile.delete();
+ check.delete();
+ }
+ // auto restart after update
+ Forge.isMobileAdventureMode = Forge.advStartup;
+ Forge.exitAnimation(true);
+ }
- //add restart after assets update
- String msg = allowDeletion ? "Resource update finished..." : "Forge misses some files for deletion.\nIf you encounter issues, try deleting the Forge/res folder and/or deleting Forge/cache/fonts folder and try to download and update the assets.";
- switch (SOptionPane.showOptionDialog(msg, "", null, ImmutableList.of("Restart"))) {
- default:
- Forge.exitAnimation(true);
+ private static void run(Runnable toRun) {
+ if (toRun != null) {
+ if (!GuiBase.isAndroid()) {
+ Forge.getSplashScreen().getProgressBar().setDescription("Loading game resources...");
+ }
+ FThreads.invokeInBackgroundThread(toRun);
+ return;
+ }
+ if (!GuiBase.isAndroid()) {
+ Forge.isMobileAdventureMode = Forge.advStartup;
+ Forge.exitAnimation(false);
}
}
}
diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties
index 74042508522..7be69997848 100644
--- a/forge-gui/res/languages/de-DE.properties
+++ b/forge-gui/res/languages/de-DE.properties
@@ -3436,4 +3436,7 @@ lblHideCollection=Sammlung Ausblenden
lblShowCollection=Sammlung Anzeigen
lblCracked=Geknackt!
lblTake=Nehmen
-lblRefund=Erstattung
\ No newline at end of file
+lblRefund=Erstattung
+lblForgeUpdateMessage=Das Update wurde hier heruntergeladen: {0}.\nForge wird nun beendet und führt den Updater aus.
+lblRelease=Freigeben
+lblSnapshot=Schnappschuss
\ No newline at end of file
diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties
index 5f2ead89cf4..8fff4005151 100644
--- a/forge-gui/res/languages/en-US.properties
+++ b/forge-gui/res/languages/en-US.properties
@@ -3169,4 +3169,7 @@ lblHideCollection=Hide Collection
lblShowCollection=Show Collection
lblCracked=Cracked!
lblTake=Take
-lblRefund=Refund
\ No newline at end of file
+lblRefund=Refund
+lblForgeUpdateMessage=Update has been downloaded here: {0}.\nForge will now exit and run the updater.
+lblRelease=Release
+lblSnapshot=Snapshot
\ No newline at end of file
diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties
index 258f1a5f6b8..102bbc1daaa 100644
--- a/forge-gui/res/languages/es-ES.properties
+++ b/forge-gui/res/languages/es-ES.properties
@@ -3450,4 +3450,7 @@ lblHideCollection=Ocultar Colección
lblShowCollection=Mostrar Colección
lblCracked=¡Agrietado!
lblTake=Llevar
-lblRefund=Reembolso
\ No newline at end of file
+lblRefund=Reembolso
+lblForgeUpdateMessage=La actualización se descargó aquí: {0}.\nForge ahora saldrá y ejecutará el actualizador.
+lblRelease=Liberar
+lblSnapshot=Instantánea
\ No newline at end of file
diff --git a/forge-gui/res/languages/fr-FR.properties b/forge-gui/res/languages/fr-FR.properties
index 76669bb1446..67d2ecd1d7a 100644
--- a/forge-gui/res/languages/fr-FR.properties
+++ b/forge-gui/res/languages/fr-FR.properties
@@ -3444,4 +3444,7 @@ lblHideCollection=Masquer la Collection
lblShowCollection=Afficher la Collection
lblCracked=Fissuré!
lblTake=Prendre
-lblRefund=Remboursement
\ No newline at end of file
+lblRefund=Remboursement
+lblForgeUpdateMessage=La mise à jour a été téléchargée ici: {0}.\nForge va maintenant quitter et exécuter le programme de mise à jour.
+lblRelease=Libérer
+lblSnapshot=Instantané
\ No newline at end of file
diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties
index 9d08f89df5b..73947da262b 100644
--- a/forge-gui/res/languages/it-IT.properties
+++ b/forge-gui/res/languages/it-IT.properties
@@ -3442,4 +3442,7 @@ lblHideCollection=Nascondi Collezione
lblShowCollection=Mostra Collezione
lblCracked=Incrinato!
lblTake=Prendere
-lblRefund=Rimborso
\ No newline at end of file
+lblRefund=Rimborso
+lblForgeUpdateMessage=L'aggiornamento è stato scaricato qui: {0}.\nForge ora uscirà ed eseguirà l'aggiornamento.
+lblRelease=Pubblicazione
+lblSnapshot=Istantanea
\ No newline at end of file
diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties
index 11fb5ebdee6..bf0e2bb66c6 100644
--- a/forge-gui/res/languages/ja-JP.properties
+++ b/forge-gui/res/languages/ja-JP.properties
@@ -3438,4 +3438,7 @@ lblHideCollection=コレクションを非表示にする
lblShowCollection=ショーコレクション
lblCracked=ひび割れた!
lblTake=取る
-lblRefund=返金
\ No newline at end of file
+lblRefund=返金
+lblForgeUpdateMessage=アップデートはここにダウンロードされました: {0}。\nForge が終了し、アップデーターが実行されます。
+lblRelease=リリース
+lblSnapshot=スナップショット
\ No newline at end of file
diff --git a/forge-gui/res/languages/pt-BR.properties b/forge-gui/res/languages/pt-BR.properties
index 6e28dd5b91f..a6413fe876c 100644
--- a/forge-gui/res/languages/pt-BR.properties
+++ b/forge-gui/res/languages/pt-BR.properties
@@ -3528,4 +3528,7 @@ lblHideCollection=Ocultar Coleção
lblShowCollection=Mostrar Coleção
lblCracked=Rachado!
lblTake=Pegar
-lblRefund=Reembolso
\ No newline at end of file
+lblRefund=Reembolso
+lblForgeUpdateMessage=A atualização foi baixada aqui: {0}.\nO Forge agora sairá e executará o atualizador.
+lblRelease=Liberar
+lblSnapshot=Instantâneo
\ No newline at end of file
diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties
index 9b65d372bce..5a5fb9d4c95 100644
--- a/forge-gui/res/languages/zh-CN.properties
+++ b/forge-gui/res/languages/zh-CN.properties
@@ -3429,4 +3429,7 @@ lblHideCollection=隐藏收藏
lblShowCollection=展会系列
lblCracked=破裂了!
lblTake=拿
-lblRefund=退款
\ No newline at end of file
+lblRefund=退款
+lblForgeUpdateMessage=更新已在此处下载:{0}。\nForge 现在将退出并运行更新程序。
+lblRelease=发布
+lblSnapshot=快照
\ No newline at end of file
diff --git a/forge-gui/src/main/java/forge/download/AutoUpdater.java b/forge-gui/src/main/java/forge/download/AutoUpdater.java
index 73907ab5379..008e8563166 100644
--- a/forge-gui/src/main/java/forge/download/AutoUpdater.java
+++ b/forge-gui/src/main/java/forge/download/AutoUpdater.java
@@ -10,6 +10,8 @@
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -57,15 +59,15 @@ public boolean updateAvailable() {
return verifyUpdateable();
}
- public boolean attemptToUpdate() {
+ public boolean attemptToUpdate(CompletableFuture cf) {
if (!verifyUpdateable()) {
return false;
}
try {
- if (downloadUpdate()) {
+ if (downloadUpdate(cf)) {
extractAndRestart();
}
- } catch(IOException | URISyntaxException e) {
+ } catch(IOException | URISyntaxException | ExecutionException | InterruptedException e) {
return false;
}
return true;
@@ -86,7 +88,7 @@ private boolean verifyUpdateable() {
return false;
} else if (updateChannel.equals("none")) {
String message = localizer.getMessage("lblYouHaventSetUpdateChannel");
- List options = ImmutableList.of("Cancel", "release", "snapshot");
+ List options = ImmutableList.of(localizer.getMessageorUseDefault("lblCancel", "Cancel"), localizer.getMessageorUseDefault("lblRelease", "Release"), localizer.getMessageorUseDefault("lblSnapshot", "Snapshot"));
int option = SOptionPane.showOptionDialog(message, localizer.getMessage("lblManualCheck"), null, options, 0);
if (option < 1) {
return false;
@@ -95,14 +97,14 @@ private boolean verifyUpdateable() {
}
if (buildVersion.contains("SNAPSHOT")) {
- if (!updateChannel.equals("snapshot")) {
+ if (!updateChannel.equalsIgnoreCase(localizer.getMessageorUseDefault("lblSnapshot", "Snapshot"))) {
System.out.println("Snapshot build versions must use snapshot update channel to work");
return false;
}
versionUrlString = SNAPSHOT_VERSION_INDEX + "version.txt";
} else {
- if (!updateChannel.equals("release")) {
+ if (!updateChannel.equalsIgnoreCase(localizer.getMessageorUseDefault("lblRelease", "Release"))) {
System.out.println("Release build versions must use release update channel to work");
return false;
}
@@ -149,16 +151,16 @@ private boolean compareBuildWithLatestChannelVersion() {
}
private void retrieveVersion() throws MalformedURLException {
- if (VERSION_FROM_METADATA && updateChannel.equals("release")) {
+ if (VERSION_FROM_METADATA && updateChannel.equalsIgnoreCase(localizer.getMessageorUseDefault("lblRelease", "Release"))) {
extractVersionFromMavenRelease();
} else {
URL versionUrl = new URL(versionUrlString);
version = FileUtil.readFileToString(versionUrl);
}
- if (updateChannel.equals("release")) {
+ if (updateChannel.equalsIgnoreCase(localizer.getMessageorUseDefault("lblRelease", "Release"))) {
packageUrl = RELEASE_VERSION_INDEX + "forge/forge-gui-desktop/" + version + "/forge-gui-desktop-" + version + ".tar.bz2";
} else {
- packageUrl = SNAPSHOT_VERSION_INDEX + "forge-gui-desktop-" + version + ".tar.bz2";
+ packageUrl = SNAPSHOT_VERSION_INDEX + "forge-installer-" + version + ".jar";
}
}
@@ -174,15 +176,15 @@ private void extractVersionFromMavenRelease() throws MalformedURLException {
}
}
- private boolean downloadUpdate() throws URISyntaxException, IOException {
+ private boolean downloadUpdate(CompletableFuture cf) throws URISyntaxException, IOException, ExecutionException, InterruptedException {
// TODO Change the "auto" to be more auto.
if (isLoading) {
// We need to preload enough of a Skins to show a dialog and a button if we're in loading
// splashScreen.prepareForDialogs();
return downloadFromBrowser();
}
-
- String message = localizer.getMessage("lblNewVersionForgeAvailableUpdateConfirm", version, buildVersion);
+ String log = cf.get();
+ String message = localizer.getMessage("lblNewVersionForgeAvailableUpdateConfirm", version, buildVersion) + log;
final List options = ImmutableList.of(localizer.getMessage("lblUpdateNow"), localizer.getMessage("lblUpdateLater"));
if (SOptionPane.showOptionDialog(message, localizer.getMessage("lblNewVersionAvailable"), null, options, 0) == 0) {
return downloadFromForge();
@@ -204,16 +206,16 @@ private boolean downloadFromBrowser() throws URISyntaxException, IOException {
}
private boolean downloadFromForge() {
- System.out.println("Downloading update from " + packageUrl + " to tmp/");
+ System.out.println("Downloading update from " + packageUrl + " to Downloads folder");
WaitCallback callback = new WaitCallback() {
@Override
public void run() {
- GuiBase.getInterface().download(new GuiDownloadZipService("Auto Updater", localizer.getMessage("lblNewVersionDownloading"), packageUrl, "tmp/", null, null) {
+ GuiBase.getInterface().download(new GuiDownloadZipService("Auto Updater", localizer.getMessage("lblNewVersionDownloading"), packageUrl, System.getProperty("user.home") + "/Downloads/", null, null) {
@Override
public void downloadAndUnzip() {
packagePath = download(version + "-upgrade.tar.bz2");
if (packagePath != null) {
- extractAndRestart();
+ restartAndUpdate(packagePath);
}
}
}, this);
@@ -224,7 +226,29 @@ public void downloadAndUnzip() {
return false;
}
-
+ private void restartAndUpdate(String packagePath) {
+ if (SOptionPane.showOptionDialog(localizer.getMessage("lblForgeUpdateMessage", packagePath), localizer.getMessage("lblRestart"), null, ImmutableList.of(localizer.getMessage("lblOK")), 0) == 0) {
+ final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
+ if (desktop != null) {
+ try {
+ File installer = new File(packagePath);
+ if (installer.exists()) {
+ if (packagePath.endsWith(".jar")) {
+ installer.setExecutable(true, false);
+ desktop.open(installer);
+ } else {
+ desktop.open(installer.getParentFile());
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ } else {
+ System.out.println(packagePath);
+ }
+ System.exit(0);
+ }
+ }
private void extractUpdate() {
// TODO Something like https://stackoverflow.com/questions/315618/how-do-i-extract-a-tar-file-in-java
final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
diff --git a/forge-gui/src/main/java/forge/interfaces/IDeviceAdapter.java b/forge-gui/src/main/java/forge/interfaces/IDeviceAdapter.java
index bfa1da0b429..835317e941f 100644
--- a/forge-gui/src/main/java/forge/interfaces/IDeviceAdapter.java
+++ b/forge-gui/src/main/java/forge/interfaces/IDeviceAdapter.java
@@ -6,6 +6,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.Date;
public interface IDeviceAdapter {
boolean isConnectedToInternet();
@@ -13,6 +14,8 @@ public interface IDeviceAdapter {
boolean isTablet();
String getDownloadsDir();
String getVersionString();
+ String getLatestChanges(String commitsAtom, Date buildDateOriginal, Date maxDate);
+ String getReleaseTag(String releaseAtom);
boolean openFile(String filename);
void setLandscapeMode(boolean landscapeMode);
void preventSystemSleep(boolean preventSleep);
diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java
index 9016e25f5f4..77fe392459d 100644
--- a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java
+++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java
@@ -25,6 +25,8 @@
import java.util.Map;
public final class ForgeConstants {
+ public static final String GITHUB_RELEASES_URL_ATOM = "https://github.com/Card-Forge/forge/releases.atom";
+ public static final String GITHUB_COMMITS_URL_ATOM = "https://github.com/Card-Forge/forge/commits/master.atom";
public static final String PATH_SEPARATOR = File.separator;
public static final String ASSETS_DIR = GuiBase.getInterface().getAssetsDir();
public static final String PROFILE_FILE = ASSETS_DIR + "forge.profile.properties";