Save game scores to an SQLite database. [Incomplete!]
Implemented basic features (mostly stable). The remaining features are mostly graphical. - Added package org.xerial.sqlite-jdbc. All scores are saved to .opsu_scores.db on table `scores` after a game completes. - Added "Scores" class to handle all game score data (including database connections). The "Score" subclass encapsulates all database fields. - Added "score viewing" constructor to GameData, for use only in the ranking screen. - Draw the grade of the highest score next to expanded song buttons in the song menu. - Added "bit" and "abbrev" fields to GameMod, used in storing/displaying scores. - Hide the retry/exit buttons in the ranking screen when viewing a score. Other changes: - Removed "objectCount" field in GameData (no longer necessary). - Removed "getID()" method in GameMod (no longer used). - Moved most drawing in GameRanking state to GameData. - Removed File parameter of "GameData.loadImages()" (leftover, no longer used). Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
parent
f71b2c25f2
commit
0bd72e731a
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,6 +5,7 @@
|
|||
/Songs/
|
||||
/.opsu.log
|
||||
/.opsu.cfg
|
||||
/.opsu_scores.db
|
||||
|
||||
# Eclipse
|
||||
/.settings/
|
||||
|
|
5
pom.xml
5
pom.xml
|
@ -133,5 +133,10 @@
|
|||
<artifactId>jlayer</artifactId>
|
||||
<version>1.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.8.7</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -74,10 +74,8 @@ public class Container extends AppGameContainer {
|
|||
}
|
||||
}
|
||||
|
||||
if (forceExit) {
|
||||
Opsu.closeSocket();
|
||||
System.exit(0);
|
||||
}
|
||||
if (forceExit)
|
||||
Opsu.exit();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,12 +18,12 @@
|
|||
|
||||
package itdelatrisu.opsu;
|
||||
|
||||
import itdelatrisu.opsu.Scores.ScoreData;
|
||||
import itdelatrisu.opsu.audio.HitSound;
|
||||
import itdelatrisu.opsu.audio.MusicController;
|
||||
import itdelatrisu.opsu.audio.SoundController;
|
||||
import itdelatrisu.opsu.audio.SoundEffect;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
@ -41,7 +41,7 @@ public class GameData {
|
|||
public static final float HP_DRAIN_MULTIPLIER = 1 / 200f;
|
||||
|
||||
/** Letter grades. */
|
||||
private enum Grade {
|
||||
public enum Grade {
|
||||
NULL (null, null),
|
||||
SS (GameImage.RANKING_SS, GameImage.RANKING_SS_SMALL),
|
||||
SSH (GameImage.RANKING_SSH, GameImage.RANKING_SSH_SMALL), // silver
|
||||
|
@ -95,9 +95,6 @@ public class GameData {
|
|||
/** Counts of each hit result so far. */
|
||||
private int[] hitResultCount;
|
||||
|
||||
/** Total number of hit objects so far, not including Katu/Geki (for calculating grade). */
|
||||
private int objectCount;
|
||||
|
||||
/** Total objects including slider hits/ticks (for determining Full Combo status). */
|
||||
private int fullObjectCount;
|
||||
|
||||
|
@ -194,21 +191,57 @@ public class GameData {
|
|||
/** Scorebar animation. */
|
||||
private Animation scorebarColour;
|
||||
|
||||
/** The associated score data. */
|
||||
private ScoreData scoreData;
|
||||
|
||||
/** Whether this object is used for gameplay (true) or score viewing (false). */
|
||||
private boolean gameplay;
|
||||
|
||||
/** Container dimensions. */
|
||||
private int width, height;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Constructor for gameplay.
|
||||
* @param width container width
|
||||
* @param height container height
|
||||
*/
|
||||
public GameData(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.gameplay = true;
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for score viewing.
|
||||
* This will initialize all parameters and images needed for the
|
||||
* {@link #drawRankingElements(Graphics, int, int)} method.
|
||||
* @param s the ScoreData object
|
||||
* @param width container width
|
||||
* @param height container height
|
||||
*/
|
||||
public GameData(ScoreData s, int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.gameplay = false;
|
||||
|
||||
this.scoreData = s;
|
||||
this.score = s.score;
|
||||
this.comboMax = s.combo;
|
||||
this.fullObjectCount = (s.perfect) ? s.combo : -1;
|
||||
this.hitResultCount = new int[HIT_MAX];
|
||||
hitResultCount[HIT_300] = s.hit300;
|
||||
hitResultCount[HIT_100] = s.hit100;
|
||||
hitResultCount[HIT_50] = s.hit50;
|
||||
hitResultCount[HIT_300G] = s.geki;
|
||||
hitResultCount[HIT_300K] = 0;
|
||||
hitResultCount[HIT_100K] = s.katu;
|
||||
hitResultCount[HIT_MISS] = s.miss;
|
||||
|
||||
loadImages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all data and re-initializes object.
|
||||
*/
|
||||
|
@ -219,42 +252,44 @@ public class GameData {
|
|||
healthDisplay = 100f;
|
||||
hitResultCount = new int[HIT_MAX];
|
||||
hitResultList = new LinkedList<OsuHitObjectResult>();
|
||||
objectCount = 0;
|
||||
fullObjectCount = 0;
|
||||
combo = 0;
|
||||
comboMax = 0;
|
||||
comboEnd = 0;
|
||||
comboBurstIndex = -1;
|
||||
scoreData = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all game score images.
|
||||
* @param dir the image directory
|
||||
*/
|
||||
public void loadImages(File dir) {
|
||||
// combo burst images
|
||||
if (GameImage.COMBO_BURST.hasSkinImages() ||
|
||||
(!GameImage.COMBO_BURST.hasSkinImage() && GameImage.COMBO_BURST.getImages() != null))
|
||||
comboBurstImages = GameImage.COMBO_BURST.getImages();
|
||||
else
|
||||
comboBurstImages = new Image[]{ GameImage.COMBO_BURST.getImage() };
|
||||
|
||||
// scorebar-colour animation
|
||||
Image[] scorebar = GameImage.SCOREBAR_COLOUR.getImages();
|
||||
scorebarColour = (scorebar != null) ? new Animation(scorebar, 60) : null;
|
||||
|
||||
// default symbol images
|
||||
defaultSymbols = new Image[10];
|
||||
defaultSymbols[0] = GameImage.DEFAULT_0.getImage();
|
||||
defaultSymbols[1] = GameImage.DEFAULT_1.getImage();
|
||||
defaultSymbols[2] = GameImage.DEFAULT_2.getImage();
|
||||
defaultSymbols[3] = GameImage.DEFAULT_3.getImage();
|
||||
defaultSymbols[4] = GameImage.DEFAULT_4.getImage();
|
||||
defaultSymbols[5] = GameImage.DEFAULT_5.getImage();
|
||||
defaultSymbols[6] = GameImage.DEFAULT_6.getImage();
|
||||
defaultSymbols[7] = GameImage.DEFAULT_7.getImage();
|
||||
defaultSymbols[8] = GameImage.DEFAULT_8.getImage();
|
||||
defaultSymbols[9] = GameImage.DEFAULT_9.getImage();
|
||||
public void loadImages() {
|
||||
// gameplay-specific images
|
||||
if (isGameplay()) {
|
||||
// combo burst images
|
||||
if (GameImage.COMBO_BURST.hasSkinImages() ||
|
||||
(!GameImage.COMBO_BURST.hasSkinImage() && GameImage.COMBO_BURST.getImages() != null))
|
||||
comboBurstImages = GameImage.COMBO_BURST.getImages();
|
||||
else
|
||||
comboBurstImages = new Image[]{ GameImage.COMBO_BURST.getImage() };
|
||||
|
||||
// scorebar-colour animation
|
||||
Image[] scorebar = GameImage.SCOREBAR_COLOUR.getImages();
|
||||
scorebarColour = (scorebar != null) ? new Animation(scorebar, 60) : null;
|
||||
|
||||
// default symbol images
|
||||
defaultSymbols = new Image[10];
|
||||
defaultSymbols[0] = GameImage.DEFAULT_0.getImage();
|
||||
defaultSymbols[1] = GameImage.DEFAULT_1.getImage();
|
||||
defaultSymbols[2] = GameImage.DEFAULT_2.getImage();
|
||||
defaultSymbols[3] = GameImage.DEFAULT_3.getImage();
|
||||
defaultSymbols[4] = GameImage.DEFAULT_4.getImage();
|
||||
defaultSymbols[5] = GameImage.DEFAULT_5.getImage();
|
||||
defaultSymbols[6] = GameImage.DEFAULT_6.getImage();
|
||||
defaultSymbols[7] = GameImage.DEFAULT_7.getImage();
|
||||
defaultSymbols[8] = GameImage.DEFAULT_8.getImage();
|
||||
defaultSymbols[9] = GameImage.DEFAULT_9.getImage();
|
||||
}
|
||||
|
||||
// score symbol images
|
||||
scoreSymbols = new HashMap<Character, Image>(14);
|
||||
|
@ -471,12 +506,11 @@ public class GameData {
|
|||
}
|
||||
|
||||
/**
|
||||
* Draws ranking elements: score, results, ranking.
|
||||
* Draws ranking elements: score, results, ranking, game mods.
|
||||
* @param g the graphics context
|
||||
* @param width the width of the container
|
||||
* @param height the height of the container
|
||||
* @param osu the OsuFile
|
||||
*/
|
||||
public void drawRankingElements(Graphics g, int width, int height) {
|
||||
public void drawRankingElements(Graphics g, OsuFile osu) {
|
||||
// grade
|
||||
Grade grade = getGrade();
|
||||
if (grade != Grade.NULL) {
|
||||
|
@ -492,6 +526,14 @@ public class GameData {
|
|||
g.setColor(Utils.COLOR_BLACK_ALPHA);
|
||||
g.fillRect(0, 0, width, rankingHeight);
|
||||
rankingTitle.draw((width * 0.97f) - rankingTitle.getWidth(), 0);
|
||||
float marginX = width * 0.01f, marginY = height * 0.01f;
|
||||
Utils.FONT_LARGE.drawString(marginX, marginY,
|
||||
String.format("%s - %s [%s]", osu.getArtist(), osu.getTitle(), osu.version), Color.white);
|
||||
Utils.FONT_MEDIUM.drawString(marginX, marginY + Utils.FONT_LARGE.getLineHeight() - 6,
|
||||
String.format("Beatmap by %s", osu.creator), Color.white);
|
||||
Utils.FONT_MEDIUM.drawString(
|
||||
marginX, marginY + Utils.FONT_LARGE.getLineHeight() + Utils.FONT_MEDIUM.getLineHeight() - 10,
|
||||
String.format("Played on %s.", scoreData.getTimeString()), Color.white);
|
||||
|
||||
// ranking panel
|
||||
Image rankingPanel = GameImage.RANKING_PANEL.getImage();
|
||||
|
@ -546,12 +588,23 @@ public class GameData {
|
|||
(int) (rankingPanelWidth / 2f + numbersX), (int) numbersY, symbolTextScale, false);
|
||||
|
||||
// full combo
|
||||
if (combo == fullObjectCount) {
|
||||
if (comboMax == fullObjectCount) {
|
||||
GameImage.RANKING_PERFECT.getImage().draw(
|
||||
width * 0.08f,
|
||||
(height * 0.99f) - GameImage.RANKING_PERFECT.getImage().getHeight()
|
||||
);
|
||||
}
|
||||
|
||||
// mod icons
|
||||
int modWidth = GameMod.AUTO.getImage().getWidth();
|
||||
float modX = (width * 0.98f) - modWidth;
|
||||
int modCount = 0;
|
||||
for (GameMod mod : GameMod.VALUES_REVERSED) {
|
||||
if ((mod.getBit() & scoreData.mods) > 0) {
|
||||
mod.getImage().draw(modX - (modCount * (modWidth / 2f)), height / 2f);
|
||||
modCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -616,30 +669,50 @@ public class GameData {
|
|||
public void changeScore(int value) { score += value; }
|
||||
|
||||
/**
|
||||
* Returns score percentage (raw score only).
|
||||
* Returns the raw score percentage.
|
||||
* @param hit300 the number of 300s
|
||||
* @param hit100 the number of 100s
|
||||
* @param hit50 the number of 50s
|
||||
* @param miss the number of misses
|
||||
* @return the percentage
|
||||
*/
|
||||
public static float getScorePercent(int hit300, int hit100, int hit50, int miss) {
|
||||
float percent = 0;
|
||||
int objectCount = hit300 + hit100 + hit50 + miss;
|
||||
if (objectCount > 0)
|
||||
percent = (hit300 * 300 + hit100 * 100 + hit50 * 50) / (objectCount * 300f) * 100f;
|
||||
return percent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw score percentage.
|
||||
*/
|
||||
private float getScorePercent() {
|
||||
float percent = 0;
|
||||
if (objectCount > 0)
|
||||
percent = ((hitResultCount[HIT_50] * 50) + (hitResultCount[HIT_100] * 100)
|
||||
+ (hitResultCount[HIT_300] * 300)) / (objectCount * 300f) * 100f;
|
||||
return percent;
|
||||
return getScorePercent(
|
||||
hitResultCount[HIT_300], hitResultCount[HIT_100],
|
||||
hitResultCount[HIT_50], hitResultCount[HIT_MISS]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns letter grade based on score data,
|
||||
* or Grade.NULL if no objects have been processed.
|
||||
* @param hit300 the number of 300s
|
||||
* @param hit100 the number of 100s
|
||||
* @param hit50 the number of 50s
|
||||
* @param miss the number of misses
|
||||
* @return the current Grade
|
||||
*/
|
||||
private Grade getGrade() {
|
||||
public static Grade getGrade(int hit300, int hit100, int hit50, int miss) {
|
||||
int objectCount = hit300 + hit100 + hit50 + miss;
|
||||
if (objectCount < 1) // avoid division by zero
|
||||
return Grade.NULL;
|
||||
|
||||
// TODO: silvers
|
||||
float percent = getScorePercent();
|
||||
float hit300ratio = hitResultCount[HIT_300] * 100f / objectCount;
|
||||
float hit50ratio = hitResultCount[HIT_50] * 100f / objectCount;
|
||||
boolean noMiss = (hitResultCount[HIT_MISS] == 0);
|
||||
float percent = getScorePercent(hit300, hit100, hit50, miss);
|
||||
float hit300ratio = hit300 * 100f / objectCount;
|
||||
float hit50ratio = hit50 * 100f / objectCount;
|
||||
boolean noMiss = (miss == 0);
|
||||
if (percent >= 100f)
|
||||
return Grade.SS;
|
||||
else if (hit300ratio >= 90f && hit50ratio < 1.0f && noMiss)
|
||||
|
@ -654,6 +727,17 @@ public class GameData {
|
|||
return Grade.D;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns letter grade based on score data,
|
||||
* or Grade.NULL if no objects have been processed.
|
||||
*/
|
||||
private Grade getGrade() {
|
||||
return getGrade(
|
||||
hitResultCount[HIT_300], hitResultCount[HIT_100],
|
||||
hitResultCount[HIT_50], hitResultCount[HIT_MISS]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the score, health, and combo burst displays based on a delta value.
|
||||
* @param delta the delta interval since the last call
|
||||
|
@ -792,25 +876,21 @@ public class GameData {
|
|||
perfectHit = true;
|
||||
hitValue = 300;
|
||||
changeHealth(5f);
|
||||
objectCount++;
|
||||
break;
|
||||
case HIT_100:
|
||||
hitValue = 100;
|
||||
changeHealth(2f);
|
||||
comboEnd |= 1;
|
||||
objectCount++;
|
||||
break;
|
||||
case HIT_50:
|
||||
hitValue = 50;
|
||||
comboEnd |= 2;
|
||||
objectCount++;
|
||||
break;
|
||||
case HIT_MISS:
|
||||
hitValue = 0;
|
||||
changeHealth(-10f);
|
||||
comboEnd |= 2;
|
||||
resetComboStreak();
|
||||
objectCount++;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
|
@ -864,4 +944,47 @@ public class GameData {
|
|||
else
|
||||
hitResultList.add(new OsuHitObjectResult(time, result, x, y, color));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ScoreData object encapsulating all game data.
|
||||
* If score data already exists, the existing object will be returned
|
||||
* (i.e. this will not overwrite existing data).
|
||||
* @param osu the OsuFile
|
||||
* @return the ScoreData object
|
||||
*/
|
||||
public ScoreData getScoreData(OsuFile osu) {
|
||||
if (scoreData != null)
|
||||
return scoreData;
|
||||
|
||||
scoreData = new ScoreData();
|
||||
scoreData.timestamp = System.currentTimeMillis() / 1000L;
|
||||
scoreData.MID = osu.beatmapID;
|
||||
scoreData.MSID = osu.beatmapSetID;
|
||||
scoreData.title = osu.title;
|
||||
scoreData.artist = osu.artist;
|
||||
scoreData.creator = osu.creator;
|
||||
scoreData.version = osu.version;
|
||||
scoreData.hit300 = hitResultCount[HIT_300];
|
||||
scoreData.hit100 = hitResultCount[HIT_100];
|
||||
scoreData.hit50 = hitResultCount[HIT_50];
|
||||
scoreData.geki = hitResultCount[HIT_300G];
|
||||
scoreData.katu = hitResultCount[HIT_300K] + hitResultCount[HIT_100K];
|
||||
scoreData.miss = hitResultCount[HIT_MISS];
|
||||
scoreData.score = score;
|
||||
scoreData.combo = comboMax;
|
||||
scoreData.perfect = (comboMax == fullObjectCount);
|
||||
int mods = 0;
|
||||
for (GameMod mod : GameMod.values()) {
|
||||
if (mod.isActive())
|
||||
mods |= mod.getBit();
|
||||
}
|
||||
scoreData.mods = mods;
|
||||
return scoreData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this object is used for gameplay.
|
||||
* @return true if gameplay, false if score viewing
|
||||
*/
|
||||
public boolean isGameplay() { return gameplay; }
|
||||
}
|
|
@ -164,7 +164,7 @@ public enum GameImage {
|
|||
SPINNER_CLEAR ("spinner-clear", "png"),
|
||||
SPINNER_OSU ("spinner-osu", "png"),
|
||||
|
||||
// Game Score
|
||||
// Score Data
|
||||
COMBO_BURST ("comboburst", "comboburst-%d", "png"),
|
||||
SCOREBAR_BG ("scorebar-bg", "png") {
|
||||
@Override
|
||||
|
|
|
@ -28,16 +28,16 @@ import org.newdawn.slick.Input;
|
|||
* Game mods.
|
||||
*/
|
||||
public enum GameMod {
|
||||
EASY (0, GameImage.MOD_EASY, Input.KEY_Q, 0.5f),
|
||||
NO_FAIL (1, GameImage.MOD_NO_FAIL, Input.KEY_W, 0.5f),
|
||||
HARD_ROCK (2, GameImage.MOD_HARD_ROCK, Input.KEY_A, 1.06f),
|
||||
SUDDEN_DEATH (3, GameImage.MOD_SUDDEN_DEATH, Input.KEY_S),
|
||||
SPUN_OUT (4, GameImage.MOD_SPUN_OUT, Input.KEY_V, 0.9f),
|
||||
AUTO (5, GameImage.MOD_AUTO, Input.KEY_B);
|
||||
// HALF_TIME (, GameImage.MOD_HALF_TIME, Input.KEY_E, 0.3f),
|
||||
// DOUBLE_TIME (, GameImage.MOD_DOUBLE_TIME, Input.KEY_D, 1.12f),
|
||||
// HIDDEN (, GameImage.MOD_HIDDEN, Input.KEY_F, 1.06f),
|
||||
// FLASHLIGHT (, GameImage.MOD_FLASHLIGHT, Input.KEY_G, 1.12f);
|
||||
EASY (0, GameImage.MOD_EASY, "EZ", 2, Input.KEY_Q, 0.5f),
|
||||
NO_FAIL (1, GameImage.MOD_NO_FAIL, "NF", 1, Input.KEY_W, 0.5f),
|
||||
HARD_ROCK (2, GameImage.MOD_HARD_ROCK, "HR", 16, Input.KEY_A, 1.06f),
|
||||
SUDDEN_DEATH (3, GameImage.MOD_SUDDEN_DEATH, "SD", 32, Input.KEY_S),
|
||||
SPUN_OUT (4, GameImage.MOD_SPUN_OUT, "SO", 4096, Input.KEY_V, 0.9f),
|
||||
AUTO (5, GameImage.MOD_AUTO, "", 2048, Input.KEY_B);
|
||||
// HALF_TIME (, GameImage.MOD_HALF_TIME, "HT", 256, Input.KEY_E, 0.3f),
|
||||
// DOUBLE_TIME (, GameImage.MOD_DOUBLE_TIME, "DT", 64, Input.KEY_D, 1.12f),
|
||||
// HIDDEN (, GameImage.MOD_HIDDEN, "HD", 8, Input.KEY_F, 1.06f),
|
||||
// FLASHLIGHT (, GameImage.MOD_FLASHLIGHT, "FL", 1024, Input.KEY_G, 1.12f);
|
||||
|
||||
/** The ID of the mod (used for positioning). */
|
||||
private int id;
|
||||
|
@ -45,7 +45,16 @@ public enum GameMod {
|
|||
/** The file name of the mod image. */
|
||||
private GameImage image;
|
||||
|
||||
/** The shortcut key associated with the mod. */
|
||||
/** The abbreviation for the mod. */
|
||||
private String abbrev;
|
||||
|
||||
/** Bit value associated with the mod. */
|
||||
private int bit;
|
||||
|
||||
/**
|
||||
* The shortcut key associated with the mod.
|
||||
* See the osu! API: https://github.com/peppy/osu-api/wiki#mods
|
||||
*/
|
||||
private int key;
|
||||
|
||||
/** The score multiplier. */
|
||||
|
@ -71,11 +80,15 @@ public enum GameMod {
|
|||
* Constructor.
|
||||
* @param id the ID of the mod (for positioning).
|
||||
* @param image the GameImage
|
||||
* @param abbrev the two-letter abbreviation
|
||||
* @param bit the bit
|
||||
* @param key the shortcut key
|
||||
*/
|
||||
GameMod(int id, GameImage image, int key) {
|
||||
GameMod(int id, GameImage image, String abbrev, int bit, int key) {
|
||||
this.id = id;
|
||||
this.image = image;
|
||||
this.abbrev = abbrev;
|
||||
this.bit = bit;
|
||||
this.key = key;
|
||||
this.multiplier = 1f;
|
||||
}
|
||||
|
@ -84,12 +97,16 @@ public enum GameMod {
|
|||
* Constructor.
|
||||
* @param id the ID of the mod (for positioning).
|
||||
* @param image the GameImage
|
||||
* @param abbrev the two-letter abbreviation
|
||||
* @param bit the bit
|
||||
* @param key the shortcut key
|
||||
* @param multiplier the score multiplier
|
||||
*/
|
||||
GameMod(int id, GameImage image, int key, float multiplier) {
|
||||
GameMod(int id, GameImage image, String abbrev, int bit, int key, float multiplier) {
|
||||
this.id = id;
|
||||
this.image = image;
|
||||
this.abbrev = abbrev;
|
||||
this.bit = bit;
|
||||
this.key = key;
|
||||
this.multiplier = multiplier;
|
||||
}
|
||||
|
@ -113,6 +130,18 @@ public enum GameMod {
|
|||
this.button.setHoverScale(1.15f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the abbreviated name of the mod.
|
||||
* @return the two-letter abbreviation
|
||||
*/
|
||||
public String getAbbreviation() { return abbrev; }
|
||||
|
||||
/**
|
||||
* Returns the bit associated with the mod.
|
||||
* @return the bit
|
||||
*/
|
||||
public int getBit() { return bit; }
|
||||
|
||||
/**
|
||||
* Returns the shortcut key for the mod.
|
||||
* @return the key
|
||||
|
@ -173,12 +202,6 @@ public enum GameMod {
|
|||
*/
|
||||
public Image getImage() { return image.getImage(); }
|
||||
|
||||
/**
|
||||
* Returns the mod ID.
|
||||
* @return the mod ID
|
||||
*/
|
||||
public int getID() { return id; }
|
||||
|
||||
/**
|
||||
* Draws the game mod.
|
||||
*/
|
||||
|
|
|
@ -137,6 +137,9 @@ public class Opsu extends StateBasedGame {
|
|||
}
|
||||
Options.TMP_DIR.deleteOnExit();
|
||||
|
||||
// initialize score database
|
||||
Scores.init();
|
||||
|
||||
// start the game
|
||||
try {
|
||||
// loop until force exit
|
||||
|
@ -161,8 +164,7 @@ public class Opsu extends StateBasedGame {
|
|||
ErrorHandler.error("Error while creating game container.", e, true);
|
||||
}
|
||||
|
||||
// close server socket
|
||||
closeSocket();
|
||||
Opsu.exit();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -188,9 +190,13 @@ public class Opsu extends StateBasedGame {
|
|||
}
|
||||
|
||||
/**
|
||||
* Closes the server socket.
|
||||
* Closes all resources and exits the application.
|
||||
*/
|
||||
public static void closeSocket() {
|
||||
public static void exit() {
|
||||
// close scores database
|
||||
Scores.closeConnection();
|
||||
|
||||
// close server socket
|
||||
if (SERVER_SOCKET != null) {
|
||||
try {
|
||||
SERVER_SOCKET.close();
|
||||
|
@ -198,5 +204,7 @@ public class Opsu extends StateBasedGame {
|
|||
ErrorHandler.error("Failed to close server socket.", e, false);
|
||||
}
|
||||
}
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,9 @@ public class Options {
|
|||
"Songs/"
|
||||
};
|
||||
|
||||
/** Score database name. */
|
||||
public static final String SCORE_DB = ".opsu_scores.db";
|
||||
|
||||
/** Font file name. */
|
||||
public static final String FONT_NAME = "kochi-gothic.ttf";
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
package itdelatrisu.opsu;
|
||||
|
||||
import itdelatrisu.opsu.GameData.Grade;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -52,9 +54,10 @@ public class OsuGroupNode {
|
|||
* Draws the button.
|
||||
* @param x the x coordinate
|
||||
* @param y the y coordinate
|
||||
* @param grade the highest grade, if any
|
||||
* @param focus true if this is the focused node
|
||||
*/
|
||||
public void draw(float x, float y, boolean focus) {
|
||||
public void draw(float x, float y, Grade grade, boolean focus) {
|
||||
boolean expanded = (osuFileIndex > -1);
|
||||
float xOffset = 0f;
|
||||
OsuFile osu;
|
||||
|
@ -77,6 +80,13 @@ public class OsuGroupNode {
|
|||
float cx = x + (bg.getWidth() * 0.05f) - xOffset;
|
||||
float cy = y + (bg.getHeight() * 0.2f) - 3;
|
||||
|
||||
if (grade != Grade.NULL) {
|
||||
Image gradeImg = grade.getSmallImage();
|
||||
gradeImg = gradeImg.getScaledCopy((bg.getHeight() * 0.45f) / gradeImg.getHeight());
|
||||
gradeImg.drawCentered(cx - bg.getWidth() * 0.01f + gradeImg.getWidth() / 2f, y + bg.getHeight() / 2.2f);
|
||||
cx += gradeImg.getWidth();
|
||||
}
|
||||
|
||||
Utils.FONT_MEDIUM.drawString(cx, cy, osu.getTitle(), textColor);
|
||||
Utils.FONT_DEFAULT.drawString(cx, cy + Utils.FONT_MEDIUM.getLineHeight() - 4,
|
||||
String.format("%s // %s", osu.getArtist(), osu.creator), textColor);
|
||||
|
|
338
src/itdelatrisu/opsu/Scores.java
Normal file
338
src/itdelatrisu/opsu/Scores.java
Normal file
|
@ -0,0 +1,338 @@
|
|||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
*
|
||||
* opsu! is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* opsu! is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package itdelatrisu.opsu;
|
||||
|
||||
import itdelatrisu.opsu.GameData.Grade;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Handles game score data.
|
||||
*/
|
||||
public class Scores {
|
||||
/** Class encapsulating all score data. */
|
||||
public static class ScoreData implements Comparable<ScoreData> {
|
||||
/** The time when the score was achieved (Unix time). */
|
||||
public long timestamp;
|
||||
|
||||
/** The beatmap and beatmap set IDs. */
|
||||
public int MID, MSID;
|
||||
|
||||
/** Beatmap metadata. */
|
||||
public String title, artist, creator, version;
|
||||
|
||||
/** Hit counts. */
|
||||
public int hit300, hit100, hit50, geki, katu, miss;
|
||||
|
||||
/** The score. */
|
||||
public long score;
|
||||
|
||||
/** The max combo. */
|
||||
public int combo;
|
||||
|
||||
/** Whether or not a full combo was achieved. */
|
||||
public boolean perfect;
|
||||
|
||||
/** Game mod bitmask. */
|
||||
public int mods;
|
||||
|
||||
/**
|
||||
* Empty constructor.
|
||||
*/
|
||||
public ScoreData() {}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param rs the ResultSet to read from (at the current cursor position)
|
||||
* @throws SQLException
|
||||
*/
|
||||
public ScoreData(ResultSet rs) throws SQLException {
|
||||
this.timestamp = rs.getLong(1);
|
||||
this.MID = rs.getInt(2);
|
||||
this.MSID = rs.getInt(3);
|
||||
this.title = rs.getString(4);
|
||||
this.artist = rs.getString(5);
|
||||
this.creator = rs.getString(6);
|
||||
this.version = rs.getString(7);
|
||||
this.hit300 = rs.getInt(8);
|
||||
this.hit100 = rs.getInt(9);
|
||||
this.hit50 = rs.getInt(10);
|
||||
this.geki = rs.getInt(11);
|
||||
this.katu = rs.getInt(12);
|
||||
this.miss = rs.getInt(13);
|
||||
this.score = rs.getLong(14);
|
||||
this.combo = rs.getInt(15);
|
||||
this.perfect = rs.getBoolean(16);
|
||||
this.mods = rs.getInt(17);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp as a string.
|
||||
*/
|
||||
public String getTimeString() {
|
||||
return new SimpleDateFormat("M/d/yyyy h:mm:ss a").format(new Date(timestamp * 1000L));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns letter grade based on score data,
|
||||
* or Grade.NULL if no objects have been processed.
|
||||
* @see GameData#getGrade(int, int, int, int)
|
||||
*/
|
||||
public Grade getGrade() {
|
||||
return GameData.getGrade(hit300, hit100, hit50, miss);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"%s | ID: (%d, %d) | %s - %s [%s] (by %s) | " +
|
||||
"Hits: (%d, %d, %d, %d, %d, %d) | Score: %d (%d combo%s) | Mods: %d",
|
||||
getTimeString(), MID, MSID, artist, title, version, creator,
|
||||
hit300, hit100, hit50, geki, katu, miss, score, combo, perfect ? ", FC" : "", mods
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ScoreData that) {
|
||||
if (this.score != that.score)
|
||||
return Long.compare(this.score, that.score);
|
||||
else
|
||||
return Long.compare(this.timestamp, that.timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
/** Database connection. */
|
||||
private static Connection connection;
|
||||
|
||||
/** Score insertion statement. */
|
||||
private static PreparedStatement insertStmt;
|
||||
|
||||
/** Score select statement. */
|
||||
private static PreparedStatement selectMapStmt, selectMapSetStmt;
|
||||
|
||||
// This class should not be instantiated.
|
||||
private Scores() {}
|
||||
|
||||
/**
|
||||
* Initializes the score database connection.
|
||||
*/
|
||||
public static void init() {
|
||||
// load the sqlite-JDBC driver using the current class loader
|
||||
try {
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
} catch (ClassNotFoundException e) {
|
||||
ErrorHandler.error("Could not load sqlite-JDBC driver.", e, true);
|
||||
}
|
||||
|
||||
// create a database connection
|
||||
try {
|
||||
connection = DriverManager.getConnection(String.format("jdbc:sqlite:%s", Options.SCORE_DB));
|
||||
} catch (SQLException e) {
|
||||
// if the error message is "out of memory", it probably means no database file is found
|
||||
ErrorHandler.error("Could not connect to score database.", e, true);
|
||||
}
|
||||
|
||||
// create the database
|
||||
createDatabase();
|
||||
|
||||
// prepare sql statements
|
||||
try {
|
||||
insertStmt = connection.prepareStatement(
|
||||
"INSERT INTO scores VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
selectMapStmt = connection.prepareStatement(
|
||||
"SELECT * FROM scores WHERE " +
|
||||
"MID = ? AND title = ? AND artist = ? AND creator = ? AND version = ?"
|
||||
);
|
||||
selectMapSetStmt = connection.prepareStatement(
|
||||
"SELECT * FROM scores WHERE " +
|
||||
"MSID = ? AND title = ? AND artist = ? AND creator = ? ORDER BY version DESC"
|
||||
);
|
||||
} catch (SQLException e) {
|
||||
ErrorHandler.error("Failed to prepare score insertion statement.", e, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the score database, if it does not exist.
|
||||
*/
|
||||
private static void createDatabase() {
|
||||
try (Statement stmt = connection.createStatement()) {
|
||||
String sql =
|
||||
"CREATE TABLE IF NOT EXISTS scores (" +
|
||||
"timestamp INTEGER PRIMARY KEY, " +
|
||||
"MID INTEGER, MSID INTEGER, " +
|
||||
"title TEXT, artist TEXT, creator TEXT, version TEXT, " +
|
||||
"hit300 INTEGER, hit100 INTEGER, hit50 INTEGER, " +
|
||||
"geki INTEGER, katu INTEGER, miss INTEGER, " +
|
||||
"score INTEGER, " +
|
||||
"combo INTEGER, " +
|
||||
"perfect BOOLEAN, " +
|
||||
"mods INTEGER" +
|
||||
")";
|
||||
stmt.executeUpdate(sql);
|
||||
} catch (SQLException e) {
|
||||
ErrorHandler.error("Could not create score database.", e, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the game score to the database.
|
||||
* @param data the GameData object
|
||||
*/
|
||||
public static void addScore(ScoreData score) {
|
||||
try {
|
||||
insertStmt.setLong(1, score.timestamp);
|
||||
insertStmt.setInt(2, score.MID);
|
||||
insertStmt.setInt(3, score.MSID);
|
||||
insertStmt.setString(4, score.title);
|
||||
insertStmt.setString(5, score.artist);
|
||||
insertStmt.setString(6, score.creator);
|
||||
insertStmt.setString(7, score.version);
|
||||
insertStmt.setInt(8, score.hit300);
|
||||
insertStmt.setInt(9, score.hit100);
|
||||
insertStmt.setInt(10, score.hit50);
|
||||
insertStmt.setInt(11, score.geki);
|
||||
insertStmt.setInt(12, score.katu);
|
||||
insertStmt.setInt(13, score.miss);
|
||||
insertStmt.setLong(14, score.score);
|
||||
insertStmt.setInt(15, score.combo);
|
||||
insertStmt.setBoolean(16, score.perfect);
|
||||
insertStmt.setInt(17, score.mods);
|
||||
insertStmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
ErrorHandler.error("Failed to save score to database.", e, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the game scores for an OsuFile map.
|
||||
* @param osu the OsuFile
|
||||
* @return all scores for the beatmap
|
||||
*/
|
||||
public static ScoreData[] getMapScores(OsuFile osu) {
|
||||
List<ScoreData> list = new ArrayList<ScoreData>();
|
||||
try {
|
||||
selectMapStmt.setInt(1, osu.beatmapID);
|
||||
selectMapStmt.setString(2, osu.title);
|
||||
selectMapStmt.setString(3, osu.artist);
|
||||
selectMapStmt.setString(4, osu.creator);
|
||||
selectMapStmt.setString(5, osu.version);
|
||||
ResultSet rs = selectMapStmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
ScoreData s = new ScoreData(rs);
|
||||
list.add(s);
|
||||
}
|
||||
rs.close();
|
||||
} catch (SQLException e) {
|
||||
ErrorHandler.error("Failed to read scores from database.", e, true);
|
||||
return null;
|
||||
}
|
||||
return getSortedArray(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the game scores for an OsuFile map set.
|
||||
* @param osu the OsuFile
|
||||
* @return all scores for the beatmap set (Version, ScoreData[])
|
||||
*/
|
||||
public static Map<String, ScoreData[]> getMapSetScores(OsuFile osu) {
|
||||
Map<String, ScoreData[]> map = new HashMap<String, ScoreData[]>();
|
||||
try {
|
||||
selectMapSetStmt.setInt(1, osu.beatmapSetID);
|
||||
selectMapSetStmt.setString(2, osu.title);
|
||||
selectMapSetStmt.setString(3, osu.artist);
|
||||
selectMapSetStmt.setString(4, osu.creator);
|
||||
ResultSet rs = selectMapSetStmt.executeQuery();
|
||||
|
||||
List<ScoreData> list = null;
|
||||
String version = ""; // sorted by version, so pass through and check for differences
|
||||
while (rs.next()) {
|
||||
ScoreData s = new ScoreData(rs);
|
||||
if (!s.version.equals(version)) {
|
||||
if (list != null)
|
||||
map.put(version, getSortedArray(list));
|
||||
version = s.version;
|
||||
list = new ArrayList<ScoreData>();
|
||||
}
|
||||
list.add(s);
|
||||
}
|
||||
if (list != null)
|
||||
map.put(version, getSortedArray(list));
|
||||
rs.close();
|
||||
} catch (SQLException e) {
|
||||
ErrorHandler.error("Failed to read scores from database.", e, true);
|
||||
return null;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sorted ScoreData array (in reverse order) from a List.
|
||||
*/
|
||||
private static ScoreData[] getSortedArray(List<ScoreData> list) {
|
||||
ScoreData[] scores = list.toArray(new ScoreData[list.size()]);
|
||||
Arrays.sort(scores, Collections.reverseOrder());
|
||||
return scores;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection to the score database.
|
||||
*/
|
||||
public static void closeConnection() {
|
||||
if (connection != null) {
|
||||
try {
|
||||
insertStmt.close();
|
||||
selectMapStmt.close();
|
||||
selectMapSetStmt.close();
|
||||
connection.close();
|
||||
} catch (SQLException e) {
|
||||
ErrorHandler.error("Failed to close score database.", e, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the entire database (for debugging purposes).
|
||||
*/
|
||||
protected static void printDatabase() {
|
||||
try (
|
||||
Statement stmt = connection.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("SELECT * FROM scores ORDER BY timestamp ASC");
|
||||
) {
|
||||
while (rs.next())
|
||||
System.out.println(new ScoreData(rs));
|
||||
} catch (SQLException e) {
|
||||
ErrorHandler.error(null, e, false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import itdelatrisu.opsu.Options;
|
|||
import itdelatrisu.opsu.OsuFile;
|
||||
import itdelatrisu.opsu.OsuHitObject;
|
||||
import itdelatrisu.opsu.OsuTimingPoint;
|
||||
import itdelatrisu.opsu.Scores;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.audio.HitSound;
|
||||
import itdelatrisu.opsu.audio.MusicController;
|
||||
|
@ -160,7 +161,6 @@ public class Game extends BasicGameState {
|
|||
|
||||
// create the associated GameData object
|
||||
data = new GameData(width, height);
|
||||
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -419,8 +419,12 @@ public class Game extends BasicGameState {
|
|||
if (objectIndex >= osu.objects.length) {
|
||||
if (checkpointLoaded) // if checkpoint used, skip ranking screen
|
||||
game.closeRequested();
|
||||
else // go to ranking screen
|
||||
else { // go to ranking screen
|
||||
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
|
||||
if (!GameMod.AUTO.isActive())
|
||||
Scores.addScore(data.getScoreData(osu));
|
||||
game.enterState(Opsu.STATE_GAMERANKING, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -800,7 +804,7 @@ public class Game extends BasicGameState {
|
|||
|
||||
// load other images...
|
||||
((GamePauseMenu) game.getState(Opsu.STATE_GAMEPAUSEMENU)).loadImages();
|
||||
data.loadImages(osu.getFile().getParentFile());
|
||||
data.loadImages();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,9 +18,8 @@
|
|||
|
||||
package itdelatrisu.opsu.states;
|
||||
|
||||
import itdelatrisu.opsu.GameImage;
|
||||
import itdelatrisu.opsu.GameMod;
|
||||
import itdelatrisu.opsu.GameData;
|
||||
import itdelatrisu.opsu.GameImage;
|
||||
import itdelatrisu.opsu.MenuButton;
|
||||
import itdelatrisu.opsu.Opsu;
|
||||
import itdelatrisu.opsu.OsuFile;
|
||||
|
@ -100,29 +99,13 @@ public class GameRanking extends BasicGameState {
|
|||
g.setBackground(Utils.COLOR_BLACK_ALPHA);
|
||||
|
||||
// ranking screen elements
|
||||
data.drawRankingElements(g, width, height);
|
||||
|
||||
// game mods
|
||||
for (GameMod mod : GameMod.VALUES_REVERSED) {
|
||||
if (mod.isActive()) {
|
||||
Image modImage = mod.getImage();
|
||||
modImage.draw(
|
||||
(width * 0.75f) + ((mod.getID() - (GameMod.SIZE / 2)) * modImage.getWidth() / 3f),
|
||||
height / 2f
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// header text
|
||||
float marginX = width * 0.01f, marginY = height * 0.01f;
|
||||
Utils.FONT_LARGE.drawString(marginX, marginY,
|
||||
String.format("%s - %s [%s]", osu.getArtist(), osu.getTitle(), osu.version), Color.white);
|
||||
Utils.FONT_MEDIUM.drawString(marginX, marginY + Utils.FONT_LARGE.getLineHeight() - 6,
|
||||
String.format("Beatmap by %s", osu.creator), Color.white);
|
||||
data.drawRankingElements(g, osu);
|
||||
|
||||
// buttons
|
||||
retryButton.draw();
|
||||
exitButton.draw();
|
||||
if (data.isGameplay()) {
|
||||
retryButton.draw();
|
||||
exitButton.draw();
|
||||
}
|
||||
Utils.getBackButton().draw();
|
||||
|
||||
Utils.drawVolume(g);
|
||||
|
@ -146,12 +129,7 @@ public class GameRanking extends BasicGameState {
|
|||
public void keyPressed(int key, char c) {
|
||||
switch (key) {
|
||||
case Input.KEY_ESCAPE:
|
||||
SoundController.playSound(SoundEffect.MENUBACK);
|
||||
SongMenu songMenu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
|
||||
songMenu.resetGameDataOnLoad();
|
||||
songMenu.resetTrackOnLoad();
|
||||
Utils.resetCursor();
|
||||
game.enterState(Opsu.STATE_SONGMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
||||
returnToSongMenu();
|
||||
break;
|
||||
case Input.KEY_F12:
|
||||
Utils.takeScreenShot();
|
||||
|
@ -165,25 +143,26 @@ public class GameRanking extends BasicGameState {
|
|||
if (button != Input.MOUSE_LEFT_BUTTON)
|
||||
return;
|
||||
|
||||
if (retryButton.contains(x, y)) {
|
||||
OsuFile osu = MusicController.getOsuFile();
|
||||
Display.setTitle(String.format("%s - %s", game.getTitle(), osu.toString()));
|
||||
((Game) game.getState(Opsu.STATE_GAME)).setRestart(Game.Restart.MANUAL);
|
||||
SoundController.playSound(SoundEffect.MENUHIT);
|
||||
game.enterState(Opsu.STATE_GAME, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
||||
} else if (exitButton.contains(x, y)) {
|
||||
SoundController.playSound(SoundEffect.MENUBACK);
|
||||
((MainMenu) game.getState(Opsu.STATE_MAINMENU)).reset();
|
||||
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).resetGameDataOnLoad();
|
||||
Utils.resetCursor();
|
||||
game.enterState(Opsu.STATE_MAINMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
||||
} else if (Utils.getBackButton().contains(x, y)) {
|
||||
SoundController.playSound(SoundEffect.MENUBACK);
|
||||
SongMenu songMenu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
|
||||
songMenu.resetGameDataOnLoad();
|
||||
songMenu.resetTrackOnLoad();
|
||||
Utils.resetCursor();
|
||||
game.enterState(Opsu.STATE_SONGMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
||||
if (data.isGameplay()) {
|
||||
if (retryButton.contains(x, y)) {
|
||||
OsuFile osu = MusicController.getOsuFile();
|
||||
Display.setTitle(String.format("%s - %s", game.getTitle(), osu.toString()));
|
||||
((Game) game.getState(Opsu.STATE_GAME)).setRestart(Game.Restart.MANUAL);
|
||||
SoundController.playSound(SoundEffect.MENUHIT);
|
||||
game.enterState(Opsu.STATE_GAME, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
||||
return;
|
||||
} else if (exitButton.contains(x, y)) {
|
||||
SoundController.playSound(SoundEffect.MENUBACK);
|
||||
((MainMenu) game.getState(Opsu.STATE_MAINMENU)).reset();
|
||||
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).resetGameDataOnLoad();
|
||||
Utils.resetCursor();
|
||||
game.enterState(Opsu.STATE_MAINMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (Utils.getBackButton().contains(x, y)) {
|
||||
returnToSongMenu();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -195,6 +174,26 @@ public class GameRanking extends BasicGameState {
|
|||
SoundController.playSound(SoundEffect.APPLAUSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leave(GameContainer container, StateBasedGame game)
|
||||
throws SlickException {
|
||||
this.data = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns to the song menu.
|
||||
*/
|
||||
private void returnToSongMenu() {
|
||||
SoundController.playSound(SoundEffect.MENUBACK);
|
||||
if (data.isGameplay()) {
|
||||
SongMenu songMenu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
|
||||
songMenu.resetGameDataOnLoad();
|
||||
songMenu.resetTrackOnLoad();
|
||||
}
|
||||
Utils.resetCursor();
|
||||
game.enterState(Opsu.STATE_SONGMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the associated GameData object.
|
||||
* @param data the GameData
|
||||
|
|
|
@ -28,6 +28,9 @@ import itdelatrisu.opsu.OsuGroupList;
|
|||
import itdelatrisu.opsu.OsuGroupNode;
|
||||
import itdelatrisu.opsu.OsuParser;
|
||||
import itdelatrisu.opsu.OszUnpacker;
|
||||
import itdelatrisu.opsu.Scores;
|
||||
import itdelatrisu.opsu.GameData.Grade;
|
||||
import itdelatrisu.opsu.Scores.ScoreData;
|
||||
import itdelatrisu.opsu.SongSort;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.audio.HitSound;
|
||||
|
@ -36,6 +39,7 @@ import itdelatrisu.opsu.audio.SoundController;
|
|||
import itdelatrisu.opsu.audio.SoundEffect;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.lwjgl.opengl.Display;
|
||||
|
@ -153,6 +157,9 @@ public class SongMenu extends BasicGameState {
|
|||
/** Beatmap reloading thread. */
|
||||
private Thread reloadThread;
|
||||
|
||||
/** Current map of scores (Version, ScoreData[]). */
|
||||
private Map<String, ScoreData[]> scoreMap;
|
||||
|
||||
// game-related variables
|
||||
private GameContainer container;
|
||||
private StateBasedGame game;
|
||||
|
@ -261,8 +268,16 @@ public class SongMenu extends BasicGameState {
|
|||
// song buttons
|
||||
OsuGroupNode node = startNode;
|
||||
for (int i = 0; i < MAX_BUTTONS && node != null; i++, node = node.next) {
|
||||
// draw the node
|
||||
float offset = (i == hoverIndex) ? hoverOffset : 0f;
|
||||
node.draw(buttonX - offset, buttonY + (i*buttonOffset), (node == focusNode));
|
||||
ScoreData[] scores = getScoreDataForNode(node);
|
||||
node.draw(
|
||||
buttonX - offset, buttonY + (i*buttonOffset),
|
||||
(scores == null) ? Grade.NULL : scores[0].getGrade(),
|
||||
(node == focusNode)
|
||||
);
|
||||
|
||||
// load glyphs
|
||||
Utils.loadGlyphs(node.osuFiles.get(0));
|
||||
}
|
||||
|
||||
|
@ -703,11 +718,17 @@ public class SongMenu extends BasicGameState {
|
|||
// reset game data
|
||||
if (resetGame) {
|
||||
((Game) game.getState(Opsu.STATE_GAME)).resetGameData();
|
||||
|
||||
// destroy skin images, if any
|
||||
for (GameImage img : GameImage.values()) {
|
||||
if (img.isSkinnable())
|
||||
img.destroySkinImage();
|
||||
}
|
||||
|
||||
// reload scores
|
||||
if (focusNode != null)
|
||||
scoreMap = Scores.getMapSetScores(focusNode.osuFiles.get(focusNode.osuFileIndex));
|
||||
|
||||
resetGame = false;
|
||||
}
|
||||
}
|
||||
|
@ -798,6 +819,9 @@ public class SongMenu extends BasicGameState {
|
|||
MusicController.play(osu, true);
|
||||
Utils.loadGlyphs(osu);
|
||||
|
||||
// load scores
|
||||
scoreMap = Scores.getMapSetScores(osu);
|
||||
|
||||
// check startNode bounds
|
||||
while (startNode.index >= OsuGroupList.get().size() + length - MAX_BUTTONS && startNode.prev != null)
|
||||
startNode = startNode.prev;
|
||||
|
@ -835,6 +859,30 @@ public class SongMenu extends BasicGameState {
|
|||
*/
|
||||
public void resetTrackOnLoad() { resetTrack = true; }
|
||||
|
||||
/**
|
||||
* Returns all the score data for an OsuGroupNode from scoreMap.
|
||||
* If no score data is available for the node, return null.
|
||||
* @param node the OsuGroupNode
|
||||
* @return the ScoreData array
|
||||
*/
|
||||
private ScoreData[] getScoreDataForNode(OsuGroupNode node) {
|
||||
if (scoreMap == null || node.osuFileIndex == -1) // node not expanded
|
||||
return null;
|
||||
|
||||
OsuFile osu = node.osuFiles.get(node.osuFileIndex);
|
||||
ScoreData[] scores = scoreMap.get(osu.version);
|
||||
if (scores == null || scores.length < 1) // no scores
|
||||
return null;
|
||||
|
||||
ScoreData s = scores[0];
|
||||
if (osu.beatmapID == s.MID && osu.beatmapSetID == s.MSID &&
|
||||
osu.title.equals(s.title) && osu.artist.equals(s.artist) &&
|
||||
osu.creator.equals(s.creator))
|
||||
return scores;
|
||||
else
|
||||
return null; // incorrect map
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the game.
|
||||
* @param osu the OsuFile to send to the game
|
||||
|
|
Loading…
Reference in New Issue
Block a user