Added "favorites" and "last played" beatmap groups, and more sorts.
- New sorts: by date added, and most played. - Sorts are moved to a dropdown menu. - Tabs are now groupings (all songs, last played, favorites). - Add/remove "favorite" beatmaps in the right-click menu. - Beatmap database is now updateable like score database (no longer drops/recreates on every update). - Various bug fixes. Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
@@ -46,8 +46,8 @@ import org.newdawn.slick.Input;
|
||||
import org.newdawn.slick.SlickException;
|
||||
import org.newdawn.slick.state.BasicGameState;
|
||||
import org.newdawn.slick.state.StateBasedGame;
|
||||
import org.newdawn.slick.state.transition.FadeInTransition;
|
||||
import org.newdawn.slick.state.transition.EmptyTransition;
|
||||
import org.newdawn.slick.state.transition.FadeInTransition;
|
||||
|
||||
/**
|
||||
* Generic button menu state.
|
||||
@@ -69,8 +69,8 @@ public class ButtonMenu extends BasicGameState {
|
||||
Button.NO.click(container, game);
|
||||
}
|
||||
},
|
||||
/** The initial beatmap management screen. */
|
||||
BEATMAP (new Button[] { Button.CLEAR_SCORES, Button.DELETE, Button.CANCEL }) {
|
||||
/** The initial beatmap management screen (for a non-"favorite" beatmap). */
|
||||
BEATMAP (new Button[] { Button.CLEAR_SCORES, Button.FAVORITE_ADD, Button.DELETE, Button.CANCEL }) {
|
||||
@Override
|
||||
public String[] getTitle(GameContainer container, StateBasedGame game) {
|
||||
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
|
||||
@@ -90,6 +90,23 @@ public class ButtonMenu extends BasicGameState {
|
||||
super.scroll(container, game, newValue);
|
||||
}
|
||||
},
|
||||
/** The initial beatmap management screen (for a "favorite" beatmap). */
|
||||
BEATMAP_FAVORITE (new Button[] { Button.CLEAR_SCORES, Button.FAVORITE_REMOVE, Button.DELETE, Button.CANCEL }) {
|
||||
@Override
|
||||
public String[] getTitle(GameContainer container, StateBasedGame game) {
|
||||
return BEATMAP.getTitle(container, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leave(GameContainer container, StateBasedGame game) {
|
||||
BEATMAP.leave(container, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scroll(GameContainer container, StateBasedGame game, int newValue) {
|
||||
BEATMAP.scroll(container, game, newValue);
|
||||
}
|
||||
},
|
||||
/** The beatmap deletion screen for a beatmap set with multiple beatmaps. */
|
||||
BEATMAP_DELETE_SELECT (new Button[] { Button.DELETE_GROUP, Button.DELETE_SONG, Button.CANCEL_DELETE }) {
|
||||
@Override
|
||||
@@ -468,6 +485,25 @@ public class ButtonMenu extends BasicGameState {
|
||||
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
|
||||
}
|
||||
},
|
||||
FAVORITE_ADD ("Add to Favorites", Color.blue) {
|
||||
@Override
|
||||
public void click(GameContainer container, StateBasedGame game) {
|
||||
SoundController.playSound(SoundEffect.MENUHIT);
|
||||
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
|
||||
node.getBeatmapSet().setFavorite(true);
|
||||
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
|
||||
}
|
||||
},
|
||||
FAVORITE_REMOVE ("Remove from Favorites", Color.blue) {
|
||||
@Override
|
||||
public void click(GameContainer container, StateBasedGame game) {
|
||||
SoundController.playSound(SoundEffect.MENUHIT);
|
||||
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
|
||||
node.getBeatmapSet().setFavorite(false);
|
||||
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).doStateActionOnLoad(MenuState.BEATMAP_FAVORITE);
|
||||
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
|
||||
}
|
||||
},
|
||||
DELETE ("Delete...", Color.red) {
|
||||
@Override
|
||||
public void click(GameContainer container, StateBasedGame game) {
|
||||
|
||||
@@ -1235,6 +1235,12 @@ public class Game extends BasicGameState {
|
||||
|
||||
// restart the game
|
||||
if (restart != Restart.FALSE) {
|
||||
// update play stats
|
||||
if (restart == Restart.NEW) {
|
||||
beatmap.incrementPlayCounter();
|
||||
BeatmapDB.updatePlayStatistics(beatmap);
|
||||
}
|
||||
|
||||
// load mods
|
||||
if (isReplay) {
|
||||
previousMods = GameMod.getModState();
|
||||
|
||||
@@ -32,6 +32,7 @@ import itdelatrisu.opsu.audio.SoundController;
|
||||
import itdelatrisu.opsu.audio.SoundEffect;
|
||||
import itdelatrisu.opsu.beatmap.Beatmap;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapDifficultyCalculator;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapGroup;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapParser;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapSet;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
||||
@@ -45,6 +46,7 @@ import itdelatrisu.opsu.db.BeatmapDB;
|
||||
import itdelatrisu.opsu.db.ScoreDB;
|
||||
import itdelatrisu.opsu.states.ButtonMenu.MenuState;
|
||||
import itdelatrisu.opsu.ui.Colors;
|
||||
import itdelatrisu.opsu.ui.DropdownMenu;
|
||||
import itdelatrisu.opsu.ui.Fonts;
|
||||
import itdelatrisu.opsu.ui.KineticScrolling;
|
||||
import itdelatrisu.opsu.ui.MenuButton;
|
||||
@@ -315,6 +317,9 @@ public class SongMenu extends BasicGameState {
|
||||
/** Whether the menu is currently scrolling to the focus node (blocks other actions). */
|
||||
private boolean isScrollingToFocusNode = false;
|
||||
|
||||
/** Sort order dropdown menu. */
|
||||
private DropdownMenu<BeatmapSortOrder> sortMenu;
|
||||
|
||||
// game-related variables
|
||||
private GameContainer container;
|
||||
private StateBasedGame game;
|
||||
@@ -351,8 +356,37 @@ public class SongMenu extends BasicGameState {
|
||||
footerLogoButton.setHoverExpand(1.2f);
|
||||
|
||||
// initialize sorts
|
||||
for (BeatmapSortOrder sort : BeatmapSortOrder.values())
|
||||
sort.init(width, headerY - SongMenu.DIVIDER_LINE_WIDTH / 2);
|
||||
int sortWidth = (int) (width * 0.12f);
|
||||
sortMenu = new DropdownMenu<BeatmapSortOrder>(container, BeatmapSortOrder.values(),
|
||||
width * 0.87f, headerY - GameImage.MENU_TAB.getImage().getHeight() * 2.25f, sortWidth) {
|
||||
@Override
|
||||
public void itemSelected(int index, BeatmapSortOrder item) {
|
||||
BeatmapSortOrder.set(item);
|
||||
if (focusNode == null)
|
||||
return;
|
||||
BeatmapSetNode oldFocusBase = BeatmapSetList.get().getBaseNode(focusNode.index);
|
||||
int oldFocusFileIndex = focusNode.beatmapIndex;
|
||||
focusNode = null;
|
||||
BeatmapSetList.get().init();
|
||||
SongMenu.this.setFocus(oldFocusBase, oldFocusFileIndex, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean menuClicked(int index) {
|
||||
if (isInputBlocked())
|
||||
return false;
|
||||
|
||||
SoundController.playSound(SoundEffect.MENUCLICK);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
sortMenu.setBackgroundColor(Colors.BLACK_BG_HOVER);
|
||||
sortMenu.setBorderColor(Colors.BLUE_DIVIDER);
|
||||
sortMenu.setChevronRightColor(Color.white);
|
||||
|
||||
// initialize group tabs
|
||||
for (BeatmapGroup group : BeatmapGroup.values())
|
||||
group.init(width, headerY - DIVIDER_LINE_WIDTH / 2);
|
||||
|
||||
// initialize score data buttons
|
||||
ScoreData.init(width, headerY + height * 0.01f);
|
||||
@@ -428,6 +462,7 @@ public class SongMenu extends BasicGameState {
|
||||
int width = container.getWidth();
|
||||
int height = container.getHeight();
|
||||
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
||||
boolean inDropdownMenu = sortMenu.contains(mouseX, mouseY);
|
||||
|
||||
// background
|
||||
if (focusNode != null) {
|
||||
@@ -499,7 +534,7 @@ public class SongMenu extends BasicGameState {
|
||||
g.clearClip();
|
||||
|
||||
// scroll bar
|
||||
if (focusScores.length > MAX_SCORE_BUTTONS && ScoreData.areaContains(mouseX, mouseY))
|
||||
if (focusScores.length > MAX_SCORE_BUTTONS && ScoreData.areaContains(mouseX, mouseY) && !inDropdownMenu)
|
||||
ScoreData.drawScrollbar(g, startScorePos.getPosition(), focusScores.length * ScoreData.getButtonOffset());
|
||||
}
|
||||
|
||||
@@ -517,7 +552,7 @@ public class SongMenu extends BasicGameState {
|
||||
Float position = MusicController.getBeatProgress();
|
||||
if (position == null) // default to 60bpm
|
||||
position = System.currentTimeMillis() % 1000 / 1000f;
|
||||
if (footerLogoButton.contains(mouseX, mouseY, 0.25f)) {
|
||||
if (footerLogoButton.contains(mouseX, mouseY, 0.25f) && !inDropdownMenu) {
|
||||
// hovering over logo: stop pulsing
|
||||
footerLogoButton.draw();
|
||||
} else {
|
||||
@@ -607,20 +642,22 @@ public class SongMenu extends BasicGameState {
|
||||
GameImage.SELECTION_OTHER_OPTIONS.getImage().drawCentered(selectOptionsButton.getX(), selectOptionsButton.getY());
|
||||
selectOptionsButton.draw();
|
||||
|
||||
// sorting tabs
|
||||
BeatmapSortOrder currentSort = BeatmapSortOrder.getSort();
|
||||
BeatmapSortOrder hoverSort = null;
|
||||
for (BeatmapSortOrder sort : BeatmapSortOrder.values()) {
|
||||
if (sort.contains(mouseX, mouseY)) {
|
||||
hoverSort = sort;
|
||||
break;
|
||||
// group tabs
|
||||
BeatmapGroup currentGroup = BeatmapGroup.current();
|
||||
BeatmapGroup hoverGroup = null;
|
||||
if (!inDropdownMenu) {
|
||||
for (BeatmapGroup group : BeatmapGroup.values()) {
|
||||
if (group.contains(mouseX, mouseY)) {
|
||||
hoverGroup = group;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (BeatmapSortOrder sort : BeatmapSortOrder.VALUES_REVERSED) {
|
||||
if (sort != currentSort)
|
||||
sort.draw(false, sort == hoverSort);
|
||||
for (BeatmapGroup group : BeatmapGroup.VALUES_REVERSED) {
|
||||
if (group != currentGroup)
|
||||
group.draw(false, group == hoverGroup);
|
||||
}
|
||||
currentSort.draw(true, false);
|
||||
currentGroup.draw(true, false);
|
||||
|
||||
// search
|
||||
boolean searchEmpty = search.getText().isEmpty();
|
||||
@@ -655,6 +692,9 @@ public class SongMenu extends BasicGameState {
|
||||
(searchResultString == null) ? "Searching..." : searchResultString, Color.white);
|
||||
}
|
||||
|
||||
// sorting options
|
||||
sortMenu.render(container, g);
|
||||
|
||||
// reloading beatmaps
|
||||
if (reloadThread != null) {
|
||||
// darken the screen
|
||||
@@ -678,15 +718,19 @@ public class SongMenu extends BasicGameState {
|
||||
if (reloadThread == null)
|
||||
MusicController.loopTrackIfEnded(true);
|
||||
else if (reloadThread.isFinished()) {
|
||||
BeatmapGroup.set(BeatmapGroup.ALL);
|
||||
BeatmapSortOrder.set(BeatmapSortOrder.TITLE);
|
||||
BeatmapSetList.get().reset();
|
||||
BeatmapSetList.get().init();
|
||||
if (BeatmapSetList.get().size() > 0) {
|
||||
// initialize song list
|
||||
BeatmapSetList.get().init();
|
||||
setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
|
||||
} else
|
||||
MusicController.playThemeSong();
|
||||
reloadThread = null;
|
||||
}
|
||||
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
||||
boolean inDropdownMenu = sortMenu.contains(mouseX, mouseY);
|
||||
UI.getBackButton().hoverUpdate(delta, mouseX, mouseY);
|
||||
selectModsButton.hoverUpdate(delta, mouseX, mouseY);
|
||||
selectRandomButton.hoverUpdate(delta, mouseX, mouseY);
|
||||
@@ -700,7 +744,9 @@ public class SongMenu extends BasicGameState {
|
||||
if (beatmapMenuTimer >= BEATMAP_MENU_DELAY) {
|
||||
beatmapMenuTimer = -1;
|
||||
if (focusNode != null) {
|
||||
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(MenuState.BEATMAP, focusNode);
|
||||
MenuState state = focusNode.getBeatmapSet().isFavorite() ?
|
||||
MenuState.BEATMAP_FAVORITE : MenuState.BEATMAP;
|
||||
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(state, focusNode);
|
||||
game.enterState(Opsu.STATE_BUTTONMENU);
|
||||
}
|
||||
return;
|
||||
@@ -789,7 +835,7 @@ public class SongMenu extends BasicGameState {
|
||||
|
||||
// mouse hover
|
||||
BeatmapSetNode node = getNodeAtPosition(mouseX, mouseY);
|
||||
if (node != null) {
|
||||
if (node != null && !inDropdownMenu) {
|
||||
if (node == hoverIndex)
|
||||
hoverOffset.update(delta);
|
||||
else {
|
||||
@@ -803,7 +849,9 @@ public class SongMenu extends BasicGameState {
|
||||
}
|
||||
|
||||
// tooltips
|
||||
if (focusScores != null && ScoreData.areaContains(mouseX, mouseY)) {
|
||||
if (sortMenu.baseContains(mouseX, mouseY))
|
||||
UI.updateTooltip(delta, "Sort by...", false);
|
||||
else if (focusScores != null && ScoreData.areaContains(mouseX, mouseY)) {
|
||||
int startScore = (int) (startScorePos.getPosition() / ScoreData.getButtonOffset());
|
||||
int offset = (int) (-startScorePos.getPosition() + startScore * ScoreData.getButtonOffset());
|
||||
int scoreButtons = Math.min(focusScores.length - startScore, MAX_SCORE_BUTTONS);
|
||||
@@ -830,11 +878,6 @@ public class SongMenu extends BasicGameState {
|
||||
if (isScrollingToFocusNode)
|
||||
return;
|
||||
|
||||
if (footerLogoButton.contains(x, y, 0.25f)) {
|
||||
startGame();
|
||||
return;
|
||||
}
|
||||
|
||||
songScrolling.pressed();
|
||||
startScorePos.pressed();
|
||||
}
|
||||
@@ -886,25 +929,42 @@ public class SongMenu extends BasicGameState {
|
||||
return;
|
||||
}
|
||||
|
||||
if (focusNode == null)
|
||||
return;
|
||||
|
||||
// sorting buttons
|
||||
for (BeatmapSortOrder sort : BeatmapSortOrder.values()) {
|
||||
if (sort.contains(x, y)) {
|
||||
if (sort != BeatmapSortOrder.getSort()) {
|
||||
BeatmapSortOrder.setSort(sort);
|
||||
// group tabs
|
||||
for (BeatmapGroup group : BeatmapGroup.values()) {
|
||||
if (group.contains(x, y)) {
|
||||
if (group != BeatmapGroup.current()) {
|
||||
BeatmapGroup.set(group);
|
||||
SoundController.playSound(SoundEffect.MENUCLICK);
|
||||
BeatmapSetNode oldFocusBase = BeatmapSetList.get().getBaseNode(focusNode.index);
|
||||
int oldFocusFileIndex = focusNode.beatmapIndex;
|
||||
focusNode = null;
|
||||
startNode = focusNode = null;
|
||||
oldFocusNode = null;
|
||||
randomStack = new Stack<SongNode>();
|
||||
songInfo = null;
|
||||
scoreMap = null;
|
||||
focusScores = null;
|
||||
search.setText("");
|
||||
searchTimer = SEARCH_DELAY;
|
||||
searchTransitionTimer = 0;
|
||||
searchResultString = null;
|
||||
BeatmapSetList.get().reset();
|
||||
BeatmapSetList.get().init();
|
||||
setFocus(oldFocusBase, oldFocusFileIndex, true, true);
|
||||
setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
|
||||
|
||||
if (BeatmapSetList.get().size() < 1 && group.getEmptyMessage() != null)
|
||||
UI.sendBarNotification(group.getEmptyMessage());
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (focusNode == null)
|
||||
return;
|
||||
|
||||
// logo: start game
|
||||
if (footerLogoButton.contains(x, y, 0.25f)) {
|
||||
startGame();
|
||||
return;
|
||||
}
|
||||
|
||||
// song buttons
|
||||
BeatmapSetNode node = getNodeAtPosition(x, y);
|
||||
if (node != null) {
|
||||
@@ -984,6 +1044,7 @@ public class SongMenu extends BasicGameState {
|
||||
search.setText("");
|
||||
searchTimer = SEARCH_DELAY;
|
||||
searchTransitionTimer = 0;
|
||||
searchResultString = null;
|
||||
} else {
|
||||
// return to main menu
|
||||
SoundController.playSound(SoundEffect.MENUBACK);
|
||||
@@ -1005,7 +1066,11 @@ public class SongMenu extends BasicGameState {
|
||||
SongNode prev;
|
||||
if (randomStack.isEmpty() || (prev = randomStack.pop()) == null)
|
||||
break;
|
||||
setFocus(prev.getNode(), prev.getIndex(), true, true);
|
||||
BeatmapSetNode node = prev.getNode();
|
||||
int expandedIndex = BeatmapSetList.get().getExpandedIndex();
|
||||
if (node.index == expandedIndex)
|
||||
node = node.next; // move past base node
|
||||
setFocus(node, prev.getIndex(), true, true);
|
||||
} else {
|
||||
// random track, add previous to stack
|
||||
randomStack.push(new SongNode(BeatmapSetList.get().getBaseNode(focusNode.index), focusNode.beatmapIndex));
|
||||
@@ -1016,7 +1081,9 @@ public class SongMenu extends BasicGameState {
|
||||
if (focusNode == null)
|
||||
break;
|
||||
SoundController.playSound(SoundEffect.MENUHIT);
|
||||
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(MenuState.BEATMAP, focusNode);
|
||||
MenuState state = focusNode.getBeatmapSet().isFavorite() ?
|
||||
MenuState.BEATMAP_FAVORITE : MenuState.BEATMAP;
|
||||
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(state, focusNode);
|
||||
game.enterState(Opsu.STATE_BUTTONMENU);
|
||||
break;
|
||||
case Input.KEY_F5:
|
||||
@@ -1190,6 +1257,8 @@ public class SongMenu extends BasicGameState {
|
||||
songChangeTimer.setTime(songChangeTimer.getDuration());
|
||||
musicIconBounceTimer.setTime(musicIconBounceTimer.getDuration());
|
||||
starStream.clear();
|
||||
sortMenu.activate();
|
||||
sortMenu.reset();
|
||||
|
||||
// reset song stack
|
||||
randomStack = new Stack<SongNode>();
|
||||
@@ -1241,6 +1310,15 @@ public class SongMenu extends BasicGameState {
|
||||
if (GameMod.AUTO.isActive())
|
||||
GameMod.AUTO.toggle(false);
|
||||
|
||||
// re-sort (in case play count updated)
|
||||
if (BeatmapSortOrder.current() == BeatmapSortOrder.PLAYS) {
|
||||
BeatmapSetNode oldFocusBase = BeatmapSetList.get().getBaseNode(focusNode.index);
|
||||
int oldFocusFileIndex = focusNode.beatmapIndex;
|
||||
focusNode = null;
|
||||
BeatmapSetList.get().init();
|
||||
setFocus(oldFocusBase, oldFocusFileIndex, true, true);
|
||||
}
|
||||
|
||||
resetGame = false;
|
||||
}
|
||||
|
||||
@@ -1325,6 +1403,19 @@ public class SongMenu extends BasicGameState {
|
||||
case RELOAD: // reload beatmaps
|
||||
reloadBeatmaps(true);
|
||||
break;
|
||||
case BEATMAP_FAVORITE: // removed favorite, reset beatmap list
|
||||
if (BeatmapGroup.current() == BeatmapGroup.FAVORITE) {
|
||||
startNode = focusNode = null;
|
||||
oldFocusNode = null;
|
||||
randomStack = new Stack<SongNode>();
|
||||
songInfo = null;
|
||||
scoreMap = null;
|
||||
focusScores = null;
|
||||
BeatmapSetList.get().reset();
|
||||
BeatmapSetList.get().init();
|
||||
setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -1338,6 +1429,7 @@ public class SongMenu extends BasicGameState {
|
||||
public void leave(GameContainer container, StateBasedGame game)
|
||||
throws SlickException {
|
||||
search.setFocus(false);
|
||||
sortMenu.deactivate();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user