Merge remote-tracking branch 'remotes/original/master' into upstream
# Conflicts: # src/itdelatrisu/opsu/Container.java # src/itdelatrisu/opsu/GameData.java # src/itdelatrisu/opsu/Options.java # src/itdelatrisu/opsu/audio/MusicController.java # src/itdelatrisu/opsu/objects/Circle.java # src/itdelatrisu/opsu/objects/Slider.java # src/itdelatrisu/opsu/render/CurveRenderState.java # src/itdelatrisu/opsu/states/Game.java # src/itdelatrisu/opsu/states/MainMenu.java # src/itdelatrisu/opsu/states/SongMenu.java # src/itdelatrisu/opsu/ui/Colors.java # src/itdelatrisu/opsu/ui/MenuButton.java
This commit is contained in:
commit
a6540044b6
|
@ -31,5 +31,6 @@ The following projects were referenced in creating opsu!:
|
||||||
|
|
||||||
Theme Song
|
Theme Song
|
||||||
----------
|
----------
|
||||||
The theme song is "On the Bach" by Jingle Punks, from the [YouTube Audio Library]
|
Rainbows - Kevin MacLeod (incompetech.com)
|
||||||
(https://www.youtube.com/audiolibrary/music).
|
Licensed under Creative Commons: By Attribution 3.0 License
|
||||||
|
http://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
BIN
res/theme.mp3
Normal file
BIN
res/theme.mp3
Normal file
Binary file not shown.
BIN
res/theme.ogg
BIN
res/theme.ogg
Binary file not shown.
|
@ -21,7 +21,9 @@ package itdelatrisu.opsu;
|
||||||
import itdelatrisu.opsu.audio.MusicController;
|
import itdelatrisu.opsu.audio.MusicController;
|
||||||
import itdelatrisu.opsu.audio.SoundController;
|
import itdelatrisu.opsu.audio.SoundController;
|
||||||
import itdelatrisu.opsu.beatmap.Beatmap;
|
import itdelatrisu.opsu.beatmap.Beatmap;
|
||||||
|
import itdelatrisu.opsu.beatmap.BeatmapGroup;
|
||||||
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
||||||
|
import itdelatrisu.opsu.beatmap.BeatmapSortOrder;
|
||||||
import itdelatrisu.opsu.beatmap.BeatmapWatchService;
|
import itdelatrisu.opsu.beatmap.BeatmapWatchService;
|
||||||
import itdelatrisu.opsu.downloads.DownloadList;
|
import itdelatrisu.opsu.downloads.DownloadList;
|
||||||
import itdelatrisu.opsu.downloads.Updater;
|
import itdelatrisu.opsu.downloads.Updater;
|
||||||
|
@ -38,12 +40,8 @@ import org.newdawn.slick.opengl.InternalTextureLoader;
|
||||||
* AppGameContainer extension that sends critical errors to ErrorHandler.
|
* AppGameContainer extension that sends critical errors to ErrorHandler.
|
||||||
*/
|
*/
|
||||||
public class Container extends AppGameContainer {
|
public class Container extends AppGameContainer {
|
||||||
/** SlickException causing game failure. */
|
/** Exception causing game failure. */
|
||||||
protected SlickException e = null;
|
protected Exception e = null;
|
||||||
|
|
||||||
private Exception anyException = null;
|
|
||||||
|
|
||||||
public static Container instance;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new container wrapping a game
|
* Create a new container wrapping a game
|
||||||
|
@ -53,9 +51,19 @@ public class Container extends AppGameContainer {
|
||||||
*/
|
*/
|
||||||
public Container(Game game) throws SlickException {
|
public Container(Game game) throws SlickException {
|
||||||
super(game);
|
super(game);
|
||||||
instance = this;
|
}
|
||||||
width = this.getWidth();
|
|
||||||
height = this.getHeight();
|
/**
|
||||||
|
* Create a new container wrapping a game
|
||||||
|
*
|
||||||
|
* @param game The game to be wrapped
|
||||||
|
* @param width The width of the display required
|
||||||
|
* @param height The height of the display required
|
||||||
|
* @param fullscreen True if we want fullscreen mode
|
||||||
|
* @throws SlickException Indicates a failure to initialise the display
|
||||||
|
*/
|
||||||
|
public Container(Game game, int width, int height, boolean fullscreen) throws SlickException {
|
||||||
|
super(game, width, height, fullscreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -67,19 +75,23 @@ public class Container extends AppGameContainer {
|
||||||
while (running())
|
while (running())
|
||||||
gameLoop();
|
gameLoop();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
anyException = e;
|
this.e = e;
|
||||||
} finally {
|
}
|
||||||
|
|
||||||
// destroy the game container
|
// destroy the game container
|
||||||
|
try {
|
||||||
close_sub();
|
close_sub();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (this.e == null) // suppress if caused by a previous exception
|
||||||
|
this.e = e;
|
||||||
|
}
|
||||||
destroy();
|
destroy();
|
||||||
|
|
||||||
if (anyException != null) {
|
// report any critical errors
|
||||||
ErrorHandler.error("Something bad happend while playing", anyException, true);
|
if (e != null) {
|
||||||
anyException = null;
|
|
||||||
} else if (e != null) {
|
|
||||||
ErrorHandler.error(null, e, true);
|
ErrorHandler.error(null, e, true);
|
||||||
e = null;
|
e = null;
|
||||||
}
|
forceExit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceExit) {
|
if (forceExit) {
|
||||||
|
@ -118,9 +130,7 @@ public class Container extends AppGameContainer {
|
||||||
Options.saveOptions();
|
Options.saveOptions();
|
||||||
|
|
||||||
// reset cursor
|
// reset cursor
|
||||||
if (UI.getCursor() != null) {
|
|
||||||
UI.getCursor().reset();
|
UI.getCursor().reset();
|
||||||
}
|
|
||||||
|
|
||||||
// destroy images
|
// destroy images
|
||||||
InternalTextureLoader.get().clear();
|
InternalTextureLoader.get().clear();
|
||||||
|
@ -137,6 +147,8 @@ public class Container extends AppGameContainer {
|
||||||
SoundController.stopTrack();
|
SoundController.stopTrack();
|
||||||
|
|
||||||
// reset BeatmapSetList data
|
// reset BeatmapSetList data
|
||||||
|
BeatmapGroup.set(BeatmapGroup.ALL);
|
||||||
|
BeatmapSortOrder.set(BeatmapSortOrder.TITLE);
|
||||||
if (BeatmapSetList.get() != null)
|
if (BeatmapSetList.get() != null)
|
||||||
BeatmapSetList.get().reset();
|
BeatmapSetList.get().reset();
|
||||||
|
|
||||||
|
|
|
@ -151,13 +151,13 @@ public class GameData {
|
||||||
HIT_SLIDER10 = 7,
|
HIT_SLIDER10 = 7,
|
||||||
HIT_SLIDER30 = 8,
|
HIT_SLIDER30 = 8,
|
||||||
HIT_MAX = 9, // not a hit result
|
HIT_MAX = 9, // not a hit result
|
||||||
HIT_SLIDER_INITIAL = 10, // not a hit result
|
HIT_SLIDER_REPEAT = 10, // not a hit result
|
||||||
HIT_SLIDER_REPEAT = 11; // not a hit result
|
HIT_ANIMATION_RESULT = 11; // not a hit result
|
||||||
|
|
||||||
/** Hit result-related images (indexed by HIT_* constants). */
|
/** Hit result-related images (indexed by HIT_* constants to HIT_MAX). */
|
||||||
private Image[] hitResults;
|
private Image[] hitResults;
|
||||||
|
|
||||||
/** Counts of each hit result so far. */
|
/** Counts of each hit result so far (indexed by HIT_* constants to HIT_MAX). */
|
||||||
private int[] hitResultCount;
|
private int[] hitResultCount;
|
||||||
|
|
||||||
/** Total objects including slider hits/ticks (for determining Full Combo status). */
|
/** Total objects including slider hits/ticks (for determining Full Combo status). */
|
||||||
|
@ -193,7 +193,7 @@ public class GameData {
|
||||||
/** Current x coordinate of the combo burst image (for sliding animation). */
|
/** Current x coordinate of the combo burst image (for sliding animation). */
|
||||||
private float comboBurstX;
|
private float comboBurstX;
|
||||||
|
|
||||||
/** Time offsets for obtaining each hit result (indexed by HIT_* constants). */
|
/** Time offsets for obtaining each hit result (indexed by HIT_* constants to HIT_MAX). */
|
||||||
private int[] hitResultOffset;
|
private int[] hitResultOffset;
|
||||||
|
|
||||||
/** List of hit result objects associated with hit objects. */
|
/** List of hit result objects associated with hit objects. */
|
||||||
|
@ -557,10 +557,11 @@ public class GameData {
|
||||||
* @param x the starting x coordinate
|
* @param x the starting x coordinate
|
||||||
* @param y the y coordinate
|
* @param y the y coordinate
|
||||||
* @param scale the scale to apply
|
* @param scale the scale to apply
|
||||||
|
* @param alpha the alpha level
|
||||||
* @param fixedsize the width to use for all symbols
|
* @param fixedsize the width to use for all symbols
|
||||||
* @param rightAlign align right (true) or left (false)
|
* @param rightAlign align right (true) or left (false)
|
||||||
*/
|
*/
|
||||||
public void drawFixedSizeSymbolString(String str, float x, float y, float scale, float fixedsize, boolean rightAlign) {
|
public void drawFixedSizeSymbolString(String str, float x, float y, float scale, float alpha, float fixedsize, boolean rightAlign) {
|
||||||
char[] c = str.toCharArray();
|
char[] c = str.toCharArray();
|
||||||
float cx = x;
|
float cx = x;
|
||||||
if (rightAlign) {
|
if (rightAlign) {
|
||||||
|
@ -569,14 +570,18 @@ public class GameData {
|
||||||
if (scale != 1.0f)
|
if (scale != 1.0f)
|
||||||
digit = digit.getScaledCopy(scale);
|
digit = digit.getScaledCopy(scale);
|
||||||
cx -= fixedsize;
|
cx -= fixedsize;
|
||||||
|
digit.setAlpha(alpha);
|
||||||
digit.draw(cx + (fixedsize - digit.getWidth()) / 2, y);
|
digit.draw(cx + (fixedsize - digit.getWidth()) / 2, y);
|
||||||
|
digit.setAlpha(1f);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < c.length; i++) {
|
for (int i = 0; i < c.length; i++) {
|
||||||
Image digit = getScoreSymbolImage(c[i]);
|
Image digit = getScoreSymbolImage(c[i]);
|
||||||
if (scale != 1.0f)
|
if (scale != 1.0f)
|
||||||
digit = digit.getScaledCopy(scale);
|
digit = digit.getScaledCopy(scale);
|
||||||
|
digit.setAlpha(alpha);
|
||||||
digit.draw(cx + (fixedsize - digit.getWidth()) / 2, y);
|
digit.draw(cx + (fixedsize - digit.getWidth()) / 2, y);
|
||||||
|
digit.setAlpha(1f);
|
||||||
cx += fixedsize;
|
cx += fixedsize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -589,9 +594,10 @@ public class GameData {
|
||||||
* @param g the graphics context
|
* @param g the graphics context
|
||||||
* @param breakPeriod if true, will not draw scorebar and combo elements, and will draw grade
|
* @param breakPeriod if true, will not draw scorebar and combo elements, and will draw grade
|
||||||
* @param firstObject true if the first hit object's start time has not yet passed
|
* @param firstObject true if the first hit object's start time has not yet passed
|
||||||
|
* @param alpha the alpha level at which to render all elements (except the hit error bar)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public void drawGameElements(Graphics g, boolean breakPeriod, boolean firstObject) {
|
public void drawGameElements(Graphics g, boolean breakPeriod, boolean firstObject, float alpha) {
|
||||||
boolean relaxAutoPilot = (GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive());
|
boolean relaxAutoPilot = (GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive());
|
||||||
int margin = (int) (width * 0.008f);
|
int margin = (int) (width * 0.008f);
|
||||||
float uiScale = GameImage.getUIscale();
|
float uiScale = GameImage.getUIscale();
|
||||||
|
@ -599,14 +605,14 @@ public class GameData {
|
||||||
// score
|
// score
|
||||||
if (!relaxAutoPilot)
|
if (!relaxAutoPilot)
|
||||||
drawFixedSizeSymbolString((scoreDisplay < 100000000) ? String.format("%08d", scoreDisplay) : Long.toString(scoreDisplay),
|
drawFixedSizeSymbolString((scoreDisplay < 100000000) ? String.format("%08d", scoreDisplay) : Long.toString(scoreDisplay),
|
||||||
width - margin, 0, 1.0f, getScoreSymbolImage('0').getWidth() - 2, true);
|
width - margin, 0, 1f, alpha, getScoreSymbolImage('0').getWidth() - 2, true);
|
||||||
|
|
||||||
// score percentage
|
// score percentage
|
||||||
int symbolHeight = getScoreSymbolImage('0').getHeight();
|
int symbolHeight = getScoreSymbolImage('0').getHeight();
|
||||||
if (!relaxAutoPilot)
|
if (!relaxAutoPilot)
|
||||||
drawSymbolString(
|
drawSymbolString(
|
||||||
String.format((scorePercentDisplay < 10f) ? "0%.2f%%" : "%.2f%%", scorePercentDisplay),
|
String.format((scorePercentDisplay < 10f) ? "0%.2f%%" : "%.2f%%", scorePercentDisplay),
|
||||||
width - margin, symbolHeight, 0.60f, 1f, true);
|
width - margin, symbolHeight, 0.60f, alpha, true);
|
||||||
|
|
||||||
// map progress circle
|
// map progress circle
|
||||||
Beatmap beatmap = MusicController.getBeatmap();
|
Beatmap beatmap = MusicController.getBeatmap();
|
||||||
|
@ -620,23 +626,27 @@ public class GameData {
|
||||||
getScoreSymbolImage('%').getWidth()
|
getScoreSymbolImage('%').getWidth()
|
||||||
) * 0.60f - circleDiameter);
|
) * 0.60f - circleDiameter);
|
||||||
if (!relaxAutoPilot) {
|
if (!relaxAutoPilot) {
|
||||||
|
float oldWhiteAlpha = Colors.WHITE_ALPHA.a;
|
||||||
|
Colors.WHITE_ALPHA.a = alpha;
|
||||||
g.setAntiAlias(true);
|
g.setAntiAlias(true);
|
||||||
g.setLineWidth(2f);
|
g.setLineWidth(2f);
|
||||||
g.setColor(Color.white);
|
g.setColor(Colors.WHITE_ALPHA);
|
||||||
g.drawOval(circleX, symbolHeight, circleDiameter, circleDiameter);
|
g.drawOval(circleX, symbolHeight, circleDiameter, circleDiameter);
|
||||||
if (trackPosition > firstObjectTime) {
|
if (trackPosition > firstObjectTime) {
|
||||||
// map progress (white)
|
// map progress (white)
|
||||||
g.fillArc(circleX, symbolHeight, circleDiameter, circleDiameter,
|
float progress = Math.min((float) (trackPosition - firstObjectTime) / (beatmap.endTime - firstObjectTime), 1f);
|
||||||
-90, -90 + (int) (360f * (trackPosition - firstObjectTime) / (beatmap.endTime - firstObjectTime))
|
g.fillArc(circleX, symbolHeight, circleDiameter, circleDiameter, -90, -90 + (int) (360f * progress));
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// lead-in time (yellow)
|
// lead-in time (yellow)
|
||||||
|
float progress = (float) trackPosition / firstObjectTime;
|
||||||
|
float oldYellowAlpha = Colors.YELLOW_ALPHA.a;
|
||||||
|
Colors.YELLOW_ALPHA.a *= alpha;
|
||||||
g.setColor(Colors.YELLOW_ALPHA);
|
g.setColor(Colors.YELLOW_ALPHA);
|
||||||
g.fillArc(circleX, symbolHeight, circleDiameter, circleDiameter,
|
g.fillArc(circleX, symbolHeight, circleDiameter, circleDiameter, -90 + (int) (360f * progress), -90);
|
||||||
-90 + (int) (360f * trackPosition / firstObjectTime), -90
|
Colors.YELLOW_ALPHA.a = oldYellowAlpha;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
g.setAntiAlias(false);
|
g.setAntiAlias(false);
|
||||||
|
Colors.WHITE_ALPHA.a = oldWhiteAlpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
// mod icons
|
// mod icons
|
||||||
|
@ -646,10 +656,12 @@ public class GameData {
|
||||||
int modCount = 0;
|
int modCount = 0;
|
||||||
for (GameMod mod : GameMod.VALUES_REVERSED) {
|
for (GameMod mod : GameMod.VALUES_REVERSED) {
|
||||||
if (mod.isActive()) {
|
if (mod.isActive()) {
|
||||||
|
mod.getImage().setAlpha(alpha);
|
||||||
mod.getImage().draw(
|
mod.getImage().draw(
|
||||||
modX - (modCount * (modWidth / 2f)),
|
modX - (modCount * (modWidth / 2f)),
|
||||||
symbolHeight + circleDiameter + 10
|
symbolHeight + circleDiameter + 10
|
||||||
);
|
);
|
||||||
|
mod.getImage().setAlpha(1f);
|
||||||
modCount++;
|
modCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -697,8 +709,8 @@ public class GameData {
|
||||||
float tickWidth = 2 * uiScale;
|
float tickWidth = 2 * uiScale;
|
||||||
for (HitErrorInfo info : hitErrorList) {
|
for (HitErrorInfo info : hitErrorList) {
|
||||||
int time = info.time;
|
int time = info.time;
|
||||||
float alpha = 1 - ((float) (trackPosition - time) / HIT_ERROR_FADE_TIME);
|
float tickAlpha = 1 - ((float) (trackPosition - time) / HIT_ERROR_FADE_TIME);
|
||||||
white.a = alpha * hitErrorAlpha;
|
white.a = tickAlpha * hitErrorAlpha;
|
||||||
g.setColor(white);
|
g.setColor(white);
|
||||||
g.fillRect((hitErrorX + info.timeDiff - 1) * uiScale, tickY, tickWidth, tickHeight);
|
g.fillRect((hitErrorX + info.timeDiff - 1) * uiScale, tickY, tickWidth, tickHeight);
|
||||||
}
|
}
|
||||||
|
@ -721,9 +733,12 @@ public class GameData {
|
||||||
float colourX = 4 * uiScale, colourY = 15 * uiScale;
|
float colourX = 4 * uiScale, colourY = 15 * uiScale;
|
||||||
Image colourCropped = colour.getSubImage(0, 0, (int) (645 * uiScale * healthRatio), colour.getHeight());
|
Image colourCropped = colour.getSubImage(0, 0, (int) (645 * uiScale * healthRatio), colour.getHeight());
|
||||||
|
|
||||||
scorebar.setAlpha(1f);
|
scorebar.setAlpha(alpha);
|
||||||
scorebar.draw(0, 0);
|
scorebar.draw(0, 0);
|
||||||
|
scorebar.setAlpha(1f);
|
||||||
|
colourCropped.setAlpha(alpha);
|
||||||
colourCropped.draw(colourX, colourY);
|
colourCropped.draw(colourX, colourY);
|
||||||
|
colourCropped.setAlpha(1f);
|
||||||
|
|
||||||
Image ki = null;
|
Image ki = null;
|
||||||
if (health >= 50f)
|
if (health >= 50f)
|
||||||
|
@ -734,7 +749,9 @@ public class GameData {
|
||||||
ki = GameImage.SCOREBAR_KI_DANGER2.getImage();
|
ki = GameImage.SCOREBAR_KI_DANGER2.getImage();
|
||||||
if (comboPopTime < COMBO_POP_TIME)
|
if (comboPopTime < COMBO_POP_TIME)
|
||||||
ki = ki.getScaledCopy(1f + (0.45f * (1f - (float) comboPopTime / COMBO_POP_TIME)));
|
ki = ki.getScaledCopy(1f + (0.45f * (1f - (float) comboPopTime / COMBO_POP_TIME)));
|
||||||
|
ki.setAlpha(alpha);
|
||||||
ki.drawCentered(colourX + colourCropped.getWidth(), colourY);
|
ki.drawCentered(colourX + colourCropped.getWidth(), colourY);
|
||||||
|
ki.setAlpha(1f);
|
||||||
|
|
||||||
// combo burst
|
// combo burst
|
||||||
if (comboBurstIndex != -1 && comboBurstAlpha > 0f) {
|
if (comboBurstIndex != -1 && comboBurstAlpha > 0f) {
|
||||||
|
@ -750,8 +767,8 @@ public class GameData {
|
||||||
float comboPopFront = 1 + comboPop * 0.08f;
|
float comboPopFront = 1 + comboPop * 0.08f;
|
||||||
String comboString = String.format("%dx", combo);
|
String comboString = String.format("%dx", combo);
|
||||||
if (comboPopTime != COMBO_POP_TIME)
|
if (comboPopTime != COMBO_POP_TIME)
|
||||||
drawSymbolString(comboString, margin, height - margin - (symbolHeight * comboPopBack), comboPopBack, 0.5f, false);
|
drawSymbolString(comboString, margin, height - margin - (symbolHeight * comboPopBack), comboPopBack, 0.5f * alpha, false);
|
||||||
drawSymbolString(comboString, margin, height - margin - (symbolHeight * comboPopFront), comboPopFront, 1f, false);
|
drawSymbolString(comboString, margin, height - margin - (symbolHeight * comboPopFront), comboPopFront, alpha, false);
|
||||||
}
|
}
|
||||||
} else if (!relaxAutoPilot) {
|
} else if (!relaxAutoPilot) {
|
||||||
// grade
|
// grade
|
||||||
|
@ -759,9 +776,9 @@ public class GameData {
|
||||||
if (grade != Grade.NULL) {
|
if (grade != Grade.NULL) {
|
||||||
Image gradeImage = grade.getSmallImage();
|
Image gradeImage = grade.getSmallImage();
|
||||||
float gradeScale = symbolHeight * 0.75f / gradeImage.getHeight();
|
float gradeScale = symbolHeight * 0.75f / gradeImage.getHeight();
|
||||||
gradeImage.getScaledCopy(gradeScale).draw(
|
gradeImage = gradeImage.getScaledCopy(gradeScale);
|
||||||
circleX - gradeImage.getWidth(), symbolHeight
|
gradeImage.setAlpha(alpha);
|
||||||
);
|
gradeImage.draw(circleX - gradeImage.getWidth(), symbolHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -786,7 +803,7 @@ public class GameData {
|
||||||
drawFixedSizeSymbolString(
|
drawFixedSizeSymbolString(
|
||||||
(score < 100000000) ? String.format("%08d", score) : Long.toString(score),
|
(score < 100000000) ? String.format("%08d", score) : Long.toString(score),
|
||||||
210 * uiScale, (rankingHeight + 50) * uiScale,
|
210 * uiScale, (rankingHeight + 50) * uiScale,
|
||||||
scoreTextScale, getScoreSymbolImage('0').getWidth() * scoreTextScale - 2, false
|
scoreTextScale, 1f, getScoreSymbolImage('0').getWidth() * scoreTextScale - 2, false
|
||||||
);
|
);
|
||||||
|
|
||||||
// result counts
|
// result counts
|
||||||
|
@ -897,63 +914,17 @@ public class GameData {
|
||||||
lighting.drawCentered(hitResult.x, hitResult.y, hitResult.color);
|
lighting.drawCentered(hitResult.x, hitResult.y, hitResult.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// hit animation
|
// hit animations (only draw when the "Hidden" mod is not enabled)
|
||||||
if (Options.isHitAnimationEnabled() &&
|
if (!GameMod.HIDDEN.isActive()) {
|
||||||
hitResult.result != HIT_MISS && (
|
drawHitAnimations(hitResult, trackPosition);
|
||||||
hitResult.hitResultType == null || // null => initial slider circle
|
|
||||||
hitResult.hitResultType == HitObjectType.CIRCLE ||
|
|
||||||
hitResult.hitResultType == HitObjectType.SLIDER_FIRST ||
|
|
||||||
hitResult.hitResultType == HitObjectType.SLIDER_LAST)) {
|
|
||||||
float progress = AnimationEquation.OUT_CUBIC.calc(
|
|
||||||
(float) Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_FADE_TIME) / HITCIRCLE_FADE_TIME);
|
|
||||||
float scale = (!hitResult.expand) ? 1f : 1f + (HITCIRCLE_ANIM_SCALE - 1f) * progress;
|
|
||||||
float alpha = 1f - progress;
|
|
||||||
|
|
||||||
if (hitResult.result == HIT_SLIDER_REPEAT) {
|
|
||||||
// repeats
|
|
||||||
Image scaledRepeat = GameImage.REVERSEARROW.getImage().getScaledCopy(scale);
|
|
||||||
scaledRepeat.setAlpha(alpha);
|
|
||||||
float ang;
|
|
||||||
if (hitResult.hitResultType == HitObjectType.SLIDER_FIRST) {
|
|
||||||
ang = hitResult.curve.getStartAngle();
|
|
||||||
} else {
|
|
||||||
ang = hitResult.curve.getEndAngle();
|
|
||||||
}
|
|
||||||
scaledRepeat.rotate(ang);
|
|
||||||
scaledRepeat.drawCentered(hitResult.x, hitResult.y, hitResult.color);
|
|
||||||
}
|
|
||||||
// "hidden" mod: circle and slider animations not drawn
|
|
||||||
else if (!GameMod.HIDDEN.isActive()) {
|
|
||||||
// slider curve
|
|
||||||
if (hitResult.curve != null) {
|
|
||||||
float oldWhiteAlpha = Colors.WHITE_FADE.a;
|
|
||||||
float oldColorAlpha = hitResult.color.a;
|
|
||||||
Colors.WHITE_FADE.a = alpha;
|
|
||||||
hitResult.color.a = alpha;
|
|
||||||
if (!Options.isShrinkingSliders()) {
|
|
||||||
hitResult.curve.draw(hitResult.color);
|
|
||||||
}
|
|
||||||
Colors.WHITE_FADE.a = oldWhiteAlpha;
|
|
||||||
hitResult.color.a = oldColorAlpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hitResult.hitResultType == null || hitResult.hitResultType == HitObjectType.CIRCLE) {
|
|
||||||
// hit circles
|
|
||||||
Image scaledHitCircle = GameImage.HITCIRCLE.getImage().getScaledCopy(scale);
|
|
||||||
Image scaledHitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(scale);
|
|
||||||
scaledHitCircle.setAlpha(alpha);
|
|
||||||
scaledHitCircleOverlay.setAlpha(alpha);
|
|
||||||
scaledHitCircle.drawCentered(hitResult.x, hitResult.y, hitResult.color);
|
|
||||||
scaledHitCircleOverlay.drawCentered(hitResult.x, hitResult.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// hit result
|
// hit result
|
||||||
if (!hitResult.hideResult && (
|
if (!hitResult.hideResult && (
|
||||||
hitResult.hitResultType == HitObjectType.CIRCLE ||
|
hitResult.hitResultType == HitObjectType.CIRCLE ||
|
||||||
hitResult.hitResultType == HitObjectType.SPINNER ||
|
hitResult.hitResultType == HitObjectType.SLIDER_FIRST ||
|
||||||
hitResult.curve != null)) {
|
hitResult.hitResultType == HitObjectType.SLIDER_LAST ||
|
||||||
|
hitResult.hitResultType == HitObjectType.SPINNER)) {
|
||||||
float scaleProgress = AnimationEquation.IN_OUT_BOUNCE.calc(
|
float scaleProgress = AnimationEquation.IN_OUT_BOUNCE.calc(
|
||||||
(float) Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_TEXT_BOUNCE_TIME) / HITCIRCLE_TEXT_BOUNCE_TIME);
|
(float) Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_TEXT_BOUNCE_TIME) / HITCIRCLE_TEXT_BOUNCE_TIME);
|
||||||
float scale = 1f + (HITCIRCLE_TEXT_ANIM_SCALE - 1f) * scaleProgress;
|
float scale = 1f + (HITCIRCLE_TEXT_ANIM_SCALE - 1f) * scaleProgress;
|
||||||
|
@ -974,6 +945,64 @@ public class GameData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the hit animations:
|
||||||
|
* circles, reverse arrows, slider curves (fading out and/or expanding).
|
||||||
|
* @param hitResult the hit result
|
||||||
|
* @param trackPosition the current track position (in ms)
|
||||||
|
*/
|
||||||
|
private void drawHitAnimations(HitObjectResult hitResult, int trackPosition) {
|
||||||
|
// fade out slider curve
|
||||||
|
if (hitResult.result != HIT_SLIDER_REPEAT && hitResult.curve != null) {
|
||||||
|
float progress = AnimationEquation.OUT_CUBIC.calc(
|
||||||
|
(float) Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_FADE_TIME) / HITCIRCLE_FADE_TIME);
|
||||||
|
float alpha = 1f - progress;
|
||||||
|
float oldWhiteAlpha = Colors.WHITE_FADE.a;
|
||||||
|
float oldColorAlpha = hitResult.color.a;
|
||||||
|
Colors.WHITE_FADE.a = hitResult.color.a = alpha;
|
||||||
|
hitResult.curve.draw(hitResult.color);
|
||||||
|
Colors.WHITE_FADE.a = oldWhiteAlpha;
|
||||||
|
hitResult.color.a = oldColorAlpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
// miss, don't draw an animation
|
||||||
|
if (hitResult.result == HIT_MISS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not a circle?
|
||||||
|
if (hitResult.hitResultType != HitObjectType.CIRCLE &&
|
||||||
|
hitResult.hitResultType != HitObjectType.SLIDER_FIRST &&
|
||||||
|
hitResult.hitResultType != HitObjectType.SLIDER_LAST) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hit circles
|
||||||
|
float progress = AnimationEquation.OUT_CUBIC.calc(
|
||||||
|
(float) Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_FADE_TIME) / HITCIRCLE_FADE_TIME);
|
||||||
|
float scale = (!hitResult.expand) ? 1f : 1f + (HITCIRCLE_ANIM_SCALE - 1f) * progress;
|
||||||
|
float alpha = 1f - progress;
|
||||||
|
if (hitResult.result == HIT_SLIDER_REPEAT) {
|
||||||
|
// repeats
|
||||||
|
Image scaledRepeat = GameImage.REVERSEARROW.getImage().getScaledCopy(scale);
|
||||||
|
scaledRepeat.setAlpha(alpha);
|
||||||
|
float ang;
|
||||||
|
if (hitResult.hitResultType == HitObjectType.SLIDER_FIRST) {
|
||||||
|
ang = hitResult.curve.getStartAngle();
|
||||||
|
} else {
|
||||||
|
ang = hitResult.curve.getEndAngle();
|
||||||
|
}
|
||||||
|
scaledRepeat.rotate(ang);
|
||||||
|
scaledRepeat.drawCentered(hitResult.x, hitResult.y, hitResult.color);
|
||||||
|
}
|
||||||
|
Image scaledHitCircle = GameImage.HITCIRCLE.getImage().getScaledCopy(scale);
|
||||||
|
Image scaledHitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(scale);
|
||||||
|
scaledHitCircle.setAlpha(alpha);
|
||||||
|
scaledHitCircleOverlay.setAlpha(alpha);
|
||||||
|
scaledHitCircle.drawCentered(hitResult.x, hitResult.y, hitResult.color);
|
||||||
|
scaledHitCircleOverlay.drawCentered(hitResult.x, hitResult.y);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes health by a given percentage, modified by drainRate.
|
* Changes health by a given percentage, modified by drainRate.
|
||||||
* @param percent the health percentage
|
* @param percent the health percentage
|
||||||
|
@ -1202,16 +1231,16 @@ public class GameData {
|
||||||
health = 0f;
|
health = 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendInitialSliderResult(int time, float x, float y, Color color, Color mirrorcolor) {
|
/**
|
||||||
hitResultList.add(new HitObjectResult(time, HIT_SLIDER_INITIAL, x, y, color, null, null, true, false));
|
* Handles a slider repeat result (animation only: arrow).
|
||||||
if (!Options.isMirror() || !GameMod.AUTO.isActive()) {
|
* @param time the repeat time
|
||||||
return;
|
* @param x the x coordinate
|
||||||
}
|
* @param y the y coordinate
|
||||||
float[] m = Utils.mirrorPoint(x, y);
|
* @param color the arrow color
|
||||||
hitResultList.add(new HitObjectResult(time, HIT_SLIDER_INITIAL, m[0], m[1], mirrorcolor, null, null, true, false));
|
* @param curve the slider curve
|
||||||
}
|
* @param type the hit object type
|
||||||
|
*/
|
||||||
public void sendRepeatSliderResult(int time, float x, float y, Color color, Curve curve, HitObjectType type) {
|
public void sendSliderRepeatResult(int time, float x, float y, Color color, Curve curve, HitObjectType type) {
|
||||||
hitResultList.add(new HitObjectResult(time, HIT_SLIDER_REPEAT, x, y, color, type, curve, true, true));
|
hitResultList.add(new HitObjectResult(time, HIT_SLIDER_REPEAT, x, y, color, type, curve, true, true));
|
||||||
if (!Options.isMirror()) {
|
if (!Options.isMirror()) {
|
||||||
return;
|
return;
|
||||||
|
@ -1220,6 +1249,18 @@ public class GameData {
|
||||||
hitResultList.add(new HitObjectResult(time, HIT_SLIDER_REPEAT, m[0], m[1], color, type, curve, true, true));
|
hitResultList.add(new HitObjectResult(time, HIT_SLIDER_REPEAT, m[0], m[1], color, type, curve, true, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a slider start result (animation only: initial circle).
|
||||||
|
* @param time the hit time
|
||||||
|
* @param x the x coordinate
|
||||||
|
* @param y the y coordinate
|
||||||
|
* @param color the slider color
|
||||||
|
* @param expand whether or not the hit result animation should expand
|
||||||
|
*/
|
||||||
|
public void sendSliderStartResult(int time, float x, float y, Color color, boolean expand) {
|
||||||
|
hitResultList.add(new HitObjectResult(time, HIT_ANIMATION_RESULT, x, y, color, HitObjectType.CIRCLE, null, expand, true));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a slider tick result.
|
* Handles a slider tick result.
|
||||||
* @param time the tick start time
|
* @param time the tick start time
|
||||||
|
@ -1229,7 +1270,7 @@ public class GameData {
|
||||||
* @param hitObject the hit object
|
* @param hitObject the hit object
|
||||||
* @param repeat the current repeat number
|
* @param repeat the current repeat number
|
||||||
*/
|
*/
|
||||||
public void sliderTickResult(int time, int result, float x, float y, HitObject hitObject, int repeat) {
|
public void sendSliderTickResult(int time, int result, float x, float y, HitObject hitObject, int repeat) {
|
||||||
int hitValue = 0;
|
int hitValue = 0;
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case HIT_SLIDER30:
|
case HIT_SLIDER30:
|
||||||
|
@ -1415,6 +1456,23 @@ public class GameData {
|
||||||
boolean expand, int repeat, Curve curve, boolean sliderHeldToEnd) {
|
boolean expand, int repeat, Curve curve, boolean sliderHeldToEnd) {
|
||||||
hitResult(time, result, x, y, color, end, hitObject, hitResultType, expand, repeat, curve, sliderHeldToEnd, true);
|
hitResult(time, result, x, y, color, end, hitObject, hitResultType, expand, repeat, curve, sliderHeldToEnd, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a hit result.
|
||||||
|
* @param time the object start time
|
||||||
|
* @param result the hit result (HIT_* constants)
|
||||||
|
* @param x the x coordinate
|
||||||
|
* @param y the y coordinate
|
||||||
|
* @param color the combo color
|
||||||
|
* @param end true if this is the last hit object in the combo
|
||||||
|
* @param hitObject the hit object
|
||||||
|
* @param hitResultType the type of hit object for the result
|
||||||
|
* @param expand whether or not the hit result animation should expand (if applicable)
|
||||||
|
* @param repeat the current repeat number (for sliders, or 0 otherwise)
|
||||||
|
* @param curve the slider curve (or null if not applicable)
|
||||||
|
* @param sliderHeldToEnd whether or not the slider was held to the end (if applicable)
|
||||||
|
* @param handleResult whether or not to send a score result
|
||||||
|
*/
|
||||||
public void hitResult(int time, int result, float x, float y, Color color,
|
public void hitResult(int time, int result, float x, float y, Color color,
|
||||||
boolean end, HitObject hitObject, HitObjectType hitResultType,
|
boolean end, HitObject hitObject, HitObjectType hitResultType,
|
||||||
boolean expand, int repeat, Curve curve, boolean sliderHeldToEnd, boolean handleResult) {
|
boolean expand, int repeat, Curve curve, boolean sliderHeldToEnd, boolean handleResult) {
|
||||||
|
@ -1431,16 +1489,6 @@ public class GameData {
|
||||||
|
|
||||||
boolean hideResult = (hitResult == HIT_300 || hitResult == HIT_300G || hitResult == HIT_300K) && !Options.isPerfectHitBurstEnabled();
|
boolean hideResult = (hitResult == HIT_300 || hitResult == HIT_300G || hitResult == HIT_300K) && !Options.isPerfectHitBurstEnabled();
|
||||||
hitResultList.add(new HitObjectResult(time, hitResult, x, y, color, hitResultType, curve, expand, hideResult));
|
hitResultList.add(new HitObjectResult(time, hitResult, x, y, color, hitResultType, curve, expand, hideResult));
|
||||||
|
|
||||||
/*
|
|
||||||
// sliders: add the other curve endpoint for the hit animation
|
|
||||||
if (curve != null) {
|
|
||||||
boolean isFirst = (hitResultType == HitObjectType.SLIDER_FIRST);
|
|
||||||
Vec2f p = curve.pointAt((isFirst) ? 1f : 0f);
|
|
||||||
HitObjectType type = (isFirst) ? HitObjectType.SLIDER_LAST : HitObjectType.SLIDER_FIRST;
|
|
||||||
hitResultList.add(new HitObjectResult(time, hitResult, p.x, p.y, color, type, null, expand, hideResult));
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -655,7 +655,7 @@ public enum GameImage {
|
||||||
* If the default image has already been loaded, this will do nothing.
|
* If the default image has already been loaded, this will do nothing.
|
||||||
*/
|
*/
|
||||||
public void setDefaultImage() {
|
public void setDefaultImage() {
|
||||||
if (defaultImage != null || defaultImages != null)
|
if (defaultImage != null || defaultImages != null || Options.getSkin() == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// try to load multiple images
|
// try to load multiple images
|
||||||
|
|
|
@ -249,9 +249,12 @@ public class Opsu extends StateBasedGame {
|
||||||
} else
|
} else
|
||||||
songMenu.resetTrackOnLoad();
|
songMenu.resetTrackOnLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset game data
|
||||||
if (UI.getCursor().isBeatmapSkinned())
|
if (UI.getCursor().isBeatmapSkinned())
|
||||||
UI.getCursor().reset();
|
UI.getCursor().reset();
|
||||||
songMenu.resetGameDataOnLoad();
|
songMenu.resetGameDataOnLoad();
|
||||||
|
|
||||||
this.enterState(Opsu.STATE_SONGMENU, new EasedFadeOutTransition(), new FadeInTransition());
|
this.enterState(Opsu.STATE_SONGMENU, new EasedFadeOutTransition(), new FadeInTransition());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,12 @@ public class Options {
|
||||||
|
|
||||||
private static boolean noSingleInstance;
|
private static boolean noSingleInstance;
|
||||||
|
|
||||||
|
/** The theme song string: {@code filename,title,artist,length(ms)} */
|
||||||
|
private static String themeString = "theme.mp3,Rainbows,Kevin MacLeod,219350";
|
||||||
|
|
||||||
|
/** The theme song timing point string (for computing beats to pulse the logo) . */
|
||||||
|
private static String themeTimingPoint = "1080,545.454545454545,4,1,0,100,0,0";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the XDG flag in the manifest (if any) is set to "true".
|
* Returns whether the XDG flag in the manifest (if any) is set to "true".
|
||||||
* @return true if XDG directories are enabled, false otherwise
|
* @return true if XDG directories are enabled, false otherwise
|
||||||
|
@ -171,8 +177,11 @@ public class Options {
|
||||||
* @return the XDG base directory, or the working directory if unavailable
|
* @return the XDG base directory, or the working directory if unavailable
|
||||||
*/
|
*/
|
||||||
private static File getXDGBaseDir(String env, String fallback) {
|
private static File getXDGBaseDir(String env, String fallback) {
|
||||||
|
File workingDir = Utils.isJarRunning() ?
|
||||||
|
Utils.getRunningDirectory().getParentFile() : Utils.getWorkingDirectory();
|
||||||
|
|
||||||
if (!USE_XDG)
|
if (!USE_XDG)
|
||||||
return new File("./");
|
return workingDir;
|
||||||
|
|
||||||
String OS = System.getProperty("os.name").toLowerCase();
|
String OS = System.getProperty("os.name").toLowerCase();
|
||||||
if (OS.indexOf("nix") >= 0 || OS.indexOf("nux") >= 0 || OS.indexOf("aix") > 0) {
|
if (OS.indexOf("nix") >= 0 || OS.indexOf("nux") >= 0 || OS.indexOf("aix") > 0) {
|
||||||
|
@ -188,7 +197,7 @@ public class Options {
|
||||||
ErrorHandler.error(String.format("Failed to create configuration folder at '%s/opsu'.", rootPath), null, false);
|
ErrorHandler.error(String.format("Failed to create configuration folder at '%s/opsu'.", rootPath), null, false);
|
||||||
return dir;
|
return dir;
|
||||||
} else
|
} else
|
||||||
return new File("./");
|
return workingDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -219,12 +228,6 @@ public class Options {
|
||||||
return (dir.isDirectory()) ? dir : null;
|
return (dir.isDirectory()) ? dir : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The theme song string:
|
|
||||||
* {@code filename,title,artist,length(ms)}
|
|
||||||
*/
|
|
||||||
private static String themeString = "theme.ogg,On the Bach,Jingle Punks,66000";
|
|
||||||
|
|
||||||
/** Game options. */
|
/** Game options. */
|
||||||
public enum GameOption {
|
public enum GameOption {
|
||||||
// internal options (not displayed in-game)
|
// internal options (not displayed in-game)
|
||||||
|
@ -275,7 +278,32 @@ public class Options {
|
||||||
public String write() { return themeString; }
|
public String write() { return themeString; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(String s) { themeString = s; }
|
public void read(String s) {
|
||||||
|
String oldThemeString = themeString;
|
||||||
|
themeString = s;
|
||||||
|
Beatmap beatmap = getThemeBeatmap();
|
||||||
|
if (beatmap == null) {
|
||||||
|
themeString = oldThemeString;
|
||||||
|
Log.warn(String.format("The theme song string [%s] is malformed.", s));
|
||||||
|
} else if (!beatmap.audioFilename.isFile()) {
|
||||||
|
themeString = oldThemeString;
|
||||||
|
Log.warn(String.format("Cannot find theme song [%s].", beatmap.audioFilename.getAbsolutePath()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
THEME_SONG_TIMINGPOINT ("ThemeSongTiming") {
|
||||||
|
@Override
|
||||||
|
public String write() { return themeTimingPoint; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read(String s) {
|
||||||
|
try {
|
||||||
|
new TimingPoint(s);
|
||||||
|
themeTimingPoint = s;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.warn(String.format("The theme song timing point [%s] is malformed.", s));
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
PORT ("Port") {
|
PORT ("Port") {
|
||||||
@Override
|
@Override
|
||||||
|
@ -1876,25 +1904,26 @@ public class Options {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a dummy Beatmap containing the theme song.
|
* Returns a dummy Beatmap containing the theme song.
|
||||||
* @return the theme song beatmap
|
* @return the theme song beatmap, or {@code null} if the theme string is malformed
|
||||||
*/
|
*/
|
||||||
public static Beatmap getThemeBeatmap() {
|
public static Beatmap getThemeBeatmap() {
|
||||||
String[] tokens = themeString.split(",");
|
String[] tokens = themeString.split(",");
|
||||||
if (tokens.length != 4) {
|
if (tokens.length != 4)
|
||||||
ErrorHandler.error("Theme song string is malformed.", null, false);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
Beatmap beatmap = new Beatmap(null);
|
Beatmap beatmap = new Beatmap(null);
|
||||||
beatmap.audioFilename = new File(tokens[0]);
|
beatmap.audioFilename = new File(tokens[0]);
|
||||||
beatmap.title = tokens[1];
|
beatmap.title = tokens[1];
|
||||||
beatmap.artist = tokens[2];
|
beatmap.artist = tokens[2];
|
||||||
beatmap.timingPoints = new ArrayList<>(1);
|
|
||||||
beatmap.timingPoints.add(new TimingPoint("-44,631.578947368421,4,1,0,100,1,0"));
|
|
||||||
try {
|
try {
|
||||||
beatmap.endTime = Integer.parseInt(tokens[3]);
|
beatmap.endTime = Integer.parseInt(tokens[3]);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
ErrorHandler.error("Theme song length is not a valid integer", e, false);
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
beatmap.timingPoints = new ArrayList<>(1);
|
||||||
|
beatmap.timingPoints.add(new TimingPoint(themeTimingPoint));
|
||||||
|
} catch (Exception e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ import java.net.SocketTimeoutException;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
@ -158,6 +159,14 @@ public class Utils {
|
||||||
anim.draw(x - (anim.getWidth() / 2f), y - (anim.getHeight() / 2f));
|
anim.draw(x - (anim.getWidth() / 2f), y - (anim.getHeight() / 2f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the luminance of a color.
|
||||||
|
* @param c the color
|
||||||
|
*/
|
||||||
|
public static float getLuminance(Color c) {
|
||||||
|
return 0.299f*c.r + 0.587f*c.g + 0.114f*c.b;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clamps a value between a lower and upper bound.
|
* Clamps a value between a lower and upper bound.
|
||||||
* @param val the value to clamp
|
* @param val the value to clamp
|
||||||
|
@ -554,6 +563,14 @@ public class Utils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current working directory.
|
||||||
|
* @return the directory
|
||||||
|
*/
|
||||||
|
public static File getWorkingDirectory() {
|
||||||
|
return Paths.get(".").toAbsolutePath().normalize().toFile();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the integer string argument as a boolean:
|
* Parses the integer string argument as a boolean:
|
||||||
* {@code 1} is {@code true}, and all other values are {@code false}.
|
* {@code 1} is {@code true}, and all other values are {@code false}.
|
||||||
|
|
|
@ -78,6 +78,12 @@ public class MusicController {
|
||||||
/** The track dim level, if dimmed. */
|
/** The track dim level, if dimmed. */
|
||||||
private static float dimLevel = 1f;
|
private static float dimLevel = 1f;
|
||||||
|
|
||||||
|
/** Current timing point index in the track, advanced by {@link #getBeatProgress()}. */
|
||||||
|
private static int timingPointIndex;
|
||||||
|
|
||||||
|
/** Last non-inherited timing point. */
|
||||||
|
private static TimingPoint lastTimingPoint;
|
||||||
|
|
||||||
// This class should not be instantiated.
|
// This class should not be instantiated.
|
||||||
private MusicController() {}
|
private MusicController() {}
|
||||||
|
|
||||||
|
@ -94,7 +100,6 @@ public class MusicController {
|
||||||
final File audioFile = beatmap.audioFilename;
|
final File audioFile = beatmap.audioFilename;
|
||||||
if (!audioFile.isFile() && !ResourceLoader.resourceExists(audioFile.getPath())) {
|
if (!audioFile.isFile() && !ResourceLoader.resourceExists(audioFile.getPath())) {
|
||||||
UI.sendBarNotification(String.format("Could not find track '%s'.", audioFile.getName()));
|
UI.sendBarNotification(String.format("Could not find track '%s'.", audioFile.getName()));
|
||||||
System.out.println(beatmap);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,8 +141,10 @@ public class MusicController {
|
||||||
player.addListener(new MusicListener() {
|
player.addListener(new MusicListener() {
|
||||||
@Override
|
@Override
|
||||||
public void musicEnded(Music music) {
|
public void musicEnded(Music music) {
|
||||||
if (music == player) // don't fire if music swapped
|
if (music == player) { // don't fire if music swapped
|
||||||
trackEnded = true;
|
trackEnded = true;
|
||||||
|
resetTimingPoint();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -159,6 +166,7 @@ public class MusicController {
|
||||||
setVolume(Options.getMusicVolume() * Options.getMasterVolume());
|
setVolume(Options.getMusicVolume() * Options.getMasterVolume());
|
||||||
trackEnded = false;
|
trackEnded = false;
|
||||||
pauseTime = 0f;
|
pauseTime = 0f;
|
||||||
|
resetTimingPoint();
|
||||||
if (loop)
|
if (loop)
|
||||||
player.loop();
|
player.loop();
|
||||||
else
|
else
|
||||||
|
@ -187,34 +195,81 @@ public class MusicController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the progress of the current beat.
|
* Gets the progress of the current beat.
|
||||||
* @return progress as a value in [0, 1], where 0 means a beat just happend and 1 means the next beat is coming now.
|
* @return a beat progress value [0,1) where 0 marks the current beat and
|
||||||
|
* 1 marks the next beat, or {@code null} if no timing information
|
||||||
|
* is available (e.g. music paused, no timing points)
|
||||||
*/
|
*/
|
||||||
public static Double getBeatProgress() {
|
public static Float getBeatProgress() {
|
||||||
if (!isPlaying() || getBeatmap() == null) {
|
if (!updateTimingPoint())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
// calculate beat progress
|
||||||
|
int trackPosition = Math.max(0, getPosition());
|
||||||
|
double beatLength = lastTimingPoint.getBeatLength() * 100.0;
|
||||||
|
int beatTime = lastTimingPoint.getTime();
|
||||||
|
if (trackPosition < beatTime)
|
||||||
|
trackPosition += (beatLength / 100.0) * (beatTime / lastTimingPoint.getBeatLength());
|
||||||
|
return (float) ((((trackPosition - beatTime) * 100.0) % beatLength) / beatLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the progress of the current measure.
|
||||||
|
* @return a measure progress value [0,1) where 0 marks the start of the measure and
|
||||||
|
* 1 marks the start of the next measure, or {@code null} if no timing information
|
||||||
|
* is available (e.g. music paused, no timing points)
|
||||||
|
*/
|
||||||
|
public static Float getMeasureProgress() { return getMeasureProgress(1); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the progress of the current measure.
|
||||||
|
* @param k the meter multiplier
|
||||||
|
* @return a measure progress value [0,1) where 0 marks the start of the measure and
|
||||||
|
* 1 marks the start of the next measure, or {@code null} if no timing information
|
||||||
|
* is available (e.g. music paused, no timing points)
|
||||||
|
*/
|
||||||
|
public static Float getMeasureProgress(int k) {
|
||||||
|
if (!updateTimingPoint())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// calculate measure progress
|
||||||
|
int trackPosition = Math.max(0, getPosition());
|
||||||
|
double measureLength = lastTimingPoint.getBeatLength() * lastTimingPoint.getMeter() * k * 100.0;
|
||||||
|
int beatTime = lastTimingPoint.getTime();
|
||||||
|
if (trackPosition < beatTime)
|
||||||
|
trackPosition += (measureLength / 100.0) * (beatTime / lastTimingPoint.getBeatLength());
|
||||||
|
return (float) ((((trackPosition - beatTime) * 100.0) % measureLength) / measureLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the timing point information for the current track position.
|
||||||
|
* @return {@code false} if timing point information is not available, {@code true} otherwise
|
||||||
|
*/
|
||||||
|
private static boolean updateTimingPoint() {
|
||||||
Beatmap map = getBeatmap();
|
Beatmap map = getBeatmap();
|
||||||
if (map.timingPoints == null) {
|
if (!isPlaying() || map == null || map.timingPoints == null || map.timingPoints.isEmpty())
|
||||||
return null;
|
return false;
|
||||||
|
|
||||||
|
// initialization
|
||||||
|
if (timingPointIndex == 0 && lastTimingPoint == null && !map.timingPoints.isEmpty()) {
|
||||||
|
TimingPoint timingPoint = map.timingPoints.get(0);
|
||||||
|
if (!timingPoint.isInherited())
|
||||||
|
lastTimingPoint = timingPoint;
|
||||||
}
|
}
|
||||||
int trackposition = getPosition();
|
|
||||||
TimingPoint p = null;
|
// advance timing point index, record last non-inherited timing point
|
||||||
float beatlen = 0f;
|
int trackPosition = getPosition();
|
||||||
int time = 0;
|
for (int i = timingPointIndex + 1; i < map.timingPoints.size(); i++) {
|
||||||
for (TimingPoint pts : map.timingPoints) {
|
TimingPoint timingPoint = map.timingPoints.get(i);
|
||||||
if (p == null || pts.getTime() < getPosition()) {
|
if (trackPosition < timingPoint.getTime())
|
||||||
p = pts;
|
break;
|
||||||
if (!p.isInherited() && p.getBeatLength() > 0) {
|
timingPointIndex = i;
|
||||||
beatlen = p.getBeatLength();
|
if (!timingPoint.isInherited() && timingPoint.getBeatLength() > 0)
|
||||||
time = p.getTime();
|
lastTimingPoint = timingPoint;
|
||||||
}
|
}
|
||||||
}
|
if (lastTimingPoint == null)
|
||||||
}
|
return false; // no timing info
|
||||||
if (p == null) {
|
|
||||||
return null;
|
return true;
|
||||||
}
|
|
||||||
double beatLength = beatlen * 100;
|
|
||||||
return (((trackposition * 100 - time * 100) % beatLength) / beatLength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -258,8 +313,10 @@ public class MusicController {
|
||||||
public static void stop() {
|
public static void stop() {
|
||||||
if (isPlaying())
|
if (isPlaying())
|
||||||
player.stop();
|
player.stop();
|
||||||
if (trackExists())
|
if (trackExists()) {
|
||||||
pauseTime = 0f;
|
pauseTime = 0f;
|
||||||
|
resetTimingPoint();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -298,7 +355,11 @@ public class MusicController {
|
||||||
* @param position the new track position (in ms)
|
* @param position the new track position (in ms)
|
||||||
*/
|
*/
|
||||||
public static boolean setPosition(int position) {
|
public static boolean setPosition(int position) {
|
||||||
return (trackExists() && position >= 0 && player.setPosition(position / 1000f));
|
if (!trackExists() || position < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
resetTimingPoint();
|
||||||
|
return (player.setPosition(position / 1000f));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -339,6 +400,7 @@ public class MusicController {
|
||||||
public static void play(boolean loop) {
|
public static void play(boolean loop) {
|
||||||
if (trackExists()) {
|
if (trackExists()) {
|
||||||
trackEnded = false;
|
trackEnded = false;
|
||||||
|
resetTimingPoint();
|
||||||
if (loop)
|
if (loop)
|
||||||
player.loop();
|
player.loop();
|
||||||
else
|
else
|
||||||
|
@ -409,6 +471,14 @@ public class MusicController {
|
||||||
setVolume(volume);
|
setVolume(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets timing point information.
|
||||||
|
*/
|
||||||
|
private static void resetTimingPoint() {
|
||||||
|
timingPointIndex = 0;
|
||||||
|
lastTimingPoint = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Completely resets MusicController state.
|
* Completely resets MusicController state.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -427,7 +497,7 @@ public class MusicController {
|
||||||
try {
|
try {
|
||||||
trackLoader.join();
|
trackLoader.join();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
e.printStackTrace();
|
ErrorHandler.error(null, e, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trackLoader = null;
|
trackLoader = null;
|
||||||
|
@ -439,6 +509,7 @@ public class MusicController {
|
||||||
themePlaying = false;
|
themePlaying = false;
|
||||||
pauseTime = 0f;
|
pauseTime = 0f;
|
||||||
trackDimmed = false;
|
trackDimmed = false;
|
||||||
|
resetTimingPoint();
|
||||||
|
|
||||||
// releases all sources from previous tracks
|
// releases all sources from previous tracks
|
||||||
destroyOpenAL();
|
destroyOpenAL();
|
||||||
|
|
|
@ -68,6 +68,18 @@ public class Beatmap implements Comparable<Beatmap> {
|
||||||
/** The star rating. */
|
/** The star rating. */
|
||||||
public double starRating = -1;
|
public double starRating = -1;
|
||||||
|
|
||||||
|
/** The timestamp this beatmap was first loaded. */
|
||||||
|
public long dateAdded = 0;
|
||||||
|
|
||||||
|
/** Whether this beatmap is marked as a "favorite". */
|
||||||
|
public boolean favorite = false;
|
||||||
|
|
||||||
|
/** Total number of times this beatmap has been played. */
|
||||||
|
public int playCount = 0;
|
||||||
|
|
||||||
|
/** The last time this beatmap was played (timestamp). */
|
||||||
|
public long lastPlayed = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [General]
|
* [General]
|
||||||
*/
|
*/
|
||||||
|
@ -501,4 +513,12 @@ public class Beatmap implements Comparable<Beatmap> {
|
||||||
String[] rgb = s.split(",");
|
String[] rgb = s.split(",");
|
||||||
this.sliderBorder = new Color(new Color(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2])));
|
this.sliderBorder = new Color(new Color(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2])));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the play counter and last played time.
|
||||||
|
*/
|
||||||
|
public void incrementPlayCounter() {
|
||||||
|
this.playCount++;
|
||||||
|
this.lastPlayed = System.currentTimeMillis();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -81,7 +81,7 @@ public class BeatmapDifficultyCalculator {
|
||||||
*/
|
*/
|
||||||
public BeatmapDifficultyCalculator(Beatmap beatmap) {
|
public BeatmapDifficultyCalculator(Beatmap beatmap) {
|
||||||
this.beatmap = beatmap;
|
this.beatmap = beatmap;
|
||||||
if (beatmap.timingPoints == null)
|
if (beatmap.breaks == null)
|
||||||
BeatmapDB.load(beatmap, BeatmapDB.LOAD_ARRAY);
|
BeatmapDB.load(beatmap, BeatmapDB.LOAD_ARRAY);
|
||||||
BeatmapParser.parseHitObjects(beatmap);
|
BeatmapParser.parseHitObjects(beatmap);
|
||||||
}
|
}
|
||||||
|
|
179
src/itdelatrisu/opsu/beatmap/BeatmapGroup.java
Normal file
179
src/itdelatrisu/opsu/beatmap/BeatmapGroup.java
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
package itdelatrisu.opsu.beatmap;
|
||||||
|
|
||||||
|
import itdelatrisu.opsu.GameImage;
|
||||||
|
import itdelatrisu.opsu.ui.MenuButton;
|
||||||
|
import itdelatrisu.opsu.ui.UI;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.PriorityQueue;
|
||||||
|
|
||||||
|
import org.newdawn.slick.Image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Beatmap groups.
|
||||||
|
*/
|
||||||
|
public enum BeatmapGroup {
|
||||||
|
/** All beatmaps (no filter). */
|
||||||
|
ALL (0, "All Songs", null),
|
||||||
|
|
||||||
|
/** Most recently played beatmaps. */
|
||||||
|
RECENT (1, "Last Played", "Your recently played beatmaps will appear in this list!") {
|
||||||
|
/** Number of elements to show. */
|
||||||
|
private static final int K = 20;
|
||||||
|
|
||||||
|
/** Returns the latest "last played" time in a beatmap set. */
|
||||||
|
private long lastPlayed(BeatmapSet set) {
|
||||||
|
long max = 0;
|
||||||
|
for (Beatmap beatmap : set) {
|
||||||
|
if (beatmap.lastPlayed > max)
|
||||||
|
max = beatmap.lastPlayed;
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArrayList<BeatmapSetNode> filter(ArrayList<BeatmapSetNode> list) {
|
||||||
|
// find top K elements
|
||||||
|
PriorityQueue<BeatmapSetNode> pq = new PriorityQueue<BeatmapSetNode>(K, new Comparator<BeatmapSetNode>() {
|
||||||
|
@Override
|
||||||
|
public int compare(BeatmapSetNode v, BeatmapSetNode w) {
|
||||||
|
return Long.compare(lastPlayed(v.getBeatmapSet()), lastPlayed(w.getBeatmapSet()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (BeatmapSetNode node : list) {
|
||||||
|
long timestamp = lastPlayed(node.getBeatmapSet());
|
||||||
|
if (timestamp == 0)
|
||||||
|
continue; // skip unplayed beatmaps
|
||||||
|
if (pq.size() < K || timestamp > lastPlayed(pq.peek().getBeatmapSet())) {
|
||||||
|
if (pq.size() == K)
|
||||||
|
pq.poll();
|
||||||
|
pq.add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return as list
|
||||||
|
ArrayList<BeatmapSetNode> filteredList = new ArrayList<BeatmapSetNode>();
|
||||||
|
for (BeatmapSetNode node : pq)
|
||||||
|
filteredList.add(node);
|
||||||
|
return filteredList;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** "Favorite" beatmaps. */
|
||||||
|
FAVORITE (2, "Favorites", "Right-click a beatmap to add it to your Favorites!") {
|
||||||
|
@Override
|
||||||
|
public ArrayList<BeatmapSetNode> filter(ArrayList<BeatmapSetNode> list) {
|
||||||
|
// find "favorite" beatmaps
|
||||||
|
ArrayList<BeatmapSetNode> filteredList = new ArrayList<BeatmapSetNode>();
|
||||||
|
for (BeatmapSetNode node : list) {
|
||||||
|
if (node.getBeatmapSet().isFavorite())
|
||||||
|
filteredList.add(node);
|
||||||
|
}
|
||||||
|
return filteredList;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The ID of the group (used for tab positioning). */
|
||||||
|
private final int id;
|
||||||
|
|
||||||
|
/** The name of the group. */
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
/** The message to display if this list is empty. */
|
||||||
|
private final String emptyMessage;
|
||||||
|
|
||||||
|
/** The tab associated with the group (displayed in Song Menu screen). */
|
||||||
|
private MenuButton tab;
|
||||||
|
|
||||||
|
/** Total number of groups. */
|
||||||
|
private static final int SIZE = values().length;
|
||||||
|
|
||||||
|
/** Array of BeatmapGroup objects in reverse order. */
|
||||||
|
public static final BeatmapGroup[] VALUES_REVERSED;
|
||||||
|
static {
|
||||||
|
VALUES_REVERSED = values();
|
||||||
|
Collections.reverse(Arrays.asList(VALUES_REVERSED));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Current group. */
|
||||||
|
private static BeatmapGroup currentGroup = ALL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current group.
|
||||||
|
* @return the current group
|
||||||
|
*/
|
||||||
|
public static BeatmapGroup current() { return currentGroup; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a new group.
|
||||||
|
* @param group the new group
|
||||||
|
*/
|
||||||
|
public static void set(BeatmapGroup group) { currentGroup = group; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
* @param id the ID of the group (for tab positioning)
|
||||||
|
* @param name the group name
|
||||||
|
* @param emptyMessage the message to display if this list is empty
|
||||||
|
*/
|
||||||
|
BeatmapGroup(int id, String name, String emptyMessage) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.emptyMessage = emptyMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the message to display if this list is empty.
|
||||||
|
* @return the message, or null if none
|
||||||
|
*/
|
||||||
|
public String getEmptyMessage() { return emptyMessage; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a filtered list of beatmap set nodes.
|
||||||
|
* @param list the unfiltered list
|
||||||
|
* @return the filtered list
|
||||||
|
*/
|
||||||
|
public ArrayList<BeatmapSetNode> filter(ArrayList<BeatmapSetNode> list) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the tab.
|
||||||
|
* @param containerWidth the container width
|
||||||
|
* @param bottomY the bottom y coordinate
|
||||||
|
*/
|
||||||
|
public void init(int containerWidth, float bottomY) {
|
||||||
|
Image tab = GameImage.MENU_TAB.getImage();
|
||||||
|
int tabWidth = tab.getWidth();
|
||||||
|
float buttonX = containerWidth / 2f;
|
||||||
|
float tabOffset = (containerWidth - buttonX - tabWidth) / (SIZE - 1);
|
||||||
|
if (tabOffset > tabWidth) { // prevent tabs from being spaced out
|
||||||
|
tabOffset = tabWidth;
|
||||||
|
buttonX = (containerWidth * 0.99f) - (tabWidth * SIZE);
|
||||||
|
}
|
||||||
|
this.tab = new MenuButton(tab,
|
||||||
|
(buttonX + (tabWidth / 2f)) + (id * tabOffset),
|
||||||
|
bottomY - (tab.getHeight() / 2f)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the coordinates are within the image bounds.
|
||||||
|
* @param x the x coordinate
|
||||||
|
* @param y the y coordinate
|
||||||
|
* @return true if within bounds
|
||||||
|
*/
|
||||||
|
public boolean contains(float x, float y) { return tab.contains(x, y); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the tab.
|
||||||
|
* @param selected whether the tab is selected (white) or not (red)
|
||||||
|
* @param isHover whether to include a hover effect (unselected only)
|
||||||
|
*/
|
||||||
|
public void draw(boolean selected, boolean isHover) {
|
||||||
|
UI.drawTab(tab.getX(), tab.getY(), name, selected, isHover);
|
||||||
|
}
|
||||||
|
}
|
|
@ -112,6 +112,7 @@ public class BeatmapParser {
|
||||||
|
|
||||||
// parse directories
|
// parse directories
|
||||||
BeatmapSetNode lastNode = null;
|
BeatmapSetNode lastNode = null;
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
for (File dir : dirs) {
|
for (File dir : dirs) {
|
||||||
currentDirectoryIndex++;
|
currentDirectoryIndex++;
|
||||||
if (!dir.isDirectory())
|
if (!dir.isDirectory())
|
||||||
|
@ -160,6 +161,7 @@ public class BeatmapParser {
|
||||||
|
|
||||||
// add to parsed beatmap list
|
// add to parsed beatmap list
|
||||||
if (beatmap != null) {
|
if (beatmap != null) {
|
||||||
|
beatmap.dateAdded = timestamp;
|
||||||
beatmaps.add(beatmap);
|
beatmaps.add(beatmap);
|
||||||
parsedBeatmaps.add(beatmap);
|
parsedBeatmaps.add(beatmap);
|
||||||
}
|
}
|
||||||
|
@ -547,21 +549,7 @@ public class BeatmapParser {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// parse timing point
|
parseTimingPoint(beatmap, line);
|
||||||
TimingPoint timingPoint = new TimingPoint(line);
|
|
||||||
|
|
||||||
// calculate BPM
|
|
||||||
if (!timingPoint.isInherited()) {
|
|
||||||
int bpm = Math.round(60000 / timingPoint.getBeatLength());
|
|
||||||
if (beatmap.bpmMin == 0)
|
|
||||||
beatmap.bpmMin = beatmap.bpmMax = bpm;
|
|
||||||
else if (bpm < beatmap.bpmMin)
|
|
||||||
beatmap.bpmMin = bpm;
|
|
||||||
else if (bpm > beatmap.bpmMax)
|
|
||||||
beatmap.bpmMax = bpm;
|
|
||||||
}
|
|
||||||
|
|
||||||
beatmap.timingPoints.add(timingPoint);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.warn(String.format("Failed to read timing point '%s' for file '%s'.",
|
Log.warn(String.format("Failed to read timing point '%s' for file '%s'.",
|
||||||
line, file.getAbsolutePath()), e);
|
line, file.getAbsolutePath()), e);
|
||||||
|
@ -678,6 +666,71 @@ public class BeatmapParser {
|
||||||
return beatmap;
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a timing point and adds it to the beatmap.
|
||||||
|
* @param beatmap the beatmap
|
||||||
|
* @param line the line containing the unparsed timing point
|
||||||
|
*/
|
||||||
|
private static void parseTimingPoint(Beatmap beatmap, String line) {
|
||||||
|
// parse timing point
|
||||||
|
TimingPoint timingPoint = new TimingPoint(line);
|
||||||
|
beatmap.timingPoints.add(timingPoint);
|
||||||
|
|
||||||
|
// calculate BPM
|
||||||
|
if (!timingPoint.isInherited()) {
|
||||||
|
int bpm = Math.round(60000 / timingPoint.getBeatLength());
|
||||||
|
if (beatmap.bpmMin == 0) {
|
||||||
|
beatmap.bpmMin = beatmap.bpmMax = bpm;
|
||||||
|
} else if (bpm < beatmap.bpmMin) {
|
||||||
|
beatmap.bpmMin = bpm;
|
||||||
|
} else if (bpm > beatmap.bpmMax) {
|
||||||
|
beatmap.bpmMax = bpm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses all timing points in a beatmap.
|
||||||
|
* @param beatmap the beatmap to parse
|
||||||
|
*/
|
||||||
|
public static void parseTimingPoints(Beatmap beatmap) {
|
||||||
|
if (beatmap.timingPoints != null) // already parsed
|
||||||
|
return;
|
||||||
|
|
||||||
|
beatmap.timingPoints = new ArrayList<TimingPoint>();
|
||||||
|
|
||||||
|
try (BufferedReader in = new BufferedReader(new FileReader(beatmap.getFile()))) {
|
||||||
|
String line = in.readLine();
|
||||||
|
while (line != null) {
|
||||||
|
line = line.trim();
|
||||||
|
if (!line.equals("[TimingPoints]"))
|
||||||
|
line = in.readLine();
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (line == null) // no timing points
|
||||||
|
return;
|
||||||
|
|
||||||
|
while ((line = in.readLine()) != null) {
|
||||||
|
line = line.trim();
|
||||||
|
if (!isValidLine(line))
|
||||||
|
continue;
|
||||||
|
if (line.charAt(0) == '[')
|
||||||
|
break;
|
||||||
|
|
||||||
|
try {
|
||||||
|
parseTimingPoint(beatmap, line);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.warn(String.format("Failed to read timing point '%s' for file '%s'.",
|
||||||
|
line, beatmap.getFile().getAbsolutePath()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
beatmap.timingPoints.trimToSize();
|
||||||
|
} catch (IOException e) {
|
||||||
|
ErrorHandler.error(String.format("Failed to read file '%s'.", beatmap.getFile().getAbsolutePath()), e, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses all hit objects in a beatmap.
|
* Parses all hit objects in a beatmap.
|
||||||
* @param beatmap the beatmap to parse
|
* @param beatmap the beatmap to parse
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package itdelatrisu.opsu.beatmap;
|
package itdelatrisu.opsu.beatmap;
|
||||||
|
|
||||||
import itdelatrisu.opsu.GameMod;
|
import itdelatrisu.opsu.GameMod;
|
||||||
|
import itdelatrisu.opsu.db.BeatmapDB;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
@ -185,4 +186,37 @@ public class BeatmapSet implements Iterable<Beatmap> {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this beatmap set is a "favorite".
|
||||||
|
*/
|
||||||
|
public boolean isFavorite() {
|
||||||
|
for (Beatmap map : beatmaps) {
|
||||||
|
if (map.favorite)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the "favorite" status of this beatmap set.
|
||||||
|
* @param flag whether this beatmap set should have "favorite" status
|
||||||
|
*/
|
||||||
|
public void setFavorite(boolean flag) {
|
||||||
|
for (Beatmap map : beatmaps) {
|
||||||
|
map.favorite = flag;
|
||||||
|
BeatmapDB.updateFavoriteStatus(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether any beatmap in this set has been played.
|
||||||
|
*/
|
||||||
|
public boolean isPlayed() {
|
||||||
|
for (Beatmap map : beatmaps) {
|
||||||
|
if (map.playCount > 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,9 @@ public class BeatmapSetList {
|
||||||
/** Total number of beatmaps (i.e. Beatmap objects). */
|
/** Total number of beatmaps (i.e. Beatmap objects). */
|
||||||
private int mapCount = 0;
|
private int mapCount = 0;
|
||||||
|
|
||||||
|
/** List containing all nodes in the current group. */
|
||||||
|
private ArrayList<BeatmapSetNode> groupNodes;
|
||||||
|
|
||||||
/** Current list of nodes (subset of parsedNodes, used for searches). */
|
/** Current list of nodes (subset of parsedNodes, used for searches). */
|
||||||
private ArrayList<BeatmapSetNode> nodes;
|
private ArrayList<BeatmapSetNode> nodes;
|
||||||
|
|
||||||
|
@ -97,7 +100,7 @@ public class BeatmapSetList {
|
||||||
* This does not erase any parsed nodes.
|
* This does not erase any parsed nodes.
|
||||||
*/
|
*/
|
||||||
public void reset() {
|
public void reset() {
|
||||||
nodes = parsedNodes;
|
nodes = groupNodes = BeatmapGroup.current().filter(parsedNodes);
|
||||||
expandedIndex = -1;
|
expandedIndex = -1;
|
||||||
expandedStartNode = expandedEndNode = null;
|
expandedStartNode = expandedEndNode = null;
|
||||||
lastQuery = "";
|
lastQuery = "";
|
||||||
|
@ -168,6 +171,7 @@ public class BeatmapSetList {
|
||||||
Beatmap beatmap = beatmapSet.get(0);
|
Beatmap beatmap = beatmapSet.get(0);
|
||||||
nodes.remove(index);
|
nodes.remove(index);
|
||||||
parsedNodes.remove(eCur);
|
parsedNodes.remove(eCur);
|
||||||
|
groupNodes.remove(eCur);
|
||||||
mapCount -= beatmapSet.size();
|
mapCount -= beatmapSet.size();
|
||||||
if (beatmap.beatmapSetID > 0)
|
if (beatmap.beatmapSetID > 0)
|
||||||
MSIDdb.remove(beatmap.beatmapSetID);
|
MSIDdb.remove(beatmap.beatmapSetID);
|
||||||
|
@ -407,7 +411,7 @@ public class BeatmapSetList {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// sort the list
|
// sort the list
|
||||||
Collections.sort(nodes, BeatmapSortOrder.getSort().getComparator());
|
Collections.sort(nodes, BeatmapSortOrder.current().getComparator());
|
||||||
expandedIndex = -1;
|
expandedIndex = -1;
|
||||||
expandedStartNode = expandedEndNode = null;
|
expandedStartNode = expandedEndNode = null;
|
||||||
|
|
||||||
|
@ -444,7 +448,7 @@ public class BeatmapSetList {
|
||||||
|
|
||||||
// if empty query, reset to original list
|
// if empty query, reset to original list
|
||||||
if (query.isEmpty() || terms.isEmpty()) {
|
if (query.isEmpty() || terms.isEmpty()) {
|
||||||
nodes = parsedNodes;
|
nodes = groupNodes;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,14 +476,14 @@ public class BeatmapSetList {
|
||||||
String type = condType.remove();
|
String type = condType.remove();
|
||||||
String operator = condOperator.remove();
|
String operator = condOperator.remove();
|
||||||
float value = condValue.remove();
|
float value = condValue.remove();
|
||||||
for (BeatmapSetNode node : parsedNodes) {
|
for (BeatmapSetNode node : groupNodes) {
|
||||||
if (node.getBeatmapSet().matches(type, operator, value))
|
if (node.getBeatmapSet().matches(type, operator, value))
|
||||||
nodes.add(node);
|
nodes.add(node);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// normal term
|
// normal term
|
||||||
String term = terms.remove();
|
String term = terms.remove();
|
||||||
for (BeatmapSetNode node : parsedNodes) {
|
for (BeatmapSetNode node : groupNodes) {
|
||||||
if (node.getBeatmapSet().matches(term))
|
if (node.getBeatmapSet().matches(term))
|
||||||
nodes.add(node);
|
nodes.add(node);
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ public class BeatmapSetNode {
|
||||||
public void draw(float x, float y, Grade grade, boolean focus) {
|
public void draw(float x, float y, Grade grade, boolean focus) {
|
||||||
Image bg = GameImage.MENU_BUTTON_BG.getImage();
|
Image bg = GameImage.MENU_BUTTON_BG.getImage();
|
||||||
boolean expanded = (beatmapIndex > -1);
|
boolean expanded = (beatmapIndex > -1);
|
||||||
Beatmap beatmap;
|
Beatmap beatmap = beatmapSet.get(expanded ? beatmapIndex : 0);
|
||||||
bg.setAlpha(0.9f);
|
bg.setAlpha(0.9f);
|
||||||
Color bgColor;
|
Color bgColor;
|
||||||
Color textColor = Options.getSkin().getSongSelectInactiveTextColor();
|
Color textColor = Options.getSkin().getSongSelectInactiveTextColor();
|
||||||
|
@ -88,11 +88,10 @@ public class BeatmapSetNode {
|
||||||
textColor = Options.getSkin().getSongSelectActiveTextColor();
|
textColor = Options.getSkin().getSongSelectActiveTextColor();
|
||||||
} else
|
} else
|
||||||
bgColor = Colors.BLUE_BUTTON;
|
bgColor = Colors.BLUE_BUTTON;
|
||||||
beatmap = beatmapSet.get(beatmapIndex);
|
} else if (beatmapSet.isPlayed())
|
||||||
} else {
|
|
||||||
bgColor = Colors.ORANGE_BUTTON;
|
bgColor = Colors.ORANGE_BUTTON;
|
||||||
beatmap = beatmapSet.get(0);
|
else
|
||||||
}
|
bgColor = Colors.PINK_BUTTON;
|
||||||
bg.draw(x, y, bgColor);
|
bg.draw(x, y, bgColor);
|
||||||
|
|
||||||
float cx = x + (bg.getWidth() * 0.043f);
|
float cx = x + (bg.getWidth() * 0.043f);
|
||||||
|
|
|
@ -18,28 +18,19 @@
|
||||||
|
|
||||||
package itdelatrisu.opsu.beatmap;
|
package itdelatrisu.opsu.beatmap;
|
||||||
|
|
||||||
import itdelatrisu.opsu.GameImage;
|
|
||||||
import itdelatrisu.opsu.ui.MenuButton;
|
|
||||||
import itdelatrisu.opsu.ui.UI;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
|
||||||
import org.newdawn.slick.Image;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Beatmap sorting orders.
|
* Beatmap sorting orders.
|
||||||
*/
|
*/
|
||||||
public enum BeatmapSortOrder {
|
public enum BeatmapSortOrder {
|
||||||
TITLE (0, "Title", new TitleOrder()),
|
TITLE ("Title", new TitleOrder()),
|
||||||
ARTIST (1, "Artist", new ArtistOrder()),
|
ARTIST ("Artist", new ArtistOrder()),
|
||||||
CREATOR (2, "Creator", new CreatorOrder()),
|
CREATOR ("Creator", new CreatorOrder()),
|
||||||
BPM (3, "BPM", new BPMOrder()),
|
BPM ("BPM", new BPMOrder()),
|
||||||
LENGTH (4, "Length", new LengthOrder());
|
LENGTH ("Length", new LengthOrder()),
|
||||||
|
DATE ("Date Added", new DateOrder()),
|
||||||
/** The ID of the sort (used for tab positioning). */
|
PLAYS ("Most Played", new PlayOrder());
|
||||||
private final int id;
|
|
||||||
|
|
||||||
/** The name of the sort. */
|
/** The name of the sort. */
|
||||||
private final String name;
|
private final String name;
|
||||||
|
@ -47,19 +38,6 @@ public enum BeatmapSortOrder {
|
||||||
/** The comparator for the sort. */
|
/** The comparator for the sort. */
|
||||||
private final Comparator<BeatmapSetNode> comparator;
|
private final Comparator<BeatmapSetNode> comparator;
|
||||||
|
|
||||||
/** The tab associated with the sort (displayed in Song Menu screen). */
|
|
||||||
private MenuButton tab;
|
|
||||||
|
|
||||||
/** Total number of sorts. */
|
|
||||||
private static final int SIZE = values().length;
|
|
||||||
|
|
||||||
/** Array of BeatmapSortOrder objects in reverse order. */
|
|
||||||
public static final BeatmapSortOrder[] VALUES_REVERSED;
|
|
||||||
static {
|
|
||||||
VALUES_REVERSED = values();
|
|
||||||
Collections.reverse(Arrays.asList(VALUES_REVERSED));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Current sort. */
|
/** Current sort. */
|
||||||
private static BeatmapSortOrder currentSort = TITLE;
|
private static BeatmapSortOrder currentSort = TITLE;
|
||||||
|
|
||||||
|
@ -67,13 +45,13 @@ public enum BeatmapSortOrder {
|
||||||
* Returns the current sort.
|
* Returns the current sort.
|
||||||
* @return the current sort
|
* @return the current sort
|
||||||
*/
|
*/
|
||||||
public static BeatmapSortOrder getSort() { return currentSort; }
|
public static BeatmapSortOrder current() { return currentSort; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a new sort.
|
* Sets a new sort.
|
||||||
* @param sort the new sort
|
* @param sort the new sort
|
||||||
*/
|
*/
|
||||||
public static void setSort(BeatmapSortOrder sort) { BeatmapSortOrder.currentSort = sort; }
|
public static void set(BeatmapSortOrder sort) { currentSort = sort; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares two BeatmapSetNode objects by title.
|
* Compares two BeatmapSetNode objects by title.
|
||||||
|
@ -135,37 +113,57 @@ public enum BeatmapSortOrder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two BeatmapSetNode objects by date added.
|
||||||
|
* Uses the latest beatmap added in each set for comparison.
|
||||||
|
*/
|
||||||
|
private static class DateOrder implements Comparator<BeatmapSetNode> {
|
||||||
|
@Override
|
||||||
|
public int compare(BeatmapSetNode v, BeatmapSetNode w) {
|
||||||
|
long vMax = 0, wMax = 0;
|
||||||
|
for (Beatmap beatmap : v.getBeatmapSet()) {
|
||||||
|
if (beatmap.dateAdded > vMax)
|
||||||
|
vMax = beatmap.dateAdded;
|
||||||
|
}
|
||||||
|
for (Beatmap beatmap : w.getBeatmapSet()) {
|
||||||
|
if (beatmap.dateAdded > wMax)
|
||||||
|
wMax = beatmap.dateAdded;
|
||||||
|
}
|
||||||
|
return Long.compare(vMax, wMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two BeatmapSetNode objects by total plays
|
||||||
|
* (summed across all beatmaps in each set).
|
||||||
|
*/
|
||||||
|
private static class PlayOrder implements Comparator<BeatmapSetNode> {
|
||||||
|
@Override
|
||||||
|
public int compare(BeatmapSetNode v, BeatmapSetNode w) {
|
||||||
|
int vTotal = 0, wTotal = 0;
|
||||||
|
for (Beatmap beatmap : v.getBeatmapSet())
|
||||||
|
vTotal += beatmap.playCount;
|
||||||
|
for (Beatmap beatmap : w.getBeatmapSet())
|
||||||
|
wTotal += beatmap.playCount;
|
||||||
|
return Integer.compare(vTotal, wTotal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
* @param id the ID of the sort (for tab positioning)
|
|
||||||
* @param name the sort name
|
* @param name the sort name
|
||||||
* @param comparator the comparator for the sort
|
* @param comparator the comparator for the sort
|
||||||
*/
|
*/
|
||||||
BeatmapSortOrder(int id, String name, Comparator<BeatmapSetNode> comparator) {
|
BeatmapSortOrder(String name, Comparator<BeatmapSetNode> comparator) {
|
||||||
this.id = id;
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.comparator = comparator;
|
this.comparator = comparator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the sort tab.
|
* Returns the sort name.
|
||||||
* @param containerWidth the container width
|
* @return the name
|
||||||
* @param bottomY the bottom y coordinate
|
|
||||||
*/
|
*/
|
||||||
public void init(int containerWidth, float bottomY) {
|
public String getName() { return name; }
|
||||||
Image tab = GameImage.MENU_TAB.getImage();
|
|
||||||
int tabWidth = tab.getWidth();
|
|
||||||
float buttonX = containerWidth / 2f;
|
|
||||||
float tabOffset = (containerWidth - buttonX - tabWidth) / (SIZE - 1);
|
|
||||||
if (tabOffset > tabWidth) { // prevent tabs from being spaced out
|
|
||||||
tabOffset = tabWidth;
|
|
||||||
buttonX = (containerWidth * 0.99f) - (tabWidth * SIZE);
|
|
||||||
}
|
|
||||||
this.tab = new MenuButton(tab,
|
|
||||||
(buttonX + (tabWidth / 2f)) + (id * tabOffset),
|
|
||||||
bottomY - (tab.getHeight() / 2f)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the comparator for the sort.
|
* Returns the comparator for the sort.
|
||||||
|
@ -173,20 +171,6 @@ public enum BeatmapSortOrder {
|
||||||
*/
|
*/
|
||||||
public Comparator<BeatmapSetNode> getComparator() { return comparator; }
|
public Comparator<BeatmapSetNode> getComparator() { return comparator; }
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Checks if the coordinates are within the image bounds.
|
public String toString() { return name; }
|
||||||
* @param x the x coordinate
|
|
||||||
* @param y the y coordinate
|
|
||||||
* @return true if within bounds
|
|
||||||
*/
|
|
||||||
public boolean contains(float x, float y) { return tab.contains(x, y); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the sort tab.
|
|
||||||
* @param selected whether the tab is selected (white) or not (red)
|
|
||||||
* @param isHover whether to include a hover effect (unselected only)
|
|
||||||
*/
|
|
||||||
public void draw(boolean selected, boolean isHover) {
|
|
||||||
UI.drawTab(tab.getX(), tab.getY(), name, selected, isHover);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -58,6 +58,14 @@ public class TimingPoint {
|
||||||
* @param line the line to be parsed
|
* @param line the line to be parsed
|
||||||
*/
|
*/
|
||||||
public TimingPoint(String line) {
|
public TimingPoint(String line) {
|
||||||
|
/**
|
||||||
|
* [TIMING POINT FORMATS]
|
||||||
|
* Non-inherited:
|
||||||
|
* offset,msPerBeat,meter,sampleType,sampleSet,volume,inherited,kiai
|
||||||
|
*
|
||||||
|
* Inherited:
|
||||||
|
* offset,velocity,meter,sampleType,sampleSet,volume,inherited,kiai
|
||||||
|
*/
|
||||||
// TODO: better support for old formats
|
// TODO: better support for old formats
|
||||||
String[] tokens = line.split(",");
|
String[] tokens = line.split(",");
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -30,6 +30,7 @@ import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -42,8 +43,30 @@ public class BeatmapDB {
|
||||||
/**
|
/**
|
||||||
* Current database version.
|
* Current database version.
|
||||||
* This value should be changed whenever the database format changes.
|
* This value should be changed whenever the database format changes.
|
||||||
|
* Add any update queries to the {@link #getUpdateQueries(int)} method.
|
||||||
*/
|
*/
|
||||||
private static final String DATABASE_VERSION = "2015-09-02";
|
private static final int DATABASE_VERSION = 20161222;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of SQL queries to apply, in order, to update from
|
||||||
|
* the given database version to the latest version.
|
||||||
|
* @param version the current version
|
||||||
|
* @return a list of SQL queries
|
||||||
|
*/
|
||||||
|
private static List<String> getUpdateQueries(int version) {
|
||||||
|
List<String> list = new LinkedList<String>();
|
||||||
|
if (version < 20161222) {
|
||||||
|
list.add("ALTER TABLE beatmaps ADD COLUMN dateAdded INTEGER");
|
||||||
|
list.add("ALTER TABLE beatmaps ADD COLUMN favorite BOOLEAN");
|
||||||
|
list.add("ALTER TABLE beatmaps ADD COLUMN playCount INTEGER");
|
||||||
|
list.add("ALTER TABLE beatmaps ADD COLUMN lastPlayed INTEGER");
|
||||||
|
list.add("UPDATE beatmaps SET dateAdded = 0, favorite = 0, playCount = 0, lastPlayed = 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add future updates here */
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
/** Minimum batch size ratio ({@code batchSize/cacheSize}) to invoke batch loading. */
|
/** Minimum batch size ratio ({@code batchSize/cacheSize}) to invoke batch loading. */
|
||||||
private static final float LOAD_BATCH_MIN_RATIO = 0.2f;
|
private static final float LOAD_BATCH_MIN_RATIO = 0.2f;
|
||||||
|
@ -58,7 +81,9 @@ public class BeatmapDB {
|
||||||
private static Connection connection;
|
private static Connection connection;
|
||||||
|
|
||||||
/** Query statements. */
|
/** Query statements. */
|
||||||
private static PreparedStatement insertStmt, selectStmt, deleteMapStmt, deleteGroupStmt, setStarsStmt, updateSizeStmt;
|
private static PreparedStatement
|
||||||
|
insertStmt, selectStmt, deleteMapStmt, deleteGroupStmt,
|
||||||
|
setStarsStmt, updatePlayStatsStmt, setFavoriteStmt, updateSizeStmt;
|
||||||
|
|
||||||
/** Current size of beatmap cache table. */
|
/** Current size of beatmap cache table. */
|
||||||
private static int cacheSize = -1;
|
private static int cacheSize = -1;
|
||||||
|
@ -75,6 +100,9 @@ public class BeatmapDB {
|
||||||
if (connection == null)
|
if (connection == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// run any database updates
|
||||||
|
updateDatabase();
|
||||||
|
|
||||||
// create the database
|
// create the database
|
||||||
createDatabase();
|
createDatabase();
|
||||||
|
|
||||||
|
@ -88,20 +116,21 @@ public class BeatmapDB {
|
||||||
// retrieve the cache size
|
// retrieve the cache size
|
||||||
getCacheSize();
|
getCacheSize();
|
||||||
|
|
||||||
// check the database version
|
|
||||||
checkVersion();
|
|
||||||
|
|
||||||
// prepare sql statements (not used here)
|
// prepare sql statements (not used here)
|
||||||
try {
|
try {
|
||||||
insertStmt = connection.prepareStatement(
|
insertStmt = connection.prepareStatement(
|
||||||
"INSERT INTO beatmaps VALUES (" +
|
"INSERT INTO beatmaps VALUES (" +
|
||||||
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," +
|
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," +
|
||||||
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," +
|
||||||
|
"?, ?, ?, ?, ?, ?" +
|
||||||
|
")"
|
||||||
);
|
);
|
||||||
selectStmt = connection.prepareStatement("SELECT * FROM beatmaps WHERE dir = ? AND file = ?");
|
selectStmt = connection.prepareStatement("SELECT * FROM beatmaps WHERE dir = ? AND file = ?");
|
||||||
deleteMapStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ? AND file = ?");
|
deleteMapStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ? AND file = ?");
|
||||||
deleteGroupStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ?");
|
deleteGroupStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ?");
|
||||||
setStarsStmt = connection.prepareStatement("UPDATE beatmaps SET stars = ? WHERE dir = ? AND file = ?");
|
setStarsStmt = connection.prepareStatement("UPDATE beatmaps SET stars = ? WHERE dir = ? AND file = ?");
|
||||||
|
updatePlayStatsStmt = connection.prepareStatement("UPDATE beatmaps SET playCount = ?, lastPlayed = ? WHERE dir = ? AND file = ?");
|
||||||
|
setFavoriteStmt = connection.prepareStatement("UPDATE beatmaps SET favorite = ? WHERE dir = ? AND file = ?");
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
ErrorHandler.error("Failed to prepare beatmap statements.", e, true);
|
ErrorHandler.error("Failed to prepare beatmap statements.", e, true);
|
||||||
}
|
}
|
||||||
|
@ -124,7 +153,8 @@ public class BeatmapDB {
|
||||||
"audioFile TEXT, audioLeadIn INTEGER, previewTime INTEGER, countdown INTEGER, sampleSet TEXT, stackLeniency REAL, " +
|
"audioFile TEXT, audioLeadIn INTEGER, previewTime INTEGER, countdown INTEGER, sampleSet TEXT, stackLeniency REAL, " +
|
||||||
"mode INTEGER, letterboxInBreaks BOOLEAN, widescreenStoryboard BOOLEAN, epilepsyWarning BOOLEAN, " +
|
"mode INTEGER, letterboxInBreaks BOOLEAN, widescreenStoryboard BOOLEAN, epilepsyWarning BOOLEAN, " +
|
||||||
"bg TEXT, sliderBorder TEXT, timingPoints TEXT, breaks TEXT, combo TEXT, " +
|
"bg TEXT, sliderBorder TEXT, timingPoints TEXT, breaks TEXT, combo TEXT, " +
|
||||||
"md5hash TEXT, stars REAL" +
|
"md5hash TEXT, stars REAL, " +
|
||||||
|
"dateAdded INTEGER, favorite BOOLEAN, playCount INTEGER, lastPlayed INTEGER" +
|
||||||
"); " +
|
"); " +
|
||||||
"CREATE TABLE IF NOT EXISTS info (" +
|
"CREATE TABLE IF NOT EXISTS info (" +
|
||||||
"key TEXT NOT NULL UNIQUE, value TEXT" +
|
"key TEXT NOT NULL UNIQUE, value TEXT" +
|
||||||
|
@ -145,29 +175,54 @@ public class BeatmapDB {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the stored table version, clears the beatmap database if different
|
* Applies any database updates by comparing the current version to the
|
||||||
* from the current version, then updates the version field.
|
* stored version. Does nothing if tables have not been created.
|
||||||
*/
|
*/
|
||||||
private static void checkVersion() {
|
private static void updateDatabase() {
|
||||||
try (Statement stmt = connection.createStatement()) {
|
try (Statement stmt = connection.createStatement()) {
|
||||||
// get the stored version
|
int version = 0;
|
||||||
String sql = "SELECT value FROM info WHERE key = 'version'";
|
|
||||||
ResultSet rs = stmt.executeQuery(sql);
|
|
||||||
String version = (rs.next()) ? rs.getString(1) : "";
|
|
||||||
rs.close();
|
|
||||||
|
|
||||||
// if different from current version, clear the database
|
// if 'info' table does not exist, assume version 0 and apply all updates
|
||||||
if (!version.equals(DATABASE_VERSION)) {
|
String sql = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'info'";
|
||||||
clearDatabase();
|
ResultSet rs = stmt.executeQuery(sql);
|
||||||
|
boolean infoExists = rs.isBeforeFirst();
|
||||||
|
rs.close();
|
||||||
|
if (!infoExists) {
|
||||||
|
// if 'beatmaps' table also does not exist, databases not yet created
|
||||||
|
sql = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'beatmaps'";
|
||||||
|
ResultSet beatmapsRS = stmt.executeQuery(sql);
|
||||||
|
boolean beatmapsExists = beatmapsRS.isBeforeFirst();
|
||||||
|
beatmapsRS.close();
|
||||||
|
if (!beatmapsExists)
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// try to retrieve stored version
|
||||||
|
sql = "SELECT value FROM info WHERE key = 'version'";
|
||||||
|
ResultSet versionRS = stmt.executeQuery(sql);
|
||||||
|
String versionString = (versionRS.next()) ? versionRS.getString(1) : "0";
|
||||||
|
versionRS.close();
|
||||||
|
try {
|
||||||
|
version = Integer.parseInt(versionString);
|
||||||
|
} catch (NumberFormatException e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// database versions match
|
||||||
|
if (version >= DATABASE_VERSION)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// apply updates
|
||||||
|
for (String query : getUpdateQueries(version))
|
||||||
|
stmt.executeUpdate(query);
|
||||||
|
|
||||||
// update version
|
// update version
|
||||||
|
if (infoExists) {
|
||||||
PreparedStatement ps = connection.prepareStatement("REPLACE INTO info (key, value) VALUES ('version', ?)");
|
PreparedStatement ps = connection.prepareStatement("REPLACE INTO info (key, value) VALUES ('version', ?)");
|
||||||
ps.setString(1, DATABASE_VERSION);
|
ps.setString(1, Integer.toString(DATABASE_VERSION));
|
||||||
ps.executeUpdate();
|
ps.executeUpdate();
|
||||||
ps.close();
|
ps.close();
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
ErrorHandler.error("Beatmap database version checks failed.", e, true);
|
ErrorHandler.error("Failed to update beatmap database.", e, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,6 +399,10 @@ public class BeatmapDB {
|
||||||
stmt.setString(40, beatmap.comboToString());
|
stmt.setString(40, beatmap.comboToString());
|
||||||
stmt.setString(41, beatmap.md5Hash);
|
stmt.setString(41, beatmap.md5Hash);
|
||||||
stmt.setDouble(42, beatmap.starRating);
|
stmt.setDouble(42, beatmap.starRating);
|
||||||
|
stmt.setLong(43, beatmap.dateAdded);
|
||||||
|
stmt.setBoolean(44, beatmap.favorite);
|
||||||
|
stmt.setInt(45, beatmap.playCount);
|
||||||
|
stmt.setLong(46, beatmap.lastPlayed);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -487,6 +546,10 @@ public class BeatmapDB {
|
||||||
beatmap.sliderBorderFromString(rs.getString(37));
|
beatmap.sliderBorderFromString(rs.getString(37));
|
||||||
beatmap.md5Hash = rs.getString(41);
|
beatmap.md5Hash = rs.getString(41);
|
||||||
beatmap.starRating = rs.getDouble(42);
|
beatmap.starRating = rs.getDouble(42);
|
||||||
|
beatmap.dateAdded = rs.getLong(43);
|
||||||
|
beatmap.favorite = rs.getBoolean(44);
|
||||||
|
beatmap.playCount = rs.getInt(45);
|
||||||
|
beatmap.lastPlayed = rs.getLong(46);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -593,6 +656,45 @@ public class BeatmapDB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the play statistics for a beatmap in the database.
|
||||||
|
* @param beatmap the beatmap
|
||||||
|
*/
|
||||||
|
public static void updatePlayStatistics(Beatmap beatmap) {
|
||||||
|
if (connection == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
updatePlayStatsStmt.setInt(1, beatmap.playCount);
|
||||||
|
updatePlayStatsStmt.setLong(2, beatmap.lastPlayed);
|
||||||
|
updatePlayStatsStmt.setString(3, beatmap.getFile().getParentFile().getName());
|
||||||
|
updatePlayStatsStmt.setString(4, beatmap.getFile().getName());
|
||||||
|
updatePlayStatsStmt.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
ErrorHandler.error(String.format("Failed to update play statistics for beatmap '%s' in database.",
|
||||||
|
beatmap.toString()), e, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the "favorite" status for a beatmap in the database.
|
||||||
|
* @param beatmap the beatmap
|
||||||
|
*/
|
||||||
|
public static void updateFavoriteStatus(Beatmap beatmap) {
|
||||||
|
if (connection == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setFavoriteStmt.setBoolean(1, beatmap.favorite);
|
||||||
|
setFavoriteStmt.setString(2, beatmap.getFile().getParentFile().getName());
|
||||||
|
setFavoriteStmt.setString(3, beatmap.getFile().getName());
|
||||||
|
setFavoriteStmt.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
ErrorHandler.error(String.format("Failed to update favorite status for beatmap '%s' in database.",
|
||||||
|
beatmap.toString()), e, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the connection to the database.
|
* Closes the connection to the database.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -360,7 +360,7 @@ public class ScoreDB {
|
||||||
ResultSet rs = selectMapSetStmt.executeQuery();
|
ResultSet rs = selectMapSetStmt.executeQuery();
|
||||||
|
|
||||||
List<ScoreData> list = null;
|
List<ScoreData> list = null;
|
||||||
String version = ""; // sorted by version, so pass through and check for differences
|
String version = null; // sorted by version, so pass through and check for differences
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
ScoreData s = new ScoreData(rs);
|
ScoreData s = new ScoreData(rs);
|
||||||
if (!s.version.equals(version)) {
|
if (!s.version.equals(version)) {
|
||||||
|
|
|
@ -179,7 +179,7 @@ public class Circle extends GameObject {
|
||||||
|
|
||||||
if (result > -1) {
|
if (result > -1) {
|
||||||
data.addHitError(hitObject.getTime(), x, y, timeDiff);
|
data.addHitError(hitObject.getTime(), x, y, timeDiff);
|
||||||
data.hitResult(trackPosition, result, this.x, this.y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
|
data.sendHitResult(trackPosition, result, this.x, this.y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,25 +195,25 @@ public class Circle extends GameObject {
|
||||||
|
|
||||||
if (trackPosition > time + hitResultOffset[GameData.HIT_50]) {
|
if (trackPosition > time + hitResultOffset[GameData.HIT_50]) {
|
||||||
if (isAutoMod) {// "auto" mod: catch any missed notes due to lag
|
if (isAutoMod) {// "auto" mod: catch any missed notes due to lag
|
||||||
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
|
data.sendHitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
|
||||||
if (Options.isMirror() && GameMod.AUTO.isActive()) {
|
if (Options.isMirror() && GameMod.AUTO.isActive()) {
|
||||||
float[] m = Utils.mirrorPoint(x, y);
|
float[] m = Utils.mirrorPoint(x, y);
|
||||||
data.hitResult(time, GameData.HIT_300, m[0], m[1], mirrorColor, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false, false);
|
data.sendHitResult(time, GameData.HIT_300, m[0], m[1], mirrorColor, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else // no more points can be scored, so send a miss
|
else // no more points can be scored, so send a miss
|
||||||
data.hitResult(trackPosition, GameData.HIT_MISS, x, y, null, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
|
data.sendHitResult(trackPosition, GameData.HIT_MISS, x, y, null, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// "auto" mod: send a perfect hit result
|
// "auto" mod: send a perfect hit result
|
||||||
else if (isAutoMod) {
|
else if (isAutoMod) {
|
||||||
if (Math.abs(trackPosition - time) < hitResultOffset[GameData.HIT_300]) {
|
if (Math.abs(trackPosition - time) < hitResultOffset[GameData.HIT_300]) {
|
||||||
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
|
data.sendHitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
|
||||||
if (Options.isMirror() && GameMod.AUTO.isActive()) {
|
if (Options.isMirror() && GameMod.AUTO.isActive()) {
|
||||||
float[] m = Utils.mirrorPoint(x, y);
|
float[] m = Utils.mirrorPoint(x, y);
|
||||||
data.hitResult(time, GameData.HIT_300, m[0], m[1], mirrorColor, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false, false);
|
data.sendHitResult(time, GameData.HIT_300, m[0], m[1], mirrorColor, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false, false);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,6 +205,7 @@ public class Slider extends GameObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
int timeDiff = hitObject.getTime() - trackPosition;
|
int timeDiff = hitObject.getTime() - trackPosition;
|
||||||
|
final int repeatCount = hitObject.getRepeatCount();
|
||||||
final int approachTime = game.getApproachTime();
|
final int approachTime = game.getApproachTime();
|
||||||
final int fadeInTime = game.getFadeInTime();
|
final int fadeInTime = game.getFadeInTime();
|
||||||
float scale = timeDiff / (float) approachTime;
|
float scale = timeDiff / (float) approachTime;
|
||||||
|
@ -219,9 +220,12 @@ public class Slider extends GameObject {
|
||||||
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
||||||
Vec2f endPos = curve.pointAt(1);
|
Vec2f endPos = curve.pointAt(1);
|
||||||
|
|
||||||
float curveAlpha = 1f;
|
float oldWhiteFadeAlpha = Colors.WHITE_FADE.a;
|
||||||
if (GameMod.HIDDEN.isActive() && trackPosition > getTime()) {
|
float sliderAlpha = 1f;
|
||||||
curveAlpha = Math.max(0f, 1f - ((float) (trackPosition - getTime()) / (getEndTime() - getTime())) * 1.05f);
|
if (GameMod.HIDDEN.isActive() && trackPosition > hitObject.getTime()) {
|
||||||
|
// "Hidden" mod: fade out sliders
|
||||||
|
Colors.WHITE_FADE.a = color.a = sliderAlpha =
|
||||||
|
Math.max(0f, 1f - ((float) (trackPosition - hitObject.getTime()) / (getEndTime() - hitObject.getTime())) * 1.05f);
|
||||||
}
|
}
|
||||||
|
|
||||||
curveColor.a = curveAlpha;
|
curveColor.a = curveAlpha;
|
||||||
|
@ -232,19 +236,37 @@ public class Slider extends GameObject {
|
||||||
if (mirror) {
|
if (mirror) {
|
||||||
g.rotate(x, y, -180f);
|
g.rotate(x, y, -180f);
|
||||||
}
|
}
|
||||||
|
// end circle (only draw if ball still has to go there)
|
||||||
/*
|
if (curveInterval == 1f && currentRepeats < repeatCount - (repeatCount % 2 == 0 ? 1 : 0)) {
|
||||||
// end circle
|
Color circleColor = new Color(color);
|
||||||
|
Color overlayColor = new Color(Colors.WHITE_FADE);
|
||||||
|
if (currentRepeats == 0) {
|
||||||
|
if (Options.isSliderSnaking()) {
|
||||||
|
// fade in end circle using decorationsAlpha when snaking sliders are enabled
|
||||||
|
circleColor.a = overlayColor.a = sliderAlpha * decorationsAlpha;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// fade in end circle after repeats
|
||||||
|
circleColor.a = overlayColor.a = sliderAlpha * getCircleAlphaAfterRepeat(trackPosition, true);
|
||||||
|
}
|
||||||
Vec2f endCircPos = curve.pointAt(curveInterval);
|
Vec2f endCircPos = curve.pointAt(curveInterval);
|
||||||
hitCircle.drawCentered(endCircPos.x, endCircPos.y, color);
|
hitCircle.drawCentered(endCircPos.x, endCircPos.y, circleColor);
|
||||||
hitCircleOverlay.drawCentered(endCircPos.x, endCircPos.y, Colors.WHITE_FADE);
|
hitCircleOverlay.drawCentered(endCircPos.x, endCircPos.y, overlayColor);
|
||||||
*/
|
}
|
||||||
|
|
||||||
// start circle, don't draw if already clicked
|
// set first circle colors to fade in after repeats
|
||||||
if (!sliderClickedInitial) {
|
Color firstCircleColor = new Color(color);
|
||||||
hitCircle.drawCentered(x, y, color);
|
Color startCircleOverlayColor = new Color(Colors.WHITE_FADE);
|
||||||
if (!overlayAboveNumber)
|
if (sliderClickedInitial) {
|
||||||
hitCircleOverlay.drawCentered(x, y, Colors.WHITE_FADE);
|
// fade in first circle after repeats
|
||||||
|
firstCircleColor.a = startCircleOverlayColor.a = sliderAlpha * getCircleAlphaAfterRepeat(trackPosition, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// start circle, only draw if ball still has to go there
|
||||||
|
if (!sliderClickedInitial || currentRepeats < repeatCount - (repeatCount % 2 == 1 ? 1 : 0)) {
|
||||||
|
hitCircle.drawCentered(x, y, firstCircleColor);
|
||||||
|
if (!overlayAboveNumber || sliderClickedInitial)
|
||||||
|
hitCircleOverlay.drawCentered(x, y, startCircleOverlayColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
g.popTransform();
|
g.popTransform();
|
||||||
|
@ -252,7 +274,7 @@ public class Slider extends GameObject {
|
||||||
// ticks
|
// ticks
|
||||||
if (ticksT != null) {
|
if (ticksT != null) {
|
||||||
drawSliderTicks(g, trackPosition, alpha, decorationsAlpha, mirror);
|
drawSliderTicks(g, trackPosition, alpha, decorationsAlpha, mirror);
|
||||||
Colors.WHITE_FADE.a = alpha;
|
Colors.WHITE_FADE.a = oldWhiteFadeAlpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
g.pushTransform();
|
g.pushTransform();
|
||||||
|
@ -269,38 +291,40 @@ public class Slider extends GameObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// draw combo number and overlay if not initially clicked
|
||||||
if (!sliderClickedInitial) {
|
if (!sliderClickedInitial) {
|
||||||
data.drawSymbolNumber(hitObject.getComboNumber(), x, y,
|
data.drawSymbolNumber(hitObject.getComboNumber(), x, y,
|
||||||
hitCircle.getWidth() * 0.40f / data.getDefaultSymbolImage(0).getHeight(), alpha);
|
hitCircle.getWidth() * 0.40f / data.getDefaultSymbolImage(0).getHeight(), alpha);
|
||||||
if (overlayAboveNumber)
|
|
||||||
hitCircleOverlay.drawCentered(x, y, Colors.WHITE_FADE);
|
if (overlayAboveNumber) {
|
||||||
|
startCircleOverlayColor.a = sliderAlpha;
|
||||||
|
hitCircleOverlay.drawCentered(x, y, startCircleOverlayColor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g.popTransform();
|
g.popTransform();
|
||||||
|
|
||||||
// repeats
|
// repeats
|
||||||
if (isCurveCompletelyDrawn) {
|
if (isCurveCompletelyDrawn) {
|
||||||
for (int tcurRepeat = currentRepeats; tcurRepeat <= currentRepeats + 1; tcurRepeat++) {
|
for (int tcurRepeat = currentRepeats; tcurRepeat <= currentRepeats + 1 && tcurRepeat < repeatCount - 1; tcurRepeat++) {
|
||||||
if (hitObject.getRepeatCount() - 1 > tcurRepeat) {
|
|
||||||
Image arrow = GameImage.REVERSEARROW.getImage();
|
Image arrow = GameImage.REVERSEARROW.getImage();
|
||||||
arrow = arrow.getScaledCopy((float) (1 + 0.2d * ((trackPosition + sliderTime * tcurRepeat) % 292) / 292));
|
arrow = arrow.getScaledCopy((float) (1 + 0.2d * ((trackPosition + sliderTime * tcurRepeat) % 292) / 292));
|
||||||
Color arrowColor = Color.white;
|
if (tcurRepeat == 0) {
|
||||||
if (tcurRepeat != currentRepeats) {
|
|
||||||
if (sliderTime == 0)
|
|
||||||
continue;
|
|
||||||
float t = Math.max(getT(trackPosition, true), 0);
|
|
||||||
arrow.setAlpha((float) (t - Math.floor(t)));
|
|
||||||
} else
|
|
||||||
arrow.setAlpha(Options.isSliderSnaking() ? decorationsAlpha : 1f);
|
arrow.setAlpha(Options.isSliderSnaking() ? decorationsAlpha : 1f);
|
||||||
|
} else {
|
||||||
|
if (!sliderClickedInitial) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
arrow.setAlpha(getCircleAlphaAfterRepeat(trackPosition, tcurRepeat % 2 == 0));
|
||||||
|
}
|
||||||
if (tcurRepeat % 2 == 0) {
|
if (tcurRepeat % 2 == 0) {
|
||||||
// last circle
|
// last circle
|
||||||
arrow.setRotation(curve.getEndAngle());
|
arrow.setRotation(curve.getEndAngle());
|
||||||
arrow.drawCentered(endPos.x, endPos.y, arrowColor);
|
arrow.drawCentered(endPos.x, endPos.y);
|
||||||
} else {
|
} else {
|
||||||
// first circle
|
// first circle
|
||||||
arrow.setRotation(curve.getStartAngle());
|
arrow.setRotation(curve.getStartAngle());
|
||||||
arrow.drawCentered(x, y, arrowColor);
|
arrow.drawCentered(x, y);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -447,6 +471,24 @@ public class Slider extends GameObject {
|
||||||
return curveIntervalTo == 1d;
|
return curveIntervalTo == 1d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the alpha level used to fade in circles & reversearrows after repeat
|
||||||
|
* @param trackPosition current trackposition, in ms
|
||||||
|
* @param endCircle request alpha for end circle (true) or start circle (false)?
|
||||||
|
* @return alpha level as float in interval [0, 1]
|
||||||
|
*/
|
||||||
|
private float getCircleAlphaAfterRepeat(int trackPosition, boolean endCircle) {
|
||||||
|
int ticksN = ticksT == null ? 0 : ticksT.length;
|
||||||
|
float t = getT(trackPosition, false);
|
||||||
|
if (endCircle) {
|
||||||
|
t = 1f - t;
|
||||||
|
}
|
||||||
|
if (currentRepeats % 2 == (endCircle ? 0 : 1)) {
|
||||||
|
t = 1f;
|
||||||
|
}
|
||||||
|
return Utils.clamp(t * (ticksN + 1), 0f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the slider hit result.
|
* Calculates the slider hit result.
|
||||||
* @return the hit result (GameData.HIT_* constants)
|
* @return the hit result (GameData.HIT_* constants)
|
||||||
|
@ -513,17 +555,19 @@ public class Slider extends GameObject {
|
||||||
|
|
||||||
float cx, cy;
|
float cx, cy;
|
||||||
HitObjectType type;
|
HitObjectType type;
|
||||||
if (currentRepeats % 2 == 0) { // last circle
|
if (currentRepeats % 2 == 0) {
|
||||||
|
// last circle
|
||||||
Vec2f lastPos = curve.pointAt(1);
|
Vec2f lastPos = curve.pointAt(1);
|
||||||
cx = lastPos.x;
|
cx = lastPos.x;
|
||||||
cy = lastPos.y;
|
cy = lastPos.y;
|
||||||
type = HitObjectType.SLIDER_LAST;
|
type = HitObjectType.SLIDER_LAST;
|
||||||
} else { // first circle
|
} else {
|
||||||
|
// first circle
|
||||||
cx = x;
|
cx = x;
|
||||||
cy = y;
|
cy = y;
|
||||||
type = HitObjectType.SLIDER_FIRST;
|
type = HitObjectType.SLIDER_FIRST;
|
||||||
}
|
}
|
||||||
data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
|
data.sendHitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
|
||||||
cx, cy, color, comboEnd, hitObject, type, sliderHeldToEnd,
|
cx, cy, color, comboEnd, hitObject, type, sliderHeldToEnd,
|
||||||
currentRepeats + 1, curve, sliderHeldToEnd);
|
currentRepeats + 1, curve, sliderHeldToEnd);
|
||||||
if (Options.isMirror() && GameMod.AUTO.isActive()) {
|
if (Options.isMirror() && GameMod.AUTO.isActive()) {
|
||||||
|
@ -550,15 +594,18 @@ public class Slider extends GameObject {
|
||||||
if (timeDiff < hitResultOffset[GameData.HIT_50]) {
|
if (timeDiff < hitResultOffset[GameData.HIT_50]) {
|
||||||
result = GameData.HIT_SLIDER30;
|
result = GameData.HIT_SLIDER30;
|
||||||
ticksHit++;
|
ticksHit++;
|
||||||
} else if (timeDiff < hitResultOffset[GameData.HIT_MISS])
|
data.sendSliderStartResult(trackPosition, this.x, this.y, color, true);
|
||||||
|
} else if (timeDiff < hitResultOffset[GameData.HIT_MISS]) {
|
||||||
result = GameData.HIT_MISS;
|
result = GameData.HIT_MISS;
|
||||||
|
data.sendSliderStartResult(trackPosition, this.x, this.y, color, false);
|
||||||
|
}
|
||||||
//else not a hit
|
//else not a hit
|
||||||
|
|
||||||
if (result > -1) {
|
if (result > -1) {
|
||||||
data.sendInitialSliderResult(trackPosition, this.x, this.y, color, mirrorColor);
|
data.sendInitialSliderResult(trackPosition, this.x, this.y, color, mirrorColor);
|
||||||
data.addHitError(hitObject.getTime(), x,y,trackPosition - hitObject.getTime());
|
data.addHitError(hitObject.getTime(), x,y,trackPosition - hitObject.getTime());
|
||||||
sliderClickedInitial = true;
|
sliderClickedInitial = true;
|
||||||
data.sliderTickResult(hitObject.getTime(), result, this.x, this.y, hitObject, currentRepeats);
|
data.sendSliderTickResult(hitObject.getTime(), result, this.x, this.y, hitObject, currentRepeats);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -579,9 +626,12 @@ public class Slider extends GameObject {
|
||||||
sliderClickedInitial = true;
|
sliderClickedInitial = true;
|
||||||
if (isAutoMod) { // "auto" mod: catch any missed notes due to lag
|
if (isAutoMod) { // "auto" mod: catch any missed notes due to lag
|
||||||
ticksHit++;
|
ticksHit++;
|
||||||
data.sliderTickResult(time, GameData.HIT_SLIDER30, x, y, hitObject, currentRepeats);
|
data.sendSliderTickResult(time, GameData.HIT_SLIDER30, x, y, hitObject, currentRepeats);
|
||||||
} else
|
data.sendSliderStartResult(time, x, y, color, true);
|
||||||
data.sliderTickResult(time, GameData.HIT_MISS, x, y, hitObject, currentRepeats);
|
} else {
|
||||||
|
data.sendSliderTickResult(time, GameData.HIT_MISS, x, y, hitObject, currentRepeats);
|
||||||
|
data.sendSliderStartResult(trackPosition, x, y, color, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// "auto" mod: send a perfect hit result
|
// "auto" mod: send a perfect hit result
|
||||||
|
@ -589,8 +639,8 @@ public class Slider extends GameObject {
|
||||||
if (Math.abs(trackPosition - time) < hitResultOffset[GameData.HIT_300]) {
|
if (Math.abs(trackPosition - time) < hitResultOffset[GameData.HIT_300]) {
|
||||||
ticksHit++;
|
ticksHit++;
|
||||||
sliderClickedInitial = true;
|
sliderClickedInitial = true;
|
||||||
data.sliderTickResult(time, GameData.HIT_SLIDER30, x, y, hitObject, currentRepeats);
|
data.sendSliderTickResult(time, GameData.HIT_SLIDER30, x, y, hitObject, currentRepeats);
|
||||||
data.sendInitialSliderResult(time, x, y, color, mirrorColor);
|
data.sendSliderStartResult(time, x, y, color, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -644,23 +694,6 @@ public class Slider extends GameObject {
|
||||||
tickIndex = 0;
|
tickIndex = 0;
|
||||||
isNewRepeat = true;
|
isNewRepeat = true;
|
||||||
tickExpandTime = TICK_EXPAND_TIME;
|
tickExpandTime = TICK_EXPAND_TIME;
|
||||||
|
|
||||||
if (Options.isReverseArrowAnimationEnabled()) {
|
|
||||||
// send hit result, to fade out reversearrow
|
|
||||||
HitObjectType type;
|
|
||||||
float posX, posY;
|
|
||||||
if (currentRepeats % 2 == 1) {
|
|
||||||
type = HitObjectType.SLIDER_LAST;
|
|
||||||
Vec2f endPos = curve.pointAt(1);
|
|
||||||
posX = endPos.x;
|
|
||||||
posY = endPos.y;
|
|
||||||
} else {
|
|
||||||
type = HitObjectType.SLIDER_FIRST;
|
|
||||||
posX = this.x;
|
|
||||||
posY = this.y;
|
|
||||||
}
|
|
||||||
data.sendRepeatSliderResult(trackPosition, posX, posY, Color.white, curve, type);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -688,19 +721,34 @@ public class Slider extends GameObject {
|
||||||
// held during new repeat
|
// held during new repeat
|
||||||
if (isNewRepeat) {
|
if (isNewRepeat) {
|
||||||
ticksHit++;
|
ticksHit++;
|
||||||
if (currentRepeats % 2 > 0) { // last circle
|
|
||||||
int lastIndex = hitObject.getSliderX().length;
|
HitObjectType type;
|
||||||
data.sliderTickResult(trackPosition, GameData.HIT_SLIDER30,
|
float posX, posY;
|
||||||
curve.getX(lastIndex), curve.getY(lastIndex), hitObject, currentRepeats);
|
if (currentRepeats % 2 > 0) {
|
||||||
} else // first circle
|
// last circle
|
||||||
data.sliderTickResult(trackPosition, GameData.HIT_SLIDER30,
|
type = HitObjectType.SLIDER_LAST;
|
||||||
c.x, c.y, hitObject, currentRepeats);
|
Vec2f endPos = curve.pointAt(1f);
|
||||||
|
posX = endPos.x;
|
||||||
|
posY = endPos.y;
|
||||||
|
} else {
|
||||||
|
// first circle
|
||||||
|
type = HitObjectType.SLIDER_FIRST;
|
||||||
|
posX = this.x;
|
||||||
|
posY = this.y;
|
||||||
|
}
|
||||||
|
data.sendSliderTickResult(trackPosition, GameData.HIT_SLIDER30,
|
||||||
|
posX, posY, hitObject, currentRepeats);
|
||||||
|
|
||||||
|
// fade out reverse arrow
|
||||||
|
float colorLuminance = Utils.getLuminance(color);
|
||||||
|
Color arrowColor = colorLuminance < 0.8f ? Color.white : Color.black;
|
||||||
|
data.sendSliderRepeatResult(trackPosition, posX, posY, arrowColor, curve, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// held during new tick
|
// held during new tick
|
||||||
if (isNewTick) {
|
if (isNewTick) {
|
||||||
ticksHit++;
|
ticksHit++;
|
||||||
data.sliderTickResult(trackPosition, GameData.HIT_SLIDER10,
|
data.sendSliderTickResult(trackPosition, GameData.HIT_SLIDER10,
|
||||||
c.x, c.y, hitObject, currentRepeats);
|
c.x, c.y, hitObject, currentRepeats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -711,9 +759,9 @@ public class Slider extends GameObject {
|
||||||
followCircleActive = false;
|
followCircleActive = false;
|
||||||
|
|
||||||
if (isNewRepeat)
|
if (isNewRepeat)
|
||||||
data.sliderTickResult(trackPosition, GameData.HIT_MISS, 0, 0, hitObject, currentRepeats);
|
data.sendSliderTickResult(trackPosition, GameData.HIT_MISS, 0, 0, hitObject, currentRepeats);
|
||||||
if (isNewTick)
|
if (isNewTick)
|
||||||
data.sliderTickResult(trackPosition, GameData.HIT_MISS, 0, 0, hitObject, currentRepeats);
|
data.sendSliderTickResult(trackPosition, GameData.HIT_MISS, 0, 0, hitObject, currentRepeats);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -255,7 +255,7 @@ public class Spinner extends GameObject {
|
||||||
else
|
else
|
||||||
result = GameData.HIT_MISS;
|
result = GameData.HIT_MISS;
|
||||||
|
|
||||||
data.hitResult(hitObject.getEndTime(), result, width / 2, height / 2,
|
data.sendHitResult(hitObject.getEndTime(), result, width / 2, height / 2,
|
||||||
Color.transparent, true, hitObject, HitObjectType.SPINNER, true, 0, null, false);
|
Color.transparent, true, hitObject, HitObjectType.SPINNER, true, 0, null, false);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,12 +101,12 @@ public abstract class Curve {
|
||||||
Curve.borderColor = borderColor;
|
Curve.borderColor = borderColor;
|
||||||
|
|
||||||
ContextCapabilities capabilities = GLContext.getCapabilities();
|
ContextCapabilities capabilities = GLContext.getCapabilities();
|
||||||
mmsliderSupported = capabilities.OpenGL20;
|
mmsliderSupported = capabilities.OpenGL30;
|
||||||
if (mmsliderSupported)
|
if (mmsliderSupported)
|
||||||
CurveRenderState.init(width, height, circleDiameter);
|
CurveRenderState.init(width, height, circleDiameter);
|
||||||
else {
|
else {
|
||||||
if (Options.getSkin().getSliderStyle() != Skin.STYLE_PEPPYSLIDER)
|
if (Options.getSkin().getSliderStyle() != Skin.STYLE_PEPPYSLIDER)
|
||||||
Log.warn("New slider style requires OpenGL 2.0.");
|
Log.warn("New slider style requires OpenGL 3.0.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,8 @@ import org.newdawn.slick.Input;
|
||||||
import org.newdawn.slick.SlickException;
|
import org.newdawn.slick.SlickException;
|
||||||
import org.newdawn.slick.state.BasicGameState;
|
import org.newdawn.slick.state.BasicGameState;
|
||||||
import org.newdawn.slick.state.StateBasedGame;
|
import org.newdawn.slick.state.StateBasedGame;
|
||||||
import org.newdawn.slick.state.transition.FadeInTransition;
|
|
||||||
import org.newdawn.slick.state.transition.EmptyTransition;
|
import org.newdawn.slick.state.transition.EmptyTransition;
|
||||||
|
import org.newdawn.slick.state.transition.FadeInTransition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic button menu state.
|
* Generic button menu state.
|
||||||
|
@ -69,8 +69,8 @@ public class ButtonMenu extends BasicGameState {
|
||||||
Button.NO.click(container, game);
|
Button.NO.click(container, game);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/** The initial beatmap management screen. */
|
/** The initial beatmap management screen (for a non-"favorite" beatmap). */
|
||||||
BEATMAP (new Button[] { Button.CLEAR_SCORES, Button.DELETE, Button.CANCEL }) {
|
BEATMAP (new Button[] { Button.CLEAR_SCORES, Button.FAVORITE_ADD, Button.DELETE, Button.CANCEL }) {
|
||||||
@Override
|
@Override
|
||||||
public String[] getTitle(GameContainer container, StateBasedGame game) {
|
public String[] getTitle(GameContainer container, StateBasedGame game) {
|
||||||
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
|
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
|
||||||
|
@ -90,6 +90,23 @@ public class ButtonMenu extends BasicGameState {
|
||||||
super.scroll(container, game, newValue);
|
super.scroll(container, game, newValue);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/** The initial beatmap management screen (for a "favorite" beatmap). */
|
||||||
|
BEATMAP_FAVORITE (new Button[] { Button.CLEAR_SCORES, Button.FAVORITE_REMOVE, Button.DELETE, Button.CANCEL }) {
|
||||||
|
@Override
|
||||||
|
public String[] getTitle(GameContainer container, StateBasedGame game) {
|
||||||
|
return BEATMAP.getTitle(container, game);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leave(GameContainer container, StateBasedGame game) {
|
||||||
|
BEATMAP.leave(container, game);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void scroll(GameContainer container, StateBasedGame game, int newValue) {
|
||||||
|
BEATMAP.scroll(container, game, newValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
/** The beatmap deletion screen for a beatmap set with multiple beatmaps. */
|
/** The beatmap deletion screen for a beatmap set with multiple beatmaps. */
|
||||||
BEATMAP_DELETE_SELECT (new Button[] { Button.DELETE_GROUP, Button.DELETE_SONG, Button.CANCEL_DELETE }) {
|
BEATMAP_DELETE_SELECT (new Button[] { Button.DELETE_GROUP, Button.DELETE_SONG, Button.CANCEL_DELETE }) {
|
||||||
@Override
|
@Override
|
||||||
|
@ -468,6 +485,25 @@ public class ButtonMenu extends BasicGameState {
|
||||||
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
|
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
FAVORITE_ADD ("Add to Favorites", Color.blue) {
|
||||||
|
@Override
|
||||||
|
public void click(GameContainer container, StateBasedGame game) {
|
||||||
|
SoundController.playSound(SoundEffect.MENUHIT);
|
||||||
|
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
|
||||||
|
node.getBeatmapSet().setFavorite(true);
|
||||||
|
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
FAVORITE_REMOVE ("Remove from Favorites", Color.blue) {
|
||||||
|
@Override
|
||||||
|
public void click(GameContainer container, StateBasedGame game) {
|
||||||
|
SoundController.playSound(SoundEffect.MENUHIT);
|
||||||
|
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
|
||||||
|
node.getBeatmapSet().setFavorite(false);
|
||||||
|
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).doStateActionOnLoad(MenuState.BEATMAP_FAVORITE);
|
||||||
|
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
|
||||||
|
}
|
||||||
|
},
|
||||||
DELETE ("Delete...", Color.red) {
|
DELETE ("Delete...", Color.red) {
|
||||||
@Override
|
@Override
|
||||||
public void click(GameContainer container, StateBasedGame game) {
|
public void click(GameContainer container, StateBasedGame game) {
|
||||||
|
|
|
@ -49,6 +49,7 @@ import itdelatrisu.opsu.replay.PlaybackSpeed;
|
||||||
import itdelatrisu.opsu.replay.Replay;
|
import itdelatrisu.opsu.replay.Replay;
|
||||||
import itdelatrisu.opsu.replay.ReplayFrame;
|
import itdelatrisu.opsu.replay.ReplayFrame;
|
||||||
import itdelatrisu.opsu.ui.*;
|
import itdelatrisu.opsu.ui.*;
|
||||||
|
import itdelatrisu.opsu.ui.animations.AnimatedValue;
|
||||||
import itdelatrisu.opsu.ui.animations.AnimationEquation;
|
import itdelatrisu.opsu.ui.animations.AnimationEquation;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -98,6 +99,9 @@ public class Game extends BasicGameState {
|
||||||
/** Screen fade-out time, in milliseconds, when health hits zero. */
|
/** Screen fade-out time, in milliseconds, when health hits zero. */
|
||||||
private static final int LOSE_FADEOUT_TIME = 500;
|
private static final int LOSE_FADEOUT_TIME = 500;
|
||||||
|
|
||||||
|
/** Game element fade-out time, in milliseconds, when the game ends. */
|
||||||
|
private static final int FINISHED_FADEOUT_TIME = 400;
|
||||||
|
|
||||||
/** Maximum rotation, in degrees, over fade out upon death. */
|
/** Maximum rotation, in degrees, over fade out upon death. */
|
||||||
private static final float MAX_ROTATION = 90f;
|
private static final float MAX_ROTATION = 90f;
|
||||||
|
|
||||||
|
@ -287,6 +291,15 @@ public class Game extends BasicGameState {
|
||||||
/** The current alpha of the scoreboard. */
|
/** The current alpha of the scoreboard. */
|
||||||
private float currentScoreboardAlpha;
|
private float currentScoreboardAlpha;
|
||||||
|
|
||||||
|
/** The star stream shown when passing another score. */
|
||||||
|
private StarStream scoreboardStarStream;
|
||||||
|
|
||||||
|
/** Whether the game is finished (last hit object passed). */
|
||||||
|
private boolean gameFinished = false;
|
||||||
|
|
||||||
|
/** Timer after game has finished, before changing states. */
|
||||||
|
private AnimatedValue gameFinishedTimer = new AnimatedValue(2500, 0, 1, AnimationEquation.LINEAR);
|
||||||
|
|
||||||
/** Music position bar background colors. */
|
/** Music position bar background colors. */
|
||||||
private static final Color
|
private static final Color
|
||||||
MUSICBAR_NORMAL = new Color(12, 9, 10, 0.25f),
|
MUSICBAR_NORMAL = new Color(12, 9, 10, 0.25f),
|
||||||
|
@ -369,6 +382,12 @@ public class Game extends BasicGameState {
|
||||||
musicBarWidth = Math.max(width * 0.005f, 7);
|
musicBarWidth = Math.max(width * 0.005f, 7);
|
||||||
musicBarHeight = height * 0.9f;
|
musicBarHeight = height * 0.9f;
|
||||||
|
|
||||||
|
// initialize scoreboard star stream
|
||||||
|
scoreboardStarStream = new StarStream(0, height * 2f / 3f, width / 4, 0, 0);
|
||||||
|
scoreboardStarStream.setPositionSpread(height / 20f);
|
||||||
|
scoreboardStarStream.setDirectionSpread(10f);
|
||||||
|
scoreboardStarStream.setDurationSpread(700, 100);
|
||||||
|
|
||||||
// create the associated GameData object
|
// create the associated GameData object
|
||||||
data = new GameData(width, height);
|
data = new GameData(width, height);
|
||||||
}
|
}
|
||||||
|
@ -513,7 +532,7 @@ public class Game extends BasicGameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Options.isHideUI() || !GameMod.AUTO.isActive()) {
|
if (!Options.isHideUI() || !GameMod.AUTO.isActive()) {
|
||||||
data.drawGameElements(g, true, objectIndex == 0);
|
data.drawGameElements(g, true, objectIndex == 0, 1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (breakLength >= 8000 &&
|
if (breakLength >= 8000 &&
|
||||||
|
@ -552,7 +571,13 @@ public class Game extends BasicGameState {
|
||||||
else {
|
else {
|
||||||
if (!GameMod.AUTO.isActive() || !Options.isHideUI()) {
|
if (!GameMod.AUTO.isActive() || !Options.isHideUI()) {
|
||||||
// game elements
|
// game elements
|
||||||
data.drawGameElements(g, false, objectIndex == 0);
|
float gameElementAlpha = 1f;
|
||||||
|
if (gameFinished) {
|
||||||
|
// game finished: fade everything out
|
||||||
|
float t = 1f - Math.min(gameFinishedTimer.getTime() / (float) FINISHED_FADEOUT_TIME, 1f);
|
||||||
|
gameElementAlpha = AnimationEquation.OUT_CUBIC.calc(t);
|
||||||
|
}
|
||||||
|
data.drawGameElements(g, false, objectIndex == 0, gameElementAlpha);
|
||||||
|
|
||||||
// skip beginning
|
// skip beginning
|
||||||
if (objectIndex == 0 &&
|
if (objectIndex == 0 &&
|
||||||
|
@ -639,6 +664,7 @@ public class Game extends BasicGameState {
|
||||||
ScoreData currentScore = data.getCurrentScoreData(beatmap, true);
|
ScoreData currentScore = data.getCurrentScoreData(beatmap, true);
|
||||||
while (currentRank > 0 && previousScores[currentRank - 1].score < currentScore.score) {
|
while (currentRank > 0 && previousScores[currentRank - 1].score < currentScore.score) {
|
||||||
currentRank--;
|
currentRank--;
|
||||||
|
scoreboardStarStream.burst(20);
|
||||||
lastRankUpdateTime = trackPosition;
|
lastRankUpdateTime = trackPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -647,6 +673,9 @@ public class Game extends BasicGameState {
|
||||||
);
|
);
|
||||||
int scoreboardPosition = 2 * container.getHeight() / 3;
|
int scoreboardPosition = 2 * container.getHeight() / 3;
|
||||||
|
|
||||||
|
// draw star stream behind the scores
|
||||||
|
scoreboardStarStream.draw();
|
||||||
|
|
||||||
if (currentRank < 4) {
|
if (currentRank < 4) {
|
||||||
// draw the (new) top 5 ranks
|
// draw the (new) top 5 ranks
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
|
@ -749,6 +778,7 @@ public class Game extends BasicGameState {
|
||||||
playbackSpeed.getButton().hoverUpdate(delta, mouseX, mouseY);
|
playbackSpeed.getButton().hoverUpdate(delta, mouseX, mouseY);
|
||||||
int trackPosition = MusicController.getPosition();
|
int trackPosition = MusicController.getPosition();
|
||||||
int firstObjectTime = beatmap.objects[0].getTime();
|
int firstObjectTime = beatmap.objects[0].getTime();
|
||||||
|
scoreboardStarStream.update(delta);
|
||||||
|
|
||||||
// returning from pause screen: must click previous mouse position
|
// returning from pause screen: must click previous mouse position
|
||||||
if (pauseTime > -1) {
|
if (pauseTime > -1) {
|
||||||
|
@ -803,11 +833,11 @@ public class Game extends BasicGameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// normal game update
|
// normal game update
|
||||||
if (!isReplay)
|
if (!isReplay && !gameFinished)
|
||||||
addReplayFrameAndRun(mouseX, mouseY, lastKeysPressed, trackPosition);
|
addReplayFrameAndRun(mouseX, mouseY, lastKeysPressed, trackPosition);
|
||||||
|
|
||||||
// watching replay
|
// watching replay
|
||||||
else {
|
else if (!gameFinished) {
|
||||||
// out of frames, use previous data
|
// out of frames, use previous data
|
||||||
if (replayIndex >= replay.frames.length)
|
if (replayIndex >= replay.frames.length)
|
||||||
updateGame(replayX, replayY, delta, MusicController.getPosition(), lastKeysPressed);
|
updateGame(replayX, replayY, delta, MusicController.getPosition(), lastKeysPressed);
|
||||||
|
@ -857,7 +887,8 @@ public class Game extends BasicGameState {
|
||||||
// update in-game scoreboard
|
// update in-game scoreboard
|
||||||
if (!Options.isHideUI() && previousScores != null && trackPosition > firstObjectTime) {
|
if (!Options.isHideUI() && previousScores != null && trackPosition > firstObjectTime) {
|
||||||
// show scoreboard if selected, and always in break
|
// show scoreboard if selected, and always in break
|
||||||
if (scoreboardVisible || breakTime > 0) {
|
// hide when game ends
|
||||||
|
if ((scoreboardVisible || breakTime > 0) && !gameFinished) {
|
||||||
currentScoreboardAlpha += 1f / SCOREBOARD_FADE_IN_TIME * delta;
|
currentScoreboardAlpha += 1f / SCOREBOARD_FADE_IN_TIME * delta;
|
||||||
if (currentScoreboardAlpha > 1f)
|
if (currentScoreboardAlpha > 1f)
|
||||||
currentScoreboardAlpha = 1f;
|
currentScoreboardAlpha = 1f;
|
||||||
|
@ -869,6 +900,14 @@ public class Game extends BasicGameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
data.updateDisplays(delta);
|
data.updateDisplays(delta);
|
||||||
|
|
||||||
|
// game finished: change state after timer expires
|
||||||
|
if (gameFinished && !gameFinishedTimer.update(delta)) {
|
||||||
|
if (checkpointLoaded) // if checkpoint used, skip ranking screen
|
||||||
|
game.closeRequested();
|
||||||
|
else // go to ranking screen
|
||||||
|
game.enterState(Opsu.STATE_GAMERANKING, new EasedFadeOutTransition(), new FadeInTransition());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -892,12 +931,8 @@ public class Game extends BasicGameState {
|
||||||
if (MusicController.trackEnded() && objectIndex < gameObjects.length)
|
if (MusicController.trackEnded() && objectIndex < gameObjects.length)
|
||||||
gameObjects[objectIndex].update(true, delta, mouseX, mouseY, false, trackPosition);
|
gameObjects[objectIndex].update(true, delta, mouseX, mouseY, false, trackPosition);
|
||||||
|
|
||||||
// if checkpoint used, skip ranking screen
|
// save score and replay
|
||||||
if (checkpointLoaded)
|
if (!checkpointLoaded) {
|
||||||
game.closeRequested();
|
|
||||||
|
|
||||||
// go to ranking screen
|
|
||||||
else {
|
|
||||||
boolean unranked = (GameMod.AUTO.isActive() || GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive());
|
boolean unranked = (GameMod.AUTO.isActive() || GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive());
|
||||||
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
|
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
|
||||||
if (isReplay)
|
if (isReplay)
|
||||||
|
@ -918,9 +953,12 @@ public class Game extends BasicGameState {
|
||||||
// add score to database
|
// add score to database
|
||||||
if (!unranked && !isReplay)
|
if (!unranked && !isReplay)
|
||||||
ScoreDB.addScore(score);
|
ScoreDB.addScore(score);
|
||||||
|
|
||||||
game.enterState(Opsu.STATE_GAMERANKING, new EasedFadeOutTransition(), new FadeInTransition());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start timer
|
||||||
|
gameFinished = true;
|
||||||
|
gameFinishedTimer.setTime(0);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1050,6 +1088,8 @@ public class Game extends BasicGameState {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyPressed(int key, char c) {
|
public void keyPressed(int key, char c) {
|
||||||
|
if (gameFinished)
|
||||||
|
return;
|
||||||
|
|
||||||
if (sbOverlay.keyPressed(key, c)) {
|
if (sbOverlay.keyPressed(key, c)) {
|
||||||
return;
|
return;
|
||||||
|
@ -1199,9 +1239,13 @@ public class Game extends BasicGameState {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mousePressed(int button, int x, int y) {
|
public void mousePressed(int button, int x, int y) {
|
||||||
|
if (gameFinished)
|
||||||
|
return;
|
||||||
|
|
||||||
if (sbOverlay.mousePressed(button, x, y)) {
|
if (sbOverlay.mousePressed(button, x, y)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// watching replay
|
// watching replay
|
||||||
if (isReplay || GameMod.AUTO.isActive()) {
|
if (isReplay || GameMod.AUTO.isActive()) {
|
||||||
if (button == Input.MOUSE_MIDDLE_BUTTON)
|
if (button == Input.MOUSE_MIDDLE_BUTTON)
|
||||||
|
@ -1294,6 +1338,9 @@ public class Game extends BasicGameState {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseReleased(int button, int x, int y) {
|
public void mouseReleased(int button, int x, int y) {
|
||||||
|
if (gameFinished)
|
||||||
|
return;
|
||||||
|
|
||||||
if (sbOverlay.mouseReleased(button, x, y)) {
|
if (sbOverlay.mouseReleased(button, x, y)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1315,6 +1362,9 @@ public class Game extends BasicGameState {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyReleased(int key, char c) {
|
public void keyReleased(int key, char c) {
|
||||||
|
if (gameFinished)
|
||||||
|
return;
|
||||||
|
|
||||||
int keys = ReplayFrame.KEY_NONE;
|
int keys = ReplayFrame.KEY_NONE;
|
||||||
if (key == Options.getGameKeyLeft())
|
if (key == Options.getGameKeyLeft())
|
||||||
keys = ReplayFrame.KEY_K1;
|
keys = ReplayFrame.KEY_K1;
|
||||||
|
@ -1343,7 +1393,7 @@ public class Game extends BasicGameState {
|
||||||
if (sbOverlay.mouseWheelMoved(newValue)) {
|
if (sbOverlay.mouseWheelMoved(newValue)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Options.isMouseWheelDisabled() || Options.isMouseDisabled())
|
if (Options.isMouseWheelDisabled())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
UI.changeVolume((newValue < 0) ? -1 : 1);
|
UI.changeVolume((newValue < 0) ? -1 : 1);
|
||||||
|
@ -1375,6 +1425,11 @@ public class Game extends BasicGameState {
|
||||||
|
|
||||||
// restart the game
|
// restart the game
|
||||||
if (restart != Restart.FALSE) {
|
if (restart != Restart.FALSE) {
|
||||||
|
// update play stats
|
||||||
|
if (restart == Restart.NEW) {
|
||||||
|
beatmap.incrementPlayCounter();
|
||||||
|
BeatmapDB.updatePlayStatistics(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
// load epilepsy warning img
|
// load epilepsy warning img
|
||||||
epiImgTime = Options.getEpilepsyWarningLength();
|
epiImgTime = Options.getEpilepsyWarningLength();
|
||||||
|
@ -1400,11 +1455,13 @@ public class Game extends BasicGameState {
|
||||||
loadImages();
|
loadImages();
|
||||||
setMapModifiers();
|
setMapModifiers();
|
||||||
retries = 0;
|
retries = 0;
|
||||||
} else if (restart == Restart.MANUAL) {
|
} else if (restart == Restart.MANUAL && !GameMod.AUTO.isActive()) {
|
||||||
// retry
|
// retry
|
||||||
retries++;
|
retries++;
|
||||||
} else if (restart == Restart.REPLAY)
|
} else if (restart == Restart.REPLAY || GameMod.AUTO.isActive()) {
|
||||||
|
// replay
|
||||||
retries = 0;
|
retries = 0;
|
||||||
|
}
|
||||||
|
|
||||||
gameObjects = new GameObject[beatmap.objects.length];
|
gameObjects = new GameObject[beatmap.objects.length];
|
||||||
playbackSpeed = PlaybackSpeed.NORMAL;
|
playbackSpeed = PlaybackSpeed.NORMAL;
|
||||||
|
@ -1613,6 +1670,11 @@ public class Game extends BasicGameState {
|
||||||
* @param trackPosition the track position
|
* @param trackPosition the track position
|
||||||
*/
|
*/
|
||||||
private void drawHitObjects(Graphics g, int trackPosition) {
|
private void drawHitObjects(Graphics g, int trackPosition) {
|
||||||
|
// draw result objects
|
||||||
|
if (!Options.isHideObjects()) {
|
||||||
|
data.drawHitResults(trackPosition);
|
||||||
|
}
|
||||||
|
|
||||||
if (Options.isMergingSliders() && knorkesliders != null) {
|
if (Options.isMergingSliders() && knorkesliders != null) {
|
||||||
knorkesliders.draw(Color.white, this.slidercurveFrom, this.slidercurveTo);
|
knorkesliders.draw(Color.white, this.slidercurveFrom, this.slidercurveTo);
|
||||||
if (Options.isMirror()) {
|
if (Options.isMirror()) {
|
||||||
|
@ -1622,6 +1684,7 @@ public class Game extends BasicGameState {
|
||||||
g.popTransform();
|
g.popTransform();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// include previous object in follow points
|
// include previous object in follow points
|
||||||
int lastObjectIndex = -1;
|
int lastObjectIndex = -1;
|
||||||
if (objectIndex > 0 && objectIndex < beatmap.objects.length &&
|
if (objectIndex > 0 && objectIndex < beatmap.objects.length &&
|
||||||
|
@ -1736,18 +1799,13 @@ public class Game extends BasicGameState {
|
||||||
|
|
||||||
// translate and rotate the object
|
// translate and rotate the object
|
||||||
g.translate(0, dt * dt * container.getHeight());
|
g.translate(0, dt * dt * container.getHeight());
|
||||||
Vec2f rotationCenter = gameObj.getPointAt(beatmap.objects[idx].getTime());
|
Vec2f rotationCenter = gameObj.getPointAt((beatmap.objects[idx].getTime() + beatmap.objects[idx].getEndTime()) / 2);
|
||||||
g.rotate(rotationCenter.x, rotationCenter.y, rotSpeed * dt);
|
g.rotate(rotationCenter.x, rotationCenter.y, rotSpeed * dt);
|
||||||
gameObj.draw(g, trackPosition, false);
|
gameObj.draw(g, trackPosition, false);
|
||||||
|
|
||||||
g.popTransform();
|
g.popTransform();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw result objects
|
|
||||||
if (!Options.isHideObjects()) {
|
|
||||||
data.drawHitResults(trackPosition);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1760,7 +1818,7 @@ public class Game extends BasicGameState {
|
||||||
}
|
}
|
||||||
this.beatmap = beatmap;
|
this.beatmap = beatmap;
|
||||||
Display.setTitle(String.format("%s - %s", game.getTitle(), beatmap.toString()));
|
Display.setTitle(String.format("%s - %s", game.getTitle(), beatmap.toString()));
|
||||||
if (beatmap.timingPoints == null)
|
if (beatmap.breaks == null)
|
||||||
BeatmapDB.load(beatmap, BeatmapDB.LOAD_ARRAY);
|
BeatmapDB.load(beatmap, BeatmapDB.LOAD_ARRAY);
|
||||||
BeatmapParser.parseHitObjects(beatmap);
|
BeatmapParser.parseHitObjects(beatmap);
|
||||||
HitSound.setDefaultSampleSet(beatmap.sampleSet);
|
HitSound.setDefaultSampleSet(beatmap.sampleSet);
|
||||||
|
@ -1792,6 +1850,9 @@ public class Game extends BasicGameState {
|
||||||
autoMousePosition = new Vec2f();
|
autoMousePosition = new Vec2f();
|
||||||
autoMousePressed = false;
|
autoMousePressed = false;
|
||||||
flashlightRadius = container.getHeight() * 2 / 3;
|
flashlightRadius = container.getHeight() * 2 / 3;
|
||||||
|
scoreboardStarStream.clear();
|
||||||
|
gameFinished = false;
|
||||||
|
gameFinishedTimer.setTime(0);
|
||||||
|
|
||||||
System.gc();
|
System.gc();
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,7 +184,7 @@ public class GamePauseMenu extends BasicGameState {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseWheelMoved(int newValue) {
|
public void mouseWheelMoved(int newValue) {
|
||||||
if (Options.isMouseWheelDisabled() || Options.isMouseDisabled())
|
if (Options.isMouseWheelDisabled())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
UI.changeVolume((newValue < 0) ? -1 : 1);
|
UI.changeVolume((newValue < 0) ? -1 : 1);
|
||||||
|
|
|
@ -32,8 +32,12 @@ import itdelatrisu.opsu.beatmap.BeatmapSetNode;
|
||||||
import itdelatrisu.opsu.beatmap.TimingPoint;
|
import itdelatrisu.opsu.beatmap.TimingPoint;
|
||||||
import itdelatrisu.opsu.downloads.Updater;
|
import itdelatrisu.opsu.downloads.Updater;
|
||||||
import itdelatrisu.opsu.states.ButtonMenu.MenuState;
|
import itdelatrisu.opsu.states.ButtonMenu.MenuState;
|
||||||
import itdelatrisu.opsu.ui.*;
|
import itdelatrisu.opsu.ui.Colors;
|
||||||
|
import itdelatrisu.opsu.ui.Fonts;
|
||||||
|
import itdelatrisu.opsu.ui.MenuButton;
|
||||||
import itdelatrisu.opsu.ui.MenuButton.Expand;
|
import itdelatrisu.opsu.ui.MenuButton.Expand;
|
||||||
|
import itdelatrisu.opsu.ui.StarFountain;
|
||||||
|
import itdelatrisu.opsu.ui.UI;
|
||||||
import itdelatrisu.opsu.ui.animations.AnimatedValue;
|
import itdelatrisu.opsu.ui.animations.AnimatedValue;
|
||||||
import itdelatrisu.opsu.ui.animations.AnimationEquation;
|
import itdelatrisu.opsu.ui.animations.AnimationEquation;
|
||||||
|
|
||||||
|
@ -51,8 +55,8 @@ import org.newdawn.slick.Input;
|
||||||
import org.newdawn.slick.SlickException;
|
import org.newdawn.slick.SlickException;
|
||||||
import org.newdawn.slick.state.BasicGameState;
|
import org.newdawn.slick.state.BasicGameState;
|
||||||
import org.newdawn.slick.state.StateBasedGame;
|
import org.newdawn.slick.state.StateBasedGame;
|
||||||
import org.newdawn.slick.state.transition.FadeInTransition;
|
|
||||||
import org.newdawn.slick.state.transition.EasedFadeOutTransition;
|
import org.newdawn.slick.state.transition.EasedFadeOutTransition;
|
||||||
|
import org.newdawn.slick.state.transition.FadeInTransition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "Main Menu" state.
|
* "Main Menu" state.
|
||||||
|
@ -117,15 +121,18 @@ public class MainMenu extends BasicGameState {
|
||||||
/** Music position bar coordinates and dimensions. */
|
/** Music position bar coordinates and dimensions. */
|
||||||
private float musicBarX, musicBarY, musicBarWidth, musicBarHeight;
|
private float musicBarX, musicBarY, musicBarWidth, musicBarHeight;
|
||||||
|
|
||||||
|
/** Last measure progress value. */
|
||||||
|
private float lastMeasureProgress = 0f;
|
||||||
|
|
||||||
|
/** The star fountain. */
|
||||||
|
private StarFountain starFountain;
|
||||||
|
|
||||||
// game-related variables
|
// game-related variables
|
||||||
private GameContainer container;
|
private GameContainer container;
|
||||||
private StateBasedGame game;
|
private StateBasedGame game;
|
||||||
private Input input;
|
private Input input;
|
||||||
private final int state;
|
private final int state;
|
||||||
|
|
||||||
private float hue = 0;
|
|
||||||
private boolean huedone = false;
|
|
||||||
|
|
||||||
public MainMenu(int state) {
|
public MainMenu(int state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
@ -203,9 +210,6 @@ public class MainMenu extends BasicGameState {
|
||||||
repoButton.setHoverAnimationDuration(350);
|
repoButton.setHoverAnimationDuration(350);
|
||||||
repoButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_BACK);
|
repoButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_BACK);
|
||||||
repoButton.setHoverExpand();
|
repoButton.setHoverExpand();
|
||||||
}
|
|
||||||
|
|
||||||
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { // only if a webpage can be opened
|
|
||||||
Image repoImg = GameImage.REPOSITORY.getImage();
|
Image repoImg = GameImage.REPOSITORY.getImage();
|
||||||
danceRepoButton = new MenuButton(repoImg,
|
danceRepoButton = new MenuButton(repoImg,
|
||||||
startX - repoImg.getWidth(), startY - repoImg.getHeight()
|
startX - repoImg.getWidth(), startY - repoImg.getHeight()
|
||||||
|
@ -228,6 +232,9 @@ public class MainMenu extends BasicGameState {
|
||||||
restartButton.setHoverAnimationEquation(AnimationEquation.LINEAR);
|
restartButton.setHoverAnimationEquation(AnimationEquation.LINEAR);
|
||||||
restartButton.setHoverRotate(360);
|
restartButton.setHoverRotate(360);
|
||||||
|
|
||||||
|
// initialize star fountain
|
||||||
|
starFountain = new StarFountain(width, height);
|
||||||
|
|
||||||
// logo animations
|
// logo animations
|
||||||
float centerOffsetX = width / 6.5f;
|
float centerOffsetX = width / 6.5f;
|
||||||
logoOpen = new AnimatedValue(100, 0, centerOffsetX, AnimationEquation.OUT_QUAD);
|
logoOpen = new AnimatedValue(100, 0, centerOffsetX, AnimationEquation.OUT_QUAD);
|
||||||
|
@ -262,6 +269,9 @@ public class MainMenu extends BasicGameState {
|
||||||
g.fillRect(0, height * 8 / 9f, width, height / 9f);
|
g.fillRect(0, height * 8 / 9f, width, height / 9f);
|
||||||
Colors.BLACK_ALPHA.a = oldAlpha;
|
Colors.BLACK_ALPHA.a = oldAlpha;
|
||||||
|
|
||||||
|
// draw star fountain
|
||||||
|
starFountain.draw();
|
||||||
|
|
||||||
// draw downloads button
|
// draw downloads button
|
||||||
downloadsButton.draw();
|
downloadsButton.draw();
|
||||||
|
|
||||||
|
@ -271,26 +281,15 @@ public class MainMenu extends BasicGameState {
|
||||||
exitButton.draw();
|
exitButton.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
// logo
|
// draw logo (pulsing)
|
||||||
Double position = MusicController.getBeatProgress();
|
Float position = MusicController.getBeatProgress();
|
||||||
Color color = Options.isColorMainMenuLogo() ? Cursor.lastCursorColor : Color.white;
|
if (position == null) // default to 60bpm
|
||||||
boolean renderPiece = position != null;
|
position = System.currentTimeMillis() % 1000 / 1000f;
|
||||||
if (position == null) {
|
float scale = 1f + position * 0.05f;
|
||||||
position = System.currentTimeMillis() % 1000 / 1000d;
|
logo.draw(Color.white, scale);
|
||||||
}
|
float ghostScale = logo.getLastScale() / scale * 1.05f;
|
||||||
double scale = 1 - (0 - position) * 0.05;
|
Image ghostLogo = GameImage.MENU_LOGO.getImage().getScaledCopy(ghostScale);
|
||||||
logo.draw(color, (float) scale);
|
ghostLogo.drawCentered(logo.getX(), logo.getY(), Colors.GHOST_LOGO);
|
||||||
if (renderPiece) {
|
|
||||||
Image piece = GameImage.MENU_LOGO_PIECE.getImage().getScaledCopy(logo.getCurrentScale());
|
|
||||||
float scaleposmodx = piece.getWidth() / 2;
|
|
||||||
float scaleposmody = piece.getHeight() / 2;
|
|
||||||
piece.rotate((float) (position * 360));
|
|
||||||
piece.draw(logo.getX() - scaleposmodx, logo.getY() - scaleposmody, color);
|
|
||||||
}
|
|
||||||
Image logoCopy = GameImage.MENU_LOGO.getImage().getScaledCopy(logo.getCurrentScale() / (float) scale * 1.05f);
|
|
||||||
float scaleposmodx = logoCopy.getWidth() / 2;
|
|
||||||
float scaleposmody = logoCopy.getHeight() / 2;
|
|
||||||
logoCopy.draw(logo.getX() - scaleposmodx, logo.getY() - scaleposmody, Colors.GHOST_LOGO);
|
|
||||||
|
|
||||||
// draw music buttons
|
// draw music buttons
|
||||||
if (MusicController.isPlaying())
|
if (MusicController.isPlaying())
|
||||||
|
@ -310,16 +309,13 @@ public class MainMenu extends BasicGameState {
|
||||||
g.fillRoundRect(musicBarX, musicBarY, musicBarWidth * musicBarPosition, musicBarHeight, 4);
|
g.fillRoundRect(musicBarX, musicBarY, musicBarWidth * musicBarPosition, musicBarHeight, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw repository button
|
// draw repository buttons
|
||||||
if (repoButton != null) {
|
if (repoButton != null) {
|
||||||
repoButton.draw();
|
repoButton.draw();
|
||||||
String text = "opsu!";
|
String text = "opsu!";
|
||||||
int fheight = Fonts.SMALL.getLineHeight();
|
int fheight = Fonts.SMALL.getLineHeight();
|
||||||
int fwidth = Fonts.SMALL.getWidth(text);
|
int fwidth = Fonts.SMALL.getWidth(text);
|
||||||
Fonts.SMALL.drawString(repoButton.getX() - fwidth / 2, repoButton.getY() - repoButton.getImage().getHeight() / 2 - fheight, text, Color.white);
|
Fonts.SMALL.drawString(repoButton.getX() - fwidth / 2, repoButton.getY() - repoButton.getImage().getHeight() / 2 - fheight, text, Color.white);
|
||||||
}
|
|
||||||
|
|
||||||
if (danceRepoButton != null) {
|
|
||||||
danceRepoButton.draw();
|
danceRepoButton.draw();
|
||||||
String text = "opsu!dance";
|
String text = "opsu!dance";
|
||||||
int fheight = Fonts.SMALL.getLineHeight();
|
int fheight = Fonts.SMALL.getLineHeight();
|
||||||
|
@ -367,15 +363,15 @@ public class MainMenu extends BasicGameState {
|
||||||
throws SlickException {
|
throws SlickException {
|
||||||
UI.update(delta);
|
UI.update(delta);
|
||||||
if (MusicController.trackEnded())
|
if (MusicController.trackEnded())
|
||||||
nextTrack(); // end of track: go to next track
|
nextTrack(false); // end of track: go to next track
|
||||||
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
||||||
logo.hoverUpdate(delta, mouseX, mouseY, 0.25f);
|
logo.hoverUpdate(delta, mouseX, mouseY, 0.25f);
|
||||||
playButton.hoverUpdate(delta, mouseX, mouseY, 0.25f);
|
playButton.hoverUpdate(delta, mouseX, mouseY, 0.25f);
|
||||||
exitButton.hoverUpdate(delta, mouseX, mouseY, 0.25f);
|
exitButton.hoverUpdate(delta, mouseX, mouseY, 0.25f);
|
||||||
if (repoButton != null)
|
if (repoButton != null) {
|
||||||
repoButton.hoverUpdate(delta, mouseX, mouseY);
|
repoButton.hoverUpdate(delta, mouseX, mouseY);
|
||||||
if (danceRepoButton != null)
|
|
||||||
danceRepoButton.hoverUpdate(delta, mouseX, mouseY);
|
danceRepoButton.hoverUpdate(delta, mouseX, mouseY);
|
||||||
|
}
|
||||||
if (Updater.get().showButton()) {
|
if (Updater.get().showButton()) {
|
||||||
updateButton.autoHoverUpdate(delta, true);
|
updateButton.autoHoverUpdate(delta, true);
|
||||||
restartButton.autoHoverUpdate(delta, false);
|
restartButton.autoHoverUpdate(delta, false);
|
||||||
|
@ -389,6 +385,7 @@ public class MainMenu extends BasicGameState {
|
||||||
noHoverUpdate |= contains;
|
noHoverUpdate |= contains;
|
||||||
musicNext.hoverUpdate(delta, !noHoverUpdate && musicNext.contains(mouseX, mouseY));
|
musicNext.hoverUpdate(delta, !noHoverUpdate && musicNext.contains(mouseX, mouseY));
|
||||||
musicPrevious.hoverUpdate(delta, !noHoverUpdate && musicPrevious.contains(mouseX, mouseY));
|
musicPrevious.hoverUpdate(delta, !noHoverUpdate && musicPrevious.contains(mouseX, mouseY));
|
||||||
|
starFountain.update(delta);
|
||||||
|
|
||||||
// window focus change: increase/decrease theme song volume
|
// window focus change: increase/decrease theme song volume
|
||||||
if (MusicController.isThemePlaying() &&
|
if (MusicController.isThemePlaying() &&
|
||||||
|
@ -400,6 +397,14 @@ public class MainMenu extends BasicGameState {
|
||||||
if (!(Options.isDynamicBackgroundEnabled() && beatmap != null && beatmap.isBackgroundLoading()))
|
if (!(Options.isDynamicBackgroundEnabled() && beatmap != null && beatmap.isBackgroundLoading()))
|
||||||
bgAlpha.update(delta);
|
bgAlpha.update(delta);
|
||||||
|
|
||||||
|
// check measure progress
|
||||||
|
Float measureProgress = MusicController.getMeasureProgress(2);
|
||||||
|
if (measureProgress != null) {
|
||||||
|
if (measureProgress < lastMeasureProgress)
|
||||||
|
starFountain.burst(true);
|
||||||
|
lastMeasureProgress = measureProgress;
|
||||||
|
}
|
||||||
|
|
||||||
// buttons
|
// buttons
|
||||||
int centerX = container.getWidth() / 2;
|
int centerX = container.getWidth() / 2;
|
||||||
float currentLogoButtonAlpha;
|
float currentLogoButtonAlpha;
|
||||||
|
@ -472,6 +477,10 @@ public class MainMenu extends BasicGameState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset measure info
|
||||||
|
lastMeasureProgress = 0f;
|
||||||
|
starFountain.clear();
|
||||||
|
|
||||||
// reset button hover states if mouse is not currently hovering over the button
|
// reset button hover states if mouse is not currently hovering over the button
|
||||||
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
||||||
if (!logo.contains(mouseX, mouseY, 0.25f))
|
if (!logo.contains(mouseX, mouseY, 0.25f))
|
||||||
|
@ -514,6 +523,7 @@ public class MainMenu extends BasicGameState {
|
||||||
// music position bar
|
// music position bar
|
||||||
if (MusicController.isPlaying()) {
|
if (MusicController.isPlaying()) {
|
||||||
if (musicPositionBarContains(x, y)) {
|
if (musicPositionBarContains(x, y)) {
|
||||||
|
lastMeasureProgress = 0f;
|
||||||
float pos = (x - musicBarX) / musicBarWidth;
|
float pos = (x - musicBarX) / musicBarWidth;
|
||||||
MusicController.setPosition((int) (pos * MusicController.getDuration()));
|
MusicController.setPosition((int) (pos * MusicController.getDuration()));
|
||||||
return;
|
return;
|
||||||
|
@ -531,10 +541,11 @@ public class MainMenu extends BasicGameState {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (musicNext.contains(x, y)) {
|
} else if (musicNext.contains(x, y)) {
|
||||||
nextTrack();
|
nextTrack(true);
|
||||||
UI.sendBarNotification(">> Next");
|
UI.sendBarNotification(">> Next");
|
||||||
return;
|
return;
|
||||||
} else if (musicPrevious.contains(x, y)) {
|
} else if (musicPrevious.contains(x, y)) {
|
||||||
|
lastMeasureProgress = 0f;
|
||||||
if (!previous.isEmpty()) {
|
if (!previous.isEmpty()) {
|
||||||
SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
|
SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
|
||||||
menu.setFocus(BeatmapSetList.get().getBaseNode(previous.pop()), -1, true, false);
|
menu.setFocus(BeatmapSetList.get().getBaseNode(previous.pop()), -1, true, false);
|
||||||
|
@ -657,7 +668,7 @@ public class MainMenu extends BasicGameState {
|
||||||
game.enterState(Opsu.STATE_DOWNLOADSMENU, new EasedFadeOutTransition(), new FadeInTransition());
|
game.enterState(Opsu.STATE_DOWNLOADSMENU, new EasedFadeOutTransition(), new FadeInTransition());
|
||||||
break;
|
break;
|
||||||
case Input.KEY_R:
|
case Input.KEY_R:
|
||||||
nextTrack();
|
nextTrack(true);
|
||||||
break;
|
break;
|
||||||
case Input.KEY_UP:
|
case Input.KEY_UP:
|
||||||
UI.changeVolume(1);
|
UI.changeVolume(1);
|
||||||
|
@ -717,9 +728,17 @@ public class MainMenu extends BasicGameState {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays the next track, and adds the previous one to the stack.
|
* Plays the next track, and adds the previous one to the stack.
|
||||||
|
* @param user {@code true} if this was user-initiated, false otherwise (track end)
|
||||||
*/
|
*/
|
||||||
private void nextTrack() {
|
private void nextTrack(boolean user) {
|
||||||
|
lastMeasureProgress = 0f;
|
||||||
boolean isTheme = MusicController.isThemePlaying();
|
boolean isTheme = MusicController.isThemePlaying();
|
||||||
|
if (isTheme && !user) {
|
||||||
|
// theme was playing, restart
|
||||||
|
// NOTE: not looping due to inaccurate track positions after loop
|
||||||
|
MusicController.playAt(0, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
|
SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
|
||||||
BeatmapSetNode node = menu.setFocus(BeatmapSetList.get().getRandomNode(), -1, true, false);
|
BeatmapSetNode node = menu.setFocus(BeatmapSetList.get().getRandomNode(), -1, true, false);
|
||||||
boolean sameAudio = false;
|
boolean sameAudio = false;
|
||||||
|
|
|
@ -32,6 +32,7 @@ import itdelatrisu.opsu.audio.SoundController;
|
||||||
import itdelatrisu.opsu.audio.SoundEffect;
|
import itdelatrisu.opsu.audio.SoundEffect;
|
||||||
import itdelatrisu.opsu.beatmap.Beatmap;
|
import itdelatrisu.opsu.beatmap.Beatmap;
|
||||||
import itdelatrisu.opsu.beatmap.BeatmapDifficultyCalculator;
|
import itdelatrisu.opsu.beatmap.BeatmapDifficultyCalculator;
|
||||||
|
import itdelatrisu.opsu.beatmap.BeatmapGroup;
|
||||||
import itdelatrisu.opsu.beatmap.BeatmapParser;
|
import itdelatrisu.opsu.beatmap.BeatmapParser;
|
||||||
import itdelatrisu.opsu.beatmap.BeatmapSet;
|
import itdelatrisu.opsu.beatmap.BeatmapSet;
|
||||||
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
||||||
|
@ -45,6 +46,7 @@ import itdelatrisu.opsu.db.BeatmapDB;
|
||||||
import itdelatrisu.opsu.db.ScoreDB;
|
import itdelatrisu.opsu.db.ScoreDB;
|
||||||
import itdelatrisu.opsu.states.ButtonMenu.MenuState;
|
import itdelatrisu.opsu.states.ButtonMenu.MenuState;
|
||||||
import itdelatrisu.opsu.ui.Colors;
|
import itdelatrisu.opsu.ui.Colors;
|
||||||
|
import itdelatrisu.opsu.ui.DropdownMenu;
|
||||||
import itdelatrisu.opsu.ui.Fonts;
|
import itdelatrisu.opsu.ui.Fonts;
|
||||||
import itdelatrisu.opsu.ui.KineticScrolling;
|
import itdelatrisu.opsu.ui.KineticScrolling;
|
||||||
import itdelatrisu.opsu.ui.MenuButton;
|
import itdelatrisu.opsu.ui.MenuButton;
|
||||||
|
@ -259,8 +261,11 @@ public class SongMenu extends BasicGameState {
|
||||||
/** Header and footer end and start y coordinates, respectively. */
|
/** Header and footer end and start y coordinates, respectively. */
|
||||||
private float headerY, footerY;
|
private float headerY, footerY;
|
||||||
|
|
||||||
/** Height of the footer */
|
/** Footer pulsing logo button. */
|
||||||
private float footerHeight;
|
private MenuButton footerLogoButton;
|
||||||
|
|
||||||
|
/** Size of the pulsing logo in the footer. */
|
||||||
|
private float footerLogoSize;
|
||||||
|
|
||||||
/** Time, in milliseconds, for fading the search bar. */
|
/** Time, in milliseconds, for fading the search bar. */
|
||||||
private int searchTransitionTimer = SEARCH_TRANSITION_TIME;
|
private int searchTransitionTimer = SEARCH_TRANSITION_TIME;
|
||||||
|
@ -309,9 +314,15 @@ public class SongMenu extends BasicGameState {
|
||||||
/** The star stream. */
|
/** The star stream. */
|
||||||
private StarStream starStream;
|
private StarStream starStream;
|
||||||
|
|
||||||
|
/** The maximum number of stars in the star stream. */
|
||||||
|
private static final int MAX_STREAM_STARS = 20;
|
||||||
|
|
||||||
/** Whether the menu is currently scrolling to the focus node (blocks other actions). */
|
/** Whether the menu is currently scrolling to the focus node (blocks other actions). */
|
||||||
private boolean isScrollingToFocusNode = false;
|
private boolean isScrollingToFocusNode = false;
|
||||||
|
|
||||||
|
/** Sort order dropdown menu. */
|
||||||
|
private DropdownMenu<BeatmapSortOrder> sortMenu;
|
||||||
|
|
||||||
// game-related variables
|
// game-related variables
|
||||||
private GameContainer container;
|
private GameContainer container;
|
||||||
private StateBasedGame game;
|
private StateBasedGame game;
|
||||||
|
@ -337,11 +348,48 @@ public class SongMenu extends BasicGameState {
|
||||||
Fonts.BOLD.getLineHeight() + Fonts.DEFAULT.getLineHeight() +
|
Fonts.BOLD.getLineHeight() + Fonts.DEFAULT.getLineHeight() +
|
||||||
Fonts.SMALL.getLineHeight();
|
Fonts.SMALL.getLineHeight();
|
||||||
footerY = height - GameImage.SELECTION_MODS.getImage().getHeight();
|
footerY = height - GameImage.SELECTION_MODS.getImage().getHeight();
|
||||||
footerHeight = height - footerY;
|
|
||||||
|
// footer logo coordinates
|
||||||
|
float footerHeight = height - footerY;
|
||||||
|
footerLogoSize = footerHeight * 3.25f;
|
||||||
|
Image logo = GameImage.MENU_LOGO.getImage();
|
||||||
|
logo = logo.getScaledCopy(footerLogoSize / logo.getWidth());
|
||||||
|
footerLogoButton = new MenuButton(logo, width - footerHeight * 0.8f, height - footerHeight * 0.65f);
|
||||||
|
footerLogoButton.setHoverAnimationDuration(1);
|
||||||
|
footerLogoButton.setHoverExpand(1.2f);
|
||||||
|
|
||||||
// initialize sorts
|
// initialize sorts
|
||||||
for (BeatmapSortOrder sort : BeatmapSortOrder.values())
|
int sortWidth = (int) (width * 0.12f);
|
||||||
sort.init(width, headerY - SongMenu.DIVIDER_LINE_WIDTH / 2);
|
sortMenu = new DropdownMenu<BeatmapSortOrder>(container, BeatmapSortOrder.values(),
|
||||||
|
width * 0.87f, headerY - GameImage.MENU_TAB.getImage().getHeight() * 2.25f, sortWidth) {
|
||||||
|
@Override
|
||||||
|
public void itemSelected(int index, BeatmapSortOrder item) {
|
||||||
|
BeatmapSortOrder.set(item);
|
||||||
|
if (focusNode == null)
|
||||||
|
return;
|
||||||
|
BeatmapSetNode oldFocusBase = BeatmapSetList.get().getBaseNode(focusNode.index);
|
||||||
|
int oldFocusFileIndex = focusNode.beatmapIndex;
|
||||||
|
focusNode = null;
|
||||||
|
BeatmapSetList.get().init();
|
||||||
|
SongMenu.this.setFocus(oldFocusBase, oldFocusFileIndex, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean menuClicked(int index) {
|
||||||
|
if (isInputBlocked())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SoundController.playSound(SoundEffect.MENUCLICK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sortMenu.setBackgroundColor(Colors.BLACK_BG_HOVER);
|
||||||
|
sortMenu.setBorderColor(Colors.BLUE_DIVIDER);
|
||||||
|
sortMenu.setChevronRightColor(Color.white);
|
||||||
|
|
||||||
|
// initialize group tabs
|
||||||
|
for (BeatmapGroup group : BeatmapGroup.values())
|
||||||
|
group.init(width, headerY - DIVIDER_LINE_WIDTH / 2);
|
||||||
|
|
||||||
// initialize score data buttons
|
// initialize score data buttons
|
||||||
ScoreData.init(width, headerY + height * 0.01f);
|
ScoreData.init(width, headerY + height * 0.01f);
|
||||||
|
@ -414,7 +462,9 @@ public class SongMenu extends BasicGameState {
|
||||||
});
|
});
|
||||||
|
|
||||||
// star stream
|
// star stream
|
||||||
starStream = new StarStream(width, height);
|
starStream = new StarStream(width, (height - GameImage.STAR.getImage().getHeight()) / 2, -width, 0, MAX_STREAM_STARS);
|
||||||
|
starStream.setPositionSpread(height / 20f);
|
||||||
|
starStream.setDirectionSpread(10f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -425,6 +475,7 @@ public class SongMenu extends BasicGameState {
|
||||||
int width = container.getWidth();
|
int width = container.getWidth();
|
||||||
int height = container.getHeight();
|
int height = container.getHeight();
|
||||||
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
||||||
|
boolean inDropdownMenu = sortMenu.contains(mouseX, mouseY);
|
||||||
|
|
||||||
// background
|
// background
|
||||||
if (focusNode != null) {
|
if (focusNode != null) {
|
||||||
|
@ -496,7 +547,7 @@ public class SongMenu extends BasicGameState {
|
||||||
g.clearClip();
|
g.clearClip();
|
||||||
|
|
||||||
// scroll bar
|
// scroll bar
|
||||||
if (focusScores.length > MAX_SCORE_BUTTONS && ScoreData.areaContains(mouseX, mouseY))
|
if (focusScores.length > MAX_SCORE_BUTTONS && ScoreData.areaContains(mouseX, mouseY) && !inDropdownMenu)
|
||||||
ScoreData.drawScrollbar(g, startScorePos.getPosition(), focusScores.length * ScoreData.getButtonOffset());
|
ScoreData.drawScrollbar(g, startScorePos.getPosition(), focusScores.length * ScoreData.getButtonOffset());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,25 +561,22 @@ public class SongMenu extends BasicGameState {
|
||||||
g.drawLine(0, footerY, width, footerY);
|
g.drawLine(0, footerY, width, footerY);
|
||||||
g.resetLineWidth();
|
g.resetLineWidth();
|
||||||
|
|
||||||
// opsu logo in bottom bar
|
// footer logo (pulsing)
|
||||||
Image logo = GameImage.MENU_LOGO.getImage();
|
Float position = MusicController.getBeatProgress();
|
||||||
float logoSize = footerHeight * 2f;
|
if (position == null) // default to 60bpm
|
||||||
logo = logo.getScaledCopy(logoSize / logo.getWidth());
|
position = System.currentTimeMillis() % 1000 / 1000f;
|
||||||
Double position = MusicController.getBeatProgress();
|
if (footerLogoButton.contains(mouseX, mouseY, 0.25f) && !inDropdownMenu) {
|
||||||
float x = width - footerHeight * 0.61f;
|
// hovering over logo: stop pulsing
|
||||||
float y = height - footerHeight * 0.40f;
|
footerLogoButton.draw();
|
||||||
if (position != null) {
|
|
||||||
Image ghostLogo = logo.getScaledCopy((float) (1 - (0 - position) * 0.15));
|
|
||||||
logo = logo.getScaledCopy((float) (1 - (position) * 0.15));
|
|
||||||
logoSize = logo.getWidth();
|
|
||||||
logo.draw(x - logoSize / 2, y - logoSize / 2);
|
|
||||||
logoSize = ghostLogo.getWidth();
|
|
||||||
float a = Colors.GHOST_LOGO.a;
|
|
||||||
Colors.GHOST_LOGO.a *= (1d - position);
|
|
||||||
ghostLogo.draw(x - logoSize / 2, y - logoSize / 2, Colors.GHOST_LOGO);
|
|
||||||
Colors.GHOST_LOGO.a = a;
|
|
||||||
} else {
|
} else {
|
||||||
logo.draw(x - logoSize / 2, y - logoSize / 2);
|
float expand = position * 0.15f;
|
||||||
|
footerLogoButton.draw(Color.white, 1f - expand);
|
||||||
|
Image ghostLogo = GameImage.MENU_LOGO.getImage();
|
||||||
|
ghostLogo = ghostLogo.getScaledCopy((1f + expand) * footerLogoSize / ghostLogo.getWidth());
|
||||||
|
float oldGhostAlpha = Colors.GHOST_LOGO.a;
|
||||||
|
Colors.GHOST_LOGO.a *= (1f - position);
|
||||||
|
ghostLogo.drawCentered(footerLogoButton.getX(), footerLogoButton.getY(), Colors.GHOST_LOGO);
|
||||||
|
Colors.GHOST_LOGO.a = oldGhostAlpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
// header
|
// header
|
||||||
|
@ -607,20 +655,22 @@ public class SongMenu extends BasicGameState {
|
||||||
GameImage.SELECTION_OTHER_OPTIONS.getImage().drawCentered(selectOptionsButton.getX(), selectOptionsButton.getY());
|
GameImage.SELECTION_OTHER_OPTIONS.getImage().drawCentered(selectOptionsButton.getX(), selectOptionsButton.getY());
|
||||||
selectOptionsButton.draw();
|
selectOptionsButton.draw();
|
||||||
|
|
||||||
// sorting tabs
|
// group tabs
|
||||||
BeatmapSortOrder currentSort = BeatmapSortOrder.getSort();
|
BeatmapGroup currentGroup = BeatmapGroup.current();
|
||||||
BeatmapSortOrder hoverSort = null;
|
BeatmapGroup hoverGroup = null;
|
||||||
for (BeatmapSortOrder sort : BeatmapSortOrder.values()) {
|
if (!inDropdownMenu) {
|
||||||
if (sort.contains(mouseX, mouseY)) {
|
for (BeatmapGroup group : BeatmapGroup.values()) {
|
||||||
hoverSort = sort;
|
if (group.contains(mouseX, mouseY)) {
|
||||||
|
hoverGroup = group;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (BeatmapSortOrder sort : BeatmapSortOrder.VALUES_REVERSED) {
|
|
||||||
if (sort != currentSort)
|
|
||||||
sort.draw(false, sort == hoverSort);
|
|
||||||
}
|
}
|
||||||
currentSort.draw(true, false);
|
for (BeatmapGroup group : BeatmapGroup.VALUES_REVERSED) {
|
||||||
|
if (group != currentGroup)
|
||||||
|
group.draw(false, group == hoverGroup);
|
||||||
|
}
|
||||||
|
currentGroup.draw(true, false);
|
||||||
|
|
||||||
// search
|
// search
|
||||||
boolean searchEmpty = search.getText().isEmpty();
|
boolean searchEmpty = search.getText().isEmpty();
|
||||||
|
@ -655,6 +705,9 @@ public class SongMenu extends BasicGameState {
|
||||||
(searchResultString == null) ? "Searching..." : searchResultString, Color.white);
|
(searchResultString == null) ? "Searching..." : searchResultString, Color.white);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sorting options
|
||||||
|
sortMenu.render(container, g);
|
||||||
|
|
||||||
// reloading beatmaps
|
// reloading beatmaps
|
||||||
if (reloadThread != null) {
|
if (reloadThread != null) {
|
||||||
// darken the screen
|
// darken the screen
|
||||||
|
@ -678,20 +731,25 @@ public class SongMenu extends BasicGameState {
|
||||||
if (reloadThread == null)
|
if (reloadThread == null)
|
||||||
MusicController.loopTrackIfEnded(true);
|
MusicController.loopTrackIfEnded(true);
|
||||||
else if (reloadThread.isFinished()) {
|
else if (reloadThread.isFinished()) {
|
||||||
|
BeatmapGroup.set(BeatmapGroup.ALL);
|
||||||
|
BeatmapSortOrder.set(BeatmapSortOrder.TITLE);
|
||||||
|
BeatmapSetList.get().reset();
|
||||||
|
BeatmapSetList.get().init();
|
||||||
if (BeatmapSetList.get().size() > 0) {
|
if (BeatmapSetList.get().size() > 0) {
|
||||||
// initialize song list
|
// initialize song list
|
||||||
BeatmapSetList.get().init();
|
|
||||||
setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
|
setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
|
||||||
} else
|
} else
|
||||||
MusicController.playThemeSong();
|
MusicController.playThemeSong();
|
||||||
reloadThread = null;
|
reloadThread = null;
|
||||||
}
|
}
|
||||||
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
||||||
|
boolean inDropdownMenu = sortMenu.contains(mouseX, mouseY);
|
||||||
UI.getBackButton().hoverUpdate(delta, mouseX, mouseY);
|
UI.getBackButton().hoverUpdate(delta, mouseX, mouseY);
|
||||||
selectModsButton.hoverUpdate(delta, mouseX, mouseY);
|
selectModsButton.hoverUpdate(delta, mouseX, mouseY);
|
||||||
selectRandomButton.hoverUpdate(delta, mouseX, mouseY);
|
selectRandomButton.hoverUpdate(delta, mouseX, mouseY);
|
||||||
selectMapOptionsButton.hoverUpdate(delta, mouseX, mouseY);
|
selectMapOptionsButton.hoverUpdate(delta, mouseX, mouseY);
|
||||||
selectOptionsButton.hoverUpdate(delta, mouseX, mouseY);
|
selectOptionsButton.hoverUpdate(delta, mouseX, mouseY);
|
||||||
|
footerLogoButton.hoverUpdate(delta, mouseX, mouseY, 0.25f);
|
||||||
|
|
||||||
// beatmap menu timer
|
// beatmap menu timer
|
||||||
if (beatmapMenuTimer > -1) {
|
if (beatmapMenuTimer > -1) {
|
||||||
|
@ -699,7 +757,9 @@ public class SongMenu extends BasicGameState {
|
||||||
if (beatmapMenuTimer >= BEATMAP_MENU_DELAY) {
|
if (beatmapMenuTimer >= BEATMAP_MENU_DELAY) {
|
||||||
beatmapMenuTimer = -1;
|
beatmapMenuTimer = -1;
|
||||||
if (focusNode != null) {
|
if (focusNode != null) {
|
||||||
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(MenuState.BEATMAP, focusNode);
|
MenuState state = focusNode.getBeatmapSet().isFavorite() ?
|
||||||
|
MenuState.BEATMAP_FAVORITE : MenuState.BEATMAP;
|
||||||
|
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(state, focusNode);
|
||||||
game.enterState(Opsu.STATE_BUTTONMENU);
|
game.enterState(Opsu.STATE_BUTTONMENU);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -788,7 +848,7 @@ public class SongMenu extends BasicGameState {
|
||||||
|
|
||||||
// mouse hover
|
// mouse hover
|
||||||
BeatmapSetNode node = getNodeAtPosition(mouseX, mouseY);
|
BeatmapSetNode node = getNodeAtPosition(mouseX, mouseY);
|
||||||
if (node != null) {
|
if (node != null && !inDropdownMenu) {
|
||||||
if (node == hoverIndex)
|
if (node == hoverIndex)
|
||||||
hoverOffset.update(delta);
|
hoverOffset.update(delta);
|
||||||
else {
|
else {
|
||||||
|
@ -802,7 +862,9 @@ public class SongMenu extends BasicGameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// tooltips
|
// tooltips
|
||||||
if (focusScores != null && ScoreData.areaContains(mouseX, mouseY)) {
|
if (sortMenu.baseContains(mouseX, mouseY))
|
||||||
|
UI.updateTooltip(delta, "Sort by...", false);
|
||||||
|
else if (focusScores != null && ScoreData.areaContains(mouseX, mouseY)) {
|
||||||
int startScore = (int) (startScorePos.getPosition() / ScoreData.getButtonOffset());
|
int startScore = (int) (startScorePos.getPosition() / ScoreData.getButtonOffset());
|
||||||
int offset = (int) (-startScorePos.getPosition() + startScore * ScoreData.getButtonOffset());
|
int offset = (int) (-startScorePos.getPosition() + startScore * ScoreData.getButtonOffset());
|
||||||
int scoreButtons = Math.min(focusScores.length - startScore, MAX_SCORE_BUTTONS);
|
int scoreButtons = Math.min(focusScores.length - startScore, MAX_SCORE_BUTTONS);
|
||||||
|
@ -880,24 +942,41 @@ public class SongMenu extends BasicGameState {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// group tabs
|
||||||
|
for (BeatmapGroup group : BeatmapGroup.values()) {
|
||||||
|
if (group.contains(x, y)) {
|
||||||
|
if (group != BeatmapGroup.current()) {
|
||||||
|
BeatmapGroup.set(group);
|
||||||
|
SoundController.playSound(SoundEffect.MENUCLICK);
|
||||||
|
startNode = focusNode = null;
|
||||||
|
oldFocusNode = null;
|
||||||
|
randomStack = new Stack<SongNode>();
|
||||||
|
songInfo = null;
|
||||||
|
scoreMap = null;
|
||||||
|
focusScores = null;
|
||||||
|
search.setText("");
|
||||||
|
searchTimer = SEARCH_DELAY;
|
||||||
|
searchTransitionTimer = SEARCH_TRANSITION_TIME;
|
||||||
|
searchResultString = null;
|
||||||
|
BeatmapSetList.get().reset();
|
||||||
|
BeatmapSetList.get().init();
|
||||||
|
setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
|
||||||
|
|
||||||
|
if (BeatmapSetList.get().size() < 1 && group.getEmptyMessage() != null)
|
||||||
|
UI.sendBarNotification(group.getEmptyMessage());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (focusNode == null)
|
if (focusNode == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// sorting buttons
|
// logo: start game
|
||||||
for (BeatmapSortOrder sort : BeatmapSortOrder.values()) {
|
if (footerLogoButton.contains(x, y, 0.25f)) {
|
||||||
if (sort.contains(x, y)) {
|
startGame();
|
||||||
if (sort != BeatmapSortOrder.getSort()) {
|
|
||||||
BeatmapSortOrder.setSort(sort);
|
|
||||||
SoundController.playSound(SoundEffect.MENUCLICK);
|
|
||||||
BeatmapSetNode oldFocusBase = BeatmapSetList.get().getBaseNode(focusNode.index);
|
|
||||||
int oldFocusFileIndex = focusNode.beatmapIndex;
|
|
||||||
focusNode = null;
|
|
||||||
BeatmapSetList.get().init();
|
|
||||||
setFocus(oldFocusBase, oldFocusFileIndex, true, true);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// song buttons
|
// song buttons
|
||||||
BeatmapSetNode node = getNodeAtPosition(x, y);
|
BeatmapSetNode node = getNodeAtPosition(x, y);
|
||||||
|
@ -978,6 +1057,7 @@ public class SongMenu extends BasicGameState {
|
||||||
search.setText("");
|
search.setText("");
|
||||||
searchTimer = SEARCH_DELAY;
|
searchTimer = SEARCH_DELAY;
|
||||||
searchTransitionTimer = 0;
|
searchTransitionTimer = 0;
|
||||||
|
searchResultString = null;
|
||||||
} else {
|
} else {
|
||||||
// return to main menu
|
// return to main menu
|
||||||
SoundController.playSound(SoundEffect.MENUBACK);
|
SoundController.playSound(SoundEffect.MENUBACK);
|
||||||
|
@ -999,7 +1079,11 @@ public class SongMenu extends BasicGameState {
|
||||||
SongNode prev;
|
SongNode prev;
|
||||||
if (randomStack.isEmpty() || (prev = randomStack.pop()) == null)
|
if (randomStack.isEmpty() || (prev = randomStack.pop()) == null)
|
||||||
break;
|
break;
|
||||||
setFocus(prev.getNode(), prev.getIndex(), true, true);
|
BeatmapSetNode node = prev.getNode();
|
||||||
|
int expandedIndex = BeatmapSetList.get().getExpandedIndex();
|
||||||
|
if (node.index == expandedIndex)
|
||||||
|
node = node.next; // move past base node
|
||||||
|
setFocus(node, prev.getIndex(), true, true);
|
||||||
} else {
|
} else {
|
||||||
// random track, add previous to stack
|
// random track, add previous to stack
|
||||||
randomStack.push(new SongNode(BeatmapSetList.get().getBaseNode(focusNode.index), focusNode.beatmapIndex));
|
randomStack.push(new SongNode(BeatmapSetList.get().getBaseNode(focusNode.index), focusNode.beatmapIndex));
|
||||||
|
@ -1010,7 +1094,9 @@ public class SongMenu extends BasicGameState {
|
||||||
if (focusNode == null)
|
if (focusNode == null)
|
||||||
break;
|
break;
|
||||||
SoundController.playSound(SoundEffect.MENUHIT);
|
SoundController.playSound(SoundEffect.MENUHIT);
|
||||||
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(MenuState.BEATMAP, focusNode);
|
MenuState state = focusNode.getBeatmapSet().isFavorite() ?
|
||||||
|
MenuState.BEATMAP_FAVORITE : MenuState.BEATMAP;
|
||||||
|
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(state, focusNode);
|
||||||
game.enterState(Opsu.STATE_BUTTONMENU);
|
game.enterState(Opsu.STATE_BUTTONMENU);
|
||||||
break;
|
break;
|
||||||
case Input.KEY_F5:
|
case Input.KEY_F5:
|
||||||
|
@ -1045,11 +1131,6 @@ public class SongMenu extends BasicGameState {
|
||||||
case Input.KEY_ENTER:
|
case Input.KEY_ENTER:
|
||||||
if (focusNode == null)
|
if (focusNode == null)
|
||||||
break;
|
break;
|
||||||
if (input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) {
|
|
||||||
// turn on "auto" mod
|
|
||||||
if (!GameMod.AUTO.isActive())
|
|
||||||
GameMod.AUTO.toggle(true);
|
|
||||||
}
|
|
||||||
startGame();
|
startGame();
|
||||||
break;
|
break;
|
||||||
case Input.KEY_DOWN:
|
case Input.KEY_DOWN:
|
||||||
|
@ -1194,6 +1275,8 @@ public class SongMenu extends BasicGameState {
|
||||||
songChangeTimer.setTime(songChangeTimer.getDuration());
|
songChangeTimer.setTime(songChangeTimer.getDuration());
|
||||||
musicIconBounceTimer.setTime(musicIconBounceTimer.getDuration());
|
musicIconBounceTimer.setTime(musicIconBounceTimer.getDuration());
|
||||||
starStream.clear();
|
starStream.clear();
|
||||||
|
sortMenu.activate();
|
||||||
|
sortMenu.reset();
|
||||||
|
|
||||||
// reset song stack
|
// reset song stack
|
||||||
randomStack = new Stack<SongNode>();
|
randomStack = new Stack<SongNode>();
|
||||||
|
@ -1241,6 +1324,15 @@ public class SongMenu extends BasicGameState {
|
||||||
focusScores = getScoreDataForNode(focusNode, true);
|
focusScores = getScoreDataForNode(focusNode, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// re-sort (in case play count updated)
|
||||||
|
if (BeatmapSortOrder.current() == BeatmapSortOrder.PLAYS) {
|
||||||
|
BeatmapSetNode oldFocusBase = BeatmapSetList.get().getBaseNode(focusNode.index);
|
||||||
|
int oldFocusFileIndex = focusNode.beatmapIndex;
|
||||||
|
focusNode = null;
|
||||||
|
BeatmapSetList.get().init();
|
||||||
|
setFocus(oldFocusBase, oldFocusFileIndex, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
resetGame = false;
|
resetGame = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1325,6 +1417,19 @@ public class SongMenu extends BasicGameState {
|
||||||
case RELOAD: // reload beatmaps
|
case RELOAD: // reload beatmaps
|
||||||
reloadBeatmaps(true);
|
reloadBeatmaps(true);
|
||||||
break;
|
break;
|
||||||
|
case BEATMAP_FAVORITE: // removed favorite, reset beatmap list
|
||||||
|
if (BeatmapGroup.current() == BeatmapGroup.FAVORITE) {
|
||||||
|
startNode = focusNode = null;
|
||||||
|
oldFocusNode = null;
|
||||||
|
randomStack = new Stack<SongNode>();
|
||||||
|
songInfo = null;
|
||||||
|
scoreMap = null;
|
||||||
|
focusScores = null;
|
||||||
|
BeatmapSetList.get().reset();
|
||||||
|
BeatmapSetList.get().init();
|
||||||
|
setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1338,6 +1443,7 @@ public class SongMenu extends BasicGameState {
|
||||||
public void leave(GameContainer container, StateBasedGame game)
|
public void leave(GameContainer container, StateBasedGame game)
|
||||||
throws SlickException {
|
throws SlickException {
|
||||||
search.setFocus(false);
|
search.setFocus(false);
|
||||||
|
sortMenu.deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1430,7 +1536,8 @@ public class SongMenu extends BasicGameState {
|
||||||
focusNode = BeatmapSetList.get().getNode(node, beatmapIndex);
|
focusNode = BeatmapSetList.get().getNode(node, beatmapIndex);
|
||||||
Beatmap beatmap = focusNode.getSelectedBeatmap();
|
Beatmap beatmap = focusNode.getSelectedBeatmap();
|
||||||
if (beatmap.timingPoints == null) {
|
if (beatmap.timingPoints == null) {
|
||||||
BeatmapParser.parseOnlyTimingPoints(beatmap);
|
// parse timing points so we can pulse the logo
|
||||||
|
BeatmapParser.parseTimingPoints(beatmap);
|
||||||
}
|
}
|
||||||
MusicController.play(beatmap, false, preview);
|
MusicController.play(beatmap, false, preview);
|
||||||
|
|
||||||
|
@ -1670,12 +1777,19 @@ public class SongMenu extends BasicGameState {
|
||||||
if (MusicController.isTrackLoading())
|
if (MusicController.isTrackLoading())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SoundController.playSound(SoundEffect.MENUHIT);
|
|
||||||
Beatmap beatmap = MusicController.getBeatmap();
|
Beatmap beatmap = MusicController.getBeatmap();
|
||||||
if (focusNode == null || beatmap != focusNode.getSelectedBeatmap()) {
|
if (focusNode == null || beatmap != focusNode.getSelectedBeatmap()) {
|
||||||
UI.sendBarNotification("Unable to load the beatmap audio.");
|
UI.sendBarNotification("Unable to load the beatmap audio.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// turn on "auto" mod if holding "ctrl" key
|
||||||
|
if (input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) {
|
||||||
|
if (!GameMod.AUTO.isActive())
|
||||||
|
GameMod.AUTO.toggle(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
SoundController.playSound(SoundEffect.MENUHIT);
|
||||||
MultiClip.destroyExtraClips();
|
MultiClip.destroyExtraClips();
|
||||||
Game gameState = (Game) game.getState(Opsu.STATE_GAME);
|
Game gameState = (Game) game.getState(Opsu.STATE_GAME);
|
||||||
gameState.loadBeatmap(beatmap);
|
gameState.loadBeatmap(beatmap);
|
||||||
|
|
|
@ -33,6 +33,7 @@ public class Colors {
|
||||||
BLUE_BACKGROUND = new Color(74, 130, 255),
|
BLUE_BACKGROUND = new Color(74, 130, 255),
|
||||||
BLUE_BUTTON = new Color(40, 129, 237),
|
BLUE_BUTTON = new Color(40, 129, 237),
|
||||||
ORANGE_BUTTON = new Color(200, 90, 3),
|
ORANGE_BUTTON = new Color(200, 90, 3),
|
||||||
|
PINK_BUTTON = new Color(223, 71, 147),
|
||||||
YELLOW_ALPHA = new Color(255, 255, 0, 0.4f),
|
YELLOW_ALPHA = new Color(255, 255, 0, 0.4f),
|
||||||
WHITE_FADE = new Color(255, 255, 255, 1f),
|
WHITE_FADE = new Color(255, 255, 255, 1f),
|
||||||
RED_HOVER = new Color(255, 112, 112),
|
RED_HOVER = new Color(255, 112, 112),
|
||||||
|
@ -50,7 +51,6 @@ public class Colors {
|
||||||
BLACK_BG_FOCUS = new Color(0, 0, 0, 0.75f),
|
BLACK_BG_FOCUS = new Color(0, 0, 0, 0.75f),
|
||||||
GHOST_LOGO = new Color(1.0f, 1.0f, 1.0f, 0.25f);
|
GHOST_LOGO = new Color(1.0f, 1.0f, 1.0f, 0.25f);
|
||||||
|
|
||||||
|
|
||||||
// This class should not be instantiated.
|
// This class should not be instantiated.
|
||||||
private Colors() {}
|
private Colors() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -320,8 +320,6 @@ public class Cursor {
|
||||||
|
|
||||||
// reset angles
|
// reset angles
|
||||||
cursorAngle = 0f;
|
cursorAngle = 0f;
|
||||||
GameImage.CURSOR.getImage().setRotation(0f);
|
|
||||||
GameImage.CURSOR_TRAIL.getImage().setRotation(0f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -95,6 +95,9 @@ public class DropdownMenu<E> extends AbstractComponent {
|
||||||
/** The chevron images. */
|
/** The chevron images. */
|
||||||
private Image chevronDown, chevronRight;
|
private Image chevronDown, chevronRight;
|
||||||
|
|
||||||
|
/** Should the next click be blocked? */
|
||||||
|
private boolean blockClick = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new dropdown menu.
|
* Creates a new dropdown menu.
|
||||||
* @param container the container rendering this menu
|
* @param container the container rendering this menu
|
||||||
|
@ -327,6 +330,7 @@ public class DropdownMenu<E> extends AbstractComponent {
|
||||||
this.expanded = false;
|
this.expanded = false;
|
||||||
this.lastUpdateTime = 0;
|
this.lastUpdateTime = 0;
|
||||||
expandProgress.setTime(0);
|
expandProgress.setTime(0);
|
||||||
|
blockClick = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -349,9 +353,21 @@ public class DropdownMenu<E> extends AbstractComponent {
|
||||||
this.itemIndex = idx;
|
this.itemIndex = idx;
|
||||||
itemSelected(idx, items[idx]);
|
itemSelected(idx, items[idx]);
|
||||||
}
|
}
|
||||||
|
blockClick = true;
|
||||||
consumeEvent();
|
consumeEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(int button, int x, int y, int clickCount) {
|
||||||
|
if (!active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (blockClick) {
|
||||||
|
blockClick = false;
|
||||||
|
consumeEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification that a new item was selected (via override).
|
* Notification that a new item was selected (via override).
|
||||||
* @param index the index of the item selected
|
* @param index the index of the item selected
|
||||||
|
|
|
@ -98,11 +98,8 @@ public class MenuButton {
|
||||||
/** The default max rotation angle of the button. */
|
/** The default max rotation angle of the button. */
|
||||||
private static final float DEFAULT_ANGLE_MAX = 30f;
|
private static final float DEFAULT_ANGLE_MAX = 30f;
|
||||||
|
|
||||||
private float currentScale = 1f;
|
/** The last scale at which the button was drawn. */
|
||||||
|
private float lastScale = 1f;
|
||||||
public float getCurrentScale() {
|
|
||||||
return currentScale;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new button from an Image.
|
* Creates a new button from an Image.
|
||||||
|
@ -172,6 +169,11 @@ public class MenuButton {
|
||||||
*/
|
*/
|
||||||
public float getY() { return y; }
|
public float getY() { return y; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last scale at which the button was drawn.
|
||||||
|
*/
|
||||||
|
public float getLastScale() { return lastScale; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets text to draw in the middle of the button.
|
* Sets text to draw in the middle of the button.
|
||||||
* @param text the text to draw
|
* @param text the text to draw
|
||||||
|
@ -197,21 +199,21 @@ public class MenuButton {
|
||||||
/**
|
/**
|
||||||
* Draws the button.
|
* Draws the button.
|
||||||
*/
|
*/
|
||||||
public void draw() { draw(Color.white, 1.0f); }
|
public void draw() { draw(Color.white, 1f); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the button with a color filter.
|
* Draws the button with a color filter.
|
||||||
* @param filter the color to filter with when drawing
|
* @param filter the color to filter with when drawing
|
||||||
*/
|
*/
|
||||||
public void draw(Color filter) { draw(filter, 1.0f); }
|
public void draw(Color filter) { draw(filter, 1f); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draw the button with a color filter and scale.
|
* Draw the button with a color filter at the given scale.
|
||||||
* @param filter the color to filter with when drawing
|
* @param filter the color to filter with when drawing
|
||||||
* @param scaleoverride the scale to use when drawing
|
* @param scaleOverride the scale to use when drawing (only works for normal images)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public void draw(Color filter, float scaleoverride) {
|
public void draw(Color filter, float scaleOverride) {
|
||||||
// animations: get current frame
|
// animations: get current frame
|
||||||
Image image = this.img;
|
Image image = this.img;
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
|
@ -219,20 +221,17 @@ public class MenuButton {
|
||||||
image = anim.getCurrentFrame();
|
image = anim.getCurrentFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
currentScale = 1f;
|
|
||||||
|
|
||||||
// normal images
|
// normal images
|
||||||
if (imgL == null) {
|
if (imgL == null) {
|
||||||
float scaleposmodx = 0;
|
float xScaleOffset = 0f, yScaleOffset = 0f;
|
||||||
float scaleposmody = 0;
|
if (scaleOverride != 1f) {
|
||||||
if (scaleoverride != 1f) {
|
image = image.getScaledCopy(scaleOverride);
|
||||||
image = image.getScaledCopy(scaleoverride);
|
xScaleOffset = image.getWidth() / 2f - xRadius;
|
||||||
scaleposmodx = image.getWidth() / 2 - xRadius;
|
yScaleOffset = image.getHeight() / 2f - yRadius;
|
||||||
scaleposmody = image.getHeight() / 2 - yRadius;
|
|
||||||
currentScale = scaleoverride;
|
|
||||||
}
|
}
|
||||||
|
lastScale = scaleOverride;
|
||||||
if (hoverEffect == 0)
|
if (hoverEffect == 0)
|
||||||
image.draw(x - xRadius - scaleposmodx, y - yRadius - scaleposmody, filter);
|
image.draw(x - xRadius, y - yRadius, filter);
|
||||||
else {
|
else {
|
||||||
float oldAlpha = image.getAlpha();
|
float oldAlpha = image.getAlpha();
|
||||||
float oldAngle = image.getRotation();
|
float oldAngle = image.getRotation();
|
||||||
|
@ -240,16 +239,18 @@ public class MenuButton {
|
||||||
if (scale.getValue() != 1f) {
|
if (scale.getValue() != 1f) {
|
||||||
image = image.getScaledCopy(scale.getValue());
|
image = image.getScaledCopy(scale.getValue());
|
||||||
image.setAlpha(oldAlpha);
|
image.setAlpha(oldAlpha);
|
||||||
scaleposmodx = image.getWidth() / 2 - xRadius;
|
if (scaleOverride != 1f) {
|
||||||
scaleposmody = image.getHeight() / 2 - yRadius;
|
xScaleOffset = image.getWidth() / 2f - xRadius;
|
||||||
currentScale *= scale.getValue();
|
yScaleOffset = image.getHeight() / 2f - yRadius;
|
||||||
|
}
|
||||||
|
lastScale *= scale.getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((hoverEffect & EFFECT_FADE) > 0)
|
if ((hoverEffect & EFFECT_FADE) > 0)
|
||||||
image.setAlpha(alpha.getValue());
|
image.setAlpha(alpha.getValue());
|
||||||
if ((hoverEffect & EFFECT_ROTATE) > 0)
|
if ((hoverEffect & EFFECT_ROTATE) > 0)
|
||||||
image.setRotation(angle.getValue());
|
image.setRotation(angle.getValue());
|
||||||
image.draw(x - xRadius - scaleposmodx, y - yRadius - scaleposmody, filter);
|
image.draw(x - xRadius - xScaleOffset, y - yRadius - yScaleOffset, filter);
|
||||||
if (image == this.img) {
|
if (image == this.img) {
|
||||||
image.setAlpha(oldAlpha);
|
image.setAlpha(oldAlpha);
|
||||||
image.setRotation(oldAngle);
|
image.setRotation(oldAngle);
|
||||||
|
|
143
src/itdelatrisu/opsu/ui/StarFountain.java
Normal file
143
src/itdelatrisu/opsu/ui/StarFountain.java
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
package itdelatrisu.opsu.ui;
|
||||||
|
|
||||||
|
import itdelatrisu.opsu.GameImage;
|
||||||
|
import itdelatrisu.opsu.ui.animations.AnimatedValue;
|
||||||
|
import itdelatrisu.opsu.ui.animations.AnimationEquation;
|
||||||
|
|
||||||
|
import org.newdawn.slick.Image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Star fountain consisting of two star streams.
|
||||||
|
*/
|
||||||
|
public class StarFountain {
|
||||||
|
/** The (approximate) number of stars in each burst. */
|
||||||
|
private static final int BURST_SIZE = 125;
|
||||||
|
|
||||||
|
/** Star streams. */
|
||||||
|
private final StarStream left, right;
|
||||||
|
|
||||||
|
/** Burst progress. */
|
||||||
|
private final AnimatedValue burstProgress = new AnimatedValue(1000, 0, 1, AnimationEquation.LINEAR);
|
||||||
|
|
||||||
|
/** The maximum direction offsets. */
|
||||||
|
private final float xDirection, yDirection;
|
||||||
|
|
||||||
|
/** Motion types. */
|
||||||
|
private enum Motion {
|
||||||
|
NONE {
|
||||||
|
@Override
|
||||||
|
public void init(StarFountain fountain) {
|
||||||
|
fountain.left.setDirection(0, fountain.yDirection);
|
||||||
|
fountain.right.setDirection(0, fountain.yDirection);
|
||||||
|
fountain.left.setDirectionSpread(20f);
|
||||||
|
fountain.right.setDirectionSpread(20f);
|
||||||
|
fountain.left.setDurationSpread(1000, 200);
|
||||||
|
fountain.right.setDurationSpread(1000, 200);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OUTWARD_SWEEP {
|
||||||
|
@Override
|
||||||
|
public void init(StarFountain fountain) {
|
||||||
|
fountain.left.setDirectionSpread(0f);
|
||||||
|
fountain.right.setDirectionSpread(0f);
|
||||||
|
fountain.left.setDurationSpread(850, 0);
|
||||||
|
fountain.right.setDurationSpread(850, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(StarFountain fountain) {
|
||||||
|
float t = fountain.burstProgress.getValue();
|
||||||
|
fountain.left.setDirection(fountain.xDirection - fountain.xDirection * 2f * t, fountain.yDirection);
|
||||||
|
fountain.right.setDirection(-fountain.xDirection + fountain.xDirection * 2f * t, fountain.yDirection);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
INWARD_SWEEP {
|
||||||
|
@Override
|
||||||
|
public void init(StarFountain fountain) { OUTWARD_SWEEP.init(fountain); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(StarFountain fountain) {
|
||||||
|
float t = fountain.burstProgress.getValue();
|
||||||
|
fountain.left.setDirection(-fountain.xDirection + fountain.xDirection * 2f * t, fountain.yDirection);
|
||||||
|
fountain.right.setDirection(fountain.xDirection - fountain.xDirection * 2f * t, fountain.yDirection);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Initializes the streams in the fountain. */
|
||||||
|
public void init(StarFountain fountain) {}
|
||||||
|
|
||||||
|
/** Updates the streams in the fountain. */
|
||||||
|
public void update(StarFountain fountain) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The current motion. */
|
||||||
|
private Motion motion = Motion.NONE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the star fountain.
|
||||||
|
* @param containerWidth the container width
|
||||||
|
* @param containerHeight the container height
|
||||||
|
*/
|
||||||
|
public StarFountain(int containerWidth, int containerHeight) {
|
||||||
|
Image img = GameImage.STAR2.getImage();
|
||||||
|
float xOffset = containerWidth * 0.125f;
|
||||||
|
this.xDirection = containerWidth / 2f - xOffset;
|
||||||
|
this.yDirection = -containerHeight * 0.85f;
|
||||||
|
this.left = new StarStream(xOffset - img.getWidth() / 2f, containerHeight, 0, yDirection, 0);
|
||||||
|
this.right = new StarStream(containerWidth - xOffset - img.getWidth() / 2f, containerHeight, 0, yDirection, 0);
|
||||||
|
left.setScaleSpread(1.1f, 0.2f);
|
||||||
|
right.setScaleSpread(1.1f, 0.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the star fountain.
|
||||||
|
*/
|
||||||
|
public void draw() {
|
||||||
|
left.draw();
|
||||||
|
right.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the stars in the fountain by a delta interval.
|
||||||
|
* @param delta the delta interval since the last call
|
||||||
|
*/
|
||||||
|
public void update(int delta) {
|
||||||
|
left.update(delta);
|
||||||
|
right.update(delta);
|
||||||
|
|
||||||
|
if (burstProgress.update(delta)) {
|
||||||
|
motion.update(this);
|
||||||
|
int size = Math.round((float) delta / burstProgress.getDuration() * BURST_SIZE);
|
||||||
|
left.burst(size);
|
||||||
|
right.burst(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a burst of stars to be processed during the next {@link #update(int)} call.
|
||||||
|
* @param wait if {@code true}, will not burst if a previous burst is in progress
|
||||||
|
*/
|
||||||
|
public void burst(boolean wait) {
|
||||||
|
if (wait && (burstProgress.getTime() < burstProgress.getDuration() || !left.isEmpty() || !right.isEmpty()))
|
||||||
|
return; // previous burst in progress
|
||||||
|
|
||||||
|
burstProgress.setTime(0);
|
||||||
|
|
||||||
|
Motion lastMotion = motion;
|
||||||
|
motion = Motion.values()[(int) (Math.random() * Motion.values().length)];
|
||||||
|
if (motion == lastMotion) // don't do the same sweep twice
|
||||||
|
motion = Motion.NONE;
|
||||||
|
motion.init(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the stars currently in the fountain.
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
left.clear();
|
||||||
|
right.clear();
|
||||||
|
burstProgress.setTime(burstProgress.getDuration());
|
||||||
|
motion = Motion.NONE;
|
||||||
|
motion.init(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ package itdelatrisu.opsu.ui;
|
||||||
|
|
||||||
import itdelatrisu.opsu.GameImage;
|
import itdelatrisu.opsu.GameImage;
|
||||||
import itdelatrisu.opsu.Utils;
|
import itdelatrisu.opsu.Utils;
|
||||||
|
import itdelatrisu.opsu.objects.curves.Vec2f;
|
||||||
import itdelatrisu.opsu.ui.animations.AnimatedValue;
|
import itdelatrisu.opsu.ui.animations.AnimatedValue;
|
||||||
import itdelatrisu.opsu.ui.animations.AnimationEquation;
|
import itdelatrisu.opsu.ui.animations.AnimationEquation;
|
||||||
|
|
||||||
|
@ -31,11 +32,35 @@ import java.util.Random;
|
||||||
import org.newdawn.slick.Image;
|
import org.newdawn.slick.Image;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Horizontal star stream.
|
* Star stream.
|
||||||
*/
|
*/
|
||||||
public class StarStream {
|
public class StarStream {
|
||||||
/** The container dimensions. */
|
/** The origin of the star stream. */
|
||||||
private final int containerWidth, containerHeight;
|
private final Vec2f position;
|
||||||
|
|
||||||
|
/** The direction of the star stream. */
|
||||||
|
private final Vec2f direction;
|
||||||
|
|
||||||
|
/** The maximum number of stars to draw at once. */
|
||||||
|
private final int maxStars;
|
||||||
|
|
||||||
|
/** The spread of the stars' starting position. */
|
||||||
|
private float positionSpread = 0f;
|
||||||
|
|
||||||
|
/** The spread of the stars' direction. */
|
||||||
|
private float directionSpread = 0f;
|
||||||
|
|
||||||
|
/** The base (mean) duration for which stars are shown, in ms. */
|
||||||
|
private int durationBase = 1300;
|
||||||
|
|
||||||
|
/** The spread of the stars' duration, in ms. */
|
||||||
|
private int durationSpread = 300;
|
||||||
|
|
||||||
|
/** The base (mean) scale at which stars are drawn. */
|
||||||
|
private float scaleBase = 1f;
|
||||||
|
|
||||||
|
/** The spread of the stars' scale.*/
|
||||||
|
private float scaleSpread = 0f;
|
||||||
|
|
||||||
/** The star image. */
|
/** The star image. */
|
||||||
private final Image starImg;
|
private final Image starImg;
|
||||||
|
@ -43,33 +68,41 @@ public class StarStream {
|
||||||
/** The current list of stars. */
|
/** The current list of stars. */
|
||||||
private final List<Star> stars;
|
private final List<Star> stars;
|
||||||
|
|
||||||
/** The maximum number of stars to draw at once. */
|
|
||||||
private static final int MAX_STARS = 20;
|
|
||||||
|
|
||||||
/** Random number generator instance. */
|
/** Random number generator instance. */
|
||||||
private final Random random;
|
private final Random random;
|
||||||
|
|
||||||
/** Contains data for a single star. */
|
/** Contains data for a single star. */
|
||||||
private class Star {
|
private class Star {
|
||||||
|
/** The star position offset. */
|
||||||
|
private final Vec2f offset;
|
||||||
|
|
||||||
|
/** The star direction vector. */
|
||||||
|
private final Vec2f dir;
|
||||||
|
|
||||||
|
/** The star image rotation angle. */
|
||||||
|
private final int angle;
|
||||||
|
|
||||||
|
/** The star image scale. */
|
||||||
|
private final float scale;
|
||||||
|
|
||||||
/** The star animation progress. */
|
/** The star animation progress. */
|
||||||
private final AnimatedValue animatedValue;
|
private final AnimatedValue animatedValue;
|
||||||
|
|
||||||
/** The star properties. */
|
|
||||||
private final int distance, yOffset, angle;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a star with the given properties.
|
* Creates a star with the given properties.
|
||||||
|
* @param offset the position offset vector
|
||||||
|
* @param direction the direction vector
|
||||||
|
* @param angle the image rotation angle
|
||||||
|
* @param scale the image scale
|
||||||
* @param duration the time, in milliseconds, to show the star
|
* @param duration the time, in milliseconds, to show the star
|
||||||
* @param distance the distance for the star to travel in {@code duration}
|
|
||||||
* @param yOffset the vertical offset from the center of the container
|
|
||||||
* @param angle the rotation angle
|
|
||||||
* @param eqn the animation equation to use
|
* @param eqn the animation equation to use
|
||||||
*/
|
*/
|
||||||
public Star(int duration, int distance, int yOffset, int angle, AnimationEquation eqn) {
|
public Star(Vec2f offset, Vec2f direction, int angle, float scale, int duration, AnimationEquation eqn) {
|
||||||
this.animatedValue = new AnimatedValue(duration, 0f, 1f, eqn);
|
this.offset = offset;
|
||||||
this.distance = distance;
|
this.dir = direction;
|
||||||
this.yOffset = yOffset;
|
|
||||||
this.angle = angle;
|
this.angle = angle;
|
||||||
|
this.scale = scale;
|
||||||
|
this.animatedValue = new AnimatedValue(duration, 0f, 1f, eqn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,8 +112,9 @@ public class StarStream {
|
||||||
float t = animatedValue.getValue();
|
float t = animatedValue.getValue();
|
||||||
starImg.setImageColor(1f, 1f, 1f, Math.min((1 - t) * 5f, 1f));
|
starImg.setImageColor(1f, 1f, 1f, Math.min((1 - t) * 5f, 1f));
|
||||||
starImg.drawEmbedded(
|
starImg.drawEmbedded(
|
||||||
containerWidth - (distance * t), ((containerHeight - starImg.getHeight()) / 2) + yOffset,
|
offset.x + t * dir.x, offset.y + t * dir.y,
|
||||||
starImg.getWidth(), starImg.getHeight(), angle);
|
starImg.getWidth() * scale, starImg.getHeight() * scale, angle
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -93,17 +127,60 @@ public class StarStream {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the star stream.
|
* Initializes the star stream.
|
||||||
* @param width the container width
|
* @param x the x position
|
||||||
* @param height the container height
|
* @param y the y position
|
||||||
|
* @param dirX the x-axis direction
|
||||||
|
* @param dirY the y-axis direction
|
||||||
|
* @param k the maximum number of stars to draw at once (excluding bursts)
|
||||||
*/
|
*/
|
||||||
public StarStream(int width, int height) {
|
public StarStream(float x, float y, float dirX, float dirY, int k) {
|
||||||
this.containerWidth = width;
|
this.position = new Vec2f(x, y);
|
||||||
this.containerHeight = height;
|
this.direction = new Vec2f(dirX, dirY);
|
||||||
|
this.maxStars = k;
|
||||||
this.starImg = GameImage.STAR2.getImage().copy();
|
this.starImg = GameImage.STAR2.getImage().copy();
|
||||||
this.stars = new ArrayList<Star>();
|
this.stars = new ArrayList<Star>(k);
|
||||||
this.random = new Random();
|
this.random = new Random();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the direction spread of this star stream.
|
||||||
|
* @param spread the spread of the stars' starting position
|
||||||
|
*/
|
||||||
|
public void setPositionSpread(float spread) { this.positionSpread = spread; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the direction of this star stream.
|
||||||
|
* @param dirX the new x-axis direction
|
||||||
|
* @param dirY the new y-axis direction
|
||||||
|
*/
|
||||||
|
public void setDirection(float dirX, float dirY) { direction.set(dirX, dirY); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the direction spread of this star stream.
|
||||||
|
* @param spread the spread of the stars' direction
|
||||||
|
*/
|
||||||
|
public void setDirectionSpread(float spread) { this.directionSpread = spread; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duration base and spread of this star stream.
|
||||||
|
* @param base the base (mean) duration for which stars are shown, in ms
|
||||||
|
* @param spread the spread of the stars' duration, in ms
|
||||||
|
*/
|
||||||
|
public void setDurationSpread(int base, int spread) {
|
||||||
|
this.durationBase = base;
|
||||||
|
this.durationSpread = spread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the scale base and spread of this star stream.
|
||||||
|
* @param base the base (mean) scale at which stars are drawn
|
||||||
|
* @param spread the spread of the stars' scale
|
||||||
|
*/
|
||||||
|
public void setScaleSpread(float base, float spread) {
|
||||||
|
this.scaleBase = base;
|
||||||
|
this.scaleSpread = spread;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the star stream.
|
* Draws the star stream.
|
||||||
*/
|
*/
|
||||||
|
@ -131,20 +208,36 @@ public class StarStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new stars
|
// create new stars
|
||||||
for (int i = stars.size(); i < MAX_STARS; i++) {
|
for (int i = stars.size(); i < maxStars; i++) {
|
||||||
if (Math.random() < ((i < 5) ? 0.25 : 0.66))
|
if (Math.random() < ((i < maxStars / 4) ? 0.25 : 0.66))
|
||||||
break;
|
break; // stagger spawning new stars
|
||||||
|
|
||||||
// generate star properties
|
stars.add(createStar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new star with randomized properties.
|
||||||
|
*/
|
||||||
|
private Star createStar() {
|
||||||
float distanceRatio = Utils.clamp((float) getGaussian(0.65, 0.25), 0.2f, 0.925f);
|
float distanceRatio = Utils.clamp((float) getGaussian(0.65, 0.25), 0.2f, 0.925f);
|
||||||
int distance = (int) (containerWidth * distanceRatio);
|
Vec2f offset = position.cpy().add(direction.cpy().nor().normalize().scale((float) getGaussian(0, positionSpread)));
|
||||||
int duration = (int) (distanceRatio * getGaussian(1300, 300));
|
Vec2f dir = direction.cpy().scale(distanceRatio).add((float) getGaussian(0, directionSpread), (float) getGaussian(0, directionSpread));
|
||||||
int yOffset = (int) getGaussian(0, containerHeight / 20);
|
|
||||||
int angle = (int) getGaussian(0, 22.5);
|
int angle = (int) getGaussian(0, 22.5);
|
||||||
|
float scale = (float) getGaussian(scaleBase, scaleSpread);
|
||||||
|
int duration = Math.max(0, (int) (distanceRatio * getGaussian(durationBase, durationSpread)));
|
||||||
AnimationEquation eqn = random.nextBoolean() ? AnimationEquation.IN_OUT_QUAD : AnimationEquation.OUT_QUAD;
|
AnimationEquation eqn = random.nextBoolean() ? AnimationEquation.IN_OUT_QUAD : AnimationEquation.OUT_QUAD;
|
||||||
|
|
||||||
stars.add(new Star(duration, distance, yOffset, angle, eqn));
|
return new Star(offset, dir, angle, scale, duration, eqn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a burst of stars instantly.
|
||||||
|
* @param count the number of stars to create
|
||||||
|
*/
|
||||||
|
public void burst(int count) {
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
stars.add(createStar());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -152,6 +245,11 @@ public class StarStream {
|
||||||
*/
|
*/
|
||||||
public void clear() { stars.clear(); }
|
public void clear() { stars.clear(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether there are any stars currently in this stream.
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() { return stars.isEmpty(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the next pseudorandom, Gaussian ("normally") distributed {@code double} value
|
* Returns the next pseudorandom, Gaussian ("normally") distributed {@code double} value
|
||||||
* with the given mean and standard deviation.
|
* with the given mean and standard deviation.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user