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/
|
/Songs/
|
||||||
/.opsu.log
|
/.opsu.log
|
||||||
/.opsu.cfg
|
/.opsu.cfg
|
||||||
|
/.opsu_scores.db
|
||||||
|
|
||||||
# Eclipse
|
# Eclipse
|
||||||
/.settings/
|
/.settings/
|
||||||
|
|
5
pom.xml
5
pom.xml
|
@ -133,5 +133,10 @@
|
||||||
<artifactId>jlayer</artifactId>
|
<artifactId>jlayer</artifactId>
|
||||||
<version>1.0.1</version>
|
<version>1.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xerial</groupId>
|
||||||
|
<artifactId>sqlite-jdbc</artifactId>
|
||||||
|
<version>3.8.7</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
|
@ -74,10 +74,8 @@ public class Container extends AppGameContainer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceExit) {
|
if (forceExit)
|
||||||
Opsu.closeSocket();
|
Opsu.exit();
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,12 +18,12 @@
|
||||||
|
|
||||||
package itdelatrisu.opsu;
|
package itdelatrisu.opsu;
|
||||||
|
|
||||||
|
import itdelatrisu.opsu.Scores.ScoreData;
|
||||||
import itdelatrisu.opsu.audio.HitSound;
|
import itdelatrisu.opsu.audio.HitSound;
|
||||||
import itdelatrisu.opsu.audio.MusicController;
|
import itdelatrisu.opsu.audio.MusicController;
|
||||||
import itdelatrisu.opsu.audio.SoundController;
|
import itdelatrisu.opsu.audio.SoundController;
|
||||||
import itdelatrisu.opsu.audio.SoundEffect;
|
import itdelatrisu.opsu.audio.SoundEffect;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -41,7 +41,7 @@ public class GameData {
|
||||||
public static final float HP_DRAIN_MULTIPLIER = 1 / 200f;
|
public static final float HP_DRAIN_MULTIPLIER = 1 / 200f;
|
||||||
|
|
||||||
/** Letter grades. */
|
/** Letter grades. */
|
||||||
private enum Grade {
|
public enum Grade {
|
||||||
NULL (null, null),
|
NULL (null, null),
|
||||||
SS (GameImage.RANKING_SS, GameImage.RANKING_SS_SMALL),
|
SS (GameImage.RANKING_SS, GameImage.RANKING_SS_SMALL),
|
||||||
SSH (GameImage.RANKING_SSH, GameImage.RANKING_SSH_SMALL), // silver
|
SSH (GameImage.RANKING_SSH, GameImage.RANKING_SSH_SMALL), // silver
|
||||||
|
@ -95,9 +95,6 @@ public class GameData {
|
||||||
/** Counts of each hit result so far. */
|
/** Counts of each hit result so far. */
|
||||||
private int[] hitResultCount;
|
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). */
|
/** Total objects including slider hits/ticks (for determining Full Combo status). */
|
||||||
private int fullObjectCount;
|
private int fullObjectCount;
|
||||||
|
|
||||||
|
@ -194,21 +191,57 @@ public class GameData {
|
||||||
/** Scorebar animation. */
|
/** Scorebar animation. */
|
||||||
private Animation scorebarColour;
|
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. */
|
/** Container dimensions. */
|
||||||
private int width, height;
|
private int width, height;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor for gameplay.
|
||||||
* @param width container width
|
* @param width container width
|
||||||
* @param height container height
|
* @param height container height
|
||||||
*/
|
*/
|
||||||
public GameData(int width, int height) {
|
public GameData(int width, int height) {
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
|
this.gameplay = true;
|
||||||
|
|
||||||
clear();
|
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.
|
* Clears all data and re-initializes object.
|
||||||
*/
|
*/
|
||||||
|
@ -219,42 +252,44 @@ public class GameData {
|
||||||
healthDisplay = 100f;
|
healthDisplay = 100f;
|
||||||
hitResultCount = new int[HIT_MAX];
|
hitResultCount = new int[HIT_MAX];
|
||||||
hitResultList = new LinkedList<OsuHitObjectResult>();
|
hitResultList = new LinkedList<OsuHitObjectResult>();
|
||||||
objectCount = 0;
|
|
||||||
fullObjectCount = 0;
|
fullObjectCount = 0;
|
||||||
combo = 0;
|
combo = 0;
|
||||||
comboMax = 0;
|
comboMax = 0;
|
||||||
comboEnd = 0;
|
comboEnd = 0;
|
||||||
comboBurstIndex = -1;
|
comboBurstIndex = -1;
|
||||||
|
scoreData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads all game score images.
|
* Loads all game score images.
|
||||||
* @param dir the image directory
|
|
||||||
*/
|
*/
|
||||||
public void loadImages(File dir) {
|
public void loadImages() {
|
||||||
// combo burst images
|
// gameplay-specific images
|
||||||
if (GameImage.COMBO_BURST.hasSkinImages() ||
|
if (isGameplay()) {
|
||||||
(!GameImage.COMBO_BURST.hasSkinImage() && GameImage.COMBO_BURST.getImages() != null))
|
// combo burst images
|
||||||
comboBurstImages = GameImage.COMBO_BURST.getImages();
|
if (GameImage.COMBO_BURST.hasSkinImages() ||
|
||||||
else
|
(!GameImage.COMBO_BURST.hasSkinImage() && GameImage.COMBO_BURST.getImages() != null))
|
||||||
comboBurstImages = new Image[]{ GameImage.COMBO_BURST.getImage() };
|
comboBurstImages = GameImage.COMBO_BURST.getImages();
|
||||||
|
else
|
||||||
|
comboBurstImages = new Image[]{ GameImage.COMBO_BURST.getImage() };
|
||||||
|
|
||||||
// scorebar-colour animation
|
// scorebar-colour animation
|
||||||
Image[] scorebar = GameImage.SCOREBAR_COLOUR.getImages();
|
Image[] scorebar = GameImage.SCOREBAR_COLOUR.getImages();
|
||||||
scorebarColour = (scorebar != null) ? new Animation(scorebar, 60) : null;
|
scorebarColour = (scorebar != null) ? new Animation(scorebar, 60) : null;
|
||||||
|
|
||||||
// default symbol images
|
// default symbol images
|
||||||
defaultSymbols = new Image[10];
|
defaultSymbols = new Image[10];
|
||||||
defaultSymbols[0] = GameImage.DEFAULT_0.getImage();
|
defaultSymbols[0] = GameImage.DEFAULT_0.getImage();
|
||||||
defaultSymbols[1] = GameImage.DEFAULT_1.getImage();
|
defaultSymbols[1] = GameImage.DEFAULT_1.getImage();
|
||||||
defaultSymbols[2] = GameImage.DEFAULT_2.getImage();
|
defaultSymbols[2] = GameImage.DEFAULT_2.getImage();
|
||||||
defaultSymbols[3] = GameImage.DEFAULT_3.getImage();
|
defaultSymbols[3] = GameImage.DEFAULT_3.getImage();
|
||||||
defaultSymbols[4] = GameImage.DEFAULT_4.getImage();
|
defaultSymbols[4] = GameImage.DEFAULT_4.getImage();
|
||||||
defaultSymbols[5] = GameImage.DEFAULT_5.getImage();
|
defaultSymbols[5] = GameImage.DEFAULT_5.getImage();
|
||||||
defaultSymbols[6] = GameImage.DEFAULT_6.getImage();
|
defaultSymbols[6] = GameImage.DEFAULT_6.getImage();
|
||||||
defaultSymbols[7] = GameImage.DEFAULT_7.getImage();
|
defaultSymbols[7] = GameImage.DEFAULT_7.getImage();
|
||||||
defaultSymbols[8] = GameImage.DEFAULT_8.getImage();
|
defaultSymbols[8] = GameImage.DEFAULT_8.getImage();
|
||||||
defaultSymbols[9] = GameImage.DEFAULT_9.getImage();
|
defaultSymbols[9] = GameImage.DEFAULT_9.getImage();
|
||||||
|
}
|
||||||
|
|
||||||
// score symbol images
|
// score symbol images
|
||||||
scoreSymbols = new HashMap<Character, Image>(14);
|
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 g the graphics context
|
||||||
* @param width the width of the container
|
* @param osu the OsuFile
|
||||||
* @param height the height of the container
|
|
||||||
*/
|
*/
|
||||||
public void drawRankingElements(Graphics g, int width, int height) {
|
public void drawRankingElements(Graphics g, OsuFile osu) {
|
||||||
// grade
|
// grade
|
||||||
Grade grade = getGrade();
|
Grade grade = getGrade();
|
||||||
if (grade != Grade.NULL) {
|
if (grade != Grade.NULL) {
|
||||||
|
@ -492,6 +526,14 @@ public class GameData {
|
||||||
g.setColor(Utils.COLOR_BLACK_ALPHA);
|
g.setColor(Utils.COLOR_BLACK_ALPHA);
|
||||||
g.fillRect(0, 0, width, rankingHeight);
|
g.fillRect(0, 0, width, rankingHeight);
|
||||||
rankingTitle.draw((width * 0.97f) - rankingTitle.getWidth(), 0);
|
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
|
// ranking panel
|
||||||
Image rankingPanel = GameImage.RANKING_PANEL.getImage();
|
Image rankingPanel = GameImage.RANKING_PANEL.getImage();
|
||||||
|
@ -546,12 +588,23 @@ public class GameData {
|
||||||
(int) (rankingPanelWidth / 2f + numbersX), (int) numbersY, symbolTextScale, false);
|
(int) (rankingPanelWidth / 2f + numbersX), (int) numbersY, symbolTextScale, false);
|
||||||
|
|
||||||
// full combo
|
// full combo
|
||||||
if (combo == fullObjectCount) {
|
if (comboMax == fullObjectCount) {
|
||||||
GameImage.RANKING_PERFECT.getImage().draw(
|
GameImage.RANKING_PERFECT.getImage().draw(
|
||||||
width * 0.08f,
|
width * 0.08f,
|
||||||
(height * 0.99f) - GameImage.RANKING_PERFECT.getImage().getHeight()
|
(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; }
|
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() {
|
private float getScorePercent() {
|
||||||
float percent = 0;
|
return getScorePercent(
|
||||||
if (objectCount > 0)
|
hitResultCount[HIT_300], hitResultCount[HIT_100],
|
||||||
percent = ((hitResultCount[HIT_50] * 50) + (hitResultCount[HIT_100] * 100)
|
hitResultCount[HIT_50], hitResultCount[HIT_MISS]
|
||||||
+ (hitResultCount[HIT_300] * 300)) / (objectCount * 300f) * 100f;
|
);
|
||||||
return percent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns letter grade based on score data,
|
* Returns letter grade based on score data,
|
||||||
* or Grade.NULL if no objects have been processed.
|
* 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
|
* @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
|
if (objectCount < 1) // avoid division by zero
|
||||||
return Grade.NULL;
|
return Grade.NULL;
|
||||||
|
|
||||||
// TODO: silvers
|
// TODO: silvers
|
||||||
float percent = getScorePercent();
|
float percent = getScorePercent(hit300, hit100, hit50, miss);
|
||||||
float hit300ratio = hitResultCount[HIT_300] * 100f / objectCount;
|
float hit300ratio = hit300 * 100f / objectCount;
|
||||||
float hit50ratio = hitResultCount[HIT_50] * 100f / objectCount;
|
float hit50ratio = hit50 * 100f / objectCount;
|
||||||
boolean noMiss = (hitResultCount[HIT_MISS] == 0);
|
boolean noMiss = (miss == 0);
|
||||||
if (percent >= 100f)
|
if (percent >= 100f)
|
||||||
return Grade.SS;
|
return Grade.SS;
|
||||||
else if (hit300ratio >= 90f && hit50ratio < 1.0f && noMiss)
|
else if (hit300ratio >= 90f && hit50ratio < 1.0f && noMiss)
|
||||||
|
@ -654,6 +727,17 @@ public class GameData {
|
||||||
return Grade.D;
|
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.
|
* Updates the score, health, and combo burst displays based on a delta value.
|
||||||
* @param delta the delta interval since the last call
|
* @param delta the delta interval since the last call
|
||||||
|
@ -792,25 +876,21 @@ public class GameData {
|
||||||
perfectHit = true;
|
perfectHit = true;
|
||||||
hitValue = 300;
|
hitValue = 300;
|
||||||
changeHealth(5f);
|
changeHealth(5f);
|
||||||
objectCount++;
|
|
||||||
break;
|
break;
|
||||||
case HIT_100:
|
case HIT_100:
|
||||||
hitValue = 100;
|
hitValue = 100;
|
||||||
changeHealth(2f);
|
changeHealth(2f);
|
||||||
comboEnd |= 1;
|
comboEnd |= 1;
|
||||||
objectCount++;
|
|
||||||
break;
|
break;
|
||||||
case HIT_50:
|
case HIT_50:
|
||||||
hitValue = 50;
|
hitValue = 50;
|
||||||
comboEnd |= 2;
|
comboEnd |= 2;
|
||||||
objectCount++;
|
|
||||||
break;
|
break;
|
||||||
case HIT_MISS:
|
case HIT_MISS:
|
||||||
hitValue = 0;
|
hitValue = 0;
|
||||||
changeHealth(-10f);
|
changeHealth(-10f);
|
||||||
comboEnd |= 2;
|
comboEnd |= 2;
|
||||||
resetComboStreak();
|
resetComboStreak();
|
||||||
objectCount++;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
|
@ -864,4 +944,47 @@ public class GameData {
|
||||||
else
|
else
|
||||||
hitResultList.add(new OsuHitObjectResult(time, result, x, y, color));
|
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_CLEAR ("spinner-clear", "png"),
|
||||||
SPINNER_OSU ("spinner-osu", "png"),
|
SPINNER_OSU ("spinner-osu", "png"),
|
||||||
|
|
||||||
// Game Score
|
// Score Data
|
||||||
COMBO_BURST ("comboburst", "comboburst-%d", "png"),
|
COMBO_BURST ("comboburst", "comboburst-%d", "png"),
|
||||||
SCOREBAR_BG ("scorebar-bg", "png") {
|
SCOREBAR_BG ("scorebar-bg", "png") {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -28,16 +28,16 @@ import org.newdawn.slick.Input;
|
||||||
* Game mods.
|
* Game mods.
|
||||||
*/
|
*/
|
||||||
public enum GameMod {
|
public enum GameMod {
|
||||||
EASY (0, GameImage.MOD_EASY, Input.KEY_Q, 0.5f),
|
EASY (0, GameImage.MOD_EASY, "EZ", 2, Input.KEY_Q, 0.5f),
|
||||||
NO_FAIL (1, GameImage.MOD_NO_FAIL, Input.KEY_W, 0.5f),
|
NO_FAIL (1, GameImage.MOD_NO_FAIL, "NF", 1, Input.KEY_W, 0.5f),
|
||||||
HARD_ROCK (2, GameImage.MOD_HARD_ROCK, Input.KEY_A, 1.06f),
|
HARD_ROCK (2, GameImage.MOD_HARD_ROCK, "HR", 16, Input.KEY_A, 1.06f),
|
||||||
SUDDEN_DEATH (3, GameImage.MOD_SUDDEN_DEATH, Input.KEY_S),
|
SUDDEN_DEATH (3, GameImage.MOD_SUDDEN_DEATH, "SD", 32, Input.KEY_S),
|
||||||
SPUN_OUT (4, GameImage.MOD_SPUN_OUT, Input.KEY_V, 0.9f),
|
SPUN_OUT (4, GameImage.MOD_SPUN_OUT, "SO", 4096, Input.KEY_V, 0.9f),
|
||||||
AUTO (5, GameImage.MOD_AUTO, Input.KEY_B);
|
AUTO (5, GameImage.MOD_AUTO, "", 2048, Input.KEY_B);
|
||||||
// HALF_TIME (, GameImage.MOD_HALF_TIME, Input.KEY_E, 0.3f),
|
// HALF_TIME (, GameImage.MOD_HALF_TIME, "HT", 256, Input.KEY_E, 0.3f),
|
||||||
// DOUBLE_TIME (, GameImage.MOD_DOUBLE_TIME, Input.KEY_D, 1.12f),
|
// DOUBLE_TIME (, GameImage.MOD_DOUBLE_TIME, "DT", 64, Input.KEY_D, 1.12f),
|
||||||
// HIDDEN (, GameImage.MOD_HIDDEN, Input.KEY_F, 1.06f),
|
// HIDDEN (, GameImage.MOD_HIDDEN, "HD", 8, Input.KEY_F, 1.06f),
|
||||||
// FLASHLIGHT (, GameImage.MOD_FLASHLIGHT, Input.KEY_G, 1.12f);
|
// FLASHLIGHT (, GameImage.MOD_FLASHLIGHT, "FL", 1024, Input.KEY_G, 1.12f);
|
||||||
|
|
||||||
/** The ID of the mod (used for positioning). */
|
/** The ID of the mod (used for positioning). */
|
||||||
private int id;
|
private int id;
|
||||||
|
@ -45,7 +45,16 @@ public enum GameMod {
|
||||||
/** The file name of the mod image. */
|
/** The file name of the mod image. */
|
||||||
private GameImage 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;
|
private int key;
|
||||||
|
|
||||||
/** The score multiplier. */
|
/** The score multiplier. */
|
||||||
|
@ -71,11 +80,15 @@ public enum GameMod {
|
||||||
* Constructor.
|
* Constructor.
|
||||||
* @param id the ID of the mod (for positioning).
|
* @param id the ID of the mod (for positioning).
|
||||||
* @param image the GameImage
|
* @param image the GameImage
|
||||||
|
* @param abbrev the two-letter abbreviation
|
||||||
|
* @param bit the bit
|
||||||
* @param key the shortcut key
|
* @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.id = id;
|
||||||
this.image = image;
|
this.image = image;
|
||||||
|
this.abbrev = abbrev;
|
||||||
|
this.bit = bit;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.multiplier = 1f;
|
this.multiplier = 1f;
|
||||||
}
|
}
|
||||||
|
@ -84,12 +97,16 @@ public enum GameMod {
|
||||||
* Constructor.
|
* Constructor.
|
||||||
* @param id the ID of the mod (for positioning).
|
* @param id the ID of the mod (for positioning).
|
||||||
* @param image the GameImage
|
* @param image the GameImage
|
||||||
|
* @param abbrev the two-letter abbreviation
|
||||||
|
* @param bit the bit
|
||||||
* @param key the shortcut key
|
* @param key the shortcut key
|
||||||
* @param multiplier the score multiplier
|
* @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.id = id;
|
||||||
this.image = image;
|
this.image = image;
|
||||||
|
this.abbrev = abbrev;
|
||||||
|
this.bit = bit;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.multiplier = multiplier;
|
this.multiplier = multiplier;
|
||||||
}
|
}
|
||||||
|
@ -113,6 +130,18 @@ public enum GameMod {
|
||||||
this.button.setHoverScale(1.15f);
|
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.
|
* Returns the shortcut key for the mod.
|
||||||
* @return the key
|
* @return the key
|
||||||
|
@ -173,12 +202,6 @@ public enum GameMod {
|
||||||
*/
|
*/
|
||||||
public Image getImage() { return image.getImage(); }
|
public Image getImage() { return image.getImage(); }
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the mod ID.
|
|
||||||
* @return the mod ID
|
|
||||||
*/
|
|
||||||
public int getID() { return id; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the game mod.
|
* Draws the game mod.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -137,6 +137,9 @@ public class Opsu extends StateBasedGame {
|
||||||
}
|
}
|
||||||
Options.TMP_DIR.deleteOnExit();
|
Options.TMP_DIR.deleteOnExit();
|
||||||
|
|
||||||
|
// initialize score database
|
||||||
|
Scores.init();
|
||||||
|
|
||||||
// start the game
|
// start the game
|
||||||
try {
|
try {
|
||||||
// loop until force exit
|
// loop until force exit
|
||||||
|
@ -161,8 +164,7 @@ public class Opsu extends StateBasedGame {
|
||||||
ErrorHandler.error("Error while creating game container.", e, true);
|
ErrorHandler.error("Error while creating game container.", e, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// close server socket
|
Opsu.exit();
|
||||||
closeSocket();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
if (SERVER_SOCKET != null) {
|
||||||
try {
|
try {
|
||||||
SERVER_SOCKET.close();
|
SERVER_SOCKET.close();
|
||||||
|
@ -198,5 +204,7 @@ public class Opsu extends StateBasedGame {
|
||||||
ErrorHandler.error("Failed to close server socket.", e, false);
|
ErrorHandler.error("Failed to close server socket.", e, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
System.exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,9 @@ public class Options {
|
||||||
"Songs/"
|
"Songs/"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Score database name. */
|
||||||
|
public static final String SCORE_DB = ".opsu_scores.db";
|
||||||
|
|
||||||
/** Font file name. */
|
/** Font file name. */
|
||||||
public static final String FONT_NAME = "kochi-gothic.ttf";
|
public static final String FONT_NAME = "kochi-gothic.ttf";
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
|
|
||||||
package itdelatrisu.opsu;
|
package itdelatrisu.opsu;
|
||||||
|
|
||||||
|
import itdelatrisu.opsu.GameData.Grade;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -52,9 +54,10 @@ public class OsuGroupNode {
|
||||||
* Draws the button.
|
* Draws the button.
|
||||||
* @param x the x coordinate
|
* @param x the x coordinate
|
||||||
* @param y the y coordinate
|
* @param y the y coordinate
|
||||||
|
* @param grade the highest grade, if any
|
||||||
* @param focus true if this is the focused node
|
* @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);
|
boolean expanded = (osuFileIndex > -1);
|
||||||
float xOffset = 0f;
|
float xOffset = 0f;
|
||||||
OsuFile osu;
|
OsuFile osu;
|
||||||
|
@ -77,6 +80,13 @@ public class OsuGroupNode {
|
||||||
float cx = x + (bg.getWidth() * 0.05f) - xOffset;
|
float cx = x + (bg.getWidth() * 0.05f) - xOffset;
|
||||||
float cy = y + (bg.getHeight() * 0.2f) - 3;
|
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_MEDIUM.drawString(cx, cy, osu.getTitle(), textColor);
|
||||||
Utils.FONT_DEFAULT.drawString(cx, cy + Utils.FONT_MEDIUM.getLineHeight() - 4,
|
Utils.FONT_DEFAULT.drawString(cx, cy + Utils.FONT_MEDIUM.getLineHeight() - 4,
|
||||||
String.format("%s // %s", osu.getArtist(), osu.creator), textColor);
|
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.OsuFile;
|
||||||
import itdelatrisu.opsu.OsuHitObject;
|
import itdelatrisu.opsu.OsuHitObject;
|
||||||
import itdelatrisu.opsu.OsuTimingPoint;
|
import itdelatrisu.opsu.OsuTimingPoint;
|
||||||
|
import itdelatrisu.opsu.Scores;
|
||||||
import itdelatrisu.opsu.Utils;
|
import itdelatrisu.opsu.Utils;
|
||||||
import itdelatrisu.opsu.audio.HitSound;
|
import itdelatrisu.opsu.audio.HitSound;
|
||||||
import itdelatrisu.opsu.audio.MusicController;
|
import itdelatrisu.opsu.audio.MusicController;
|
||||||
|
@ -160,7 +161,6 @@ public class Game extends BasicGameState {
|
||||||
|
|
||||||
// create the associated GameData object
|
// create the associated GameData object
|
||||||
data = new GameData(width, height);
|
data = new GameData(width, height);
|
||||||
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -419,8 +419,12 @@ public class Game extends BasicGameState {
|
||||||
if (objectIndex >= osu.objects.length) {
|
if (objectIndex >= osu.objects.length) {
|
||||||
if (checkpointLoaded) // if checkpoint used, skip ranking screen
|
if (checkpointLoaded) // if checkpoint used, skip ranking screen
|
||||||
game.closeRequested();
|
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));
|
game.enterState(Opsu.STATE_GAMERANKING, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -800,7 +804,7 @@ public class Game extends BasicGameState {
|
||||||
|
|
||||||
// load other images...
|
// load other images...
|
||||||
((GamePauseMenu) game.getState(Opsu.STATE_GAMEPAUSEMENU)).loadImages();
|
((GamePauseMenu) game.getState(Opsu.STATE_GAMEPAUSEMENU)).loadImages();
|
||||||
data.loadImages(osu.getFile().getParentFile());
|
data.loadImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,9 +18,8 @@
|
||||||
|
|
||||||
package itdelatrisu.opsu.states;
|
package itdelatrisu.opsu.states;
|
||||||
|
|
||||||
import itdelatrisu.opsu.GameImage;
|
|
||||||
import itdelatrisu.opsu.GameMod;
|
|
||||||
import itdelatrisu.opsu.GameData;
|
import itdelatrisu.opsu.GameData;
|
||||||
|
import itdelatrisu.opsu.GameImage;
|
||||||
import itdelatrisu.opsu.MenuButton;
|
import itdelatrisu.opsu.MenuButton;
|
||||||
import itdelatrisu.opsu.Opsu;
|
import itdelatrisu.opsu.Opsu;
|
||||||
import itdelatrisu.opsu.OsuFile;
|
import itdelatrisu.opsu.OsuFile;
|
||||||
|
@ -100,29 +99,13 @@ public class GameRanking extends BasicGameState {
|
||||||
g.setBackground(Utils.COLOR_BLACK_ALPHA);
|
g.setBackground(Utils.COLOR_BLACK_ALPHA);
|
||||||
|
|
||||||
// ranking screen elements
|
// ranking screen elements
|
||||||
data.drawRankingElements(g, width, height);
|
data.drawRankingElements(g, osu);
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// buttons
|
// buttons
|
||||||
retryButton.draw();
|
if (data.isGameplay()) {
|
||||||
exitButton.draw();
|
retryButton.draw();
|
||||||
|
exitButton.draw();
|
||||||
|
}
|
||||||
Utils.getBackButton().draw();
|
Utils.getBackButton().draw();
|
||||||
|
|
||||||
Utils.drawVolume(g);
|
Utils.drawVolume(g);
|
||||||
|
@ -146,12 +129,7 @@ public class GameRanking extends BasicGameState {
|
||||||
public void keyPressed(int key, char c) {
|
public void keyPressed(int key, char c) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case Input.KEY_ESCAPE:
|
case Input.KEY_ESCAPE:
|
||||||
SoundController.playSound(SoundEffect.MENUBACK);
|
returnToSongMenu();
|
||||||
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));
|
|
||||||
break;
|
break;
|
||||||
case Input.KEY_F12:
|
case Input.KEY_F12:
|
||||||
Utils.takeScreenShot();
|
Utils.takeScreenShot();
|
||||||
|
@ -165,25 +143,26 @@ public class GameRanking extends BasicGameState {
|
||||||
if (button != Input.MOUSE_LEFT_BUTTON)
|
if (button != Input.MOUSE_LEFT_BUTTON)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (retryButton.contains(x, y)) {
|
if (data.isGameplay()) {
|
||||||
OsuFile osu = MusicController.getOsuFile();
|
if (retryButton.contains(x, y)) {
|
||||||
Display.setTitle(String.format("%s - %s", game.getTitle(), osu.toString()));
|
OsuFile osu = MusicController.getOsuFile();
|
||||||
((Game) game.getState(Opsu.STATE_GAME)).setRestart(Game.Restart.MANUAL);
|
Display.setTitle(String.format("%s - %s", game.getTitle(), osu.toString()));
|
||||||
SoundController.playSound(SoundEffect.MENUHIT);
|
((Game) game.getState(Opsu.STATE_GAME)).setRestart(Game.Restart.MANUAL);
|
||||||
game.enterState(Opsu.STATE_GAME, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
SoundController.playSound(SoundEffect.MENUHIT);
|
||||||
} else if (exitButton.contains(x, y)) {
|
game.enterState(Opsu.STATE_GAME, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
||||||
SoundController.playSound(SoundEffect.MENUBACK);
|
return;
|
||||||
((MainMenu) game.getState(Opsu.STATE_MAINMENU)).reset();
|
} else if (exitButton.contains(x, y)) {
|
||||||
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).resetGameDataOnLoad();
|
SoundController.playSound(SoundEffect.MENUBACK);
|
||||||
Utils.resetCursor();
|
((MainMenu) game.getState(Opsu.STATE_MAINMENU)).reset();
|
||||||
game.enterState(Opsu.STATE_MAINMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).resetGameDataOnLoad();
|
||||||
} else if (Utils.getBackButton().contains(x, y)) {
|
Utils.resetCursor();
|
||||||
SoundController.playSound(SoundEffect.MENUBACK);
|
game.enterState(Opsu.STATE_MAINMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
||||||
SongMenu songMenu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
|
return;
|
||||||
songMenu.resetGameDataOnLoad();
|
}
|
||||||
songMenu.resetTrackOnLoad();
|
}
|
||||||
Utils.resetCursor();
|
if (Utils.getBackButton().contains(x, y)) {
|
||||||
game.enterState(Opsu.STATE_SONGMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
returnToSongMenu();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,6 +174,26 @@ public class GameRanking extends BasicGameState {
|
||||||
SoundController.playSound(SoundEffect.APPLAUSE);
|
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.
|
* Sets the associated GameData object.
|
||||||
* @param data the GameData
|
* @param data the GameData
|
||||||
|
|
|
@ -28,6 +28,9 @@ import itdelatrisu.opsu.OsuGroupList;
|
||||||
import itdelatrisu.opsu.OsuGroupNode;
|
import itdelatrisu.opsu.OsuGroupNode;
|
||||||
import itdelatrisu.opsu.OsuParser;
|
import itdelatrisu.opsu.OsuParser;
|
||||||
import itdelatrisu.opsu.OszUnpacker;
|
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.SongSort;
|
||||||
import itdelatrisu.opsu.Utils;
|
import itdelatrisu.opsu.Utils;
|
||||||
import itdelatrisu.opsu.audio.HitSound;
|
import itdelatrisu.opsu.audio.HitSound;
|
||||||
|
@ -36,6 +39,7 @@ import itdelatrisu.opsu.audio.SoundController;
|
||||||
import itdelatrisu.opsu.audio.SoundEffect;
|
import itdelatrisu.opsu.audio.SoundEffect;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
import org.lwjgl.opengl.Display;
|
import org.lwjgl.opengl.Display;
|
||||||
|
@ -153,6 +157,9 @@ public class SongMenu extends BasicGameState {
|
||||||
/** Beatmap reloading thread. */
|
/** Beatmap reloading thread. */
|
||||||
private Thread reloadThread;
|
private Thread reloadThread;
|
||||||
|
|
||||||
|
/** Current map of scores (Version, ScoreData[]). */
|
||||||
|
private Map<String, ScoreData[]> scoreMap;
|
||||||
|
|
||||||
// game-related variables
|
// game-related variables
|
||||||
private GameContainer container;
|
private GameContainer container;
|
||||||
private StateBasedGame game;
|
private StateBasedGame game;
|
||||||
|
@ -261,8 +268,16 @@ public class SongMenu extends BasicGameState {
|
||||||
// song buttons
|
// song buttons
|
||||||
OsuGroupNode node = startNode;
|
OsuGroupNode node = startNode;
|
||||||
for (int i = 0; i < MAX_BUTTONS && node != null; i++, node = node.next) {
|
for (int i = 0; i < MAX_BUTTONS && node != null; i++, node = node.next) {
|
||||||
|
// draw the node
|
||||||
float offset = (i == hoverIndex) ? hoverOffset : 0f;
|
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));
|
Utils.loadGlyphs(node.osuFiles.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -703,11 +718,17 @@ public class SongMenu extends BasicGameState {
|
||||||
// reset game data
|
// reset game data
|
||||||
if (resetGame) {
|
if (resetGame) {
|
||||||
((Game) game.getState(Opsu.STATE_GAME)).resetGameData();
|
((Game) game.getState(Opsu.STATE_GAME)).resetGameData();
|
||||||
|
|
||||||
// destroy skin images, if any
|
// destroy skin images, if any
|
||||||
for (GameImage img : GameImage.values()) {
|
for (GameImage img : GameImage.values()) {
|
||||||
if (img.isSkinnable())
|
if (img.isSkinnable())
|
||||||
img.destroySkinImage();
|
img.destroySkinImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reload scores
|
||||||
|
if (focusNode != null)
|
||||||
|
scoreMap = Scores.getMapSetScores(focusNode.osuFiles.get(focusNode.osuFileIndex));
|
||||||
|
|
||||||
resetGame = false;
|
resetGame = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -798,6 +819,9 @@ public class SongMenu extends BasicGameState {
|
||||||
MusicController.play(osu, true);
|
MusicController.play(osu, true);
|
||||||
Utils.loadGlyphs(osu);
|
Utils.loadGlyphs(osu);
|
||||||
|
|
||||||
|
// load scores
|
||||||
|
scoreMap = Scores.getMapSetScores(osu);
|
||||||
|
|
||||||
// check startNode bounds
|
// check startNode bounds
|
||||||
while (startNode.index >= OsuGroupList.get().size() + length - MAX_BUTTONS && startNode.prev != null)
|
while (startNode.index >= OsuGroupList.get().size() + length - MAX_BUTTONS && startNode.prev != null)
|
||||||
startNode = startNode.prev;
|
startNode = startNode.prev;
|
||||||
|
@ -835,6 +859,30 @@ public class SongMenu extends BasicGameState {
|
||||||
*/
|
*/
|
||||||
public void resetTrackOnLoad() { resetTrack = true; }
|
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.
|
* Starts the game.
|
||||||
* @param osu the OsuFile to send to the game
|
* @param osu the OsuFile to send to the game
|
||||||
|
|
Loading…
Reference in New Issue
Block a user