Follow-up to c6791c4.

- Added score management menu for deleting individual scores.
- Store generated title string lists in the beatmap menus to prevent performance issues.

Allow using the right mouse button (as well as left mouse button) in all states.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2015-02-13 03:45:38 -05:00
parent c6791c4714
commit 7cc4ff51d0
7 changed files with 189 additions and 55 deletions

View File

@ -45,7 +45,7 @@ public class ScoreDB {
private static PreparedStatement selectMapStmt, selectMapSetStmt;
/** Score deletion statement. */
private static PreparedStatement deleteStmt;
private static PreparedStatement deleteSongStmt, deleteScoreStmt;
// This class should not be instantiated.
private ScoreDB() {}
@ -85,10 +85,16 @@ public class ScoreDB {
"SELECT * FROM scores WHERE " +
"MSID = ? AND title = ? AND artist = ? AND creator = ? ORDER BY version DESC"
);
deleteStmt = connection.prepareStatement(
deleteSongStmt = connection.prepareStatement(
"DELETE FROM scores WHERE " +
"MID = ? AND title = ? AND artist = ? AND creator = ? AND version = ?"
);
deleteScoreStmt = connection.prepareStatement(
"DELETE FROM scores WHERE " +
"timestamp = ? AND MID = ? AND MSID = ? AND title = ? AND artist = ? AND " +
"creator = ? AND version = ? AND hit300 = ? AND hit100 = ? AND hit50 = ? AND " +
"geki = ? AND katu = ? AND miss = ? AND score = ? AND combo = ? AND perfect = ? AND mods = ?"
);
} catch (SQLException e) {
ErrorHandler.error("Failed to prepare score insertion statement.", e, true);
}
@ -123,46 +129,70 @@ public class ScoreDB {
*/
public static void addScore(ScoreData data) {
try {
insertStmt.setLong(1, data.timestamp);
insertStmt.setInt(2, data.MID);
insertStmt.setInt(3, data.MSID);
insertStmt.setString(4, data.title);
insertStmt.setString(5, data.artist);
insertStmt.setString(6, data.creator);
insertStmt.setString(7, data.version);
insertStmt.setInt(8, data.hit300);
insertStmt.setInt(9, data.hit100);
insertStmt.setInt(10, data.hit50);
insertStmt.setInt(11, data.geki);
insertStmt.setInt(12, data.katu);
insertStmt.setInt(13, data.miss);
insertStmt.setLong(14, data.score);
insertStmt.setInt(15, data.combo);
insertStmt.setBoolean(16, data.perfect);
insertStmt.setInt(17, data.mods);
setStatementFields(insertStmt, data);
insertStmt.executeUpdate();
} catch (SQLException e) {
ErrorHandler.error("Failed to save score to database.", e, true);
}
}
/**
* Deletes the given score from the database.
* @param data the score to delete
*/
public static void deleteScore(ScoreData data) {
try {
setStatementFields(deleteScoreStmt, data);
deleteScoreStmt.executeUpdate();
} catch (SQLException e) {
ErrorHandler.error("Failed to delete score from database.", e, true);
}
}
/**
* Deletes all the scores for the given beatmap from the database.
* @param osu the OsuFile object
*/
public static void deleteScore(OsuFile osu) {
try {
deleteStmt.setInt(1, osu.beatmapID);
deleteStmt.setString(2, osu.title);
deleteStmt.setString(3, osu.artist);
deleteStmt.setString(4, osu.creator);
deleteStmt.setString(5, osu.version);
deleteStmt.executeUpdate();
deleteSongStmt.setInt(1, osu.beatmapID);
deleteSongStmt.setString(2, osu.title);
deleteSongStmt.setString(3, osu.artist);
deleteSongStmt.setString(4, osu.creator);
deleteSongStmt.setString(5, osu.version);
deleteSongStmt.executeUpdate();
} catch (SQLException e) {
ErrorHandler.error("Failed to delete score from database.", e, true);
ErrorHandler.error("Failed to delete scores from database.", e, true);
}
}
/**
* Sets all statement fields using a given ScoreData object.
* @param stmt the statement to set fields for
* @param data the score data
* @throws SQLException
*/
private static void setStatementFields(PreparedStatement stmt, ScoreData data)
throws SQLException {
stmt.setLong(1, data.timestamp);
stmt.setInt(2, data.MID);
stmt.setInt(3, data.MSID);
stmt.setString(4, data.title);
stmt.setString(5, data.artist);
stmt.setString(6, data.creator);
stmt.setString(7, data.version);
stmt.setInt(8, data.hit300);
stmt.setInt(9, data.hit100);
stmt.setInt(10, data.hit50);
stmt.setInt(11, data.geki);
stmt.setInt(12, data.katu);
stmt.setInt(13, data.miss);
stmt.setLong(14, data.score);
stmt.setInt(15, data.combo);
stmt.setBoolean(16, data.perfect);
stmt.setInt(17, data.mods);
}
/**
* Retrieves the game scores for an OsuFile map.
* @param osu the OsuFile

View File

@ -23,10 +23,12 @@ import itdelatrisu.opsu.MenuButton;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.OsuGroupList;
import itdelatrisu.opsu.OsuGroupNode;
import itdelatrisu.opsu.ScoreData;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.audio.SoundController;
import itdelatrisu.opsu.audio.SoundEffect;
import java.util.ArrayList;
import java.util.List;
import org.newdawn.slick.Color;
@ -108,6 +110,17 @@ public class ButtonMenu extends BasicGameState {
public void leave(GameContainer container, StateBasedGame game) {
Button.RELOAD_CANCEL.click(container, game);
}
},
SCORE (new Button[] { Button.DELETE_SCORE, Button.CLOSE }) {
@Override
public String[] getTitle(GameContainer container, StateBasedGame game) {
return new String[] { "Score Management" };
}
@Override
public void leave(GameContainer container, StateBasedGame game) {
Button.CLOSE.click(container, game);
}
};
/** The buttons in the state. */
@ -116,6 +129,9 @@ public class ButtonMenu extends BasicGameState {
/** The associated MenuButton objects. */
private MenuButton[] menuButtons;
/** The actual title string list, generated upon entering the state. */
private List<String> actualTitle;
/** Initial x coordinate offsets left/right of center (for shifting animation), times width. (TODO) */
private static final float OFFSET_WIDTH_RATIO = 1 / 18f;
@ -162,18 +178,11 @@ public class ButtonMenu extends BasicGameState {
*/
public void draw(GameContainer container, StateBasedGame game, Graphics g) {
// draw title
String[] title = getTitle(container, game);
float c = container.getWidth() * 0.02f;
int maxLineWidth = container.getWidth() - (int) (c * 2);
int lineHeight = Utils.FONT_LARGE.getLineHeight();
for (int i = 0, j = 0; i < title.length; i++, j++) {
// wrap text if too long
if (Utils.FONT_LARGE.getWidth(title[i]) > maxLineWidth) {
List<String> list = Utils.wrap(title[i], Utils.FONT_LARGE, maxLineWidth);
for (String str : list)
Utils.FONT_LARGE.drawString(c, c + (j++ * lineHeight), str, Color.white);
} else
Utils.FONT_LARGE.drawString(c, c + (j * lineHeight), title[i], Color.white);
if (actualTitle != null) {
float c = container.getWidth() * 0.02f;
int lineHeight = Utils.FONT_LARGE.getLineHeight();
for (int i = 0, size = actualTitle.size(); i < size; i++)
Utils.FONT_LARGE.drawString(c, c + (i * lineHeight), actualTitle.get(i), Color.white);
}
// draw buttons
@ -252,6 +261,19 @@ public class ButtonMenu extends BasicGameState {
menuButtons[i].setX(center + ((i % 2 == 0) ? centerOffset * -1 : centerOffset));
menuButtons[i].resetHover();
}
// create title string list
actualTitle = new ArrayList<String>();
String[] title = getTitle(container, game);
int maxLineWidth = (int) (container.getWidth() * 0.96f);
for (int i = 0; i < title.length; i++) {
// wrap text if too long
if (Utils.FONT_LARGE.getWidth(title[i]) > maxLineWidth) {
List<String> list = Utils.wrap(title[i], Utils.FONT_LARGE, maxLineWidth);
actualTitle.addAll(list);
} else
actualTitle.add(title[i]);
}
}
/**
@ -347,6 +369,21 @@ public class ButtonMenu extends BasicGameState {
public void click(GameContainer container, StateBasedGame game) {
CANCEL.click(container, game);
}
},
DELETE_SCORE ("Delete score", Color.green) {
@Override
public void click(GameContainer container, StateBasedGame game) {
SoundController.playSound(SoundEffect.MENUHIT);
ScoreData scoreData = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getScoreData();
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).doStateActionOnLoad(MenuState.SCORE, scoreData);
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition(Color.black));
}
},
CLOSE ("Close", Color.gray) {
@Override
public void click(GameContainer container, StateBasedGame game) {
CANCEL.click(container, game);
}
};
/** The text to show on the button. */
@ -389,6 +426,9 @@ public class ButtonMenu extends BasicGameState {
/** The song node to process in the state. */
private OsuGroupNode node;
/** The score data to process in the state. */
private ScoreData scoreData;
// game-related variables
private GameContainer container;
private StateBasedGame game;
@ -441,7 +481,7 @@ public class ButtonMenu extends BasicGameState {
@Override
public void mousePressed(int button, int x, int y) {
// check mouse button
if (button != Input.MOUSE_LEFT_BUTTON)
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
if (menuState != null)
@ -476,20 +516,41 @@ public class ButtonMenu extends BasicGameState {
* Changes the menu state.
* @param menuState the new menu state
*/
public void setMenuState(MenuState menuState) { setMenuState(menuState, null); }
public void setMenuState(MenuState menuState) { setMenuState(menuState, null, null); }
/**
* Changes the menu state.
* @param menuState the new menu state
* @param node the song node to process in the state
*/
public void setMenuState(MenuState menuState, OsuGroupNode node) {
public void setMenuState(MenuState menuState, OsuGroupNode node) { setMenuState(menuState, node, null); }
/**
* Changes the menu state.
* @param menuState the new menu state
* @param scoreData the score scoreData
*/
public void setMenuState(MenuState menuState, ScoreData scoreData) { setMenuState(menuState, null, scoreData); }
/**
* Changes the menu state.
* @param menuState the new menu state
* @param node the song node to process in the state
* @param scoreData the score scoreData
*/
private void setMenuState(MenuState menuState, OsuGroupNode node, ScoreData scoreData) {
this.menuState = menuState;
this.node = node;
this.scoreData = scoreData;
}
/**
* Returns the song node being processed, or null if none.
*/
public OsuGroupNode getNode() { return node; }
private OsuGroupNode getNode() { return node; }
/**
* Returns the score data being processed, or null if none.
*/
private ScoreData getScoreData() { return scoreData; }
}

View File

@ -413,7 +413,7 @@ public class DownloadsMenu extends BasicGameState {
@Override
public void mousePressed(int button, int x, int y) {
// check mouse button
if (button != Input.MOUSE_LEFT_BUTTON)
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
// block input during beatmap importing

View File

@ -63,10 +63,14 @@ import org.newdawn.slick.state.transition.FadeOutTransition;
public class Game extends BasicGameState {
/** Game restart states. */
public enum Restart {
FALSE, // no restart
NEW, // first time loading song
MANUAL, // retry
LOSE; // health is zero: no-continue/force restart
/** No restart. */
FALSE,
/** First time loading the song. */
NEW,
/** Manual retry. */
MANUAL,
/** Health is zero: no-continue/force restart. */
LOSE;
}
/** Minimum time before start of song, in milliseconds, to process skip-related actions. */

View File

@ -140,7 +140,7 @@ public class GameRanking extends BasicGameState {
@Override
public void mousePressed(int button, int x, int y) {
// check mouse button
if (button != Input.MOUSE_LEFT_BUTTON)
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
if (data.isGameplay()) {

View File

@ -341,7 +341,7 @@ public class MainMenu extends BasicGameState {
@Override
public void mousePressed(int button, int x, int y) {
// check mouse button
if (button != Input.MOUSE_LEFT_BUTTON)
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
// music button actions

View File

@ -168,6 +168,9 @@ public class SongMenu extends BasicGameState {
/** If non-null, the node that stateAction acts upon. */
private OsuGroupNode stateActionNode;
/** If non-null, the score data that stateAction acts upon. */
private ScoreData stateActionScore;
/** Timer before moving to the beatmap menu with the current focus node. */
private int beatmapMenuTimer = -1;
@ -582,10 +585,17 @@ public class SongMenu extends BasicGameState {
if (rank >= focusScores.length)
break;
if (ScoreData.buttonContains(x, y, i)) {
// view score
GameData data = new GameData(focusScores[rank], container.getWidth(), container.getHeight());
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
game.enterState(Opsu.STATE_GAMERANKING, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
SoundController.playSound(SoundEffect.MENUHIT);
if (button != Input.MOUSE_RIGHT_BUTTON) {
// view score
GameData data = new GameData(focusScores[rank], container.getWidth(), container.getHeight());
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
game.enterState(Opsu.STATE_GAMERANKING, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
} else {
// score management
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(MenuState.SCORE, focusScores[rank]);
game.enterState(Opsu.STATE_BUTTONMENU);
}
return;
}
}
@ -818,7 +828,7 @@ public class SongMenu extends BasicGameState {
// state-based action
if (stateAction != null) {
switch (stateAction) {
case BEATMAP: // clear scores
case BEATMAP: // clear all scores
if (stateActionNode == null || stateActionNode.osuFileIndex == -1)
break;
OsuFile osu = stateActionNode.osuFiles.get(stateActionNode.osuFileIndex);
@ -828,6 +838,14 @@ public class SongMenu extends BasicGameState {
scoreMap.remove(osu.version);
}
break;
case SCORE: // clear single score
if (stateActionScore == null)
break;
ScoreDB.deleteScore(stateActionScore);
scoreMap = ScoreDB.getMapSetScores(focusNode.osuFiles.get(focusNode.osuFileIndex));
focusScores = getScoreDataForNode(focusNode, true);
startScore = 0;
break;
case BEATMAP_DELETE_CONFIRM: // delete song group
if (stateActionNode == null)
break;
@ -926,6 +944,7 @@ public class SongMenu extends BasicGameState {
}
stateAction = null;
stateActionNode = null;
stateActionScore = null;
}
}
@ -1061,7 +1080,7 @@ public class SongMenu extends BasicGameState {
* Performs an action based on a menu state upon entering this state.
* @param menuState the menu state determining the action
*/
public void doStateActionOnLoad(MenuState menuState) { doStateActionOnLoad(menuState, null); }
public void doStateActionOnLoad(MenuState menuState) { doStateActionOnLoad(menuState, null, null); }
/**
* Performs an action based on a menu state upon entering this state.
@ -1069,8 +1088,28 @@ public class SongMenu extends BasicGameState {
* @param node the song node to perform the action on
*/
public void doStateActionOnLoad(MenuState menuState, OsuGroupNode node) {
doStateActionOnLoad(menuState, node, null);
}
/**
* Performs an action based on a menu state upon entering this state.
* @param menuState the menu state determining the action
* @param scoreData the score data to perform the action on
*/
public void doStateActionOnLoad(MenuState menuState, ScoreData scoreData) {
doStateActionOnLoad(menuState, null, scoreData);
}
/**
* Performs an action based on a menu state upon entering this state.
* @param menuState the menu state determining the action
* @param node the song node to perform the action on
* @param scoreData the score data to perform the action on
*/
private void doStateActionOnLoad(MenuState menuState, OsuGroupNode node, ScoreData scoreData) {
stateAction = menuState;
stateActionNode = node;
stateActionScore = scoreData;
}
/**