Merge remote-tracking branch 'remotes/original/master' into upstream

# Conflicts:
#	src/itdelatrisu/opsu/Container.java
#	src/itdelatrisu/opsu/GameData.java
#	src/itdelatrisu/opsu/Options.java
#	src/itdelatrisu/opsu/audio/MusicController.java
#	src/itdelatrisu/opsu/objects/Circle.java
#	src/itdelatrisu/opsu/objects/Slider.java
#	src/itdelatrisu/opsu/render/CurveRenderState.java
#	src/itdelatrisu/opsu/states/Game.java
#	src/itdelatrisu/opsu/states/MainMenu.java
#	src/itdelatrisu/opsu/states/SongMenu.java
#	src/itdelatrisu/opsu/ui/Colors.java
#	src/itdelatrisu/opsu/ui/MenuButton.java
This commit is contained in:
yugecin
2016-12-24 14:35:20 +01:00
36 changed files with 1667 additions and 569 deletions

View File

@@ -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();
}
}

View File

@@ -81,7 +81,7 @@ public class BeatmapDifficultyCalculator {
*/
public BeatmapDifficultyCalculator(Beatmap beatmap) {
this.beatmap = beatmap;
if (beatmap.timingPoints == null)
if (beatmap.breaks == null)
BeatmapDB.load(beatmap, BeatmapDB.LOAD_ARRAY);
BeatmapParser.parseHitObjects(beatmap);
}

View 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);
}
}

View File

@@ -112,6 +112,7 @@ public class BeatmapParser {
// parse directories
BeatmapSetNode lastNode = null;
long timestamp = System.currentTimeMillis();
for (File dir : dirs) {
currentDirectoryIndex++;
if (!dir.isDirectory())
@@ -160,6 +161,7 @@ public class BeatmapParser {
// add to parsed beatmap list
if (beatmap != null) {
beatmap.dateAdded = timestamp;
beatmaps.add(beatmap);
parsedBeatmaps.add(beatmap);
}
@@ -547,21 +549,7 @@ public class BeatmapParser {
break;
try {
// parse timing point
TimingPoint timingPoint = new TimingPoint(line);
// calculate BPM
if (!timingPoint.isInherited()) {
int bpm = Math.round(60000 / timingPoint.getBeatLength());
if (beatmap.bpmMin == 0)
beatmap.bpmMin = beatmap.bpmMax = bpm;
else if (bpm < beatmap.bpmMin)
beatmap.bpmMin = bpm;
else if (bpm > beatmap.bpmMax)
beatmap.bpmMax = bpm;
}
beatmap.timingPoints.add(timingPoint);
parseTimingPoint(beatmap, line);
} catch (Exception e) {
Log.warn(String.format("Failed to read timing point '%s' for file '%s'.",
line, file.getAbsolutePath()), e);
@@ -678,6 +666,71 @@ public class BeatmapParser {
return beatmap;
}
/**
* Parses a timing point and adds it to the beatmap.
* @param beatmap the beatmap
* @param line the line containing the unparsed timing point
*/
private static void parseTimingPoint(Beatmap beatmap, String line) {
// parse timing point
TimingPoint timingPoint = new TimingPoint(line);
beatmap.timingPoints.add(timingPoint);
// calculate BPM
if (!timingPoint.isInherited()) {
int bpm = Math.round(60000 / timingPoint.getBeatLength());
if (beatmap.bpmMin == 0) {
beatmap.bpmMin = beatmap.bpmMax = bpm;
} else if (bpm < beatmap.bpmMin) {
beatmap.bpmMin = bpm;
} else if (bpm > beatmap.bpmMax) {
beatmap.bpmMax = bpm;
}
}
}
/**
* Parses all timing points in a beatmap.
* @param beatmap the beatmap to parse
*/
public static void parseTimingPoints(Beatmap beatmap) {
if (beatmap.timingPoints != null) // already parsed
return;
beatmap.timingPoints = new ArrayList<TimingPoint>();
try (BufferedReader in = new BufferedReader(new FileReader(beatmap.getFile()))) {
String line = in.readLine();
while (line != null) {
line = line.trim();
if (!line.equals("[TimingPoints]"))
line = in.readLine();
else
break;
}
if (line == null) // no timing points
return;
while ((line = in.readLine()) != null) {
line = line.trim();
if (!isValidLine(line))
continue;
if (line.charAt(0) == '[')
break;
try {
parseTimingPoint(beatmap, line);
} catch (Exception e) {
Log.warn(String.format("Failed to read timing point '%s' for file '%s'.",
line, beatmap.getFile().getAbsolutePath()), e);
}
}
beatmap.timingPoints.trimToSize();
} catch (IOException e) {
ErrorHandler.error(String.format("Failed to read file '%s'.", beatmap.getFile().getAbsolutePath()), e, false);
}
}
/**
* Parses all hit objects in a beatmap.
* @param beatmap the beatmap to parse

View File

@@ -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,37 @@ 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);
}
}
/**
* Returns whether any beatmap in this set has been played.
*/
public boolean isPlayed() {
for (Beatmap map : beatmaps) {
if (map.playCount > 0)
return true;
}
return false;
}
}

View File

@@ -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);
}

View File

@@ -75,7 +75,7 @@ public class BeatmapSetNode {
public void draw(float x, float y, Grade grade, boolean focus) {
Image bg = GameImage.MENU_BUTTON_BG.getImage();
boolean expanded = (beatmapIndex > -1);
Beatmap beatmap;
Beatmap beatmap = beatmapSet.get(expanded ? beatmapIndex : 0);
bg.setAlpha(0.9f);
Color bgColor;
Color textColor = Options.getSkin().getSongSelectInactiveTextColor();
@@ -88,11 +88,10 @@ public class BeatmapSetNode {
textColor = Options.getSkin().getSongSelectActiveTextColor();
} else
bgColor = Colors.BLUE_BUTTON;
beatmap = beatmapSet.get(beatmapIndex);
} else {
} else if (beatmapSet.isPlayed())
bgColor = Colors.ORANGE_BUTTON;
beatmap = beatmapSet.get(0);
}
else
bgColor = Colors.PINK_BUTTON;
bg.draw(x, y, bgColor);
float cx = x + (bg.getWidth() * 0.043f);

View File

@@ -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; }
}

View File

@@ -58,6 +58,14 @@ public class TimingPoint {
* @param line the line to be parsed
*/
public TimingPoint(String line) {
/**
* [TIMING POINT FORMATS]
* Non-inherited:
* offset,msPerBeat,meter,sampleType,sampleSet,volume,inherited,kiai
*
* Inherited:
* offset,velocity,meter,sampleType,sampleSet,volume,inherited,kiai
*/
// TODO: better support for old formats
String[] tokens = line.split(",");
try {