n) continue;
+ i = html.indexOf('>', i + HREF_TAG.length());
+ if (i == -1 || i >= n) continue;
+ j = html.indexOf('<', i + 1);
+ if (j == -1 || j > n) continue;
+ String beatmap = html.substring(i + 1, j).trim();
+
+ // find date
+ i = html.indexOf(UPDATED, j);
+ if (i == -1 || i >= n) continue;
+ j = html.indexOf('<', i + UPDATED.length());
+ if (j == -1 || j > n) continue;
+ String date = html.substring(i + UPDATED.length(), j).trim();
+
+ // parse id, title, and artist
+ Matcher m = BEATMAP_PATTERN.matcher(beatmap);
+ if (!m.matches())
+ continue;
+
+ nodeList.add(new DownloadNode(Integer.parseInt(m.group(1)), date, m.group(3), null, m.group(2), null, ""));
+ }
+
+ nodes = nodeList.toArray(new DownloadNode[nodeList.size()]);
+
+ // store total result count
+ this.totalResults = nodes.length;
+ } catch (MalformedURLException | UnsupportedEncodingException e) {
+ ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
+ }
+ return nodes;
+ }
+
+ @Override
+ public int minQueryLength() { return 0; }
+
+ @Override
+ public int totalResults() { return totalResults; }
+}
diff --git a/src/itdelatrisu/opsu/downloads/servers/OsuMirrorServer.java b/src/itdelatrisu/opsu/downloads/servers/OsuMirrorServer.java
index d7179664..834f2b9f 100644
--- a/src/itdelatrisu/opsu/downloads/servers/OsuMirrorServer.java
+++ b/src/itdelatrisu/opsu/downloads/servers/OsuMirrorServer.java
@@ -39,6 +39,8 @@
/**
* Download server: http://loli.al/
+ *
+ * This server went offline in August 2015.
*/
public class OsuMirrorServer extends DownloadServer {
/** Server name. */
diff --git a/src/itdelatrisu/opsu/downloads/servers/YaSOnlineServer.java b/src/itdelatrisu/opsu/downloads/servers/YaSOnlineServer.java
new file mode 100644
index 00000000..95846bfd
--- /dev/null
+++ b/src/itdelatrisu/opsu/downloads/servers/YaSOnlineServer.java
@@ -0,0 +1,204 @@
+/*
+ * opsu! - an open-source osu! client
+ * Copyright (C) 2014, 2015 Jeffrey Han
+ *
+ * opsu! is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * opsu! is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with opsu!. If not, see .
+ */
+package itdelatrisu.opsu.downloads.servers;
+
+import itdelatrisu.opsu.ErrorHandler;
+import itdelatrisu.opsu.Utils;
+import itdelatrisu.opsu.downloads.DownloadNode;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+import org.json.JSONObject;
+
+/**
+ * Download server: http://osu.yas-online.net/
+ */
+public class YaSOnlineServer extends DownloadServer {
+ /** Server name. */
+ private static final String SERVER_NAME = "YaS Online";
+
+ /** Formatted download URL (returns JSON): {@code beatmapSetID} */
+ private static final String DOWNLOAD_URL = "https://osu.yas-online.net/json.mapdata.php?mapId=%d";
+
+ /**
+ * Formatted download fetch URL: {@code downloadLink}
+ * (e.g. {@code /fetch/49125122158ef360a66a07bce2d0483596913843-m-10418.osz})
+ */
+ private static final String DOWNLOAD_FETCH_URL = "https://osu.yas-online.net%s";
+
+ /** Maximum beatmaps displayed per page. */
+ private static final int PAGE_LIMIT = 25;
+
+ /** Formatted home URL: {@code page} */
+ private static final String HOME_URL = "https://osu.yas-online.net/json.maplist.php?o=%d";
+
+ /** Formatted search URL: {@code query} */
+ private static final String SEARCH_URL = "https://osu.yas-online.net/json.search.php?searchQuery=%s";
+
+ /** Total result count from the last query. */
+ private int totalResults = -1;
+
+ /** Max server download ID seen (for approximating total pages). */
+ private int maxServerID = 0;
+
+ /** Constructor. */
+ public YaSOnlineServer() {}
+
+ @Override
+ public String getName() { return SERVER_NAME; }
+
+ @Override
+ public String getDownloadURL(int beatmapSetID) {
+ try {
+ // TODO: do this asynchronously (will require lots of changes...)
+ return getDownloadURLFromMapData(beatmapSetID);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the beatmap download URL by downloading its map data.
+ *
+ * This is needed because there is no other way to find a beatmap's direct
+ * download URL.
+ * @param beatmapSetID the beatmap set ID
+ * @return the URL string, or null if the address could not be determined
+ * @throws IOException if any connection error occurred
+ */
+ private String getDownloadURLFromMapData(int beatmapSetID) throws IOException {
+ try {
+ // read JSON
+ String search = String.format(DOWNLOAD_URL, beatmapSetID);
+ JSONObject json = Utils.readJsonObjectFromUrl(new URL(search));
+ JSONObject results;
+ if (json == null ||
+ !json.getString("result").equals("success") ||
+ (results = json.getJSONObject("success")).length() == 0) {
+ return null;
+ }
+
+ // parse result
+ Iterator> keys = results.keys();
+ if (!keys.hasNext())
+ return null;
+ String key = (String) keys.next();
+ JSONObject item = results.getJSONObject(key);
+ String downloadLink = item.getString("downloadLink");
+ return String.format(DOWNLOAD_FETCH_URL, downloadLink);
+ } catch (MalformedURLException | UnsupportedEncodingException e) {
+ ErrorHandler.error(String.format("Problem retrieving download URL for beatmap '%d'.", beatmapSetID), e, true);
+ return null;
+ }
+ }
+
+ @Override
+ public DownloadNode[] resultList(String query, int page, boolean rankedOnly) throws IOException {
+ DownloadNode[] nodes = null;
+ try {
+ // read JSON
+ String search;
+ boolean isSearch;
+ if (query.isEmpty()) {
+ isSearch = false;
+ search = String.format(HOME_URL, (page - 1) * PAGE_LIMIT);
+ } else {
+ isSearch = true;
+ search = String.format(SEARCH_URL, URLEncoder.encode(query, "UTF-8"));
+ }
+ JSONObject json = Utils.readJsonObjectFromUrl(new URL(search));
+ if (json == null) {
+ this.totalResults = -1;
+ return null;
+ }
+ JSONObject results;
+ if (!json.getString("result").equals("success") ||
+ (results = json.getJSONObject("success")).length() == 0) {
+ this.totalResults = 0;
+ return new DownloadNode[0];
+ }
+
+ // parse result list
+ List nodeList = new ArrayList();
+ for (Object obj : results.keySet()) {
+ String key = (String) obj;
+ JSONObject item = results.getJSONObject(key);
+
+ // parse title and artist
+ String title, artist;
+ String str = item.getString("map");
+ int index = str.indexOf(" - ");
+ if (index > -1) {
+ title = str.substring(0, index);
+ artist = str.substring(index + 3);
+ } else { // should never happen...
+ title = str;
+ artist = "?";
+ }
+
+ // only contains date added if part of a beatmap pack
+ int added = item.getInt("added");
+ String date = (added == 0) ? "?" : formatDate(added);
+
+ // approximate page count
+ int serverID = item.getInt("id");
+ if (serverID > maxServerID)
+ maxServerID = serverID;
+
+ nodeList.add(new DownloadNode(item.getInt("mapid"), date, title, null, artist, null, ""));
+ }
+ nodes = nodeList.toArray(new DownloadNode[nodeList.size()]);
+
+ // store total result count
+ if (isSearch)
+ this.totalResults = nodes.length;
+ else
+ this.totalResults = maxServerID;
+ } catch (MalformedURLException | UnsupportedEncodingException e) {
+ ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true);
+ }
+ return nodes;
+ }
+
+ @Override
+ public int minQueryLength() { return 3; }
+
+ @Override
+ public int totalResults() { return totalResults; }
+
+ /**
+ * Returns a formatted date string from a raw date.
+ * @param timestamp the UTC timestamp, in seconds
+ * @return the formatted date
+ */
+ private String formatDate(int timestamp) {
+ Date d = new Date(timestamp * 1000L);
+ DateFormat fmt = new SimpleDateFormat("d MMM yyyy HH:mm:ss");
+ return fmt.format(d);
+ }
+}
diff --git a/src/itdelatrisu/opsu/objects/Circle.java b/src/itdelatrisu/opsu/objects/Circle.java
index f2b56cbb..b996d343 100644
--- a/src/itdelatrisu/opsu/objects/Circle.java
+++ b/src/itdelatrisu/opsu/objects/Circle.java
@@ -101,10 +101,16 @@ public void draw(Graphics g, int trackPosition) {
float approachScale = 1 + scale * 3;
float alpha = Utils.clamp(1 - fadeinScale, 0, 1);
+ if (GameMod.HIDDEN.isActive()) {
+ float fadeOutScale = -(float)(timeDiff-game.getApproachTime())/game.getDecayTime();
+ float fadeOutAlpha = Utils.clamp(1-fadeOutScale, 0, 1);
+ alpha = Math.min(alpha, fadeOutAlpha);
+ }
+
float oldAlpha = Utils.COLOR_WHITE_FADE.a;
Utils.COLOR_WHITE_FADE.a = color.a = alpha;
- if (timeDiff >= 0)
+ if (timeDiff >= 0 && !GameMod.HIDDEN.isActive())
GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale).drawCentered(x, y, color);
GameImage.HITCIRCLE.getImage().drawCentered(x, y, color);
boolean overlayAboveNumber = Options.getSkin().isHitCircleOverlayAboveNumber();
diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java
index 3ca27899..25490ccd 100644
--- a/src/itdelatrisu/opsu/objects/Slider.java
+++ b/src/itdelatrisu/opsu/objects/Slider.java
@@ -214,6 +214,11 @@ public void draw(Graphics g, int trackPosition) {
tick.drawCentered(c[0], c[1], Utils.COLOR_WHITE_FADE);
}
}
+ if (GameMod.HIDDEN.isActive()) {
+ float fadeOutScale = -(float) (timeDiff - game.getApproachTime()) / game.getDecayTime();
+ float fadeOutAlpha = Utils.clamp(1 - fadeOutScale, 0, 1);
+ alpha = Math.min(alpha, fadeOutAlpha);
+ }
if (sliderClickedInitial)
; // don't draw current combo number if already clicked
else
@@ -247,7 +252,8 @@ public void draw(Graphics g, int trackPosition) {
if (timeDiff >= 0) {
// approach circle
- GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale).drawCentered(x, y, color);
+ if (!GameMod.HIDDEN.isActive())
+ GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale).drawCentered(x, y, color);
} else {
// Since update() might not have run before drawing during a replay, the
// slider time may not have been calculated, which causes NAN numbers and flicker.
diff --git a/src/itdelatrisu/opsu/objects/Spinner.java b/src/itdelatrisu/opsu/objects/Spinner.java
index 1a64856b..91dc7233 100644
--- a/src/itdelatrisu/opsu/objects/Spinner.java
+++ b/src/itdelatrisu/opsu/objects/Spinner.java
@@ -166,7 +166,7 @@ public Spinner(HitObject hitObject, Game game, GameData data) {
final int maxVel = 48;
final int minTime = 2000;
final int maxTime = 5000;
- maxStoredDeltaAngles = (int) Utils.clamp((hitObject.getEndTime() - hitObject.getTime() - minTime)
+ maxStoredDeltaAngles = Utils.clamp((hitObject.getEndTime() - hitObject.getTime() - minTime)
* (maxVel - minVel) / (maxTime - minTime) + minVel, minVel, maxVel);
storedDeltaAngle = new float[maxStoredDeltaAngles];
@@ -214,13 +214,15 @@ public void draw(Graphics g, int trackPosition) {
spinnerMetreSub.draw(0, height - spinnerMetreSub.getHeight());
// main spinner elements
- float approachScale = 1 - Utils.clamp(((float) timeDiff / (hitObject.getTime() - hitObject.getEndTime())), 0f, 1f);
GameImage.SPINNER_CIRCLE.getImage().setAlpha(alpha);
GameImage.SPINNER_CIRCLE.getImage().setRotation(drawRotation * 360f);
GameImage.SPINNER_CIRCLE.getImage().drawCentered(width / 2, height / 2);
- Image approachCircleScaled = GameImage.SPINNER_APPROACHCIRCLE.getImage().getScaledCopy(approachScale);
- approachCircleScaled.setAlpha(alpha);
- approachCircleScaled.drawCentered(width / 2, height / 2);
+ if (!GameMod.HIDDEN.isActive()) {
+ float approachScale = 1 - Utils.clamp(((float) timeDiff / (hitObject.getTime() - hitObject.getEndTime())), 0f, 1f);
+ Image approachCircleScaled = GameImage.SPINNER_APPROACHCIRCLE.getImage().getScaledCopy(approachScale);
+ approachCircleScaled.setAlpha(alpha);
+ approachCircleScaled.drawCentered(width / 2, height / 2);
+ }
GameImage.SPINNER_SPIN.getImage().setAlpha(alpha);
GameImage.SPINNER_SPIN.getImage().drawCentered(width / 2, height * 3 / 4);
diff --git a/src/itdelatrisu/opsu/objects/curves/Curve.java b/src/itdelatrisu/opsu/objects/curves/Curve.java
index 8db2a0a0..aa0183d6 100644
--- a/src/itdelatrisu/opsu/objects/curves/Curve.java
+++ b/src/itdelatrisu/opsu/objects/curves/Curve.java
@@ -92,12 +92,13 @@ public static void init(int width, int height, float circleSize, Color borderCol
//ContextCapabilities capabilities = GLContext.getCapabilities();
- mmsliderSupported = true;//capabilities.GL_EXT_framebuffer_object && capabilities.OpenGL32;
+ //mmsliderSupported = capabilities.GL_EXT_framebuffer_object && capabilities.OpenGL30;
+ mmsliderSupported = true;
if (mmsliderSupported)
CurveRenderState.init(width, height, circleSize);
else {
if (Options.getSkin().getSliderStyle() != Skin.STYLE_PEPPYSLIDER)
- Log.warn("New slider style requires FBO support and OpenGL 3.2.");
+ Log.warn("New slider style requires FBO support and OpenGL 3.0.");
}
}
diff --git a/src/itdelatrisu/opsu/render/CurveRenderState.java b/src/itdelatrisu/opsu/render/CurveRenderState.java
index 7440c0cb..9397236f 100644
--- a/src/itdelatrisu/opsu/render/CurveRenderState.java
+++ b/src/itdelatrisu/opsu/render/CurveRenderState.java
@@ -125,10 +125,19 @@ public void draw(Color color, Color borderColor, Vec2f[] curve) {
fbo = mapping;
int old_fb = GL11.glGetInteger(GL30.GL_FRAMEBUFFER_BINDING);
- int old_tex = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
+ /*
+ int oldFb = GL11.glGetInteger(EXTFramebufferObject.GL_FRAMEBUFFER_BINDING_EXT);
+ */
+ int oldTex = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
- //GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, fbo.getID());
- //GL11.glViewport(0, 0, fbo.width, fbo.height);
+ //glGetInteger requires a buffer of size 16, even though just 4
+ //values are returned in this specific case
+ /*
+ IntBuffer oldViewport = BufferUtils.createIntBuffer(16);
+ GL11.glGetInteger(GL11.GL_VIEWPORT, oldViewport);
+ EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, fbo.getID());
+ GL11.glViewport(0, 0, fbo.width, fbo.height);
+ */
fbo.bind();
GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
@@ -138,9 +147,13 @@ public void draw(Color color, Color borderColor, Vec2f[] curve) {
fbo.unbind();
- GL11.glBindTexture(GL11.GL_TEXTURE_2D, old_tex);
+
+ GL11.glBindTexture(GL11.GL_TEXTURE_2D, oldTex);
+ /*
+ EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, oldFb);
+ GL11.glViewport(oldViewport.get(0), oldViewport.get(1), oldViewport.get(2), oldViewport.get(3));
+ */
GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, old_fb);
- //GL11.glViewport(0, 0, containerWidth, containerHeight);
Utils.COLOR_WHITE_FADE.a = alpha;
}
@@ -164,41 +177,6 @@ public void draw(Color color, Color borderColor, Vec2f[] curve) {
Graphics.getGraphics().setColor(new Color(1f, 1f, 1f, alpha));
fbo.draw(0, 0, containerWidth, containerHeight);
-
-
- //fbo.bindTexture();
- //fbo.fbo.getColorBufferTexture().
- //GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
- //GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo.getTextureID());
- //GL11.glBindTexture(GL11.GL_TEXTURE_2D, staticState.gradientTexture);
-
- /*GL11.glBegin();
- //setup buffers
- int vtx_buf = GL15.glGenBuffers();
- FloatBuffer buff = BufferUtils.createByteBuffer(9999).asFloatBuffer();
-
- float tas = 1;//0.7f;
- buff.put(new float[]{
- 0,0,-tas,-tas,
- 0,1,-tas,tas,
- 1,1,tas,tas,
- 1,0,tas,-tas
- });
- buff.flip();
- GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vtx_buf);
- GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buff, GL15.GL_STATIC_DRAW);
-
- //use shader
- //draw buffers
- GL20.glVertexAttribPointer(GL11.vertLoc, 2, GL11.GL_FLOAT, false, 4*4, 4*2);
- GL20.glVertexAttribPointer(GL11.texCoordLoc, 2, GL11.GL_FLOAT, false, 4*4, 0);
- GL20.glUniform4f(GL11.colBorderLoc, 1, 1, 1,alpha);
-
- GL11.glDrawArrays(GL11.GL_TRIANGLE_FAN, 0, 4);
- GL15.glDeleteBuffers(vtx_buf);
-
- GL11.glEnd();
- //*/
}
/**
@@ -447,7 +425,7 @@ public void initGradient() {
Image slider = GameImage.SLIDER_GRADIENT.getImage().getScaledCopy(1.0f / GameImage.getUIscale());
/*
- //staticState.gradientTexture = GL11.glGenTextures();
+ staticState.gradientTexture = GL11.glGenTextures();
ByteBuffer buff = BufferUtils.createByteBuffer(slider.getWidth() * 4);
for (int i = 0; i < slider.getWidth(); ++i) {
Color col = slider.getColor(i, 0);
@@ -579,6 +557,7 @@ public void initShaderProgram() {
}
GL20.glAttachShader(program, vtxShdr);
GL20.glAttachShader(program, frgShdr);
+ GL30.glBindFragDataLocation(program, 0, "out_colour");
GL20.glLinkProgram(program);
res = GL20.glGetProgrami(program, GL20.GL_LINK_STATUS);
if (res != GL11.GL_TRUE) {
diff --git a/src/itdelatrisu/opsu/render/Rendertarget.java b/src/itdelatrisu/opsu/render/Rendertarget.java
index 6dacaff8..dc18804d 100644
--- a/src/itdelatrisu/opsu/render/Rendertarget.java
+++ b/src/itdelatrisu/opsu/render/Rendertarget.java
@@ -24,9 +24,6 @@
/*
import org.lwjgl.opengl.GL11;
-import org.lwjgl.opengl.GL20;
-import org.lwjgl.opengl.GL30;
-import org.lwjgl.opengl.GL32;
*/
/**
@@ -59,9 +56,9 @@ private Rendertarget(int width, int height) {
this.width = width;
this.height = height;
/*
- fboID = GL30.glGenFramebuffers();
+ fboID = EXTFramebufferObject.glGenFramebuffersEXT();
textureID = GL11.glGenTextures();
- depthBufferID = GL30.glGenRenderbuffers();
+ depthBufferID = EXTFramebufferObject.glGenRenderbuffersEXT();
*/
fbo = new FrameBuffer(Format.RGBA8888, width, height, true);
}
@@ -71,7 +68,7 @@ private Rendertarget(int width, int height) {
*/
public void bind() {
/*
- GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, fboID);
+ EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, fboID);
Gdx.gl.glFramebufferRenderbuffer(Gdx.gl.GL_FRAMEBUFFER, Gdx.gl.GL_COLOR_ATTACHMENT0, Gdx.gl.GL_RENDERBUFFER, textureID);
Gdx.gl.glFramebufferTexture2D(Gdx.gl.GL_FRAMEBUFFER, Gdx.gl.GL_COLOR_ATTACHMENT0, Gdx.gl.GL_TEXTURE_2D, textureID, 0);
Gdx.gl.glFramebufferRenderbuffer(Gdx.gl.GL_FRAMEBUFFER, Gdx.gl.GL_DEPTH_ATTACHMENT, Gdx.gl.GL_RENDERBUFFER, depthBufferID);
@@ -111,7 +108,9 @@ public int getTextureID() {
* Bind the default framebuffer.
*/
public static void unbind() {
- //GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0);
+ /*
+ EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0);
+ */
if(currentFBO != null)
currentFBO.end();
}
@@ -123,78 +122,28 @@ public static void unbind() {
* @param height the height
*/
public static Rendertarget createRTTFramebuffer(int width, int height) {
- //int old_framebuffer = GL11.glGetInteger(GL30.GL_READ_FRAMEBUFFER_BINDING);
- //int old_texture = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
+ /*
+ int old_framebuffer = GL11.glGetInteger(EXTFramebufferObject.GL_FRAMEBUFFER_BINDING_EXT);
+ int old_texture = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
+ */
Rendertarget buffer = new Rendertarget(width,height);
- //buffer.bind();
-
/*
+ buffer.bind();
+
int fboTexture = buffer.textureID;
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fboTexture);
- GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, null);
+ GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, 4, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_INT, (ByteBuffer) null);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
- GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
- GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
-
- GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, buffer.depthBufferID);
- GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL11.GL_DEPTH_COMPONENT16, width, height);
- GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER, buffer.depthBufferID);
-
- GL32.glFramebufferTexture(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, fboTexture, 0);
- //GL20.glDrawBuffers(GL30.GL_COLOR_ATTACHMENT0);
- GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_RENDERBUFFER, fboTexture);
-
- */
- /*
- int texH = height;
- int texW = width;
- int tb = buffer.textureID;
- int rb = buffer.depthBufferID;
- int fb = buffer.fboID;
- Gdx.gl.glBindTexture(Gdx.gl.GL_TEXTURE_2D, tb);
- Gdx.gl.glTexParameteri(Gdx.gl.GL_TEXTURE_2D, Gdx.gl.GL_TEXTURE_MIN_FILTER, Gdx.gl.GL_NEAREST);
- Gdx.gl.glTexParameteri(Gdx.gl.GL_TEXTURE_2D, Gdx.gl.GL_TEXTURE_MAG_FILTER, Gdx.gl.GL_NEAREST);
- Gdx.gl.glTexParameteri(Gdx.gl.GL_TEXTURE_2D, Gdx.gl.GL_TEXTURE_WRAP_S, Gdx.gl.GL_CLAMP_TO_EDGE);
- Gdx.gl.glTexParameteri(Gdx.gl.GL_TEXTURE_2D, Gdx.gl.GL_TEXTURE_WRAP_T, Gdx.gl.GL_CLAMP_TO_EDGE);
- Gdx.gl.glTexImage2D(Gdx.gl.GL_TEXTURE_2D, 0, Gdx.gl.GL_RGBA, texH, texH, 0, Gdx.gl.GL_RGBA, Gdx.gl.GL_UNSIGNED_BYTE,
- //bbuf
- null
- );
-
- Gdx.gl.glBindRenderbuffer(Gdx.gl.GL_RENDERBUFFER, rb);
- Gdx.gl.glRenderbufferStorage(Gdx.gl.GL_RENDERBUFFER, Gdx.gl.GL_DEPTH_COMPONENT16, texW, texH);
- //Gdx.gl.glBindRenderbuffer(Gdx.gl.GL_RENDERBUFFER, rb2);
- //Gdx.gl.glRenderbufferStorage(Gdx.gl.GL_RENDERBUFFER, Gdx.gl.GL_DEPTH_COMPONENT16, texW, texH);
-
- // Bind the framebuffer
- Gdx.gl.glBindFramebuffer(Gdx.gl.GL_FRAMEBUFFER, fb);
-
- // specify texture as color attachment
- Gdx.gl.glFramebufferRenderbuffer(Gdx.gl.GL_FRAMEBUFFER, Gdx.gl.GL_COLOR_ATTACHMENT0, Gdx.gl.GL_RENDERBUFFER, tb);
- Gdx.gl.glFramebufferTexture2D(Gdx.gl.GL_FRAMEBUFFER, Gdx.gl.GL_COLOR_ATTACHMENT0, Gdx.gl.GL_TEXTURE_2D, tb, 0);
-
- // attach render buffer as depth buffer
- Gdx.gl.glFramebufferRenderbuffer(Gdx.gl.GL_FRAMEBUFFER, Gdx.gl.GL_DEPTH_ATTACHMENT, Gdx.gl.GL_RENDERBUFFER, rb);
- //Gdx.gl.glFramebufferRenderbuffer(Gdx.gl.GL_FRAMEBUFFER, Gdx.gl.GL_STENCIL_ATTACHMENT, Gdx.gl.GL_RENDERBUFFER, rb2);
-
- switch( GL20.glCheckFramebufferStatus(GL20.GL_FRAMEBUFFER)){
- case GL20.GL_FRAMEBUFFER_COMPLETE:
- System.out.println("Frame Buffer Complete");
- break;
-
- case GL20.GL_FRAMEBUFFER_UNSUPPORTED:
- System.out.println("Frame Buffer unsupported");
- break;
-
- default:
- System.out.println("Frame Buffer unknown" + GL20.glCheckFramebufferStatus(GL20.GL_FRAMEBUFFER) );
- }
-
-
+ EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, buffer.depthBufferID);
+ EXTFramebufferObject.glRenderbufferStorageEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, GL11.GL_DEPTH_COMPONENT, width, height);
+ EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT, buffer.depthBufferID);
+
+ EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT, GL11.GL_TEXTURE_2D, fboTexture, 0);
+
GL11.glBindTexture(GL11.GL_TEXTURE_2D, old_texture);
- GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, old_framebuffer);
+ EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, old_framebuffer);
*/
return buffer;
@@ -205,9 +154,11 @@ public static Rendertarget createRTTFramebuffer(int width, int height) {
* to use this rendertarget with OpenGL after calling this method.
*/
public void destroyRTT() {
- //GL30.glDeleteFramebuffers(fboID);
- //GL30.glDeleteRenderbuffers(depthBufferID);
- //GL11.glDeleteTextures(textureID);
+ /*
+ EXTFramebufferObject.glDeleteFramebuffersEXT(fboID);
+ EXTFramebufferObject.glDeleteRenderbuffersEXT(depthBufferID);
+ GL11.glDeleteTextures(textureID);
+ */
fbo.dispose();
}
}
diff --git a/src/itdelatrisu/opsu/states/ButtonMenu.java b/src/itdelatrisu/opsu/states/ButtonMenu.java
index c626fa99..02cdbe55 100644
--- a/src/itdelatrisu/opsu/states/ButtonMenu.java
+++ b/src/itdelatrisu/opsu/states/ButtonMenu.java
@@ -33,6 +33,8 @@
import itdelatrisu.opsu.beatmap.BeatmapSetNode;
import itdelatrisu.opsu.ui.MenuButton;
import itdelatrisu.opsu.ui.UI;
+import itdelatrisu.opsu.ui.animations.AnimatedValue;
+import itdelatrisu.opsu.ui.animations.AnimationEquation;
import java.util.ArrayList;
import java.util.List;
@@ -263,8 +265,11 @@ public void scroll(GameContainer container, StateBasedGame game, int newValue) {
/** The actual title string list, generated upon entering the state. */
private List actualTitle;
+ /** The horizontal center offset, used for the initial button animation. */
+ private AnimatedValue centerOffset;
+
/** Initial x coordinate offsets left/right of center (for shifting animation), times width. (TODO) */
- private static final float OFFSET_WIDTH_RATIO = 1 / 18f;
+ private static final float OFFSET_WIDTH_RATIO = 1 / 25f;
/**
* Constructor.
@@ -338,18 +343,14 @@ public void draw(GameContainer container, StateBasedGame game, Graphics g) {
*/
public void update(GameContainer container, int delta, int mouseX, int mouseY) {
float center = container.getWidth() / 2f;
+ boolean centerOffsetUpdated = centerOffset.update(delta);
+ float centerOffsetX = centerOffset.getValue();
for (int i = 0; i < buttons.length; i++) {
menuButtons[i].hoverUpdate(delta, mouseX, mouseY);
// move button to center
- float x = menuButtons[i].getX();
- if (i % 2 == 0) {
- if (x < center)
- menuButtons[i].setX(Math.min(x + (delta / 5f), center));
- } else {
- if (x > center)
- menuButtons[i].setX(Math.max(x - (delta / 5f), center));
- }
+ if (centerOffsetUpdated)
+ menuButtons[i].setX((i % 2 == 0) ? center + centerOffsetX : center - centerOffsetX);
}
}
@@ -406,9 +407,10 @@ public void scroll(GameContainer container, StateBasedGame game, int newValue) {
*/
public void enter(GameContainer container, StateBasedGame game) {
float center = container.getWidth() / 2f;
- float centerOffset = container.getWidth() * OFFSET_WIDTH_RATIO;
+ float centerOffsetX = container.getWidth() * OFFSET_WIDTH_RATIO;
+ centerOffset = new AnimatedValue(700, centerOffsetX, 0, AnimationEquation.OUT_BOUNCE);
for (int i = 0; i < buttons.length; i++) {
- menuButtons[i].setX(center + ((i % 2 == 0) ? centerOffset * -1 : centerOffset));
+ menuButtons[i].setX(center + ((i % 2 == 0) ? centerOffsetX : centerOffsetX * -1));
menuButtons[i].resetHover();
}
diff --git a/src/itdelatrisu/opsu/states/DownloadsMenu.java b/src/itdelatrisu/opsu/states/DownloadsMenu.java
index d40fe6a4..ec229e15 100644
--- a/src/itdelatrisu/opsu/states/DownloadsMenu.java
+++ b/src/itdelatrisu/opsu/states/DownloadsMenu.java
@@ -36,7 +36,8 @@
import itdelatrisu.opsu.downloads.servers.BloodcatServer;
import itdelatrisu.opsu.downloads.servers.DownloadServer;
import itdelatrisu.opsu.downloads.servers.HexideServer;
-import itdelatrisu.opsu.downloads.servers.OsuMirrorServer;
+import itdelatrisu.opsu.downloads.servers.MnetworkServer;
+import itdelatrisu.opsu.downloads.servers.YaSOnlineServer;
import itdelatrisu.opsu.ui.MenuButton;
import itdelatrisu.opsu.ui.UI;
@@ -76,7 +77,9 @@ public class DownloadsMenu extends BasicGameState {
private static final int MIN_REQUEST_INTERVAL = 300;
/** Available beatmap download servers. */
- private static final DownloadServer[] SERVERS = { new BloodcatServer(), new OsuMirrorServer(), new HexideServer() };
+ private static final DownloadServer[] SERVERS = {
+ new BloodcatServer(), new HexideServer(), new YaSOnlineServer(), new MnetworkServer()
+ };
/** The beatmap download server index. */
private int serverIndex = 0;
@@ -138,9 +141,6 @@ private enum Page { RESET, CURRENT, PREVIOUS, NEXT };
/** Page direction for last query. */
private Page lastQueryDir = Page.RESET;
- /** Number of active requests. */
- private int activeRequests = 0;
-
/** Previous and next page buttons. */
private MenuButton prevPage, nextPage;
@@ -156,6 +156,92 @@ private enum Page { RESET, CURRENT, PREVIOUS, NEXT };
/** The bar notification to send upon entering the state. */
private String barNotificationOnLoad;
+ /** Search query, executed in {@code queryThread}. */
+ private SearchQuery searchQuery;
+
+ /** Search query helper class. */
+ private class SearchQuery implements Runnable {
+ /** The search query. */
+ private String query;
+
+ /** The download server. */
+ private DownloadServer server;
+
+ /** Whether the query was interrupted. */
+ private boolean interrupted = false;
+
+ /** Whether the query has completed execution. */
+ private boolean complete = false;
+
+ /**
+ * Constructor.
+ * @param query the search query
+ * @param server the download server
+ */
+ public SearchQuery(String query, DownloadServer server) {
+ this.query = query;
+ this.server = server;
+ }
+
+ /** Interrupt the query and prevent the results from being processed, if not already complete. */
+ public void interrupt() { interrupted = true; }
+
+ /** Returns whether the query has completed execution. */
+ public boolean isComplete() { return complete; }
+
+ @Override
+ public void run() {
+ // check page direction
+ Page lastPageDir = pageDir;
+ pageDir = Page.RESET;
+ int lastPageSize = (resultList != null) ? resultList.length : 0;
+ int newPage = page;
+ if (lastPageDir == Page.RESET)
+ newPage = 1;
+ else if (lastPageDir == Page.NEXT)
+ newPage++;
+ else if (lastPageDir == Page.PREVIOUS)
+ newPage--;
+ try {
+ DownloadNode[] nodes = server.resultList(query, newPage, rankedOnly);
+ if (!interrupted) {
+ // update page total
+ page = newPage;
+ if (nodes != null) {
+ if (lastPageDir == Page.NEXT)
+ pageResultTotal += nodes.length;
+ else if (lastPageDir == Page.PREVIOUS)
+ pageResultTotal -= lastPageSize;
+ else if (lastPageDir == Page.RESET)
+ pageResultTotal = nodes.length;
+ } else
+ pageResultTotal = 0;
+
+ resultList = nodes;
+ totalResults = server.totalResults();
+ focusResult = -1;
+ startResult = 0;
+ if (nodes == null)
+ searchResultString = "An error has occurred.";
+ else {
+ if (query.isEmpty())
+ searchResultString = "Type to search!";
+ else if (totalResults == 0 || resultList.length == 0)
+ searchResultString = "No results found.";
+ else
+ searchResultString = String.format("%d result%s found!",
+ totalResults, (totalResults == 1) ? "" : "s");
+ }
+ }
+ } catch (IOException e) {
+ if (!interrupted)
+ searchResultString = "Could not establish connection to server.";
+ } finally {
+ complete = true;
+ }
+ }
+ }
+
// game-related variables
private GameContainer container;
private StateBasedGame game;
@@ -396,72 +482,22 @@ public void update(GameContainer container, StateBasedGame game, int delta)
searchTimer = 0;
searchTimerReset = false;
- final String query = search.getText().trim().toLowerCase();
- final DownloadServer server = SERVERS[serverIndex];
+ String query = search.getText().trim().toLowerCase();
+ DownloadServer server = SERVERS[serverIndex];
if ((lastQuery == null || !query.equals(lastQuery)) &&
(query.length() == 0 || query.length() >= server.minQueryLength())) {
lastQuery = query;
lastQueryDir = pageDir;
- if (queryThread != null && queryThread.isAlive())
+ if (queryThread != null && queryThread.isAlive()) {
queryThread.interrupt();
+ if (searchQuery != null)
+ searchQuery.interrupt();
+ }
// execute query
- queryThread = new Thread() {
- @Override
- public void run() {
- activeRequests++;
-
- // check page direction
- Page lastPageDir = pageDir;
- pageDir = Page.RESET;
- int lastPageSize = (resultList != null) ? resultList.length : 0;
- int newPage = page;
- if (lastPageDir == Page.RESET)
- newPage = 1;
- else if (lastPageDir == Page.NEXT)
- newPage++;
- else if (lastPageDir == Page.PREVIOUS)
- newPage--;
- try {
- DownloadNode[] nodes = server.resultList(query, newPage, rankedOnly);
- if (activeRequests - 1 == 0) {
- // update page total
- page = newPage;
- if (nodes != null) {
- if (lastPageDir == Page.NEXT)
- pageResultTotal += nodes.length;
- else if (lastPageDir == Page.PREVIOUS)
- pageResultTotal -= lastPageSize;
- else if (lastPageDir == Page.RESET)
- pageResultTotal = nodes.length;
- } else
- pageResultTotal = 0;
-
- resultList = nodes;
- totalResults = server.totalResults();
- focusResult = -1;
- startResult = 0;
- if (nodes == null)
- searchResultString = "An error has occurred.";
- else {
- if (query.isEmpty())
- searchResultString = "Type to search!";
- else if (totalResults == 0 || resultList.length == 0)
- searchResultString = "No results found.";
- else
- searchResultString = String.format("%d result%s found!",
- totalResults, (totalResults == 1) ? "" : "s");
- }
- }
- } catch (IOException e) {
- searchResultString = "Could not establish connection to server.";
- } finally {
- activeRequests--;
- queryThread = null;
- }
- }
- };
+ searchQuery = new SearchQuery(query, server);
+ queryThread = new Thread(searchQuery);
queryThread.start();
}
}
@@ -594,23 +630,27 @@ public void update(LineEvent event) {
// pages
if (nodes.length > 0) {
if (page > 1 && prevPage.contains(x, y)) {
- if (lastQueryDir == Page.PREVIOUS && queryThread != null && queryThread.isAlive())
+ if (lastQueryDir == Page.PREVIOUS && searchQuery != null && !searchQuery.isComplete())
; // don't send consecutive requests
else {
SoundController.playSound(SoundEffect.MENUCLICK);
pageDir = Page.PREVIOUS;
lastQuery = null;
+ if (searchQuery != null)
+ searchQuery.interrupt();
resetSearchTimer();
}
return;
}
if (pageResultTotal < totalResults && nextPage.contains(x, y)) {
- if (lastQueryDir == Page.NEXT && queryThread != null && queryThread.isAlive())
+ if (lastQueryDir == Page.NEXT && searchQuery != null && !searchQuery.isComplete())
; // don't send consecutive requests
else {
SoundController.playSound(SoundEffect.MENUCLICK);
pageDir = Page.NEXT;
lastQuery = null;
+ if (searchQuery != null)
+ searchQuery.interrupt();
resetSearchTimer();
return;
}
@@ -663,6 +703,8 @@ public void run() {
search.setText("");
lastQuery = null;
pageDir = Page.RESET;
+ if (searchQuery != null)
+ searchQuery.interrupt();
resetSearchTimer();
return;
}
@@ -671,6 +713,8 @@ public void run() {
rankedOnly = !rankedOnly;
lastQuery = null;
pageDir = Page.RESET;
+ if (searchQuery != null)
+ searchQuery.interrupt();
resetSearchTimer();
return;
}
@@ -687,6 +731,8 @@ public void run() {
serverIndex = (serverIndex + 1) % SERVERS.length;
lastQuery = null;
pageDir = Page.RESET;
+ if (searchQuery != null)
+ searchQuery.interrupt();
resetSearchTimer();
return;
}
@@ -790,6 +836,8 @@ public void keyPressed(int key, char c) {
SoundController.playSound(SoundEffect.MENUCLICK);
lastQuery = null;
pageDir = Page.CURRENT;
+ if (searchQuery != null)
+ searchQuery.interrupt();
resetSearchTimer();
break;
case Input.KEY_F7:
diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java
index 58bb298c..dc445087 100644
--- a/src/itdelatrisu/opsu/states/Game.java
+++ b/src/itdelatrisu/opsu/states/Game.java
@@ -50,6 +50,7 @@
import itdelatrisu.opsu.replay.ReplayFrame;
import itdelatrisu.opsu.ui.MenuButton;
import itdelatrisu.opsu.ui.UI;
+import itdelatrisu.opsu.ui.animations.AnimationEquation;
//import java.io.File;
@@ -119,6 +120,10 @@ public enum Restart {
/** Hit object approach time, in milliseconds. */
private int approachTime;
+ /** Decay time for elements in "Hidden" mod, in milliseconds. */
+ //TODO: figure out actual formula for decay time
+ private int decayTime = 800;
+
/** Time offsets for obtaining each hit result (indexed by HIT_* constants). */
private int[] hitResultOffset;
@@ -624,6 +629,8 @@ else if(Options.isInGamePauseEnabled() &&
cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY);
}
+ Utils.FONT_DEFAULT.drawString(10,10, MusicController.getPosition()+"");
+
if (isReplay)
UI.draw(g, replayX, replayY, replayKeyPressed);
else if (GameMod.AUTO.isActive())
@@ -1180,6 +1187,13 @@ public void enter(GameContainer container, StateBasedGame game)
// restart the game
if (restart != Restart.FALSE) {
+ // load mods
+ if (isReplay) {
+ previousMods = GameMod.getModState();
+ GameMod.loadModState(replay.mods);
+ }
+
+ // check restart state
if (restart == Restart.NEW) {
// new game
loadImages();
@@ -1264,10 +1278,6 @@ else if (hitObject.isSpinner())
// load replay frames
if (isReplay) {
- // load mods
- previousMods = GameMod.getModState();
- GameMod.loadModState(replay.mods);
-
// load initial data
replayX = container.getWidth() / 2;
replayY = container.getHeight() / 2;
@@ -1509,6 +1519,8 @@ private void loadImages() {
Image skip = GameImage.SKIP.getImage();
skipButton = new MenuButton(skip, width - skip.getWidth() / 2f, height - (skip.getHeight() / 2f));
}
+ skipButton.setHoverAnimationDuration(350);
+ skipButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_BACK);
skipButton.setHoverExpand(1.1f, MenuButton.Expand.UP_LEFT);
Image pause = GameImage.MUSIC_PAUSE.getImage().getScaledCopy( 2.5f*Options.getMobileUIScale(0.2f) / Options.getMobileUIScale());
@@ -1599,6 +1611,11 @@ private void setMapModifiers() {
*/
public int getApproachTime() { return approachTime; }
+ /**
+ * Returns the object decay time in the "Hidden" mod, in milliseconds.
+ */
+ public int getDecayTime() { return decayTime; }
+
/**
* Returns an array of hit result offset times, in milliseconds (indexed by GameData.HIT_* constants).
*/
diff --git a/src/itdelatrisu/opsu/states/GamePauseMenu.java b/src/itdelatrisu/opsu/states/GamePauseMenu.java
index f7451f3f..423eebec 100644
--- a/src/itdelatrisu/opsu/states/GamePauseMenu.java
+++ b/src/itdelatrisu/opsu/states/GamePauseMenu.java
@@ -29,6 +29,7 @@
import itdelatrisu.opsu.audio.SoundEffect;
import itdelatrisu.opsu.ui.MenuButton;
import itdelatrisu.opsu.ui.UI;
+import itdelatrisu.opsu.ui.animations.AnimationEquation;
/*
import org.lwjgl.input.Keyboard;
import org.newdawn.slick.Color;
@@ -230,6 +231,14 @@ public void loadImages() {
continueButton = new MenuButton(GameImage.PAUSE_CONTINUE.getImage(), width / 2f, height * 0.25f);
retryButton = new MenuButton(GameImage.PAUSE_RETRY.getImage(), width / 2f, height * 0.5f);
backButton = new MenuButton(GameImage.PAUSE_BACK.getImage(), width / 2f, height * 0.75f);
+ final int buttonAnimationDuration = 300;
+ continueButton.setHoverAnimationDuration(buttonAnimationDuration);
+ retryButton.setHoverAnimationDuration(buttonAnimationDuration);
+ backButton.setHoverAnimationDuration(buttonAnimationDuration);
+ final AnimationEquation buttonAnimationEquation = AnimationEquation.IN_OUT_BACK;
+ continueButton.setHoverAnimationEquation(buttonAnimationEquation);
+ retryButton.setHoverAnimationEquation(buttonAnimationEquation);
+ backButton.setHoverAnimationEquation(buttonAnimationEquation);
continueButton.setHoverExpand();
retryButton.setHoverExpand();
backButton.setHoverExpand();
diff --git a/src/itdelatrisu/opsu/states/MainMenu.java b/src/itdelatrisu/opsu/states/MainMenu.java
index a37d1879..38dde49e 100644
--- a/src/itdelatrisu/opsu/states/MainMenu.java
+++ b/src/itdelatrisu/opsu/states/MainMenu.java
@@ -35,6 +35,8 @@
import itdelatrisu.opsu.ui.MenuButton;
import itdelatrisu.opsu.ui.MenuButton.Expand;
import itdelatrisu.opsu.ui.UI;
+import itdelatrisu.opsu.ui.animations.AnimatedValue;
+import itdelatrisu.opsu.ui.animations.AnimationEquation;
/*
@@ -65,7 +67,7 @@
*/
public class MainMenu extends BasicGameState {
/** Idle time, in milliseconds, before returning the logo to its original position. */
- private static final short MOVE_DELAY = 5000;
+ private static final short LOGO_IDLE_DELAY = 10000;
/** Max alpha level of the menu background. */
private static final float BG_MAX_ALPHA = 0.9f;
@@ -73,12 +75,21 @@ public class MainMenu extends BasicGameState {
/** Logo button that reveals other buttons on click. */
private MenuButton logo;
- /** Whether or not the logo has been clicked. */
- private boolean logoClicked = false;
+ /** Logo states. */
+ private enum LogoState { DEFAULT, OPENING, OPEN, CLOSING }
+
+ /** Current logo state. */
+ private LogoState logoState = LogoState.DEFAULT;
/** Delay timer, in milliseconds, before starting to move the logo back to the center. */
private int logoTimer = 0;
+ /** Logo horizontal offset for opening and closing actions. */
+ private AnimatedValue logoOpen, logoClose;
+
+ /** Logo button alpha levels. */
+ private AnimatedValue logoButtonAlpha;
+
/** Main "Play" and "Exit" buttons. */
private MenuButton playButton, exitButton;
@@ -91,8 +102,8 @@ public class MainMenu extends BasicGameState {
/** Button linking to repository. */
private MenuButton repoButton;
- /** Button for installing updates. */
- private MenuButton updateButton;
+ /** Buttons for installing updates. */
+ private MenuButton updateButton, restartButton;
/** Application start time, for drawing the total running time. */
private long programStartTime;
@@ -101,7 +112,7 @@ public class MainMenu extends BasicGameState {
private Stack previous;
/** Background alpha level (for fade-in effect). */
- private float bgAlpha = 0f;
+ private AnimatedValue bgAlpha = new AnimatedValue(1100, 0f, BG_MAX_ALPHA, AnimationEquation.LINEAR);
/** Whether or not a notification was already sent upon entering. */
private boolean enterNotification = false;
@@ -149,9 +160,18 @@ public void init(GameContainer container, StateBasedGame game)
exitButton = new MenuButton(exitImg,
width * 0.75f - exitOffset, (height / 2) + (exitImg.getHeight() / 2f)
);
- logo.setHoverExpand(1.05f);
- playButton.setHoverExpand(1.05f);
- exitButton.setHoverExpand(1.05f);
+ final int logoAnimationDuration = 350;
+ logo.setHoverAnimationDuration(logoAnimationDuration);
+ playButton.setHoverAnimationDuration(logoAnimationDuration);
+ exitButton.setHoverAnimationDuration(logoAnimationDuration);
+ final AnimationEquation logoAnimationEquation = AnimationEquation.IN_OUT_BACK;
+ logo.setHoverAnimationEquation(logoAnimationEquation);
+ playButton.setHoverAnimationEquation(logoAnimationEquation);
+ exitButton.setHoverAnimationEquation(logoAnimationEquation);
+ final float logoHoverScale = 1.08f;
+ logo.setHoverExpand(logoHoverScale);
+ playButton.setHoverExpand(logoHoverScale);
+ exitButton.setHoverExpand(logoHoverScale);
// initialize music buttons
int musicWidth = GameImage.MUSIC_PLAY.getImage().getWidth();
@@ -174,24 +194,40 @@ public void init(GameContainer container, StateBasedGame game)
// initialize downloads button
Image dlImg = GameImage.DOWNLOADS.getImage();
downloadsButton = new MenuButton(dlImg, width - dlImg.getWidth() / 2f, height / 2f);
+ downloadsButton.setHoverAnimationDuration(350);
+ downloadsButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_BACK);
downloadsButton.setHoverExpand(1.03f, Expand.LEFT);
// initialize repository button
float startX = width * 0.997f, startY = height * 0.997f;
- if (Desktop.isDesktopSupported()) { // only if a webpage can be opened
+ if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { // only if a webpage can be opened
Image repoImg = GameImage.REPOSITORY.getImage();
repoButton = new MenuButton(repoImg,
startX - repoImg.getWidth(), startY - repoImg.getHeight()
);
+ repoButton.setHoverAnimationDuration(350);
+ repoButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_BACK);
repoButton.setHoverExpand();
- startX -= repoImg.getWidth() * 1.75f;
- } else
- startX -= width * 0.005f;
+ }
- // initialize update button
- Image bangImg = GameImage.BANG.getImage();
- updateButton = new MenuButton(bangImg, startX - bangImg.getWidth(), startY - bangImg.getHeight());
- updateButton.setHoverExpand(1.15f);
+ // initialize update buttons
+ float updateX = width / 2f, updateY = height * 17 / 18f;
+ Image downloadImg = GameImage.DOWNLOAD.getImage();
+ updateButton = new MenuButton(downloadImg, updateX, updateY);
+ updateButton.setHoverAnimationDuration(400);
+ updateButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_QUAD);
+ updateButton.setHoverExpand(1.1f);
+ Image updateImg = GameImage.UPDATE.getImage();
+ restartButton = new MenuButton(updateImg, updateX, updateY);
+ restartButton.setHoverAnimationDuration(2000);
+ restartButton.setHoverAnimationEquation(AnimationEquation.LINEAR);
+ restartButton.setHoverRotate(360);
+
+ // logo animations
+ float centerOffsetX = width / 5f;
+ logoOpen = new AnimatedValue(400, 0, centerOffsetX, AnimationEquation.OUT_QUAD);
+ logoClose = new AnimatedValue(2200, centerOffsetX, 0, AnimationEquation.OUT_QUAD);
+ logoButtonAlpha = new AnimatedValue(300, 0f, 1f, AnimationEquation.LINEAR);
reset();
}
@@ -205,11 +241,11 @@ public void render(GameContainer container, StateBasedGame game, Graphics g)
// draw background
Beatmap beatmap = MusicController.getBeatmap();
if (Options.isDynamicBackgroundEnabled() &&
- beatmap != null && beatmap.drawBG(width, height, bgAlpha, true))
+ beatmap != null && beatmap.drawBG(width, height, bgAlpha.getValue(), true))
;
else {
Image bg = GameImage.MENU_BG.getImage();
- bg.setAlpha(bgAlpha);
+ bg.setAlpha(bgAlpha.getValue());
bg.draw();
}
@@ -225,7 +261,7 @@ public void render(GameContainer container, StateBasedGame game, Graphics g)
downloadsButton.draw();
// draw buttons
- if (logoTimer > 0) {
+ if (logoState == LogoState.OPEN || logoState == LogoState.CLOSING) {
playButton.draw();
exitButton.draw();
}
@@ -255,22 +291,11 @@ public void render(GameContainer container, StateBasedGame game, Graphics g)
// draw update button
if (Updater.get().showButton()) {
- Color updateColor = null;
- switch (Updater.get().getStatus()) {
- case UPDATE_AVAILABLE:
- updateColor = Color.red;
- break;
- case UPDATE_DOWNLOADED:
- updateColor = Color.green;
- break;
- case UPDATE_DOWNLOADING:
- updateColor = Color.yellow;
- break;
- default:
- updateColor = Color.white;
- break;
- }
- updateButton.draw(updateColor);
+ Updater.Status status = Updater.get().getStatus();
+ if (status == Updater.Status.UPDATE_AVAILABLE || status == Updater.Status.UPDATE_DOWNLOADING)
+ updateButton.draw();
+ else if (status == Updater.Status.UPDATE_DOWNLOADED)
+ restartButton.draw();
}
// draw text
@@ -309,7 +334,10 @@ public void update(GameContainer container, StateBasedGame game, int delta)
exitButton.hoverUpdate(delta, mouseX, mouseY, 0.25f);
if (repoButton != null)
repoButton.hoverUpdate(delta, mouseX, mouseY);
- updateButton.hoverUpdate(delta, mouseX, mouseY);
+ if (Updater.get().showButton()) {
+ updateButton.autoHoverUpdate(delta, true);
+ restartButton.autoHoverUpdate(delta, false);
+ }
downloadsButton.hoverUpdate(delta, mouseX, mouseY);
// ensure only one button is in hover state at once
boolean noHoverUpdate = musicPositionBarContains(mouseX, mouseY);
@@ -326,46 +354,44 @@ public void update(GameContainer container, StateBasedGame game, int delta)
MusicController.toggleTrackDimmed(0.33f);
// fade in background
- if (bgAlpha < BG_MAX_ALPHA) {
- bgAlpha += delta / 1000f;
- if (bgAlpha > BG_MAX_ALPHA)
- bgAlpha = BG_MAX_ALPHA;
- }
+ bgAlpha.update(delta);
// buttons
- if (logoClicked) {
- if (logoTimer == 0) { // shifting to left
- if (logo.getX() > container.getWidth() / 3.3f)
- logo.setX(logo.getX() - delta);
- else
- logoTimer = 1;
- } else if (logoTimer >= MOVE_DELAY) // timer over: shift back to center
- logoClicked = false;
- else { // increment timer
- logoTimer += delta;
- if (logoTimer <= 500) {
- // fade in buttons
- playButton.getImage().setAlpha(logoTimer / 400f);
- exitButton.getImage().setAlpha(logoTimer / 400f);
- }
- }
- } else {
- // fade out buttons
- if (logoTimer > 0) {
- float alpha = playButton.getImage().getAlpha();
- if (alpha > 0f) {
- playButton.getImage().setAlpha(alpha - (delta / 200f));
- exitButton.getImage().setAlpha(alpha - (delta / 200f));
- } else
- logoTimer = 0;
+ int centerX = container.getWidth() / 2;
+ float currentLogoButtonAlpha;
+ switch (logoState) {
+ case DEFAULT:
+ break;
+ case OPENING:
+ if (logoOpen.update(delta)) // shifting to left
+ logo.setX(centerX - logoOpen.getValue());
+ else {
+ logoState = LogoState.OPEN;
+ logoTimer = 0;
+ logoButtonAlpha.setTime(0);
}
-
- // move back to original location
- if (logo.getX() < container.getWidth() / 2) {
- logo.setX(logo.getX() + (delta / 3f));
- if (logo.getX() > container.getWidth() / 2)
- logo.setX(container.getWidth() / 2);
+ break;
+ case OPEN:
+ if (logoButtonAlpha.update(delta)) { // fade in buttons
+ currentLogoButtonAlpha = logoButtonAlpha.getValue();
+ playButton.getImage().setAlpha(currentLogoButtonAlpha);
+ exitButton.getImage().setAlpha(currentLogoButtonAlpha);
+ } else if (logoTimer >= LOGO_IDLE_DELAY) { // timer over: shift back to center
+ logoState = LogoState.CLOSING;
+ logoClose.setTime(0);
+ logoTimer = 0;
+ } else // increment timer
+ logoTimer += delta;
+ break;
+ case CLOSING:
+ if (logoButtonAlpha.update(-delta)) { // fade out buttons
+ currentLogoButtonAlpha = logoButtonAlpha.getValue();
+ playButton.getImage().setAlpha(currentLogoButtonAlpha);
+ exitButton.getImage().setAlpha(currentLogoButtonAlpha);
}
+ if (logoClose.update(delta)) // shifting to right
+ logo.setX(centerX - logoClose.getValue());
+ break;
}
// tooltips
@@ -377,8 +403,12 @@ else if (musicNext.contains(mouseX, mouseY))
UI.updateTooltip(delta, "Next track", false);
else if (musicPrevious.contains(mouseX, mouseY))
UI.updateTooltip(delta, "Previous track", false);
- else if (Updater.get().showButton() && updateButton.contains(mouseX, mouseY))
- UI.updateTooltip(delta, Updater.get().getStatus().getDescription(), true);
+ else if (Updater.get().showButton()) {
+ Updater.Status status = Updater.get().getStatus();
+ if (((status == Updater.Status.UPDATE_AVAILABLE || status == Updater.Status.UPDATE_DOWNLOADING) && updateButton.contains(mouseX, mouseY)) ||
+ (status == Updater.Status.UPDATE_DOWNLOADED && restartButton.contains(mouseX, mouseY)))
+ UI.updateTooltip(delta, status.getDescription(), true);
+ }
}
@Override
@@ -418,8 +448,8 @@ public void enter(GameContainer container, StateBasedGame game)
musicPrevious.resetHover();
if (repoButton != null && !repoButton.contains(mouseX, mouseY))
repoButton.resetHover();
- if (!updateButton.contains(mouseX, mouseY))
- updateButton.resetHover();
+ updateButton.resetHover();
+ restartButton.resetHover();
if (!downloadsButton.contains(mouseX, mouseY))
downloadsButton.resetHover();
}
@@ -455,71 +485,85 @@ public void mousePressed(int button, int x, int y) {
MusicController.resume();
UI.sendBarNotification("Play");
}
+ return;
} else if (musicNext.contains(x, y)) {
nextTrack();
UI.sendBarNotification(">> Next");
+ return;
} else if (musicPrevious.contains(x, y)) {
if (!previous.isEmpty()) {
SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
menu.setFocus(BeatmapSetList.get().getBaseNode(previous.pop()), -1, true, false);
if (Options.isDynamicBackgroundEnabled())
- bgAlpha = 0f;
+ bgAlpha.setTime(0);
} else
MusicController.setPosition(0);
UI.sendBarNotification("<< Previous");
+ return;
}
// downloads button actions
- else if (downloadsButton.contains(x, y)) {
+ if (downloadsButton.contains(x, y)) {
SoundController.playSound(SoundEffect.MENUHIT);
game.enterState(Opsu.STATE_DOWNLOADSMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
+ return;
}
// repository button actions
- else if (repoButton != null && repoButton.contains(x, y)) {
+ if (repoButton != null && repoButton.contains(x, y)) {
try {
Desktop.getDesktop().browse(Options.REPOSITORY_URI);
+ } catch (UnsupportedOperationException e) {
+ UI.sendBarNotification("The repository web page could not be opened.");
} catch (IOException e) {
ErrorHandler.error("Could not browse to repository URI.", e, false);
}
+ return;
}
// update button actions
- else if (Updater.get().showButton() && updateButton.contains(x, y)) {
- switch (Updater.get().getStatus()) {
- case UPDATE_AVAILABLE:
+ if (Updater.get().showButton()) {
+ Updater.Status status = Updater.get().getStatus();
+ if (updateButton.contains(x, y) && status == Updater.Status.UPDATE_AVAILABLE) {
SoundController.playSound(SoundEffect.MENUHIT);
Updater.get().startDownload();
- break;
- case UPDATE_DOWNLOADED:
+ updateButton.removeHoverEffects();
+ updateButton.setHoverAnimationDuration(800);
+ updateButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_QUAD);
+ updateButton.setHoverFade(0.6f);
+ return;
+ } else if (restartButton.contains(x, y) && status == Updater.Status.UPDATE_DOWNLOADED) {
SoundController.playSound(SoundEffect.MENUHIT);
Updater.get().prepareUpdate();
container.setForceExit(false);
container.exit();
- break;
- default:
- break;
+ return;
}
}
// start moving logo (if clicked)
- else if (!logoClicked) {
+ if (logoState == LogoState.DEFAULT || logoState == LogoState.CLOSING) {
if (logo.contains(x, y, 0.25f)) {
- logoClicked = true;
+ logoState = LogoState.OPENING;
+ logoOpen.setTime(0);
logoTimer = 0;
playButton.getImage().setAlpha(0f);
exitButton.getImage().setAlpha(0f);
SoundController.playSound(SoundEffect.MENUHIT);
+ return;
}
}
// other button actions (if visible)
- else if (logoClicked) {
+ else if (logoState == LogoState.OPEN || logoState == LogoState.OPENING) {
if (logo.contains(x, y, 0.25f) || playButton.contains(x, y, 0.25f)) {
SoundController.playSound(SoundEffect.MENUHIT);
enterSongMenu();
- } else if (exitButton.contains(x, y, 0.25f))
+ return;
+ } else if (exitButton.contains(x, y, 0.25f)) {
container.exit();
+ return;
+ }
}
}
@@ -539,8 +583,9 @@ public void keyPressed(int key, char c) {
break;
case Input.KEY_P:
SoundController.playSound(SoundEffect.MENUHIT);
- if (!logoClicked) {
- logoClicked = true;
+ if (logoState == LogoState.DEFAULT || logoState == LogoState.CLOSING) {
+ logoState = LogoState.OPENING;
+ logoOpen.setTime(0);
logoTimer = 0;
playButton.getImage().setAlpha(0f);
exitButton.getImage().setAlpha(0f);
@@ -588,8 +633,11 @@ private boolean musicPositionBarContains(float cx, float cy) {
public void reset() {
// reset logo
logo.setX(container.getWidth() / 2);
- logoClicked = false;
+ logoOpen.setTime(0);
+ logoClose.setTime(0);
+ logoButtonAlpha.setTime(0);
logoTimer = 0;
+ logoState = LogoState.DEFAULT;
logo.resetHover();
playButton.resetHover();
@@ -601,6 +649,7 @@ public void reset() {
if (repoButton != null)
repoButton.resetHover();
updateButton.resetHover();
+ restartButton.resetHover();
downloadsButton.resetHover();
}
@@ -618,7 +667,7 @@ private void nextTrack() {
previous.add(node.index);
}
if (Options.isDynamicBackgroundEnabled() && !sameAudio && !MusicController.isThemePlaying())
- bgAlpha = 0f;
+ bgAlpha.setTime(0);
}
/**
diff --git a/src/itdelatrisu/opsu/states/OptionsMenu.java b/src/itdelatrisu/opsu/states/OptionsMenu.java
index 19de8fdb..4a83db8a 100644
--- a/src/itdelatrisu/opsu/states/OptionsMenu.java
+++ b/src/itdelatrisu/opsu/states/OptionsMenu.java
@@ -101,7 +101,9 @@ private enum OptionTab {
GameOption.FIXED_OD,
GameOption.CHECKPOINT,
GameOption.REPLAY_SEEKING,
- GameOption.LOAD_HD_IMAGES,
+ /*
+ GameOption.DISABLE_UPDATER
+ */
}),
EXTRA ("Extras", new GameOption[] {
GameOption.MOBILE_UI_SCALING,
@@ -213,12 +215,12 @@ public void init(GameContainer container, StateBasedGame game)
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g)
throws SlickException {
- g.setBackground(Utils.COLOR_BLACK_ALPHA);
-
int width = container.getWidth();
int height = container.getHeight();
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
- float lineY = OptionTab.DISPLAY.button.getY() + (GameImage.MENU_TAB.getImage().getHeight() / 2f);
+
+ // background
+ GameImage.OPTIONS_BG.getImage().draw();
// title
float marginX = width * 0.015f, marginY = height * 0.01f;
@@ -226,9 +228,6 @@ public void render(GameContainer container, StateBasedGame game, Graphics g)
Utils.FONT_DEFAULT.drawString(marginX, marginY + Utils.FONT_XLARGE.getLineHeight() * 0.92f,
"Change the way opsu! behaves", Color.white);
- // background
- GameImage.OPTIONS_BG.getImage().draw(0, lineY);
-
// game options
g.setLineWidth(1f);
GameOption hoverOption = (keyEntryLeft) ? GameOption.KEY_LEFT :
@@ -256,6 +255,7 @@ public void render(GameContainer container, StateBasedGame game, Graphics g)
currentTab.getName(), true, false);
g.setColor(Color.white);
g.setLineWidth(2f);
+ float lineY = OptionTab.DISPLAY.button.getY() + (GameImage.MENU_TAB.getImage().getHeight() / 2f);
g.drawLine(0, lineY, width, lineY);
g.resetLineWidth();
diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java
index dbec838e..da984092 100644
--- a/src/itdelatrisu/opsu/states/SongMenu.java
+++ b/src/itdelatrisu/opsu/states/SongMenu.java
@@ -43,6 +43,8 @@
import itdelatrisu.opsu.states.ButtonMenu.MenuState;
import itdelatrisu.opsu.ui.MenuButton;
import itdelatrisu.opsu.ui.UI;
+import itdelatrisu.opsu.ui.animations.AnimatedValue;
+import itdelatrisu.opsu.ui.animations.AnimationEquation;
//import java.io.File;
import java.util.Map;
@@ -141,8 +143,8 @@ public SongNode(BeatmapSetNode node, int index) {
/** Button coordinate values. */
private float buttonX, buttonY, buttonOffset, buttonWidth, buttonHeight;
- /** Current x offset of song buttons for mouse hover, in pixels. */
- private float hoverOffset = 0f;
+ /** Horizontal offset of song buttons for mouse hover, in pixels. */
+ private AnimatedValue hoverOffset = new AnimatedValue(250, 0, MAX_HOVER_OFFSET, AnimationEquation.OUT_QUART);
/** Current index of hovered song button. */
private int hoverIndex = -1;
@@ -313,7 +315,7 @@ public void render(GameContainer container, StateBasedGame game, Graphics g)
g.setClip(0, (int) (headerY + DIVIDER_LINE_WIDTH / 2), width, (int) (footerY - headerY));
for (int i = songButtonIndex; i <= MAX_SONG_BUTTONS && node != null; i++, node = node.next) {
// draw the node
- float offset = (i == hoverIndex) ? hoverOffset : 0f;
+ float offset = (i == hoverIndex) ? hoverOffset.getValue() : 0f;
ScoreData[] scores = getScoreDataForNode(node, false);
node.draw(buttonX - offset, buttonY + (i*buttonOffset) + DIVIDER_LINE_WIDTH / 2,
(scores == null) ? Grade.NULL : scores[0].getGrade(), (node == focusNode));
@@ -566,15 +568,11 @@ public void update(GameContainer container, StateBasedGame game, int delta)
float cx = (node.index == BeatmapSetList.get().getExpandedIndex()) ? buttonX * 0.9f : buttonX;
if ((mouseX > cx && mouseX < cx + buttonWidth) &&
(mouseY > buttonY + (i * buttonOffset) && mouseY < buttonY + (i * buttonOffset) + buttonHeight)) {
- if (i == hoverIndex) {
- if (hoverOffset < MAX_HOVER_OFFSET) {
- hoverOffset += delta / 3f;
- if (hoverOffset > MAX_HOVER_OFFSET)
- hoverOffset = MAX_HOVER_OFFSET;
- }
- } else {
+ if (i == hoverIndex)
+ hoverOffset.update(delta);
+ else {
hoverIndex = i;
- hoverOffset = 0f;
+ hoverOffset.setTime(0);
}
isHover = true;
break;
@@ -582,7 +580,7 @@ public void update(GameContainer container, StateBasedGame game, int delta)
}
}
if (!isHover) {
- hoverOffset = 0f;
+ hoverOffset.setTime(0);
hoverIndex = -1;
} else
return;
@@ -666,7 +664,7 @@ public void mouseClicked(int button, int x, int y, int clickCount) {
float cx = (node.index == expandedIndex) ? buttonX * 0.9f : buttonX;
if ((x > cx && x < cx + buttonWidth) &&
(y > buttonY + (i * buttonOffset) && y < buttonY + (i * buttonOffset) + buttonHeight)) {
- float oldHoverOffset = hoverOffset;
+ int oldHoverOffsetTime = hoverOffset.getTime();
int oldHoverIndex = hoverIndex;
// clicked node is already expanded
@@ -691,7 +689,7 @@ public void mouseClicked(int button, int x, int y, int clickCount) {
}
// restore hover data
- hoverOffset = oldHoverOffset;
+ hoverOffset.setTime(oldHoverOffsetTime);
hoverIndex = oldHoverIndex;
// open beatmap menu
@@ -827,11 +825,11 @@ public void keyPressed(int key, char c) {
if (next != null) {
SoundController.playSound(SoundEffect.MENUCLICK);
BeatmapSetNode oldStartNode = startNode;
- float oldHoverOffset = hoverOffset;
+ int oldHoverOffsetTime = hoverOffset.getTime();
int oldHoverIndex = hoverIndex;
setFocus(next, 0, false, true);
if (startNode == oldStartNode) {
- hoverOffset = oldHoverOffset;
+ hoverOffset.setTime(oldHoverOffsetTime);
hoverIndex = oldHoverIndex;
}
}
@@ -843,11 +841,11 @@ public void keyPressed(int key, char c) {
if (prev != null) {
SoundController.playSound(SoundEffect.MENUCLICK);
BeatmapSetNode oldStartNode = startNode;
- float oldHoverOffset = hoverOffset;
+ int oldHoverOffsetTime = hoverOffset.getTime();
int oldHoverIndex = hoverIndex;
setFocus(prev, (prev.index == focusNode.index) ? 0 : prev.getBeatmapSet().size() - 1, false, true);
if (startNode == oldStartNode) {
- hoverOffset = oldHoverOffset;
+ hoverOffset.setTime(oldHoverOffsetTime);
hoverIndex = oldHoverIndex;
}
}
@@ -945,7 +943,7 @@ public void enter(GameContainer container, StateBasedGame game)
selectRandomButton.resetHover();
selectMapOptionsButton.resetHover();
selectOptionsButton.resetHover();
- hoverOffset = 0f;
+ hoverOffset.setTime(0);
hoverIndex = -1;
startScore = 0;
beatmapMenuTimer = -1;
@@ -1083,7 +1081,7 @@ else if (startNode.next != null)
oldFocusNode = null;
randomStack = new Stack();
songInfo = null;
- hoverOffset = 0f;
+ hoverOffset.setTime(0);
hoverIndex = -1;
search.setText("");
searchTimer = SEARCH_DELAY;
@@ -1164,7 +1162,7 @@ private void changeIndex(int shift) {
break;
}
if (shifted) {
- hoverOffset = 0f;
+ hoverOffset.setTime(0);
hoverIndex = -1;
}
return;
@@ -1182,7 +1180,7 @@ public BeatmapSetNode setFocus(BeatmapSetNode node, int beatmapIndex, boolean ch
if (node == null)
return null;
- hoverOffset = 0f;
+ hoverOffset.setTime(0);
hoverIndex = -1;
songInfo = null;
BeatmapSetNode oldFocus = focusNode;
diff --git a/src/itdelatrisu/opsu/states/Splash.java b/src/itdelatrisu/opsu/states/Splash.java
index 2f424984..b547df17 100644
--- a/src/itdelatrisu/opsu/states/Splash.java
+++ b/src/itdelatrisu/opsu/states/Splash.java
@@ -31,6 +31,8 @@
import itdelatrisu.opsu.beatmap.BeatmapSetList;
import itdelatrisu.opsu.replay.ReplayImporter;
import itdelatrisu.opsu.ui.UI;
+import itdelatrisu.opsu.ui.animations.AnimatedValue;
+import itdelatrisu.opsu.ui.animations.AnimationEquation;
/*
import java.io.File;
@@ -38,7 +40,6 @@
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
-import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
@@ -50,6 +51,9 @@
* Loads game resources and enters "Main Menu" state.
*/
public class Splash extends BasicGameState {
+ /** Minimum time, in milliseconds, to display the splash screen (and fade in the logo). */
+ private static final int MIN_SPLASH_TIME = 400;
+
/** Whether or not loading has completed. */
private boolean finished = false;
@@ -62,6 +66,9 @@ public class Splash extends BasicGameState {
/** Whether the skin being loaded is a new skin (for program restarts). */
private boolean newSkin = false;
+ /** Logo alpha level. */
+ private AnimatedValue logoAlpha;
+
// game-related variables
private int state;
private GameContainer container;
@@ -83,6 +90,8 @@ public void init(GameContainer container, StateBasedGame game)
// load Utils class first (needed in other 'init' methods)
Utils.init(container, game);
+ // fade in logo
+ this.logoAlpha = new AnimatedValue(MIN_SPLASH_TIME, 0f, 1f, AnimationEquation.LINEAR);
GameImage.MENU_LOGO.getImage().setAlpha(0f);
}
@@ -147,13 +156,11 @@ public void run() {
}
// fade in logo
- Image logo = GameImage.MENU_LOGO.getImage();
- float alpha = logo.getAlpha();
- if (alpha < 1f)
- logo.setAlpha(alpha + (delta / 500f));
+ if (logoAlpha.update(delta))
+ GameImage.MENU_LOGO.getImage().setAlpha(logoAlpha.getValue());
// change states when loading complete
- if (finished && alpha >= 1f) {
+ if (finished && logoAlpha.getValue() >= 1f) {
// initialize song list
if (BeatmapSetList.get().size() > 0) {
BeatmapSetList.get().init();
diff --git a/src/itdelatrisu/opsu/ui/Cursor.java b/src/itdelatrisu/opsu/ui/Cursor.java
index e2d50a69..ee1d50ad 100644
--- a/src/itdelatrisu/opsu/ui/Cursor.java
+++ b/src/itdelatrisu/opsu/ui/Cursor.java
@@ -133,7 +133,7 @@ public void draw(int mouseX, int mouseY, boolean mousePressed) {
cursorMiddle = GameImage.CURSOR_MIDDLE.getImage();
int removeCount = 0;
- int FPSmod = (Options.getTargetFPS() / 60);
+ float FPSmod = Math.max(container.getFPS(), 1) / 60f;
Skin skin = Options.getSkin();
// scale cursor
@@ -143,8 +143,6 @@ public void draw(int mouseX, int mouseY, boolean mousePressed) {
if (cursorScale != 1f) {
cursor = cursor.getScaledCopy(cursorScale);
cursorTrail = cursorTrail.getScaledCopy(cursorScale);
- if (hasMiddle)
- cursorMiddle = cursorMiddle.getScaledCopy(cursorScale);
}
// TODO: use an image buffer
@@ -159,13 +157,13 @@ public void draw(int mouseX, int mouseY, boolean mousePressed) {
lastX = mouseX;
lastY = mouseY;
- removeCount = (cursorX.size() / (6 * FPSmod)) + 1;
+ removeCount = (int) (cursorX.size() / (6 * FPSmod)) + 1;
} else {
// old style: sample one point at a time
cursorX.add(mouseX);
cursorY.add(mouseY);
- int max = 10 * FPSmod;
+ int max = (int) (10 * FPSmod);
if (cursorX.size() > max)
removeCount = cursorX.size() - max;
}
diff --git a/src/itdelatrisu/opsu/ui/MenuButton.java b/src/itdelatrisu/opsu/ui/MenuButton.java
index e7f964e0..1ac2fd9b 100644
--- a/src/itdelatrisu/opsu/ui/MenuButton.java
+++ b/src/itdelatrisu/opsu/ui/MenuButton.java
@@ -19,6 +19,8 @@
package itdelatrisu.opsu.ui;
import itdelatrisu.opsu.Utils;
+import itdelatrisu.opsu.ui.animations.AnimatedValue;
+import itdelatrisu.opsu.ui.animations.AnimationEquation;
import fluddokt.opsu.fake.*;
/*
@@ -66,11 +68,26 @@ public class MenuButton {
/** The hover actions for this button. */
private int hoverEffect = 0;
- /** The current and max scale of the button. */
- private float scale = 1f, hoverScale = 1.25f;
+ /** The hover animation duration, in milliseconds. */
+ private int animationDuration = 100;
- /** The current and base alpha level of the button. */
- private float alpha = 1f, baseAlpha = 0.75f;
+ /** The hover animation equation. */
+ private AnimationEquation animationEqn = AnimationEquation.LINEAR;
+
+ /** Whether the animation is advancing forwards (if advancing automatically). */
+ private boolean autoAnimationForward = true;
+
+ /** The scale of the button. */
+ private AnimatedValue scale;
+
+ /** The default max scale of the button. */
+ private static final float DEFAULT_SCALE_MAX = 1.25f;
+
+ /** The alpha level of the button. */
+ private AnimatedValue alpha;
+
+ /** The default base alpha level of the button. */
+ private static final float DEFAULT_ALPHA_BASE = 0.75f;
/** The scaled expansion direction for the button. */
private Expand dir = Expand.CENTER;
@@ -78,8 +95,11 @@ public class MenuButton {
/** Scaled expansion directions. */
public enum Expand { CENTER, UP, RIGHT, LEFT, DOWN, UP_RIGHT, UP_LEFT, DOWN_RIGHT, DOWN_LEFT; }
- /** The current and max rotation angles of the button. */
- private float angle = 0f, maxAngle = 30f;
+ /** The rotation angle of the button. */
+ private AnimatedValue angle;
+
+ /** The default max rotation angle of the button. */
+ private static final float DEFAULT_ANGLE_MAX = 30f;
/**
* Creates a new button from an Image.
@@ -195,15 +215,15 @@ public void draw(Color filter) {
float oldAlpha = image.getAlpha();
float oldAngle = image.getRotation();
if ((hoverEffect & EFFECT_EXPAND) > 0) {
- if (scale != 1f) {
- image = image.getScaledCopy(scale);
+ if (scale.getValue() != 1f) {
+ image = image.getScaledCopy(scale.getValue());
image.setAlpha(oldAlpha);
}
}
if ((hoverEffect & EFFECT_FADE) > 0)
- image.setAlpha(alpha);
+ image.setAlpha(alpha.getValue());
if ((hoverEffect & EFFECT_ROTATE) > 0)
- image.setRotation(angle);
+ image.setRotation(angle.getValue());
image.draw(x - xRadius, y - yRadius, filter);
if (image == this.img) {
image.setAlpha(oldAlpha);
@@ -220,9 +240,10 @@ public void draw(Color filter) {
imgR.draw(x + xRadius - imgR.getWidth(), y - yRadius, filter);
} else if ((hoverEffect & EFFECT_FADE) > 0) {
float a = image.getAlpha(), aL = imgL.getAlpha(), aR = imgR.getAlpha();
- image.setAlpha(alpha);
- imgL.setAlpha(alpha);
- imgR.setAlpha(alpha);
+ float currentAlpha = alpha.getValue();
+ image.setAlpha(currentAlpha);
+ imgL.setAlpha(currentAlpha);
+ imgR.setAlpha(currentAlpha);
image.draw(x - xRadius + imgL.getWidth(), y - yRadius, filter);
imgL.draw(x - xRadius, y - yRadius, filter);
imgR.draw(x + xRadius - imgR.getWidth(), y - yRadius, filter);
@@ -270,28 +291,63 @@ public boolean contains(float cx, float cy, float alpha) {
*/
public void resetHover() {
if ((hoverEffect & EFFECT_EXPAND) > 0) {
- this.scale = 1f;
+ scale.setTime(0);
setHoverRadius();
}
if ((hoverEffect & EFFECT_FADE) > 0)
- this.alpha = baseAlpha;
+ alpha.setTime(0);
if ((hoverEffect & EFFECT_ROTATE) > 0)
- this.angle = 0f;
+ angle.setTime(0);
+ autoAnimationForward = true;
}
/**
* Removes all hover effects that have been set for the button.
*/
- public void removeHoverEffects() { hoverEffect = 0; }
+ public void removeHoverEffects() {
+ this.hoverEffect = 0;
+ this.scale = null;
+ this.alpha = null;
+ this.angle = null;
+ autoAnimationForward = true;
+ }
+
+ /**
+ * Sets the hover animation duration.
+ * @param duration the duration, in milliseconds
+ */
+ public void setHoverAnimationDuration(int duration) {
+ this.animationDuration = duration;
+ if (scale != null)
+ scale.setDuration(duration);
+ if (alpha != null)
+ alpha.setDuration(duration);
+ if (angle != null)
+ angle.setDuration(duration);
+ }
+
+ /**
+ * Sets the hover animation equation.
+ * @param eqn the equation to use
+ */
+ public void setHoverAnimationEquation(AnimationEquation eqn) {
+ this.animationEqn = eqn;
+ if (scale != null)
+ scale.setEquation(eqn);
+ if (alpha != null)
+ alpha.setEquation(eqn);
+ if (angle != null)
+ angle.setEquation(eqn);
+ }
/**
* Sets the "expand" hover effect.
*/
- public void setHoverExpand() { hoverEffect |= EFFECT_EXPAND; }
+ public void setHoverExpand() { setHoverExpand(DEFAULT_SCALE_MAX, this.dir); }
/**
* Sets the "expand" hover effect.
- * @param scale the maximum scale factor (default 1.25f)
+ * @param scale the maximum scale factor
*/
public void setHoverExpand(float scale) { setHoverExpand(scale, this.dir); }
@@ -299,45 +355,45 @@ public void resetHover() {
* Sets the "expand" hover effect.
* @param dir the expansion direction
*/
- public void setHoverExpand(Expand dir) { setHoverExpand(this.hoverScale, dir); }
+ public void setHoverExpand(Expand dir) { setHoverExpand(DEFAULT_SCALE_MAX, dir); }
/**
* Sets the "expand" hover effect.
- * @param scale the maximum scale factor (default 1.25f)
+ * @param scale the maximum scale factor
* @param dir the expansion direction
*/
public void setHoverExpand(float scale, Expand dir) {
hoverEffect |= EFFECT_EXPAND;
- this.hoverScale = scale;
+ this.scale = new AnimatedValue(animationDuration, 1f, scale, animationEqn);
this.dir = dir;
}
/**
* Sets the "fade" hover effect.
*/
- public void setHoverFade() { hoverEffect |= EFFECT_FADE; }
+ public void setHoverFade() { setHoverFade(DEFAULT_ALPHA_BASE); }
/**
* Sets the "fade" hover effect.
- * @param baseAlpha the base alpha level to fade in from (default 0.7f)
+ * @param baseAlpha the base alpha level to fade in from
*/
public void setHoverFade(float baseAlpha) {
hoverEffect |= EFFECT_FADE;
- this.baseAlpha = baseAlpha;
+ this.alpha = new AnimatedValue(animationDuration, baseAlpha, 1f, animationEqn);
}
/**
* Sets the "rotate" hover effect.
*/
- public void setHoverRotate() { hoverEffect |= EFFECT_ROTATE; }
+ public void setHoverRotate() { setHoverRotate(DEFAULT_ANGLE_MAX); }
/**
* Sets the "rotate" hover effect.
- * @param maxAngle the maximum rotation angle, in degrees (default 30f)
+ * @param maxAngle the maximum rotation angle, in degrees
*/
public void setHoverRotate(float maxAngle) {
hoverEffect |= EFFECT_ROTATE;
- this.maxAngle = maxAngle;
+ this.angle = new AnimatedValue(animationDuration, 0f, maxAngle, animationEqn);
}
/**
@@ -374,45 +430,53 @@ public void hoverUpdate(int delta, boolean isHover) {
if (hoverEffect == 0)
return;
+ int d = delta * (isHover ? 1 : -1);
+
// scale the button
if ((hoverEffect & EFFECT_EXPAND) > 0) {
- int sign = 0;
- if (isHover && scale < hoverScale)
- sign = 1;
- else if (!isHover && scale > 1f)
- sign = -1;
- if (sign != 0) {
- scale = Utils.getBoundedValue(scale, sign * (hoverScale - 1f) * delta / 100f, 1, hoverScale);
+ if (scale.update(d))
setHoverRadius();
- }
}
// fade the button
- if ((hoverEffect & EFFECT_FADE) > 0) {
- int sign = 0;
- if (isHover && alpha < 1f)
- sign = 1;
- else if (!isHover && alpha > baseAlpha)
- sign = -1;
- if (sign != 0)
- alpha = Utils.getBoundedValue(alpha, sign * (1f - baseAlpha) * delta / 200f, baseAlpha, 1f);
- }
+ if ((hoverEffect & EFFECT_FADE) > 0)
+ alpha.update(d);
// rotate the button
- if ((hoverEffect & EFFECT_ROTATE) > 0) {
- int sign = 0;
- boolean right = (maxAngle > 0);
- if (isHover && angle != maxAngle)
- sign = (right) ? 1 : -1;
- else if (!isHover && angle != 0)
- sign = (right) ? -1 : 1;
- if (sign != 0) {
- float diff = sign * Math.abs(maxAngle) * delta / 125f;
- angle = (right) ?
- Utils.getBoundedValue(angle, diff, 0, maxAngle) :
- Utils.getBoundedValue(angle, diff, maxAngle, 0);
+ if ((hoverEffect & EFFECT_ROTATE) > 0)
+ angle.update(d);
+ }
+
+ /**
+ * Automatically advances the hover animation in a loop.
+ * @param delta the delta interval
+ * @param reverseAtEnd whether to reverse or restart the animation upon reaching the end
+ */
+ public void autoHoverUpdate(int delta, boolean reverseAtEnd) {
+ if (hoverEffect == 0)
+ return;
+
+ int time = ((hoverEffect & EFFECT_EXPAND) > 0) ? scale.getTime() :
+ ((hoverEffect & EFFECT_FADE) > 0) ? alpha.getTime() :
+ ((hoverEffect & EFFECT_ROTATE) > 0) ? angle.getTime() : -1;
+ if (time == -1)
+ return;
+
+ int d = delta * (autoAnimationForward ? 1 : -1);
+ if (Utils.getBoundedValue(time, d, 0, animationDuration) == time) {
+ if (reverseAtEnd)
+ autoAnimationForward = !autoAnimationForward;
+ else {
+ if ((hoverEffect & EFFECT_EXPAND) > 0)
+ scale.setTime(0);
+ if ((hoverEffect & EFFECT_FADE) > 0)
+ alpha.setTime(0);
+ if ((hoverEffect & EFFECT_ROTATE) > 0)
+ angle.setTime(0);
}
}
+
+ hoverUpdate(delta, autoAnimationForward);
}
/**
@@ -425,10 +489,11 @@ private void setHoverRadius() {
image = anim.getCurrentFrame();
int xOffset = 0, yOffset = 0;
+ float currentScale = scale.getValue();
if (dir != Expand.CENTER) {
// offset by difference between normal/scaled image dimensions
- xOffset = (int) ((scale - 1f) * image.getWidth());
- yOffset = (int) ((scale - 1f) * image.getHeight());
+ xOffset = (int) ((currentScale - 1f) * image.getWidth());
+ yOffset = (int) ((currentScale - 1f) * image.getHeight());
if (dir == Expand.UP || dir == Expand.DOWN)
xOffset = 0; // no horizontal offset
if (dir == Expand.RIGHT || dir == Expand.LEFT)
@@ -438,7 +503,7 @@ private void setHoverRadius() {
if (dir == Expand.DOWN || dir == Expand.DOWN_LEFT || dir == Expand.DOWN_RIGHT)
yOffset *= -1; // flip y for down
}
- this.xRadius = ((image.getWidth() * scale) + xOffset) / 2f;
- this.yRadius = ((image.getHeight() * scale) + yOffset) / 2f;
+ this.xRadius = ((image.getWidth() * currentScale) + xOffset) / 2f;
+ this.yRadius = ((image.getHeight() * currentScale) + yOffset) / 2f;
}
}
diff --git a/src/itdelatrisu/opsu/ui/UI.java b/src/itdelatrisu/opsu/ui/UI.java
index 2a513e2c..ec119ec2 100644
--- a/src/itdelatrisu/opsu/ui/UI.java
+++ b/src/itdelatrisu/opsu/ui/UI.java
@@ -28,6 +28,8 @@
import itdelatrisu.opsu.audio.SoundController;
import itdelatrisu.opsu.beatmap.BeatmapParser;
import itdelatrisu.opsu.replay.ReplayImporter;
+import itdelatrisu.opsu.ui.animations.AnimatedValue;
+import itdelatrisu.opsu.ui.animations.AnimationEquation;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
@@ -74,11 +76,8 @@ public class UI {
/** Whether or not to check the current tooltip for line breaks. */
private static boolean tooltipNewlines;
- /** The current tooltip timer. */
- private static int tooltipTimer = -1;
-
- /** Duration, in milliseconds, to fade tooltips. */
- private static final int TOOLTIP_FADE_TIME = 200;
+ /** The alpha level of the current tooltip (if any). */
+ private static AnimatedValue tooltipAlpha = new AnimatedValue(200, 0f, 1f, AnimationEquation.LINEAR);
// game-related variables
private static GameContainer container;
@@ -110,6 +109,8 @@ public static void init(GameContainer container, StateBasedGame game)
Image back = GameImage.MENU_BACK.getImage();
backButton = new MenuButton(back, back.getWidth() / 2f, container.getHeight() - (back.getHeight() / 2f));
}
+ backButton.setHoverAnimationDuration(350);
+ backButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_BACK);
backButton.setHoverExpand(MenuButton.Expand.UP_RIGHT);
}
@@ -121,8 +122,7 @@ public static void update(int delta) {
cursor.update(delta);
updateVolumeDisplay(delta);
updateBarNotification(delta);
- if (tooltipTimer > 0)
- tooltipTimer -= delta;
+ tooltipAlpha.update(-delta);
}
/**
@@ -364,12 +364,7 @@ public static void updateTooltip(int delta, String s, boolean newlines) {
if (s != null) {
tooltip = s;
tooltipNewlines = newlines;
- if (tooltipTimer <= 0)
- tooltipTimer = delta;
- else
- tooltipTimer += delta * 2;
- if (tooltipTimer > TOOLTIP_FADE_TIME)
- tooltipTimer = TOOLTIP_FADE_TIME;
+ tooltipAlpha.update(delta * 2);
}
}
@@ -379,7 +374,7 @@ public static void updateTooltip(int delta, String s, boolean newlines) {
* @param g the graphics context
*/
public static void drawTooltip(Graphics g) {
- if (tooltipTimer <= 0 || tooltip == null)
+ if (tooltipAlpha.getTime() == 0 || tooltip == null)
return;
int containerWidth = container.getWidth(), containerHeight = container.getHeight();
@@ -412,7 +407,7 @@ else if (y < margin)
y = margin;
// draw tooltip text inside a filled rectangle
- float alpha = (float) tooltipTimer / TOOLTIP_FADE_TIME;
+ float alpha = tooltipAlpha.getValue();
float oldAlpha = Utils.COLOR_BLACK_ALPHA.a;
Utils.COLOR_BLACK_ALPHA.a = alpha;
g.setColor(Utils.COLOR_BLACK_ALPHA);
@@ -434,7 +429,7 @@ else if (y < margin)
* Resets the tooltip.
*/
public static void resetTooltip() {
- tooltipTimer = -1;
+ tooltipAlpha.setTime(0);
tooltip = null;
}
diff --git a/src/itdelatrisu/opsu/ui/animations/AnimatedValue.java b/src/itdelatrisu/opsu/ui/animations/AnimatedValue.java
new file mode 100644
index 00000000..5e1636f9
--- /dev/null
+++ b/src/itdelatrisu/opsu/ui/animations/AnimatedValue.java
@@ -0,0 +1,124 @@
+/*
+ * opsu! - an open-source osu! client
+ * Copyright (C) 2014, 2015 Jeffrey Han
+ *
+ * opsu! is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * opsu! is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with opsu!. If not, see .
+ */
+
+package itdelatrisu.opsu.ui.animations;
+
+import itdelatrisu.opsu.Utils;
+
+/**
+ * Utility class for updating a value using an animation equation.
+ */
+public class AnimatedValue {
+ /** The animation duration, in milliseconds. */
+ private int duration;
+
+ /** The current time, in milliseconds. */
+ private int time;
+
+ /** The base value. */
+ private float base;
+
+ /** The maximum difference from the base value. */
+ private float diff;
+
+ /** The current value. */
+ private float value;
+
+ /** The animation equation to use. */
+ private AnimationEquation eqn;
+
+ /**
+ * Constructor.
+ * @param duration the total animation duration, in milliseconds
+ * @param min the minimum value
+ * @param max the maximum value
+ * @param eqn the animation equation to use
+ */
+ public AnimatedValue(int duration, float min, float max, AnimationEquation eqn) {
+ this.time = 0;
+ this.duration = duration;
+ this.value = min;
+ this.base = min;
+ this.diff = max - min;
+ this.eqn = eqn;
+ }
+
+ /**
+ * Returns the current value.
+ */
+ public float getValue() { return value; }
+
+ /**
+ * Returns the current animation time, in milliseconds.
+ */
+ public int getTime() { return time; }
+
+ /**
+ * Sets the animation time manually.
+ * @param time the new time, in milliseconds
+ */
+ public void setTime(int time) {
+ this.time = Utils.clamp(time, 0, duration);
+ updateValue();
+ }
+
+ /**
+ * Sets the animation duration.
+ * @param duration the new duration, in milliseconds
+ */
+ public void setDuration(int duration) {
+ this.duration = duration;
+ int newTime = Utils.clamp(time, 0, duration);
+ if (time != newTime) {
+ this.time = newTime;
+ updateValue();
+ }
+ }
+
+ /**
+ * Sets the animation equation to use.
+ * @param eqn the new equation
+ */
+ public void setEquation(AnimationEquation eqn) {
+ this.eqn = eqn;
+ updateValue();
+ }
+
+ /**
+ * Updates the animation by a delta interval.
+ * @param delta the delta interval since the last call.
+ * @return true if an update was applied, false if the animation was not updated
+ */
+ public boolean update(int delta) {
+ int newTime = Utils.getBoundedValue(time, delta, 0, duration);
+ if (time != newTime) {
+ this.time = newTime;
+ updateValue();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Recalculates the value by applying the animation equation with the current time.
+ */
+ private void updateValue() {
+ float t = eqn.calc((float) time / duration);
+ this.value = base + (t * diff);
+ }
+}
diff --git a/src/itdelatrisu/opsu/ui/animations/AnimationEquation.java b/src/itdelatrisu/opsu/ui/animations/AnimationEquation.java
new file mode 100644
index 00000000..a30c5b30
--- /dev/null
+++ b/src/itdelatrisu/opsu/ui/animations/AnimationEquation.java
@@ -0,0 +1,309 @@
+/*
+ * opsu! - an open-source osu! client
+ * Copyright (C) 2014, 2015 Jeffrey Han
+ *
+ * opsu! is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * opsu! is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with opsu!. If not, see .
+ */
+
+package itdelatrisu.opsu.ui.animations;
+
+
+/*
+ * These equations are copyright (c) 2001 Robert Penner, all rights reserved,
+ * and are open source under the BSD License.
+ * http://www.opensource.org/licenses/bsd-license.php
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the author nor the names of contributors may be used
+ * to endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Easing functions for animations.
+ *
+ * @author Robert Penner (http://robertpenner.com/easing/)
+ * @author CharlotteGore (https://github.com/CharlotteGore/functional-easing)
+ */
+public enum AnimationEquation {
+ /* Linear */
+ LINEAR {
+ @Override
+ public float calc(float t) { return t; }
+ },
+
+ /* Quadratic */
+ IN_QUAD {
+ @Override
+ public float calc(float t) { return t * t; }
+ },
+ OUT_QUAD {
+ @Override
+ public float calc(float t) { return -1 * t * (t - 2); }
+ },
+ IN_OUT_QUAD {
+ @Override
+ public float calc(float t) {
+ t = t * 2;
+ if (t < 1)
+ return 0.5f * t * t;
+ t = t - 1;
+ return -0.5f * (t * (t - 2) - 1);
+ }
+ },
+
+ /* Cubic */
+ IN_CUBIC {
+ @Override
+ public float calc(float t) { return t * t * t; }
+ },
+ OUT_CUBIC {
+ @Override
+ public float calc(float t) {
+ t = t - 1;
+ return t * t * t + 1;
+ }
+ },
+ IN_OUT_CUBIC {
+ @Override
+ public float calc(float t) {
+ t = t * 2;
+ if (t < 1)
+ return 0.5f * t * t * t;
+ t = t - 2;
+ return 0.5f * (t * t * t + 2);
+ }
+ },
+
+ /* Quartic */
+ IN_QUART {
+ @Override
+ public float calc(float t) { return t * t * t * t; }
+ },
+ OUT_QUART {
+ @Override
+ public float calc(float t) {
+ t = t - 1;
+ return -1 * (t * t * t * t - 1);
+ }
+ },
+ IN_OUT_QUART {
+ @Override
+ public float calc(float t) {
+ t = t * 2;
+ if (t < 1)
+ return 0.5f * t * t * t * t;
+ t = t - 2;
+ return -0.5f * (t * t * t * t - 2);
+ }
+ },
+
+ /* Quintic */
+ IN_QUINT {
+ @Override
+ public float calc(float t) { return t * t * t * t * t; }
+ },
+ OUT_QUINT {
+ @Override
+ public float calc(float t) {
+ t = t - 1;
+ return (t * t * t * t * t + 1);
+ }
+ },
+ IN_OUT_QUINT {
+ @Override
+ public float calc(float t) {
+ t = t * 2;
+ if (t < 1)
+ return 0.5f * t * t * t * t * t;
+ t = t - 2;
+ return 0.5f * (t * t * t * t * t + 2);
+ }
+ },
+
+ /* Sine */
+ IN_SINE {
+ @Override
+ public float calc(float t) { return -1 * (float) Math.cos(t * (Math.PI / 2)) + 1; }
+ },
+ OUT_SINE {
+ @Override
+ public float calc(float t) { return (float) Math.sin(t * (Math.PI / 2)); }
+ },
+ IN_OUT_SINE {
+ @Override
+ public float calc(float t) { return (float) (Math.cos(Math.PI * t) - 1) / -2; }
+ },
+
+ /* Exponential */
+ IN_EXPO {
+ @Override
+ public float calc(float t) { return (t == 0) ? 0 : (float) Math.pow(2, 10 * (t - 1)); }
+ },
+ OUT_EXPO {
+ @Override
+ public float calc(float t) { return (t == 1) ? 1 : (float) -Math.pow(2, -10 * t) + 1; }
+ },
+ IN_OUT_EXPO {
+ @Override
+ public float calc(float t) {
+ if (t == 0 || t == 1)
+ return t;
+ t = t * 2;
+ if (t < 1)
+ return 0.5f * (float) Math.pow(2, 10 * (t - 1));
+ t = t - 1;
+ return 0.5f * ((float) -Math.pow(2, -10 * t) + 2);
+ }
+ },
+
+ /* Circular */
+ IN_CIRC {
+ @Override
+ public float calc(float t) { return -1 * ((float) Math.sqrt(1 - t * t) - 1); }
+ },
+ OUT_CIRC {
+ @Override
+ public float calc(float t) {
+ t = t - 1;
+ return (float) Math.sqrt(1 - t * t);
+ }
+ },
+ IN_OUT_CIRC {
+ @Override
+ public float calc(float t) {
+ t = t * 2;
+ if (t < 1)
+ return -0.5f * ((float) Math.sqrt(1 - t * t) - 1);
+ t = t - 2;
+ return 0.5f * ((float) Math.sqrt(1 - t * t) + 1);
+ }
+ },
+
+ /* Back */
+ IN_BACK {
+ @Override
+ public float calc(float t) { return t * t * ((OVERSHOOT + 1) * t - OVERSHOOT); }
+ },
+ OUT_BACK {
+ @Override
+ public float calc(float t) {
+ t = t - 1;
+ return t * t * ((OVERSHOOT + 1) * t + OVERSHOOT) + 1;
+ }
+ },
+ IN_OUT_BACK {
+ @Override
+ public float calc(float t) {
+ float overshoot = OVERSHOOT * 1.525f;
+ t = t * 2;
+ if (t < 1)
+ return 0.5f * (t * t * ((overshoot + 1) * t - overshoot));
+ t = t - 2;
+ return 0.5f * (t * t * ((overshoot + 1) * t + overshoot) + 2);
+ }
+ },
+
+ /* Bounce */
+ IN_BOUNCE {
+ @Override
+ public float calc(float t) { return 1 - OUT_BOUNCE.calc(1 - t); }
+ },
+ OUT_BOUNCE {
+ @Override
+ public float calc(float t) {
+ if (t < 0.36363636f)
+ return 7.5625f * t * t;
+ else if (t < 0.72727273f) {
+ t = t - 0.54545454f;
+ return 7.5625f * t * t + 0.75f;
+ } else if (t < 0.90909091f) {
+ t = t - 0.81818182f;
+ return 7.5625f * t * t + 0.9375f;
+ } else {
+ t = t - 0.95454546f;
+ return 7.5625f * t * t + 0.984375f;
+ }
+ }
+ },
+ IN_OUT_BOUNCE {
+ @Override
+ public float calc(float t) {
+ if (t < 0.5f)
+ return IN_BOUNCE.calc(t * 2) * 0.5f;
+ return OUT_BOUNCE.calc(t * 2 - 1) * 0.5f + 0.5f;
+ }
+ },
+
+ /* Elastic */
+ IN_ELASTIC {
+ @Override
+ public float calc(float t) {
+ if (t == 0 || t == 1)
+ return t;
+ float period = 0.3f;
+ t = t - 1;
+ return -((float) Math.pow(2, 10 * t) * (float) Math.sin(((t - period / 4) * (Math.PI * 2)) / period));
+ }
+ },
+ OUT_ELASTIC {
+ @Override
+ public float calc(float t) {
+ if (t == 0 || t == 1)
+ return t;
+ float period = 0.3f;
+ return (float) Math.pow(2, -10 * t) * (float) Math.sin((t - period / 4) * (Math.PI * 2) / period) + 1;
+ }
+ },
+ IN_OUT_ELASTIC {
+ @Override
+ public float calc(float t) {
+ if (t == 0 || t == 1)
+ return t;
+ float period = 0.44999996f;
+ t = t * 2 - 1;
+ if (t < 0)
+ return -0.5f * ((float) Math.pow(2, 10 * t) * (float) Math.sin((t - period / 4) * (Math.PI * 2) / period));
+ return (float) Math.pow(2, -10 * t) * (float) Math.sin((t - period / 4) * (Math.PI * 2) / period) * 0.5f + 1;
+ }
+ };
+
+ /** Overshoot constant for "back" easings. */
+ private static final float OVERSHOOT = 1.70158f;
+
+ /**
+ * Calculates a new {@code t} value using the animation equation.
+ * @param t the raw {@code t} value [0,1]
+ * @return the new {@code t} value [0,1]
+ */
+ public abstract float calc(float t);
+}
diff --git a/src/org/newdawn/slick/Image.java b/src/org/newdawn/slick/Image.java
index 1737a808..38d9e8be 100644
--- a/src/org/newdawn/slick/Image.java
+++ b/src/org/newdawn/slick/Image.java
@@ -595,6 +595,7 @@ public void draw(float x, float y) {
* @param y The y location to draw the image at
* @param filter The color to filter with when drawing
*/
+ @Override
public void draw(float x, float y, Color filter) {
init();
draw(x,y,width,height, filter);
@@ -719,6 +720,7 @@ public void draw(float x,float y,float scale,Color filter) {
* @param height
* The height to render the image at
*/
+ @Override
public void draw(float x,float y,float width,float height) {
init();
draw(x,y,width,height,Color.white);
@@ -797,6 +799,7 @@ public void drawSheared(float x,float y, float hshear, float vshear, Color filte
* @param height The height to render the image at
* @param filter The color to filter with while drawing
*/
+ @Override
public void draw(float x,float y,float width,float height,Color filter) {
if (alpha != 1) {
if (filter == null) {
diff --git a/src/org/newdawn/slick/Input.java b/src/org/newdawn/slick/Input.java
index 1066ca34..c74eff95 100644
--- a/src/org/newdawn/slick/Input.java
+++ b/src/org/newdawn/slick/Input.java
@@ -1233,7 +1233,7 @@ public void poll(int width, int height) {
}
while (Mouse.next()) {
- if (Mouse.getEventButton() >= 0) {
+ if (Mouse.getEventButton() >= 0 && Mouse.getEventButton() < mousePressed.length) {
if (Mouse.getEventButtonState()) {
consumed = false;
mousePressed[Mouse.getEventButton()] = true;