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:
parent
4446487575
commit
ed06a8b0ac
|
@ -21,7 +21,9 @@ package itdelatrisu.opsu;
|
||||||
import itdelatrisu.opsu.audio.MusicController;
|
import itdelatrisu.opsu.audio.MusicController;
|
||||||
import itdelatrisu.opsu.audio.SoundController;
|
import itdelatrisu.opsu.audio.SoundController;
|
||||||
import itdelatrisu.opsu.beatmap.Beatmap;
|
import itdelatrisu.opsu.beatmap.Beatmap;
|
||||||
|
import itdelatrisu.opsu.beatmap.BeatmapGroup;
|
||||||
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
||||||
|
import itdelatrisu.opsu.beatmap.BeatmapSortOrder;
|
||||||
import itdelatrisu.opsu.beatmap.BeatmapWatchService;
|
import itdelatrisu.opsu.beatmap.BeatmapWatchService;
|
||||||
import itdelatrisu.opsu.downloads.DownloadList;
|
import itdelatrisu.opsu.downloads.DownloadList;
|
||||||
import itdelatrisu.opsu.downloads.Updater;
|
import itdelatrisu.opsu.downloads.Updater;
|
||||||
|
@ -145,6 +147,8 @@ public class Container extends AppGameContainer {
|
||||||
SoundController.stopTrack();
|
SoundController.stopTrack();
|
||||||
|
|
||||||
// reset BeatmapSetList data
|
// reset BeatmapSetList data
|
||||||
|
BeatmapGroup.set(BeatmapGroup.ALL);
|
||||||
|
BeatmapSortOrder.set(BeatmapSortOrder.TITLE);
|
||||||
if (BeatmapSetList.get() != null)
|
if (BeatmapSetList.get() != null)
|
||||||
BeatmapSetList.get().reset();
|
BeatmapSetList.get().reset();
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,18 @@ public class Beatmap implements Comparable<Beatmap> {
|
||||||
/** The star rating. */
|
/** The star rating. */
|
||||||
public double starRating = -1;
|
public double starRating = -1;
|
||||||
|
|
||||||
|
/** The timestamp this beatmap was first loaded. */
|
||||||
|
public long dateAdded = 0;
|
||||||
|
|
||||||
|
/** Whether this beatmap is marked as a "favorite". */
|
||||||
|
public boolean favorite = false;
|
||||||
|
|
||||||
|
/** Total number of times this beatmap has been played. */
|
||||||
|
public int playCount = 0;
|
||||||
|
|
||||||
|
/** The last time this beatmap was played (timestamp). */
|
||||||
|
public long lastPlayed = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [General]
|
* [General]
|
||||||
*/
|
*/
|
||||||
|
@ -501,4 +513,12 @@ public class Beatmap implements Comparable<Beatmap> {
|
||||||
String[] rgb = s.split(",");
|
String[] rgb = s.split(",");
|
||||||
this.sliderBorder = new Color(new Color(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2])));
|
this.sliderBorder = new Color(new Color(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2])));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the play counter and last played time.
|
||||||
|
*/
|
||||||
|
public void incrementPlayCounter() {
|
||||||
|
this.playCount++;
|
||||||
|
this.lastPlayed = System.currentTimeMillis();
|
||||||
|
}
|
||||||
}
|
}
|
179
src/itdelatrisu/opsu/beatmap/BeatmapGroup.java
Normal file
179
src/itdelatrisu/opsu/beatmap/BeatmapGroup.java
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
package itdelatrisu.opsu.beatmap;
|
||||||
|
|
||||||
|
import itdelatrisu.opsu.GameImage;
|
||||||
|
import itdelatrisu.opsu.ui.MenuButton;
|
||||||
|
import itdelatrisu.opsu.ui.UI;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.PriorityQueue;
|
||||||
|
|
||||||
|
import org.newdawn.slick.Image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Beatmap groups.
|
||||||
|
*/
|
||||||
|
public enum BeatmapGroup {
|
||||||
|
/** All beatmaps (no filter). */
|
||||||
|
ALL (0, "All Songs", null),
|
||||||
|
|
||||||
|
/** Most recently played beatmaps. */
|
||||||
|
RECENT (1, "Last Played", "Your recently played beatmaps will appear in this list!") {
|
||||||
|
/** Number of elements to show. */
|
||||||
|
private static final int K = 20;
|
||||||
|
|
||||||
|
/** Returns the latest "last played" time in a beatmap set. */
|
||||||
|
private long lastPlayed(BeatmapSet set) {
|
||||||
|
long max = 0;
|
||||||
|
for (Beatmap beatmap : set) {
|
||||||
|
if (beatmap.lastPlayed > max)
|
||||||
|
max = beatmap.lastPlayed;
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArrayList<BeatmapSetNode> filter(ArrayList<BeatmapSetNode> list) {
|
||||||
|
// find top K elements
|
||||||
|
PriorityQueue<BeatmapSetNode> pq = new PriorityQueue<BeatmapSetNode>(K, new Comparator<BeatmapSetNode>() {
|
||||||
|
@Override
|
||||||
|
public int compare(BeatmapSetNode v, BeatmapSetNode w) {
|
||||||
|
return Long.compare(lastPlayed(v.getBeatmapSet()), lastPlayed(w.getBeatmapSet()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (BeatmapSetNode node : list) {
|
||||||
|
long timestamp = lastPlayed(node.getBeatmapSet());
|
||||||
|
if (timestamp == 0)
|
||||||
|
continue; // skip unplayed beatmaps
|
||||||
|
if (pq.size() < K || timestamp > lastPlayed(pq.peek().getBeatmapSet())) {
|
||||||
|
if (pq.size() == K)
|
||||||
|
pq.poll();
|
||||||
|
pq.add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return as list
|
||||||
|
ArrayList<BeatmapSetNode> filteredList = new ArrayList<BeatmapSetNode>();
|
||||||
|
for (BeatmapSetNode node : pq)
|
||||||
|
filteredList.add(node);
|
||||||
|
return filteredList;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** "Favorite" beatmaps. */
|
||||||
|
FAVORITE (2, "Favorites", "Right-click a beatmap to add it to your Favorites!") {
|
||||||
|
@Override
|
||||||
|
public ArrayList<BeatmapSetNode> filter(ArrayList<BeatmapSetNode> list) {
|
||||||
|
// find "favorite" beatmaps
|
||||||
|
ArrayList<BeatmapSetNode> filteredList = new ArrayList<BeatmapSetNode>();
|
||||||
|
for (BeatmapSetNode node : list) {
|
||||||
|
if (node.getBeatmapSet().isFavorite())
|
||||||
|
filteredList.add(node);
|
||||||
|
}
|
||||||
|
return filteredList;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The ID of the group (used for tab positioning). */
|
||||||
|
private final int id;
|
||||||
|
|
||||||
|
/** The name of the group. */
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
/** The message to display if this list is empty. */
|
||||||
|
private final String emptyMessage;
|
||||||
|
|
||||||
|
/** The tab associated with the group (displayed in Song Menu screen). */
|
||||||
|
private MenuButton tab;
|
||||||
|
|
||||||
|
/** Total number of groups. */
|
||||||
|
private static final int SIZE = values().length;
|
||||||
|
|
||||||
|
/** Array of BeatmapGroup objects in reverse order. */
|
||||||
|
public static final BeatmapGroup[] VALUES_REVERSED;
|
||||||
|
static {
|
||||||
|
VALUES_REVERSED = values();
|
||||||
|
Collections.reverse(Arrays.asList(VALUES_REVERSED));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Current group. */
|
||||||
|
private static BeatmapGroup currentGroup = ALL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current group.
|
||||||
|
* @return the current group
|
||||||
|
*/
|
||||||
|
public static BeatmapGroup current() { return currentGroup; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a new group.
|
||||||
|
* @param group the new group
|
||||||
|
*/
|
||||||
|
public static void set(BeatmapGroup group) { currentGroup = group; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
* @param id the ID of the group (for tab positioning)
|
||||||
|
* @param name the group name
|
||||||
|
* @param emptyMessage the message to display if this list is empty
|
||||||
|
*/
|
||||||
|
BeatmapGroup(int id, String name, String emptyMessage) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.emptyMessage = emptyMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the message to display if this list is empty.
|
||||||
|
* @return the message, or null if none
|
||||||
|
*/
|
||||||
|
public String getEmptyMessage() { return emptyMessage; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a filtered list of beatmap set nodes.
|
||||||
|
* @param list the unfiltered list
|
||||||
|
* @return the filtered list
|
||||||
|
*/
|
||||||
|
public ArrayList<BeatmapSetNode> filter(ArrayList<BeatmapSetNode> list) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the tab.
|
||||||
|
* @param containerWidth the container width
|
||||||
|
* @param bottomY the bottom y coordinate
|
||||||
|
*/
|
||||||
|
public void init(int containerWidth, float bottomY) {
|
||||||
|
Image tab = GameImage.MENU_TAB.getImage();
|
||||||
|
int tabWidth = tab.getWidth();
|
||||||
|
float buttonX = containerWidth / 2f;
|
||||||
|
float tabOffset = (containerWidth - buttonX - tabWidth) / (SIZE - 1);
|
||||||
|
if (tabOffset > tabWidth) { // prevent tabs from being spaced out
|
||||||
|
tabOffset = tabWidth;
|
||||||
|
buttonX = (containerWidth * 0.99f) - (tabWidth * SIZE);
|
||||||
|
}
|
||||||
|
this.tab = new MenuButton(tab,
|
||||||
|
(buttonX + (tabWidth / 2f)) + (id * tabOffset),
|
||||||
|
bottomY - (tab.getHeight() / 2f)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the coordinates are within the image bounds.
|
||||||
|
* @param x the x coordinate
|
||||||
|
* @param y the y coordinate
|
||||||
|
* @return true if within bounds
|
||||||
|
*/
|
||||||
|
public boolean contains(float x, float y) { return tab.contains(x, y); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the tab.
|
||||||
|
* @param selected whether the tab is selected (white) or not (red)
|
||||||
|
* @param isHover whether to include a hover effect (unselected only)
|
||||||
|
*/
|
||||||
|
public void draw(boolean selected, boolean isHover) {
|
||||||
|
UI.drawTab(tab.getX(), tab.getY(), name, selected, isHover);
|
||||||
|
}
|
||||||
|
}
|
|
@ -120,6 +120,7 @@ public class BeatmapParser {
|
||||||
|
|
||||||
// parse directories
|
// parse directories
|
||||||
BeatmapSetNode lastNode = null;
|
BeatmapSetNode lastNode = null;
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
for (File dir : dirs) {
|
for (File dir : dirs) {
|
||||||
currentDirectoryIndex++;
|
currentDirectoryIndex++;
|
||||||
if (!dir.isDirectory())
|
if (!dir.isDirectory())
|
||||||
|
@ -163,6 +164,7 @@ public class BeatmapParser {
|
||||||
|
|
||||||
// add to parsed beatmap list
|
// add to parsed beatmap list
|
||||||
if (beatmap != null) {
|
if (beatmap != null) {
|
||||||
|
beatmap.dateAdded = timestamp;
|
||||||
beatmaps.add(beatmap);
|
beatmaps.add(beatmap);
|
||||||
parsedBeatmaps.add(beatmap);
|
parsedBeatmaps.add(beatmap);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package itdelatrisu.opsu.beatmap;
|
package itdelatrisu.opsu.beatmap;
|
||||||
|
|
||||||
import itdelatrisu.opsu.GameMod;
|
import itdelatrisu.opsu.GameMod;
|
||||||
|
import itdelatrisu.opsu.db.BeatmapDB;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
@ -185,4 +186,26 @@ public class BeatmapSet implements Iterable<Beatmap> {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this beatmap set is a "favorite".
|
||||||
|
*/
|
||||||
|
public boolean isFavorite() {
|
||||||
|
for (Beatmap map : beatmaps) {
|
||||||
|
if (map.favorite)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the "favorite" status of this beatmap set.
|
||||||
|
* @param flag whether this beatmap set should have "favorite" status
|
||||||
|
*/
|
||||||
|
public void setFavorite(boolean flag) {
|
||||||
|
for (Beatmap map : beatmaps) {
|
||||||
|
map.favorite = flag;
|
||||||
|
BeatmapDB.updateFavoriteStatus(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,9 @@ public class BeatmapSetList {
|
||||||
/** Total number of beatmaps (i.e. Beatmap objects). */
|
/** Total number of beatmaps (i.e. Beatmap objects). */
|
||||||
private int mapCount = 0;
|
private int mapCount = 0;
|
||||||
|
|
||||||
|
/** List containing all nodes in the current group. */
|
||||||
|
private ArrayList<BeatmapSetNode> groupNodes;
|
||||||
|
|
||||||
/** Current list of nodes (subset of parsedNodes, used for searches). */
|
/** Current list of nodes (subset of parsedNodes, used for searches). */
|
||||||
private ArrayList<BeatmapSetNode> nodes;
|
private ArrayList<BeatmapSetNode> nodes;
|
||||||
|
|
||||||
|
@ -97,7 +100,7 @@ public class BeatmapSetList {
|
||||||
* This does not erase any parsed nodes.
|
* This does not erase any parsed nodes.
|
||||||
*/
|
*/
|
||||||
public void reset() {
|
public void reset() {
|
||||||
nodes = parsedNodes;
|
nodes = groupNodes = BeatmapGroup.current().filter(parsedNodes);
|
||||||
expandedIndex = -1;
|
expandedIndex = -1;
|
||||||
expandedStartNode = expandedEndNode = null;
|
expandedStartNode = expandedEndNode = null;
|
||||||
lastQuery = "";
|
lastQuery = "";
|
||||||
|
@ -168,6 +171,7 @@ public class BeatmapSetList {
|
||||||
Beatmap beatmap = beatmapSet.get(0);
|
Beatmap beatmap = beatmapSet.get(0);
|
||||||
nodes.remove(index);
|
nodes.remove(index);
|
||||||
parsedNodes.remove(eCur);
|
parsedNodes.remove(eCur);
|
||||||
|
groupNodes.remove(eCur);
|
||||||
mapCount -= beatmapSet.size();
|
mapCount -= beatmapSet.size();
|
||||||
if (beatmap.beatmapSetID > 0)
|
if (beatmap.beatmapSetID > 0)
|
||||||
MSIDdb.remove(beatmap.beatmapSetID);
|
MSIDdb.remove(beatmap.beatmapSetID);
|
||||||
|
@ -407,7 +411,7 @@ public class BeatmapSetList {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// sort the list
|
// sort the list
|
||||||
Collections.sort(nodes, BeatmapSortOrder.getSort().getComparator());
|
Collections.sort(nodes, BeatmapSortOrder.current().getComparator());
|
||||||
expandedIndex = -1;
|
expandedIndex = -1;
|
||||||
expandedStartNode = expandedEndNode = null;
|
expandedStartNode = expandedEndNode = null;
|
||||||
|
|
||||||
|
@ -444,7 +448,7 @@ public class BeatmapSetList {
|
||||||
|
|
||||||
// if empty query, reset to original list
|
// if empty query, reset to original list
|
||||||
if (query.isEmpty() || terms.isEmpty()) {
|
if (query.isEmpty() || terms.isEmpty()) {
|
||||||
nodes = parsedNodes;
|
nodes = groupNodes;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,14 +476,14 @@ public class BeatmapSetList {
|
||||||
String type = condType.remove();
|
String type = condType.remove();
|
||||||
String operator = condOperator.remove();
|
String operator = condOperator.remove();
|
||||||
float value = condValue.remove();
|
float value = condValue.remove();
|
||||||
for (BeatmapSetNode node : parsedNodes) {
|
for (BeatmapSetNode node : groupNodes) {
|
||||||
if (node.getBeatmapSet().matches(type, operator, value))
|
if (node.getBeatmapSet().matches(type, operator, value))
|
||||||
nodes.add(node);
|
nodes.add(node);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// normal term
|
// normal term
|
||||||
String term = terms.remove();
|
String term = terms.remove();
|
||||||
for (BeatmapSetNode node : parsedNodes) {
|
for (BeatmapSetNode node : groupNodes) {
|
||||||
if (node.getBeatmapSet().matches(term))
|
if (node.getBeatmapSet().matches(term))
|
||||||
nodes.add(node);
|
nodes.add(node);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,28 +18,19 @@
|
||||||
|
|
||||||
package itdelatrisu.opsu.beatmap;
|
package itdelatrisu.opsu.beatmap;
|
||||||
|
|
||||||
import itdelatrisu.opsu.GameImage;
|
|
||||||
import itdelatrisu.opsu.ui.MenuButton;
|
|
||||||
import itdelatrisu.opsu.ui.UI;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
|
||||||
import org.newdawn.slick.Image;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Beatmap sorting orders.
|
* Beatmap sorting orders.
|
||||||
*/
|
*/
|
||||||
public enum BeatmapSortOrder {
|
public enum BeatmapSortOrder {
|
||||||
TITLE (0, "Title", new TitleOrder()),
|
TITLE ("Title", new TitleOrder()),
|
||||||
ARTIST (1, "Artist", new ArtistOrder()),
|
ARTIST ("Artist", new ArtistOrder()),
|
||||||
CREATOR (2, "Creator", new CreatorOrder()),
|
CREATOR ("Creator", new CreatorOrder()),
|
||||||
BPM (3, "BPM", new BPMOrder()),
|
BPM ("BPM", new BPMOrder()),
|
||||||
LENGTH (4, "Length", new LengthOrder());
|
LENGTH ("Length", new LengthOrder()),
|
||||||
|
DATE ("Date Added", new DateOrder()),
|
||||||
/** The ID of the sort (used for tab positioning). */
|
PLAYS ("Most Played", new PlayOrder());
|
||||||
private final int id;
|
|
||||||
|
|
||||||
/** The name of the sort. */
|
/** The name of the sort. */
|
||||||
private final String name;
|
private final String name;
|
||||||
|
@ -47,19 +38,6 @@ public enum BeatmapSortOrder {
|
||||||
/** The comparator for the sort. */
|
/** The comparator for the sort. */
|
||||||
private final Comparator<BeatmapSetNode> comparator;
|
private final Comparator<BeatmapSetNode> comparator;
|
||||||
|
|
||||||
/** The tab associated with the sort (displayed in Song Menu screen). */
|
|
||||||
private MenuButton tab;
|
|
||||||
|
|
||||||
/** Total number of sorts. */
|
|
||||||
private static final int SIZE = values().length;
|
|
||||||
|
|
||||||
/** Array of BeatmapSortOrder objects in reverse order. */
|
|
||||||
public static final BeatmapSortOrder[] VALUES_REVERSED;
|
|
||||||
static {
|
|
||||||
VALUES_REVERSED = values();
|
|
||||||
Collections.reverse(Arrays.asList(VALUES_REVERSED));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Current sort. */
|
/** Current sort. */
|
||||||
private static BeatmapSortOrder currentSort = TITLE;
|
private static BeatmapSortOrder currentSort = TITLE;
|
||||||
|
|
||||||
|
@ -67,13 +45,13 @@ public enum BeatmapSortOrder {
|
||||||
* Returns the current sort.
|
* Returns the current sort.
|
||||||
* @return the current sort
|
* @return the current sort
|
||||||
*/
|
*/
|
||||||
public static BeatmapSortOrder getSort() { return currentSort; }
|
public static BeatmapSortOrder current() { return currentSort; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a new sort.
|
* Sets a new sort.
|
||||||
* @param sort the new sort
|
* @param sort the new sort
|
||||||
*/
|
*/
|
||||||
public static void setSort(BeatmapSortOrder sort) { BeatmapSortOrder.currentSort = sort; }
|
public static void set(BeatmapSortOrder sort) { currentSort = sort; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares two BeatmapSetNode objects by title.
|
* Compares two BeatmapSetNode objects by title.
|
||||||
|
@ -135,37 +113,57 @@ public enum BeatmapSortOrder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two BeatmapSetNode objects by date added.
|
||||||
|
* Uses the latest beatmap added in each set for comparison.
|
||||||
|
*/
|
||||||
|
private static class DateOrder implements Comparator<BeatmapSetNode> {
|
||||||
|
@Override
|
||||||
|
public int compare(BeatmapSetNode v, BeatmapSetNode w) {
|
||||||
|
long vMax = 0, wMax = 0;
|
||||||
|
for (Beatmap beatmap : v.getBeatmapSet()) {
|
||||||
|
if (beatmap.dateAdded > vMax)
|
||||||
|
vMax = beatmap.dateAdded;
|
||||||
|
}
|
||||||
|
for (Beatmap beatmap : w.getBeatmapSet()) {
|
||||||
|
if (beatmap.dateAdded > wMax)
|
||||||
|
wMax = beatmap.dateAdded;
|
||||||
|
}
|
||||||
|
return Long.compare(vMax, wMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two BeatmapSetNode objects by total plays
|
||||||
|
* (summed across all beatmaps in each set).
|
||||||
|
*/
|
||||||
|
private static class PlayOrder implements Comparator<BeatmapSetNode> {
|
||||||
|
@Override
|
||||||
|
public int compare(BeatmapSetNode v, BeatmapSetNode w) {
|
||||||
|
int vTotal = 0, wTotal = 0;
|
||||||
|
for (Beatmap beatmap : v.getBeatmapSet())
|
||||||
|
vTotal += beatmap.playCount;
|
||||||
|
for (Beatmap beatmap : w.getBeatmapSet())
|
||||||
|
wTotal += beatmap.playCount;
|
||||||
|
return Integer.compare(vTotal, wTotal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
* @param id the ID of the sort (for tab positioning)
|
|
||||||
* @param name the sort name
|
* @param name the sort name
|
||||||
* @param comparator the comparator for the sort
|
* @param comparator the comparator for the sort
|
||||||
*/
|
*/
|
||||||
BeatmapSortOrder(int id, String name, Comparator<BeatmapSetNode> comparator) {
|
BeatmapSortOrder(String name, Comparator<BeatmapSetNode> comparator) {
|
||||||
this.id = id;
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.comparator = comparator;
|
this.comparator = comparator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the sort tab.
|
* Returns the sort name.
|
||||||
* @param containerWidth the container width
|
* @return the name
|
||||||
* @param bottomY the bottom y coordinate
|
|
||||||
*/
|
*/
|
||||||
public void init(int containerWidth, float bottomY) {
|
public String getName() { return name; }
|
||||||
Image tab = GameImage.MENU_TAB.getImage();
|
|
||||||
int tabWidth = tab.getWidth();
|
|
||||||
float buttonX = containerWidth / 2f;
|
|
||||||
float tabOffset = (containerWidth - buttonX - tabWidth) / (SIZE - 1);
|
|
||||||
if (tabOffset > tabWidth) { // prevent tabs from being spaced out
|
|
||||||
tabOffset = tabWidth;
|
|
||||||
buttonX = (containerWidth * 0.99f) - (tabWidth * SIZE);
|
|
||||||
}
|
|
||||||
this.tab = new MenuButton(tab,
|
|
||||||
(buttonX + (tabWidth / 2f)) + (id * tabOffset),
|
|
||||||
bottomY - (tab.getHeight() / 2f)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the comparator for the sort.
|
* Returns the comparator for the sort.
|
||||||
|
@ -173,20 +171,6 @@ public enum BeatmapSortOrder {
|
||||||
*/
|
*/
|
||||||
public Comparator<BeatmapSetNode> getComparator() { return comparator; }
|
public Comparator<BeatmapSetNode> getComparator() { return comparator; }
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Checks if the coordinates are within the image bounds.
|
public String toString() { return name; }
|
||||||
* @param x the x coordinate
|
|
||||||
* @param y the y coordinate
|
|
||||||
* @return true if within bounds
|
|
||||||
*/
|
|
||||||
public boolean contains(float x, float y) { return tab.contains(x, y); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the sort tab.
|
|
||||||
* @param selected whether the tab is selected (white) or not (red)
|
|
||||||
* @param isHover whether to include a hover effect (unselected only)
|
|
||||||
*/
|
|
||||||
public void draw(boolean selected, boolean isHover) {
|
|
||||||
UI.drawTab(tab.getX(), tab.getY(), name, selected, isHover);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -30,6 +30,7 @@ import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -42,8 +43,30 @@ public class BeatmapDB {
|
||||||
/**
|
/**
|
||||||
* Current database version.
|
* Current database version.
|
||||||
* This value should be changed whenever the database format changes.
|
* This value should be changed whenever the database format changes.
|
||||||
|
* Add any update queries to the {@link #getUpdateQueries(int)} method.
|
||||||
*/
|
*/
|
||||||
private static final String DATABASE_VERSION = "2015-09-02";
|
private static final int DATABASE_VERSION = 20161222;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of SQL queries to apply, in order, to update from
|
||||||
|
* the given database version to the latest version.
|
||||||
|
* @param version the current version
|
||||||
|
* @return a list of SQL queries
|
||||||
|
*/
|
||||||
|
private static List<String> getUpdateQueries(int version) {
|
||||||
|
List<String> list = new LinkedList<String>();
|
||||||
|
if (version < 20161222) {
|
||||||
|
list.add("ALTER TABLE beatmaps ADD COLUMN dateAdded INTEGER");
|
||||||
|
list.add("ALTER TABLE beatmaps ADD COLUMN favorite BOOLEAN");
|
||||||
|
list.add("ALTER TABLE beatmaps ADD COLUMN playCount INTEGER");
|
||||||
|
list.add("ALTER TABLE beatmaps ADD COLUMN lastPlayed INTEGER");
|
||||||
|
list.add("UPDATE beatmaps SET dateAdded = 0, favorite = 0, playCount = 0, lastPlayed = 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add future updates here */
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
/** Minimum batch size ratio ({@code batchSize/cacheSize}) to invoke batch loading. */
|
/** Minimum batch size ratio ({@code batchSize/cacheSize}) to invoke batch loading. */
|
||||||
private static final float LOAD_BATCH_MIN_RATIO = 0.2f;
|
private static final float LOAD_BATCH_MIN_RATIO = 0.2f;
|
||||||
|
@ -58,7 +81,9 @@ public class BeatmapDB {
|
||||||
private static Connection connection;
|
private static Connection connection;
|
||||||
|
|
||||||
/** Query statements. */
|
/** Query statements. */
|
||||||
private static PreparedStatement insertStmt, selectStmt, deleteMapStmt, deleteGroupStmt, setStarsStmt, updateSizeStmt;
|
private static PreparedStatement
|
||||||
|
insertStmt, selectStmt, deleteMapStmt, deleteGroupStmt,
|
||||||
|
setStarsStmt, updatePlayStatsStmt, setFavoriteStmt, updateSizeStmt;
|
||||||
|
|
||||||
/** Current size of beatmap cache table. */
|
/** Current size of beatmap cache table. */
|
||||||
private static int cacheSize = -1;
|
private static int cacheSize = -1;
|
||||||
|
@ -75,6 +100,9 @@ public class BeatmapDB {
|
||||||
if (connection == null)
|
if (connection == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// run any database updates
|
||||||
|
updateDatabase();
|
||||||
|
|
||||||
// create the database
|
// create the database
|
||||||
createDatabase();
|
createDatabase();
|
||||||
|
|
||||||
|
@ -88,20 +116,21 @@ public class BeatmapDB {
|
||||||
// retrieve the cache size
|
// retrieve the cache size
|
||||||
getCacheSize();
|
getCacheSize();
|
||||||
|
|
||||||
// check the database version
|
|
||||||
checkVersion();
|
|
||||||
|
|
||||||
// prepare sql statements (not used here)
|
// prepare sql statements (not used here)
|
||||||
try {
|
try {
|
||||||
insertStmt = connection.prepareStatement(
|
insertStmt = connection.prepareStatement(
|
||||||
"INSERT INTO beatmaps VALUES (" +
|
"INSERT INTO beatmaps VALUES (" +
|
||||||
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," +
|
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," +
|
||||||
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," +
|
||||||
|
"?, ?, ?, ?, ?, ?" +
|
||||||
|
")"
|
||||||
);
|
);
|
||||||
selectStmt = connection.prepareStatement("SELECT * FROM beatmaps WHERE dir = ? AND file = ?");
|
selectStmt = connection.prepareStatement("SELECT * FROM beatmaps WHERE dir = ? AND file = ?");
|
||||||
deleteMapStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ? AND file = ?");
|
deleteMapStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ? AND file = ?");
|
||||||
deleteGroupStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ?");
|
deleteGroupStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ?");
|
||||||
setStarsStmt = connection.prepareStatement("UPDATE beatmaps SET stars = ? WHERE dir = ? AND file = ?");
|
setStarsStmt = connection.prepareStatement("UPDATE beatmaps SET stars = ? WHERE dir = ? AND file = ?");
|
||||||
|
updatePlayStatsStmt = connection.prepareStatement("UPDATE beatmaps SET playCount = ?, lastPlayed = ? WHERE dir = ? AND file = ?");
|
||||||
|
setFavoriteStmt = connection.prepareStatement("UPDATE beatmaps SET favorite = ? WHERE dir = ? AND file = ?");
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
ErrorHandler.error("Failed to prepare beatmap statements.", e, true);
|
ErrorHandler.error("Failed to prepare beatmap statements.", e, true);
|
||||||
}
|
}
|
||||||
|
@ -124,7 +153,8 @@ public class BeatmapDB {
|
||||||
"audioFile TEXT, audioLeadIn INTEGER, previewTime INTEGER, countdown INTEGER, sampleSet TEXT, stackLeniency REAL, " +
|
"audioFile TEXT, audioLeadIn INTEGER, previewTime INTEGER, countdown INTEGER, sampleSet TEXT, stackLeniency REAL, " +
|
||||||
"mode INTEGER, letterboxInBreaks BOOLEAN, widescreenStoryboard BOOLEAN, epilepsyWarning BOOLEAN, " +
|
"mode INTEGER, letterboxInBreaks BOOLEAN, widescreenStoryboard BOOLEAN, epilepsyWarning BOOLEAN, " +
|
||||||
"bg TEXT, sliderBorder TEXT, timingPoints TEXT, breaks TEXT, combo TEXT, " +
|
"bg TEXT, sliderBorder TEXT, timingPoints TEXT, breaks TEXT, combo TEXT, " +
|
||||||
"md5hash TEXT, stars REAL" +
|
"md5hash TEXT, stars REAL, " +
|
||||||
|
"dateAdded INTEGER, favorite BOOLEAN, playCount INTEGER, lastPlayed INTEGER" +
|
||||||
"); " +
|
"); " +
|
||||||
"CREATE TABLE IF NOT EXISTS info (" +
|
"CREATE TABLE IF NOT EXISTS info (" +
|
||||||
"key TEXT NOT NULL UNIQUE, value TEXT" +
|
"key TEXT NOT NULL UNIQUE, value TEXT" +
|
||||||
|
@ -145,29 +175,54 @@ public class BeatmapDB {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the stored table version, clears the beatmap database if different
|
* Applies any database updates by comparing the current version to the
|
||||||
* from the current version, then updates the version field.
|
* stored version. Does nothing if tables have not been created.
|
||||||
*/
|
*/
|
||||||
private static void checkVersion() {
|
private static void updateDatabase() {
|
||||||
try (Statement stmt = connection.createStatement()) {
|
try (Statement stmt = connection.createStatement()) {
|
||||||
// get the stored version
|
int version = 0;
|
||||||
String sql = "SELECT value FROM info WHERE key = 'version'";
|
|
||||||
ResultSet rs = stmt.executeQuery(sql);
|
|
||||||
String version = (rs.next()) ? rs.getString(1) : "";
|
|
||||||
rs.close();
|
|
||||||
|
|
||||||
// if different from current version, clear the database
|
// if 'info' table does not exist, assume version 0 and apply all updates
|
||||||
if (!version.equals(DATABASE_VERSION)) {
|
String sql = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'info'";
|
||||||
clearDatabase();
|
ResultSet rs = stmt.executeQuery(sql);
|
||||||
|
boolean infoExists = rs.isBeforeFirst();
|
||||||
|
rs.close();
|
||||||
|
if (!infoExists) {
|
||||||
|
// if 'beatmaps' table also does not exist, databases not yet created
|
||||||
|
sql = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'beatmaps'";
|
||||||
|
ResultSet beatmapsRS = stmt.executeQuery(sql);
|
||||||
|
boolean beatmapsExists = beatmapsRS.isBeforeFirst();
|
||||||
|
beatmapsRS.close();
|
||||||
|
if (!beatmapsExists)
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// try to retrieve stored version
|
||||||
|
sql = "SELECT value FROM info WHERE key = 'version'";
|
||||||
|
ResultSet versionRS = stmt.executeQuery(sql);
|
||||||
|
String versionString = (versionRS.next()) ? versionRS.getString(1) : "0";
|
||||||
|
versionRS.close();
|
||||||
|
try {
|
||||||
|
version = Integer.parseInt(versionString);
|
||||||
|
} catch (NumberFormatException e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// database versions match
|
||||||
|
if (version >= DATABASE_VERSION)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// apply updates
|
||||||
|
for (String query : getUpdateQueries(version))
|
||||||
|
stmt.executeUpdate(query);
|
||||||
|
|
||||||
// update version
|
// update version
|
||||||
|
if (infoExists) {
|
||||||
PreparedStatement ps = connection.prepareStatement("REPLACE INTO info (key, value) VALUES ('version', ?)");
|
PreparedStatement ps = connection.prepareStatement("REPLACE INTO info (key, value) VALUES ('version', ?)");
|
||||||
ps.setString(1, DATABASE_VERSION);
|
ps.setString(1, Integer.toString(DATABASE_VERSION));
|
||||||
ps.executeUpdate();
|
ps.executeUpdate();
|
||||||
ps.close();
|
ps.close();
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
ErrorHandler.error("Beatmap database version checks failed.", e, true);
|
ErrorHandler.error("Failed to update beatmap database.", e, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,6 +399,10 @@ public class BeatmapDB {
|
||||||
stmt.setString(40, beatmap.comboToString());
|
stmt.setString(40, beatmap.comboToString());
|
||||||
stmt.setString(41, beatmap.md5Hash);
|
stmt.setString(41, beatmap.md5Hash);
|
||||||
stmt.setDouble(42, beatmap.starRating);
|
stmt.setDouble(42, beatmap.starRating);
|
||||||
|
stmt.setLong(43, beatmap.dateAdded);
|
||||||
|
stmt.setBoolean(44, beatmap.favorite);
|
||||||
|
stmt.setInt(45, beatmap.playCount);
|
||||||
|
stmt.setLong(46, beatmap.lastPlayed);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -487,6 +546,10 @@ public class BeatmapDB {
|
||||||
beatmap.sliderBorderFromString(rs.getString(37));
|
beatmap.sliderBorderFromString(rs.getString(37));
|
||||||
beatmap.md5Hash = rs.getString(41);
|
beatmap.md5Hash = rs.getString(41);
|
||||||
beatmap.starRating = rs.getDouble(42);
|
beatmap.starRating = rs.getDouble(42);
|
||||||
|
beatmap.dateAdded = rs.getLong(43);
|
||||||
|
beatmap.favorite = rs.getBoolean(44);
|
||||||
|
beatmap.playCount = rs.getInt(45);
|
||||||
|
beatmap.lastPlayed = rs.getLong(46);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -593,6 +656,45 @@ public class BeatmapDB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the play statistics for a beatmap in the database.
|
||||||
|
* @param beatmap the beatmap
|
||||||
|
*/
|
||||||
|
public static void updatePlayStatistics(Beatmap beatmap) {
|
||||||
|
if (connection == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
updatePlayStatsStmt.setInt(1, beatmap.playCount);
|
||||||
|
updatePlayStatsStmt.setLong(2, beatmap.lastPlayed);
|
||||||
|
updatePlayStatsStmt.setString(3, beatmap.getFile().getParentFile().getName());
|
||||||
|
updatePlayStatsStmt.setString(4, beatmap.getFile().getName());
|
||||||
|
updatePlayStatsStmt.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
ErrorHandler.error(String.format("Failed to update play statistics for beatmap '%s' in database.",
|
||||||
|
beatmap.toString()), e, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the "favorite" status for a beatmap in the database.
|
||||||
|
* @param beatmap the beatmap
|
||||||
|
*/
|
||||||
|
public static void updateFavoriteStatus(Beatmap beatmap) {
|
||||||
|
if (connection == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setFavoriteStmt.setBoolean(1, beatmap.favorite);
|
||||||
|
setFavoriteStmt.setString(2, beatmap.getFile().getParentFile().getName());
|
||||||
|
setFavoriteStmt.setString(3, beatmap.getFile().getName());
|
||||||
|
setFavoriteStmt.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
ErrorHandler.error(String.format("Failed to update favorite status for beatmap '%s' in database.",
|
||||||
|
beatmap.toString()), e, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the connection to the database.
|
* Closes the connection to the database.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -46,8 +46,8 @@ import org.newdawn.slick.Input;
|
||||||
import org.newdawn.slick.SlickException;
|
import org.newdawn.slick.SlickException;
|
||||||
import org.newdawn.slick.state.BasicGameState;
|
import org.newdawn.slick.state.BasicGameState;
|
||||||
import org.newdawn.slick.state.StateBasedGame;
|
import org.newdawn.slick.state.StateBasedGame;
|
||||||
import org.newdawn.slick.state.transition.FadeInTransition;
|
|
||||||
import org.newdawn.slick.state.transition.EmptyTransition;
|
import org.newdawn.slick.state.transition.EmptyTransition;
|
||||||
|
import org.newdawn.slick.state.transition.FadeInTransition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic button menu state.
|
* Generic button menu state.
|
||||||
|
@ -69,8 +69,8 @@ public class ButtonMenu extends BasicGameState {
|
||||||
Button.NO.click(container, game);
|
Button.NO.click(container, game);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/** The initial beatmap management screen. */
|
/** The initial beatmap management screen (for a non-"favorite" beatmap). */
|
||||||
BEATMAP (new Button[] { Button.CLEAR_SCORES, Button.DELETE, Button.CANCEL }) {
|
BEATMAP (new Button[] { Button.CLEAR_SCORES, Button.FAVORITE_ADD, Button.DELETE, Button.CANCEL }) {
|
||||||
@Override
|
@Override
|
||||||
public String[] getTitle(GameContainer container, StateBasedGame game) {
|
public String[] getTitle(GameContainer container, StateBasedGame game) {
|
||||||
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
|
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
|
||||||
|
@ -90,6 +90,23 @@ public class ButtonMenu extends BasicGameState {
|
||||||
super.scroll(container, game, newValue);
|
super.scroll(container, game, newValue);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/** The initial beatmap management screen (for a "favorite" beatmap). */
|
||||||
|
BEATMAP_FAVORITE (new Button[] { Button.CLEAR_SCORES, Button.FAVORITE_REMOVE, Button.DELETE, Button.CANCEL }) {
|
||||||
|
@Override
|
||||||
|
public String[] getTitle(GameContainer container, StateBasedGame game) {
|
||||||
|
return BEATMAP.getTitle(container, game);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leave(GameContainer container, StateBasedGame game) {
|
||||||
|
BEATMAP.leave(container, game);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void scroll(GameContainer container, StateBasedGame game, int newValue) {
|
||||||
|
BEATMAP.scroll(container, game, newValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
/** The beatmap deletion screen for a beatmap set with multiple beatmaps. */
|
/** The beatmap deletion screen for a beatmap set with multiple beatmaps. */
|
||||||
BEATMAP_DELETE_SELECT (new Button[] { Button.DELETE_GROUP, Button.DELETE_SONG, Button.CANCEL_DELETE }) {
|
BEATMAP_DELETE_SELECT (new Button[] { Button.DELETE_GROUP, Button.DELETE_SONG, Button.CANCEL_DELETE }) {
|
||||||
@Override
|
@Override
|
||||||
|
@ -468,6 +485,25 @@ public class ButtonMenu extends BasicGameState {
|
||||||
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
|
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
FAVORITE_ADD ("Add to Favorites", Color.blue) {
|
||||||
|
@Override
|
||||||
|
public void click(GameContainer container, StateBasedGame game) {
|
||||||
|
SoundController.playSound(SoundEffect.MENUHIT);
|
||||||
|
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
|
||||||
|
node.getBeatmapSet().setFavorite(true);
|
||||||
|
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
FAVORITE_REMOVE ("Remove from Favorites", Color.blue) {
|
||||||
|
@Override
|
||||||
|
public void click(GameContainer container, StateBasedGame game) {
|
||||||
|
SoundController.playSound(SoundEffect.MENUHIT);
|
||||||
|
BeatmapSetNode node = ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).getNode();
|
||||||
|
node.getBeatmapSet().setFavorite(false);
|
||||||
|
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).doStateActionOnLoad(MenuState.BEATMAP_FAVORITE);
|
||||||
|
game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition());
|
||||||
|
}
|
||||||
|
},
|
||||||
DELETE ("Delete...", Color.red) {
|
DELETE ("Delete...", Color.red) {
|
||||||
@Override
|
@Override
|
||||||
public void click(GameContainer container, StateBasedGame game) {
|
public void click(GameContainer container, StateBasedGame game) {
|
||||||
|
|
|
@ -1235,6 +1235,12 @@ public class Game extends BasicGameState {
|
||||||
|
|
||||||
// restart the game
|
// restart the game
|
||||||
if (restart != Restart.FALSE) {
|
if (restart != Restart.FALSE) {
|
||||||
|
// update play stats
|
||||||
|
if (restart == Restart.NEW) {
|
||||||
|
beatmap.incrementPlayCounter();
|
||||||
|
BeatmapDB.updatePlayStatistics(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
// load mods
|
// load mods
|
||||||
if (isReplay) {
|
if (isReplay) {
|
||||||
previousMods = GameMod.getModState();
|
previousMods = GameMod.getModState();
|
||||||
|
|
|
@ -32,6 +32,7 @@ import itdelatrisu.opsu.audio.SoundController;
|
||||||
import itdelatrisu.opsu.audio.SoundEffect;
|
import itdelatrisu.opsu.audio.SoundEffect;
|
||||||
import itdelatrisu.opsu.beatmap.Beatmap;
|
import itdelatrisu.opsu.beatmap.Beatmap;
|
||||||
import itdelatrisu.opsu.beatmap.BeatmapDifficultyCalculator;
|
import itdelatrisu.opsu.beatmap.BeatmapDifficultyCalculator;
|
||||||
|
import itdelatrisu.opsu.beatmap.BeatmapGroup;
|
||||||
import itdelatrisu.opsu.beatmap.BeatmapParser;
|
import itdelatrisu.opsu.beatmap.BeatmapParser;
|
||||||
import itdelatrisu.opsu.beatmap.BeatmapSet;
|
import itdelatrisu.opsu.beatmap.BeatmapSet;
|
||||||
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
||||||
|
@ -45,6 +46,7 @@ import itdelatrisu.opsu.db.BeatmapDB;
|
||||||
import itdelatrisu.opsu.db.ScoreDB;
|
import itdelatrisu.opsu.db.ScoreDB;
|
||||||
import itdelatrisu.opsu.states.ButtonMenu.MenuState;
|
import itdelatrisu.opsu.states.ButtonMenu.MenuState;
|
||||||
import itdelatrisu.opsu.ui.Colors;
|
import itdelatrisu.opsu.ui.Colors;
|
||||||
|
import itdelatrisu.opsu.ui.DropdownMenu;
|
||||||
import itdelatrisu.opsu.ui.Fonts;
|
import itdelatrisu.opsu.ui.Fonts;
|
||||||
import itdelatrisu.opsu.ui.KineticScrolling;
|
import itdelatrisu.opsu.ui.KineticScrolling;
|
||||||
import itdelatrisu.opsu.ui.MenuButton;
|
import itdelatrisu.opsu.ui.MenuButton;
|
||||||
|
@ -315,6 +317,9 @@ public class SongMenu extends BasicGameState {
|
||||||
/** Whether the menu is currently scrolling to the focus node (blocks other actions). */
|
/** Whether the menu is currently scrolling to the focus node (blocks other actions). */
|
||||||
private boolean isScrollingToFocusNode = false;
|
private boolean isScrollingToFocusNode = false;
|
||||||
|
|
||||||
|
/** Sort order dropdown menu. */
|
||||||
|
private DropdownMenu<BeatmapSortOrder> sortMenu;
|
||||||
|
|
||||||
// game-related variables
|
// game-related variables
|
||||||
private GameContainer container;
|
private GameContainer container;
|
||||||
private StateBasedGame game;
|
private StateBasedGame game;
|
||||||
|
@ -351,8 +356,37 @@ public class SongMenu extends BasicGameState {
|
||||||
footerLogoButton.setHoverExpand(1.2f);
|
footerLogoButton.setHoverExpand(1.2f);
|
||||||
|
|
||||||
// initialize sorts
|
// initialize sorts
|
||||||
for (BeatmapSortOrder sort : BeatmapSortOrder.values())
|
int sortWidth = (int) (width * 0.12f);
|
||||||
sort.init(width, headerY - SongMenu.DIVIDER_LINE_WIDTH / 2);
|
sortMenu = new DropdownMenu<BeatmapSortOrder>(container, BeatmapSortOrder.values(),
|
||||||
|
width * 0.87f, headerY - GameImage.MENU_TAB.getImage().getHeight() * 2.25f, sortWidth) {
|
||||||
|
@Override
|
||||||
|
public void itemSelected(int index, BeatmapSortOrder item) {
|
||||||
|
BeatmapSortOrder.set(item);
|
||||||
|
if (focusNode == null)
|
||||||
|
return;
|
||||||
|
BeatmapSetNode oldFocusBase = BeatmapSetList.get().getBaseNode(focusNode.index);
|
||||||
|
int oldFocusFileIndex = focusNode.beatmapIndex;
|
||||||
|
focusNode = null;
|
||||||
|
BeatmapSetList.get().init();
|
||||||
|
SongMenu.this.setFocus(oldFocusBase, oldFocusFileIndex, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean menuClicked(int index) {
|
||||||
|
if (isInputBlocked())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SoundController.playSound(SoundEffect.MENUCLICK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sortMenu.setBackgroundColor(Colors.BLACK_BG_HOVER);
|
||||||
|
sortMenu.setBorderColor(Colors.BLUE_DIVIDER);
|
||||||
|
sortMenu.setChevronRightColor(Color.white);
|
||||||
|
|
||||||
|
// initialize group tabs
|
||||||
|
for (BeatmapGroup group : BeatmapGroup.values())
|
||||||
|
group.init(width, headerY - DIVIDER_LINE_WIDTH / 2);
|
||||||
|
|
||||||
// initialize score data buttons
|
// initialize score data buttons
|
||||||
ScoreData.init(width, headerY + height * 0.01f);
|
ScoreData.init(width, headerY + height * 0.01f);
|
||||||
|
@ -428,6 +462,7 @@ public class SongMenu extends BasicGameState {
|
||||||
int width = container.getWidth();
|
int width = container.getWidth();
|
||||||
int height = container.getHeight();
|
int height = container.getHeight();
|
||||||
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
||||||
|
boolean inDropdownMenu = sortMenu.contains(mouseX, mouseY);
|
||||||
|
|
||||||
// background
|
// background
|
||||||
if (focusNode != null) {
|
if (focusNode != null) {
|
||||||
|
@ -499,7 +534,7 @@ public class SongMenu extends BasicGameState {
|
||||||
g.clearClip();
|
g.clearClip();
|
||||||
|
|
||||||
// scroll bar
|
// scroll bar
|
||||||
if (focusScores.length > MAX_SCORE_BUTTONS && ScoreData.areaContains(mouseX, mouseY))
|
if (focusScores.length > MAX_SCORE_BUTTONS && ScoreData.areaContains(mouseX, mouseY) && !inDropdownMenu)
|
||||||
ScoreData.drawScrollbar(g, startScorePos.getPosition(), focusScores.length * ScoreData.getButtonOffset());
|
ScoreData.drawScrollbar(g, startScorePos.getPosition(), focusScores.length * ScoreData.getButtonOffset());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -517,7 +552,7 @@ public class SongMenu extends BasicGameState {
|
||||||
Float position = MusicController.getBeatProgress();
|
Float position = MusicController.getBeatProgress();
|
||||||
if (position == null) // default to 60bpm
|
if (position == null) // default to 60bpm
|
||||||
position = System.currentTimeMillis() % 1000 / 1000f;
|
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
|
// hovering over logo: stop pulsing
|
||||||
footerLogoButton.draw();
|
footerLogoButton.draw();
|
||||||
} else {
|
} else {
|
||||||
|
@ -607,20 +642,22 @@ public class SongMenu extends BasicGameState {
|
||||||
GameImage.SELECTION_OTHER_OPTIONS.getImage().drawCentered(selectOptionsButton.getX(), selectOptionsButton.getY());
|
GameImage.SELECTION_OTHER_OPTIONS.getImage().drawCentered(selectOptionsButton.getX(), selectOptionsButton.getY());
|
||||||
selectOptionsButton.draw();
|
selectOptionsButton.draw();
|
||||||
|
|
||||||
// sorting tabs
|
// group tabs
|
||||||
BeatmapSortOrder currentSort = BeatmapSortOrder.getSort();
|
BeatmapGroup currentGroup = BeatmapGroup.current();
|
||||||
BeatmapSortOrder hoverSort = null;
|
BeatmapGroup hoverGroup = null;
|
||||||
for (BeatmapSortOrder sort : BeatmapSortOrder.values()) {
|
if (!inDropdownMenu) {
|
||||||
if (sort.contains(mouseX, mouseY)) {
|
for (BeatmapGroup group : BeatmapGroup.values()) {
|
||||||
hoverSort = sort;
|
if (group.contains(mouseX, mouseY)) {
|
||||||
|
hoverGroup = group;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (BeatmapSortOrder sort : BeatmapSortOrder.VALUES_REVERSED) {
|
|
||||||
if (sort != currentSort)
|
|
||||||
sort.draw(false, sort == hoverSort);
|
|
||||||
}
|
}
|
||||||
currentSort.draw(true, false);
|
for (BeatmapGroup group : BeatmapGroup.VALUES_REVERSED) {
|
||||||
|
if (group != currentGroup)
|
||||||
|
group.draw(false, group == hoverGroup);
|
||||||
|
}
|
||||||
|
currentGroup.draw(true, false);
|
||||||
|
|
||||||
// search
|
// search
|
||||||
boolean searchEmpty = search.getText().isEmpty();
|
boolean searchEmpty = search.getText().isEmpty();
|
||||||
|
@ -655,6 +692,9 @@ public class SongMenu extends BasicGameState {
|
||||||
(searchResultString == null) ? "Searching..." : searchResultString, Color.white);
|
(searchResultString == null) ? "Searching..." : searchResultString, Color.white);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sorting options
|
||||||
|
sortMenu.render(container, g);
|
||||||
|
|
||||||
// reloading beatmaps
|
// reloading beatmaps
|
||||||
if (reloadThread != null) {
|
if (reloadThread != null) {
|
||||||
// darken the screen
|
// darken the screen
|
||||||
|
@ -678,15 +718,19 @@ public class SongMenu extends BasicGameState {
|
||||||
if (reloadThread == null)
|
if (reloadThread == null)
|
||||||
MusicController.loopTrackIfEnded(true);
|
MusicController.loopTrackIfEnded(true);
|
||||||
else if (reloadThread.isFinished()) {
|
else if (reloadThread.isFinished()) {
|
||||||
|
BeatmapGroup.set(BeatmapGroup.ALL);
|
||||||
|
BeatmapSortOrder.set(BeatmapSortOrder.TITLE);
|
||||||
|
BeatmapSetList.get().reset();
|
||||||
|
BeatmapSetList.get().init();
|
||||||
if (BeatmapSetList.get().size() > 0) {
|
if (BeatmapSetList.get().size() > 0) {
|
||||||
// initialize song list
|
// initialize song list
|
||||||
BeatmapSetList.get().init();
|
|
||||||
setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
|
setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
|
||||||
} else
|
} else
|
||||||
MusicController.playThemeSong();
|
MusicController.playThemeSong();
|
||||||
reloadThread = null;
|
reloadThread = null;
|
||||||
}
|
}
|
||||||
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
||||||
|
boolean inDropdownMenu = sortMenu.contains(mouseX, mouseY);
|
||||||
UI.getBackButton().hoverUpdate(delta, mouseX, mouseY);
|
UI.getBackButton().hoverUpdate(delta, mouseX, mouseY);
|
||||||
selectModsButton.hoverUpdate(delta, mouseX, mouseY);
|
selectModsButton.hoverUpdate(delta, mouseX, mouseY);
|
||||||
selectRandomButton.hoverUpdate(delta, mouseX, mouseY);
|
selectRandomButton.hoverUpdate(delta, mouseX, mouseY);
|
||||||
|
@ -700,7 +744,9 @@ public class SongMenu extends BasicGameState {
|
||||||
if (beatmapMenuTimer >= BEATMAP_MENU_DELAY) {
|
if (beatmapMenuTimer >= BEATMAP_MENU_DELAY) {
|
||||||
beatmapMenuTimer = -1;
|
beatmapMenuTimer = -1;
|
||||||
if (focusNode != null) {
|
if (focusNode != null) {
|
||||||
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(MenuState.BEATMAP, focusNode);
|
MenuState state = focusNode.getBeatmapSet().isFavorite() ?
|
||||||
|
MenuState.BEATMAP_FAVORITE : MenuState.BEATMAP;
|
||||||
|
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(state, focusNode);
|
||||||
game.enterState(Opsu.STATE_BUTTONMENU);
|
game.enterState(Opsu.STATE_BUTTONMENU);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -789,7 +835,7 @@ public class SongMenu extends BasicGameState {
|
||||||
|
|
||||||
// mouse hover
|
// mouse hover
|
||||||
BeatmapSetNode node = getNodeAtPosition(mouseX, mouseY);
|
BeatmapSetNode node = getNodeAtPosition(mouseX, mouseY);
|
||||||
if (node != null) {
|
if (node != null && !inDropdownMenu) {
|
||||||
if (node == hoverIndex)
|
if (node == hoverIndex)
|
||||||
hoverOffset.update(delta);
|
hoverOffset.update(delta);
|
||||||
else {
|
else {
|
||||||
|
@ -803,7 +849,9 @@ public class SongMenu extends BasicGameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// tooltips
|
// tooltips
|
||||||
if (focusScores != null && ScoreData.areaContains(mouseX, mouseY)) {
|
if (sortMenu.baseContains(mouseX, mouseY))
|
||||||
|
UI.updateTooltip(delta, "Sort by...", false);
|
||||||
|
else if (focusScores != null && ScoreData.areaContains(mouseX, mouseY)) {
|
||||||
int startScore = (int) (startScorePos.getPosition() / ScoreData.getButtonOffset());
|
int startScore = (int) (startScorePos.getPosition() / ScoreData.getButtonOffset());
|
||||||
int offset = (int) (-startScorePos.getPosition() + startScore * ScoreData.getButtonOffset());
|
int offset = (int) (-startScorePos.getPosition() + startScore * ScoreData.getButtonOffset());
|
||||||
int scoreButtons = Math.min(focusScores.length - startScore, MAX_SCORE_BUTTONS);
|
int scoreButtons = Math.min(focusScores.length - startScore, MAX_SCORE_BUTTONS);
|
||||||
|
@ -830,11 +878,6 @@ public class SongMenu extends BasicGameState {
|
||||||
if (isScrollingToFocusNode)
|
if (isScrollingToFocusNode)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (footerLogoButton.contains(x, y, 0.25f)) {
|
|
||||||
startGame();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
songScrolling.pressed();
|
songScrolling.pressed();
|
||||||
startScorePos.pressed();
|
startScorePos.pressed();
|
||||||
}
|
}
|
||||||
|
@ -886,24 +929,41 @@ public class SongMenu extends BasicGameState {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// group tabs
|
||||||
|
for (BeatmapGroup group : BeatmapGroup.values()) {
|
||||||
|
if (group.contains(x, y)) {
|
||||||
|
if (group != BeatmapGroup.current()) {
|
||||||
|
BeatmapGroup.set(group);
|
||||||
|
SoundController.playSound(SoundEffect.MENUCLICK);
|
||||||
|
startNode = focusNode = null;
|
||||||
|
oldFocusNode = null;
|
||||||
|
randomStack = new Stack<SongNode>();
|
||||||
|
songInfo = null;
|
||||||
|
scoreMap = null;
|
||||||
|
focusScores = null;
|
||||||
|
search.setText("");
|
||||||
|
searchTimer = SEARCH_DELAY;
|
||||||
|
searchTransitionTimer = 0;
|
||||||
|
searchResultString = null;
|
||||||
|
BeatmapSetList.get().reset();
|
||||||
|
BeatmapSetList.get().init();
|
||||||
|
setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
|
||||||
|
|
||||||
|
if (BeatmapSetList.get().size() < 1 && group.getEmptyMessage() != null)
|
||||||
|
UI.sendBarNotification(group.getEmptyMessage());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (focusNode == null)
|
if (focusNode == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// sorting buttons
|
// logo: start game
|
||||||
for (BeatmapSortOrder sort : BeatmapSortOrder.values()) {
|
if (footerLogoButton.contains(x, y, 0.25f)) {
|
||||||
if (sort.contains(x, y)) {
|
startGame();
|
||||||
if (sort != BeatmapSortOrder.getSort()) {
|
|
||||||
BeatmapSortOrder.setSort(sort);
|
|
||||||
SoundController.playSound(SoundEffect.MENUCLICK);
|
|
||||||
BeatmapSetNode oldFocusBase = BeatmapSetList.get().getBaseNode(focusNode.index);
|
|
||||||
int oldFocusFileIndex = focusNode.beatmapIndex;
|
|
||||||
focusNode = null;
|
|
||||||
BeatmapSetList.get().init();
|
|
||||||
setFocus(oldFocusBase, oldFocusFileIndex, true, true);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// song buttons
|
// song buttons
|
||||||
BeatmapSetNode node = getNodeAtPosition(x, y);
|
BeatmapSetNode node = getNodeAtPosition(x, y);
|
||||||
|
@ -984,6 +1044,7 @@ public class SongMenu extends BasicGameState {
|
||||||
search.setText("");
|
search.setText("");
|
||||||
searchTimer = SEARCH_DELAY;
|
searchTimer = SEARCH_DELAY;
|
||||||
searchTransitionTimer = 0;
|
searchTransitionTimer = 0;
|
||||||
|
searchResultString = null;
|
||||||
} else {
|
} else {
|
||||||
// return to main menu
|
// return to main menu
|
||||||
SoundController.playSound(SoundEffect.MENUBACK);
|
SoundController.playSound(SoundEffect.MENUBACK);
|
||||||
|
@ -1005,7 +1066,11 @@ public class SongMenu extends BasicGameState {
|
||||||
SongNode prev;
|
SongNode prev;
|
||||||
if (randomStack.isEmpty() || (prev = randomStack.pop()) == null)
|
if (randomStack.isEmpty() || (prev = randomStack.pop()) == null)
|
||||||
break;
|
break;
|
||||||
setFocus(prev.getNode(), prev.getIndex(), true, true);
|
BeatmapSetNode node = prev.getNode();
|
||||||
|
int expandedIndex = BeatmapSetList.get().getExpandedIndex();
|
||||||
|
if (node.index == expandedIndex)
|
||||||
|
node = node.next; // move past base node
|
||||||
|
setFocus(node, prev.getIndex(), true, true);
|
||||||
} else {
|
} else {
|
||||||
// random track, add previous to stack
|
// random track, add previous to stack
|
||||||
randomStack.push(new SongNode(BeatmapSetList.get().getBaseNode(focusNode.index), focusNode.beatmapIndex));
|
randomStack.push(new SongNode(BeatmapSetList.get().getBaseNode(focusNode.index), focusNode.beatmapIndex));
|
||||||
|
@ -1016,7 +1081,9 @@ public class SongMenu extends BasicGameState {
|
||||||
if (focusNode == null)
|
if (focusNode == null)
|
||||||
break;
|
break;
|
||||||
SoundController.playSound(SoundEffect.MENUHIT);
|
SoundController.playSound(SoundEffect.MENUHIT);
|
||||||
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(MenuState.BEATMAP, focusNode);
|
MenuState state = focusNode.getBeatmapSet().isFavorite() ?
|
||||||
|
MenuState.BEATMAP_FAVORITE : MenuState.BEATMAP;
|
||||||
|
((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(state, focusNode);
|
||||||
game.enterState(Opsu.STATE_BUTTONMENU);
|
game.enterState(Opsu.STATE_BUTTONMENU);
|
||||||
break;
|
break;
|
||||||
case Input.KEY_F5:
|
case Input.KEY_F5:
|
||||||
|
@ -1190,6 +1257,8 @@ public class SongMenu extends BasicGameState {
|
||||||
songChangeTimer.setTime(songChangeTimer.getDuration());
|
songChangeTimer.setTime(songChangeTimer.getDuration());
|
||||||
musicIconBounceTimer.setTime(musicIconBounceTimer.getDuration());
|
musicIconBounceTimer.setTime(musicIconBounceTimer.getDuration());
|
||||||
starStream.clear();
|
starStream.clear();
|
||||||
|
sortMenu.activate();
|
||||||
|
sortMenu.reset();
|
||||||
|
|
||||||
// reset song stack
|
// reset song stack
|
||||||
randomStack = new Stack<SongNode>();
|
randomStack = new Stack<SongNode>();
|
||||||
|
@ -1241,6 +1310,15 @@ public class SongMenu extends BasicGameState {
|
||||||
if (GameMod.AUTO.isActive())
|
if (GameMod.AUTO.isActive())
|
||||||
GameMod.AUTO.toggle(false);
|
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;
|
resetGame = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1325,6 +1403,19 @@ public class SongMenu extends BasicGameState {
|
||||||
case RELOAD: // reload beatmaps
|
case RELOAD: // reload beatmaps
|
||||||
reloadBeatmaps(true);
|
reloadBeatmaps(true);
|
||||||
break;
|
break;
|
||||||
|
case BEATMAP_FAVORITE: // removed favorite, reset beatmap list
|
||||||
|
if (BeatmapGroup.current() == BeatmapGroup.FAVORITE) {
|
||||||
|
startNode = focusNode = null;
|
||||||
|
oldFocusNode = null;
|
||||||
|
randomStack = new Stack<SongNode>();
|
||||||
|
songInfo = null;
|
||||||
|
scoreMap = null;
|
||||||
|
focusScores = null;
|
||||||
|
BeatmapSetList.get().reset();
|
||||||
|
BeatmapSetList.get().init();
|
||||||
|
setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1338,6 +1429,7 @@ public class SongMenu extends BasicGameState {
|
||||||
public void leave(GameContainer container, StateBasedGame game)
|
public void leave(GameContainer container, StateBasedGame game)
|
||||||
throws SlickException {
|
throws SlickException {
|
||||||
search.setFocus(false);
|
search.setFocus(false);
|
||||||
|
sortMenu.deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -95,6 +95,9 @@ public class DropdownMenu<E> extends AbstractComponent {
|
||||||
/** The chevron images. */
|
/** The chevron images. */
|
||||||
private Image chevronDown, chevronRight;
|
private Image chevronDown, chevronRight;
|
||||||
|
|
||||||
|
/** Should the next click be blocked? */
|
||||||
|
private boolean blockClick = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new dropdown menu.
|
* Creates a new dropdown menu.
|
||||||
* @param container the container rendering this menu
|
* @param container the container rendering this menu
|
||||||
|
@ -327,6 +330,7 @@ public class DropdownMenu<E> extends AbstractComponent {
|
||||||
this.expanded = false;
|
this.expanded = false;
|
||||||
this.lastUpdateTime = 0;
|
this.lastUpdateTime = 0;
|
||||||
expandProgress.setTime(0);
|
expandProgress.setTime(0);
|
||||||
|
blockClick = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -349,9 +353,21 @@ public class DropdownMenu<E> extends AbstractComponent {
|
||||||
this.itemIndex = idx;
|
this.itemIndex = idx;
|
||||||
itemSelected(idx, items[idx]);
|
itemSelected(idx, items[idx]);
|
||||||
}
|
}
|
||||||
|
blockClick = true;
|
||||||
consumeEvent();
|
consumeEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(int button, int x, int y, int clickCount) {
|
||||||
|
if (!active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (blockClick) {
|
||||||
|
blockClick = false;
|
||||||
|
consumeEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification that a new item was selected (via override).
|
* Notification that a new item was selected (via override).
|
||||||
* @param index the index of the item selected
|
* @param index the index of the item selected
|
||||||
|
|
Loading…
Reference in New Issue
Block a user