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:
@@ -68,6 +68,18 @@ public class Beatmap implements Comparable<Beatmap> {
|
||||
/** The star rating. */
|
||||
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]
|
||||
*/
|
||||
@@ -501,4 +513,12 @@ public class Beatmap implements Comparable<Beatmap> {
|
||||
String[] rgb = s.split(",");
|
||||
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
|
||||
BeatmapSetNode lastNode = null;
|
||||
long timestamp = System.currentTimeMillis();
|
||||
for (File dir : dirs) {
|
||||
currentDirectoryIndex++;
|
||||
if (!dir.isDirectory())
|
||||
@@ -163,6 +164,7 @@ public class BeatmapParser {
|
||||
|
||||
// add to parsed beatmap list
|
||||
if (beatmap != null) {
|
||||
beatmap.dateAdded = timestamp;
|
||||
beatmaps.add(beatmap);
|
||||
parsedBeatmaps.add(beatmap);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package itdelatrisu.opsu.beatmap;
|
||||
|
||||
import itdelatrisu.opsu.GameMod;
|
||||
import itdelatrisu.opsu.db.BeatmapDB;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
@@ -185,4 +186,26 @@ public class BeatmapSet implements Iterable<Beatmap> {
|
||||
|
||||
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). */
|
||||
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). */
|
||||
private ArrayList<BeatmapSetNode> nodes;
|
||||
|
||||
@@ -97,7 +100,7 @@ public class BeatmapSetList {
|
||||
* This does not erase any parsed nodes.
|
||||
*/
|
||||
public void reset() {
|
||||
nodes = parsedNodes;
|
||||
nodes = groupNodes = BeatmapGroup.current().filter(parsedNodes);
|
||||
expandedIndex = -1;
|
||||
expandedStartNode = expandedEndNode = null;
|
||||
lastQuery = "";
|
||||
@@ -168,6 +171,7 @@ public class BeatmapSetList {
|
||||
Beatmap beatmap = beatmapSet.get(0);
|
||||
nodes.remove(index);
|
||||
parsedNodes.remove(eCur);
|
||||
groupNodes.remove(eCur);
|
||||
mapCount -= beatmapSet.size();
|
||||
if (beatmap.beatmapSetID > 0)
|
||||
MSIDdb.remove(beatmap.beatmapSetID);
|
||||
@@ -407,7 +411,7 @@ public class BeatmapSetList {
|
||||
return;
|
||||
|
||||
// sort the list
|
||||
Collections.sort(nodes, BeatmapSortOrder.getSort().getComparator());
|
||||
Collections.sort(nodes, BeatmapSortOrder.current().getComparator());
|
||||
expandedIndex = -1;
|
||||
expandedStartNode = expandedEndNode = null;
|
||||
|
||||
@@ -444,7 +448,7 @@ public class BeatmapSetList {
|
||||
|
||||
// if empty query, reset to original list
|
||||
if (query.isEmpty() || terms.isEmpty()) {
|
||||
nodes = parsedNodes;
|
||||
nodes = groupNodes;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -472,14 +476,14 @@ public class BeatmapSetList {
|
||||
String type = condType.remove();
|
||||
String operator = condOperator.remove();
|
||||
float value = condValue.remove();
|
||||
for (BeatmapSetNode node : parsedNodes) {
|
||||
for (BeatmapSetNode node : groupNodes) {
|
||||
if (node.getBeatmapSet().matches(type, operator, value))
|
||||
nodes.add(node);
|
||||
}
|
||||
} else {
|
||||
// normal term
|
||||
String term = terms.remove();
|
||||
for (BeatmapSetNode node : parsedNodes) {
|
||||
for (BeatmapSetNode node : groupNodes) {
|
||||
if (node.getBeatmapSet().matches(term))
|
||||
nodes.add(node);
|
||||
}
|
||||
|
||||
@@ -18,28 +18,19 @@
|
||||
|
||||
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 org.newdawn.slick.Image;
|
||||
|
||||
/**
|
||||
* Beatmap sorting orders.
|
||||
*/
|
||||
public enum BeatmapSortOrder {
|
||||
TITLE (0, "Title", new TitleOrder()),
|
||||
ARTIST (1, "Artist", new ArtistOrder()),
|
||||
CREATOR (2, "Creator", new CreatorOrder()),
|
||||
BPM (3, "BPM", new BPMOrder()),
|
||||
LENGTH (4, "Length", new LengthOrder());
|
||||
|
||||
/** The ID of the sort (used for tab positioning). */
|
||||
private final int id;
|
||||
TITLE ("Title", new TitleOrder()),
|
||||
ARTIST ("Artist", new ArtistOrder()),
|
||||
CREATOR ("Creator", new CreatorOrder()),
|
||||
BPM ("BPM", new BPMOrder()),
|
||||
LENGTH ("Length", new LengthOrder()),
|
||||
DATE ("Date Added", new DateOrder()),
|
||||
PLAYS ("Most Played", new PlayOrder());
|
||||
|
||||
/** The name of the sort. */
|
||||
private final String name;
|
||||
@@ -47,19 +38,6 @@ public enum BeatmapSortOrder {
|
||||
/** The comparator for the sort. */
|
||||
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. */
|
||||
private static BeatmapSortOrder currentSort = TITLE;
|
||||
|
||||
@@ -67,13 +45,13 @@ public enum BeatmapSortOrder {
|
||||
* Returns the current sort.
|
||||
* @return the current sort
|
||||
*/
|
||||
public static BeatmapSortOrder getSort() { return currentSort; }
|
||||
public static BeatmapSortOrder current() { return currentSort; }
|
||||
|
||||
/**
|
||||
* Sets a 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.
|
||||
@@ -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.
|
||||
* @param id the ID of the sort (for tab positioning)
|
||||
* @param name the sort name
|
||||
* @param comparator the comparator for the sort
|
||||
*/
|
||||
BeatmapSortOrder(int id, String name, Comparator<BeatmapSetNode> comparator) {
|
||||
this.id = id;
|
||||
BeatmapSortOrder(String name, Comparator<BeatmapSetNode> comparator) {
|
||||
this.name = name;
|
||||
this.comparator = comparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the sort tab.
|
||||
* @param containerWidth the container width
|
||||
* @param bottomY the bottom y coordinate
|
||||
* Returns the sort name.
|
||||
* @return the name
|
||||
*/
|
||||
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)
|
||||
);
|
||||
}
|
||||
public String getName() { return name; }
|
||||
|
||||
/**
|
||||
* Returns the comparator for the sort.
|
||||
@@ -173,20 +171,6 @@ public enum BeatmapSortOrder {
|
||||
*/
|
||||
public Comparator<BeatmapSetNode> getComparator() { return comparator; }
|
||||
|
||||
/**
|
||||
* 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 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);
|
||||
}
|
||||
@Override
|
||||
public String toString() { return name; }
|
||||
}
|
||||
Reference in New Issue
Block a user