WIPTest Merge Fixes

This commit is contained in:
fd 2015-06-13 21:16:27 -04:00
parent fd3f7fdabe
commit 038a5d5779
18 changed files with 75 additions and 3687 deletions

View File

@ -1241,11 +1241,12 @@ public class GameData {
}
}
public void sliderFinalResult(int time, int hitSlider30, float x, float y,
OsuHitObject hitObject, int currentRepeats) {
HitObject hitObject, int currentRepeats) {
score += 30;
}
/**
* https://osu.ppy.sh/wiki/Score
* Returns the score for a hit based on the following score formula:
* <p>
* Score = Hit Value + Hit Value * (Combo * Difficulty * Mod) / 25
@ -1256,10 +1257,15 @@ public class GameData {
* <li><strong>Mod:</strong> mod multipliers
* </ul>
* @param hitValue the hit value
* @param hitObject
* @return the score value
*/
private int getScoreForHit(int hitValue) {
return hitValue + (int) (hitValue * (Math.max(combo - 1, 0) * difficulty * GameMod.getScoreMultiplier()) / 25);
private int getScoreForHit(int hitValue, HitObject hitObject) {
int comboMulti = Math.max(combo - 1, 0);
if(hitObject.isSlider()){
comboMulti += 1;
}
return (hitValue + (int)(hitValue * (comboMulti * getDifficultyMultiplier() * GameMod.getScoreMultiplier()) / 25));
}
/**
@ -1310,25 +1316,9 @@ public class GameData {
hitObject.getEdgeHitSoundType(repeat),
hitObject.getSampleSet(repeat),
hitObject.getAdditionSampleSet(repeat));
//TODO merge conflict
/**
* https://osu.ppy.sh/wiki/Score
* [SCORE FORMULA]
* Score = Hit Value + Hit Value * (Combo * Difficulty * Mod) / 25
* - Hit Value: hit result (50, 100, 300), slider ticks, spinner bonus
* - Combo: combo before this hit - 1 (minimum 0)
* - Difficulty: the beatmap difficulty
* - Mod: mod multipliers
*/
int comboMulti = Math.max(combo - 1, 0);
if(hitObject.isSlider()){
comboMulti += 1;
}
score += (hitValue + (hitValue * (comboMulti * getDifficultyMultiplier() * GameMod.getScoreMultiplier()) / 25));
// calculate score and increment combo streak
changeScore(getScoreForHit(hitValue));
changeScore(getScoreForHit(hitValue, hitObject));
incrementComboStreak();
//merge conflict end
}
@ -1398,8 +1388,10 @@ public class GameData {
return difficultyMultiplier;
}
/**
* https://osu.ppy.sh/wiki/Score#How_to_calculate_the_Difficulty_multiplier
*/
public void calculateDifficultyMultiplier() {
//https://osu.ppy.sh/wiki/Score#How_to_calculate_the_Difficulty_multiplier
//TODO THE LIES ( difficultyMultiplier )
/*
924 3x1/4 beat notes 0.14stars
@ -1408,8 +1400,9 @@ public class GameData {
seems to be based on hitobject density? (Total Objects/Time)
*/
difficultyMultiplier = (int)((circleSize + difficulty + drainRate) / 6) + 2;
float mult = ((circleSize + difficulty + drainRate) / 6) + 1.5f;
System.out.println("diffuculty Multiplier : "+ mult);
difficultyMultiplier = (int)mult;
}
/**
* Returns a ScoreData object encapsulating all game data.

View File

@ -1,440 +0,0 @@
//TODO rename
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014, 2015 Jeffrey Han
*
* opsu! is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu! is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import org.newdawn.slick.Color;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.util.Log;
/**
* Data type storing parsed data from OSU files.
*/
public class OsuFile implements Comparable<OsuFile> {
/** Game modes. */
public static final byte MODE_OSU = 0, MODE_TAIKO = 1, MODE_CTB = 2, MODE_MANIA = 3;
/** Map of all loaded background images. */
private static HashMap<OsuFile, Image> bgImageMap = new HashMap<OsuFile, Image>();
/** Maximum number of cached images before all get erased. */
private static final int MAX_CACHE_SIZE = 10;
/** The OSU File object associated with this OsuFile. */
private File file;
/**
* [General]
*/
/** Audio file object. */
public File audioFilename;
/** Delay time before music starts (in ms). */
public int audioLeadIn = 0;
/** Audio hash (deprecated). */
// public String audioHash = "";
/** Start position of music preview (in ms). */
public int previewTime = -1;
/** Countdown type (0:disabled, 1:normal, 2:half, 3:double). */
public byte countdown = 0;
/** Sound samples ("None", "Normal", "Soft"). */
public String sampleSet = "";
/** How often closely placed hit objects will be stacked together. */
public float stackLeniency = 0.7f;
/** Game mode (MODE_* constants). */
public byte mode = MODE_OSU;
/** Whether the letterbox (top/bottom black bars) appears during breaks. */
public boolean letterboxInBreaks = false;
/** Whether the storyboard should be widescreen. */
public boolean widescreenStoryboard = false;
/** Whether to show an epilepsy warning. */
public boolean epilepsyWarning = false;
/**
* [Editor]
*/
/** List of editor bookmarks (in ms). */
// public int[] bookmarks;
/** Multiplier for "Distance Snap". */
// public float distanceSpacing = 0f;
/** Beat division. */
// public byte beatDivisor = 0;
/** Size of grid for "Grid Snap". */
// public int gridSize = 0;
/** Zoom in the editor timeline. */
// public int timelineZoom = 0;
/**
* [Metadata]
*/
/** Song title. */
public String title = "", titleUnicode = "";
/** Song artist. */
public String artist = "", artistUnicode = "";
/** Beatmap creator. */
public String creator = "";
/** Beatmap difficulty. */
public String version = "";
/** Song source. */
public String source = "";
/** Song tags (for searching). */
public String tags = "";
/** Beatmap ID. */
public int beatmapID = 0;
/** Beatmap set ID. */
public int beatmapSetID = 0;
/**
* [Difficulty]
*/
/** HP: Health drain rate (0:easy ~ 10:hard) */
public float HPDrainRate = 5f;
/** CS: Size of circles and sliders (0:large ~ 10:small). */
public float circleSize = 4f;
/** OD: Affects timing window, spinners, and approach speed (0:easy ~ 10:hard). */
public float overallDifficulty = 5f;
/** AR: How long circles stay on the screen (0:long ~ 10:short). */
public float approachRate = -1f;
/** Slider movement speed multiplier. */
public float sliderMultiplier = 1f;
/** Rate at which slider ticks are placed (x per beat). */
public float sliderTickRate = 1f;
/**
* [Events]
*/
/** Background image file name. */
public String bg;
/** Background video file name. */
// public String video;
/** All break periods (start time, end time, ...). */
public ArrayList<Integer> breaks;
/**
* [TimingPoints]
*/
/** All timing points. */
public ArrayList<OsuTimingPoint> timingPoints;
/** Song BPM range. */
public int bpmMin = 0, bpmMax = 0;
/**
* [Colours]
*/
/** Combo colors (max 8). */
public Color[] combo;
/** md5 hash of this file */
public String md5Hash;
/**
* [HitObjects]
*/
/** All hit objects. */
public OsuHitObject[] objects;
/** Number of individual objects. */
public int
hitObjectCircle = 0,
hitObjectSlider = 0,
hitObjectSpinner = 0;
/** Last object end time (in ms). */
public int endTime = -1;
/**
* Destroys all cached background images and resets the cache.
*/
public static void clearImageCache() {
for (Image img : bgImageMap.values()) {
if (img != null && !img.isDestroyed()) {
try {
img.destroy();
} catch (SlickException e) {
Log.warn(String.format("Failed to destroy image '%s'.", img.getResourceReference()), e);
}
}
}
resetImageCache();
}
/**
* Resets the image cache.
* This does NOT destroy images, so be careful of memory leaks!
*/
public static void resetImageCache() {
bgImageMap = new HashMap<OsuFile, Image>();
}
/**
* Constructor.
* @param file the file associated with this OsuFile
*/
public OsuFile(File file) {
this.file = file;
}
/**
* Returns the associated file object.
* @return the File object
*/
public File getFile() { return file; }
/**
* Returns the song title.
* If configured, the Unicode string will be returned instead.
* @return the song title
*/
public String getTitle() {
return (Options.useUnicodeMetadata() && !titleUnicode.isEmpty()) ? titleUnicode : title;
}
/**
* Returns the song artist.
* If configured, the Unicode string will be returned instead.
* @return the song artist
*/
public String getArtist() {
return (Options.useUnicodeMetadata() && !artistUnicode.isEmpty()) ? artistUnicode : artist;
}
/**
* Draws the background associated with the OsuFile.
* @param width the container width
* @param height the container height
* @param alpha the alpha value
* @param stretch if true, stretch to screen dimensions; otherwise, maintain aspect ratio
* @return true if successful, false if any errors were produced
*/
public boolean drawBG(int width, int height, float alpha, boolean stretch) {
if (bg == null)
return false;
try {
Image bgImage = bgImageMap.get(this);
if (bgImage == null) {
if (bgImageMap.size() > MAX_CACHE_SIZE)
clearImageCache();
bgImage = new Image(new File(file.getParentFile(), bg).getAbsolutePath());
bgImageMap.put(this, bgImage);
}
int swidth = width;
int sheight = height;
if (!stretch) {
// fit image to screen
if (bgImage.getWidth() / (float) bgImage.getHeight() > width / (float) height) // x > y
sheight = (int) (width * bgImage.getHeight() / (float) bgImage.getWidth());
else
swidth = (int) (height * bgImage.getWidth() / (float) bgImage.getHeight());
} else {
// fill screen while maintaining aspect ratio
if (bgImage.getWidth() / (float) bgImage.getHeight() > width / (float) height) // x > y
swidth = (int) (height * bgImage.getWidth() / (float) bgImage.getHeight());
else
sheight = (int) (width * bgImage.getHeight() / (float) bgImage.getWidth());
}
bgImage = bgImage.getScaledCopy(swidth, sheight);
bgImage.setAlpha(alpha);
bgImage.drawCentered(width / 2, height / 2);
} catch (Exception e) {
Log.warn(String.format("Failed to get background image '%s'.", bg), e);
bg = null; // don't try to load the file again until a restart
return false;
}
return true;
}
/**
* Compares two OsuFile objects first by overall difficulty, then by total objects.
*/
@Override
public int compareTo(OsuFile that) {
int cmp = Float.compare(this.overallDifficulty, that.overallDifficulty);
if (cmp == 0)
cmp = Integer.compare(
this.hitObjectCircle + this.hitObjectSlider + this.hitObjectSpinner,
that.hitObjectCircle + that.hitObjectSlider + that.hitObjectSpinner
);
return cmp;
}
/**
* Returns a formatted string: "Artist - Title [Version]"
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("%s - %s [%s]", getArtist(), getTitle(), version);
}
/**
* Returns the {@link #breaks} field formatted as a string,
* or null if the field is null.
*/
public String breaksToString() {
if (breaks == null)
return null;
StringBuilder sb = new StringBuilder();
for (int i : breaks) {
sb.append(i);
sb.append(',');
}
if (sb.length() > 0)
sb.setLength(sb.length() - 1);
return sb.toString();
}
/**
* Sets the {@link #breaks} field from a string.
* @param s the string
*/
public void breaksFromString(String s) {
if (s == null)
return;
this.breaks = new ArrayList<Integer>();
String[] tokens = s.split(",");
for (int i = 0; i < tokens.length; i++)
breaks.add(Integer.parseInt(tokens[i]));
}
/**
* Returns the {@link #timingPoints} field formatted as a string,
* or null if the field is null.
*/
public String timingPointsToString() {
if (timingPoints == null)
return null;
StringBuilder sb = new StringBuilder();
for (OsuTimingPoint p : timingPoints) {
sb.append(p.toString());
sb.append('|');
}
if (sb.length() > 0)
sb.setLength(sb.length() - 1);
return sb.toString();
}
/**
* Sets the {@link #timingPoints} field from a string.
* @param s the string
*/
public void timingPointsFromString(String s) {
this.timingPoints = new ArrayList<OsuTimingPoint>();
if (s == null)
return;
String[] tokens = s.split("\\|");
for (int i = 0; i < tokens.length; i++) {
try {
timingPoints.add(new OsuTimingPoint(tokens[i]));
} catch (Exception e) {
Log.warn(String.format("Failed to read timing point '%s'.", tokens[i]), e);
}
}
timingPoints.trimToSize();
}
/**
* Returns the {@link #combo} field formatted as a string,
* or null if the field is null or the default combo.
*/
public String comboToString() {
if (combo == null || combo == Utils.DEFAULT_COMBO)
return null;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < combo.length; i++) {
Color c = combo[i];
sb.append(c.getRed());
sb.append(',');
sb.append(c.getGreen());
sb.append(',');
sb.append(c.getBlue());
sb.append('|');
}
if (sb.length() > 0)
sb.setLength(sb.length() - 1);
return sb.toString();
}
/**
* Sets the {@link #combo} field from a string.
* @param s the string
*/
public void comboFromString(String s) {
this.combo = Utils.DEFAULT_COMBO;
if (s == null)
return;
LinkedList<Color> colors = new LinkedList<Color>();
String[] tokens = s.split("\\|");
for (int i = 0; i < tokens.length; i++) {
String[] rgb = tokens[i].split(",");
colors.add(new Color(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2])));
}
if (!colors.isEmpty())
this.combo = colors.toArray(new Color[colors.size()]);
}
}

View File

@ -1,516 +0,0 @@
//TODO rename
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014, 2015 Jeffrey Han
*
* opsu! is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu! is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu;
import itdelatrisu.opsu.audio.MusicController;
import itdelatrisu.opsu.db.OsuDB;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Indexed, expanding, doubly-linked list data type for song groups.
*/
public class OsuGroupList {
/** Song group structure (each group contains of an ArrayList of OsuFiles). */
private static OsuGroupList list;
/** Search pattern for conditional expressions. */
private static final Pattern SEARCH_CONDITION_PATTERN = Pattern.compile(
"(ar|cs|od|hp|bpm|length)(=|==|>|>=|<|<=)((\\d*\\.)?\\d+)"
);
/** List containing all parsed nodes. */
private ArrayList<OsuGroupNode> parsedNodes;
/** Total number of beatmaps (i.e. OsuFile objects). */
private int mapCount = 0;
/** Current list of nodes (subset of parsedNodes, used for searches). */
private ArrayList<OsuGroupNode> nodes;
/** Set of all beatmap set IDs for the parsed beatmaps. */
private HashSet<Integer> MSIDdb;
/** Map of all hash to OsuFile . */
public HashMap<String, OsuFile> beatmapHashesToFile;
/** Index of current expanded node (-1 if no node is expanded). */
private int expandedIndex;
/** Start and end nodes of expanded group. */
private OsuGroupNode expandedStartNode, expandedEndNode;
/** The last search query. */
private String lastQuery;
/**
* Creates a new instance of this class (overwriting any previous instance).
*/
public static void create() { list = new OsuGroupList(); }
/**
* Returns the single instance of this class.
*/
public static OsuGroupList get() { return list; }
/**
* Constructor.
*/
private OsuGroupList() {
parsedNodes = new ArrayList<OsuGroupNode>();
MSIDdb = new HashSet<Integer>();
beatmapHashesToFile = new HashMap<String, OsuFile>();
reset();
}
/**
* Resets the list's fields.
* This does not erase any parsed nodes.
*/
public void reset() {
nodes = parsedNodes;
expandedIndex = -1;
expandedStartNode = expandedEndNode = null;
lastQuery = "";
}
/**
* Returns the number of elements.
*/
public int size() { return nodes.size(); }
/**
* Adds a song group.
* @param osuFiles the list of OsuFile objects in the group
* @return the new OsuGroupNode
*/
public OsuGroupNode addSongGroup(ArrayList<OsuFile> osuFiles) {
OsuGroupNode node = new OsuGroupNode(osuFiles);
parsedNodes.add(node);
mapCount += osuFiles.size();
// add beatmap set ID to set
int msid = osuFiles.get(0).beatmapSetID;
if (msid > 0)
MSIDdb.add(msid);
for(OsuFile f : osuFiles) {
beatmapHashesToFile.put(f.md5Hash, f);
}
return node;
}
/**
* Deletes a song group from the list, and also deletes the beatmap
* directory associated with the node.
* @param node the node containing the song group to delete
* @return true if the song group was deleted, false otherwise
*/
public boolean deleteSongGroup(OsuGroupNode node) {
if (node == null)
return false;
// re-link base nodes
int index = node.index;
OsuGroupNode ePrev = getBaseNode(index - 1), eCur = getBaseNode(index), eNext = getBaseNode(index + 1);
if (ePrev != null) {
if (ePrev.index == expandedIndex)
expandedEndNode.next = eNext;
else if (eNext != null && eNext.index == expandedIndex)
ePrev.next = expandedStartNode;
else
ePrev.next = eNext;
}
if (eNext != null) {
if (eNext.index == expandedIndex)
expandedStartNode.prev = ePrev;
else if (ePrev != null && ePrev.index == expandedIndex)
eNext.prev = expandedEndNode;
else
eNext.prev = ePrev;
}
// remove all node references
OsuFile osu = node.osuFiles.get(0);
nodes.remove(index);
parsedNodes.remove(eCur);
mapCount -= node.osuFiles.size();
if (osu.beatmapSetID > 0)
MSIDdb.remove(osu.beatmapSetID);
// reset indices
for (int i = index, size = size(); i < size; i++)
nodes.get(i).index = i;
if (index == expandedIndex) {
expandedIndex = -1;
expandedStartNode = expandedEndNode = null;
} else if (expandedIndex > index) {
expandedIndex--;
OsuGroupNode expandedNode = expandedStartNode;
for (int i = 0, size = expandedNode.osuFiles.size();
i < size && expandedNode != null;
i++, expandedNode = expandedNode.next)
expandedNode.index = expandedIndex;
}
// stop playing the track
File dir = osu.getFile().getParentFile();
if (MusicController.trackExists() || MusicController.isTrackLoading()) {
File audioFile = MusicController.getOsuFile().audioFilename;
if (audioFile != null && audioFile.equals(osu.audioFilename)) {
MusicController.reset();
System.gc(); // TODO: why can't files be deleted without calling this?
}
}
// remove entry from cache
OsuDB.delete(dir.getName());
// delete the associated directory
try {
Utils.deleteToTrash(dir);
} catch (IOException e) {
ErrorHandler.error("Could not delete song group.", e, true);
}
return true;
}
/**
* Deletes a song from a song group, and also deletes the beatmap file.
* If this causes the song group to be empty, then the song group and
* beatmap directory will be deleted altogether.
* @param node the node containing the song group to delete (expanded only)
* @return true if the song or song group was deleted, false otherwise
* @see #deleteSongGroup(OsuGroupNode)
*/
public boolean deleteSong(OsuGroupNode node) {
if (node == null || node.osuFileIndex == -1 || node.index != expandedIndex)
return false;
// last song in group?
int size = node.osuFiles.size();
if (node.osuFiles.size() == 1)
return deleteSongGroup(node);
// reset indices
OsuGroupNode expandedNode = node.next;
for (int i = node.osuFileIndex + 1;
i < size && expandedNode != null && expandedNode.index == node.index;
i++, expandedNode = expandedNode.next)
expandedNode.osuFileIndex--;
// remove song reference
OsuFile osu = node.osuFiles.remove(node.osuFileIndex);
mapCount--;
// re-link nodes
if (node.prev != null)
node.prev.next = node.next;
if (node.next != null)
node.next.prev = node.prev;
// remove entry from cache
File file = osu.getFile();
OsuDB.delete(file.getParentFile().getName(), file.getName());
// delete the associated file
try {
Utils.deleteToTrash(file);
} catch (IOException e) {
ErrorHandler.error("Could not delete song.", e, true);
}
return true;
}
/**
* Returns the total number of parsed maps (i.e. OsuFile objects).
*/
public int getMapCount() { return mapCount; }
/**
* Returns the total number of parsed maps sets.
*/
public int getMapSetCount() { return parsedNodes.size(); }
/**
* Returns the OsuGroupNode at an index, disregarding expansions.
* @param index the node index
*/
public OsuGroupNode getBaseNode(int index) {
if (index < 0 || index >= size())
return null;
return nodes.get(index);
}
/**
* Returns a random base node.
*/
public OsuGroupNode getRandomNode() {
OsuGroupNode node = getBaseNode((int) (Math.random() * size()));
if (node != null && node.index == expandedIndex) // don't choose an expanded group node
node = node.next;
return node;
}
/**
* Returns the OsuGroupNode a given number of positions forward or backwards.
* @param node the starting node
* @param shift the number of nodes to shift forward (+) or backward (-).
*/
public OsuGroupNode getNode(OsuGroupNode node, int shift) {
OsuGroupNode startNode = node;
if (shift > 0) {
for (int i = 0; i < shift && startNode != null; i++)
startNode = startNode.next;
} else {
for (int i = 0; i < shift && startNode != null; i++)
startNode = startNode.prev;
}
return startNode;
}
/**
* Returns the index of the expanded node (or -1 if nothing is expanded).
*/
public int getExpandedIndex() { return expandedIndex; }
/**
* Expands the node at an index by inserting a new node for each OsuFile
* in that node and hiding the group node.
* @return the first of the newly-inserted nodes
*/
public OsuGroupNode expand(int index) {
// undo the previous expansion
unexpand();
OsuGroupNode node = getBaseNode(index);
if (node == null)
return null;
expandedStartNode = expandedEndNode = null;
// create new nodes
ArrayList<OsuFile> osuFiles = node.osuFiles;
OsuGroupNode prevNode = node.prev;
OsuGroupNode nextNode = node.next;
for (int i = 0, size = node.osuFiles.size(); i < size; i++) {
OsuGroupNode newNode = new OsuGroupNode(osuFiles);
newNode.index = index;
newNode.osuFileIndex = i;
newNode.prev = node;
// unlink the group node
if (i == 0) {
expandedStartNode = newNode;
newNode.prev = prevNode;
if (prevNode != null)
prevNode.next = newNode;
}
node.next = newNode;
node = node.next;
}
if (nextNode != null) {
node.next = nextNode;
nextNode.prev = node;
}
expandedEndNode = node;
expandedIndex = index;
return expandedStartNode;
}
/**
* Undoes the current expansion, if any.
*/
private void unexpand() {
if (expandedIndex < 0 || expandedIndex >= size())
return;
// recreate surrounding links
OsuGroupNode
ePrev = getBaseNode(expandedIndex - 1),
eCur = getBaseNode(expandedIndex),
eNext = getBaseNode(expandedIndex + 1);
if (ePrev != null)
ePrev.next = eCur;
eCur.prev = ePrev;
eCur.index = expandedIndex;
eCur.next = eNext;
if (eNext != null)
eNext.prev = eCur;
expandedIndex = -1;
expandedStartNode = expandedEndNode = null;
return;
}
/**
* Initializes the links in the list.
*/
public void init() {
if (size() < 1)
return;
// sort the list
Collections.sort(nodes, SongSort.getSort().getComparator());
expandedIndex = -1;
expandedStartNode = expandedEndNode = null;
// create links
OsuGroupNode lastNode = nodes.get(0);
lastNode.index = 0;
lastNode.prev = null;
for (int i = 1, size = size(); i < size; i++) {
OsuGroupNode node = nodes.get(i);
lastNode.next = node;
node.index = i;
node.prev = lastNode;
lastNode = node;
}
lastNode.next = null;
}
/**
* Creates a new list of song groups in which each group contains a match to a search query.
* @param query the search query (terms separated by spaces)
* @return false if query is the same as the previous one, true otherwise
*/
public boolean search(String query) {
if (query == null)
return false;
// don't redo the same search
query = query.trim().toLowerCase();
if (lastQuery != null && query.equals(lastQuery))
return false;
lastQuery = query;
LinkedList<String> terms = new LinkedList<String>(Arrays.asList(query.split("\\s+")));
// if empty query, reset to original list
if (query.isEmpty() || terms.isEmpty()) {
nodes = parsedNodes;
return true;
}
// find and remove any conditional search terms
LinkedList<String> condType = new LinkedList<String>();
LinkedList<String> condOperator = new LinkedList<String>();
LinkedList<Float> condValue = new LinkedList<Float>();
Iterator<String> termIter = terms.iterator();
while (termIter.hasNext()) {
String term = termIter.next();
Matcher m = SEARCH_CONDITION_PATTERN.matcher(term);
if (m.find()) {
condType.add(m.group(1));
condOperator.add(m.group(2));
condValue.add(Float.parseFloat(m.group(3)));
termIter.remove();
}
}
// build an initial list from first search term
nodes = new ArrayList<OsuGroupNode>();
if (terms.isEmpty()) {
// conditional term
String type = condType.remove();
String operator = condOperator.remove();
float value = condValue.remove();
for (OsuGroupNode node : parsedNodes) {
if (node.matches(type, operator, value))
nodes.add(node);
}
} else {
// normal term
String term = terms.remove();
for (OsuGroupNode node : parsedNodes) {
if (node.matches(term))
nodes.add(node);
}
}
// iterate through remaining normal search terms
while (!terms.isEmpty()) {
if (nodes.isEmpty())
return true;
String term = terms.remove();
// remove nodes from list if they don't match all terms
Iterator<OsuGroupNode> nodeIter = nodes.iterator();
while (nodeIter.hasNext()) {
OsuGroupNode node = nodeIter.next();
if (!node.matches(term))
nodeIter.remove();
}
}
// iterate through remaining conditional terms
while (!condType.isEmpty()) {
if (nodes.isEmpty())
return true;
String type = condType.remove();
String operator = condOperator.remove();
float value = condValue.remove();
// remove nodes from list if they don't match all terms
Iterator<OsuGroupNode> nodeIter = nodes.iterator();
while (nodeIter.hasNext()) {
OsuGroupNode node = nodeIter.next();
if (!node.matches(type, operator, value))
nodeIter.remove();
}
}
return true;
}
/**
* Returns whether or not the list contains the given beatmap set ID.
* <p>
* Note that IDs for older maps might have been improperly parsed, so
* there is no guarantee that this method will return an accurate value.
* @param id the beatmap set ID to check
* @return true if id is in the list
*/
public boolean containsBeatmapSetID(int id) { return MSIDdb.contains(id); }
public OsuFile getFileFromBeatmapHash(String beatmapHash) {
return beatmapHashesToFile.get(beatmapHash);
}
}

View File

@ -1,543 +0,0 @@
//TODO rename
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014, 2015 Jeffrey Han
*
* opsu! is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu! is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu;
import java.text.DecimalFormat;
import java.text.NumberFormat;
/**
* Data type representing a hit object.
*/
public class OsuHitObject {
/** Hit object types (bits). */
public static final int
TYPE_CIRCLE = 1,
TYPE_SLIDER = 2,
TYPE_NEWCOMBO = 4, // not an object
TYPE_SPINNER = 8;
/** Hit object type names. */
private static final String
CIRCLE = "circle",
SLIDER = "slider",
SPINNER = "spinner",
UNKNOWN = "unknown object";
/** Hit sound types (bits). */
public static final byte
SOUND_NORMAL = 0,
SOUND_WHISTLE = 2,
SOUND_FINISH = 4,
SOUND_CLAP = 8;
/**
* Slider curve types.
* (Deprecated: only Beziers are currently used.)
*/
public static final char
SLIDER_CATMULL = 'C',
SLIDER_BEZIER = 'B',
SLIDER_LINEAR = 'L',
SLIDER_PASSTHROUGH = 'P';
/** Max hit object coordinates. */
private static final int
MAX_X = 512,
MAX_Y = 384;
/** The x and y multipliers for hit object coordinates. */
private static float xMultiplier, yMultiplier;
/** The x and y offsets for hit object coordinates. */
private static int
xOffset, // offset right of border
yOffset; // offset below health bar
/** The container height. */
private static int containerHeight;
/** The offset per stack. */
private static float stackOffset;
/**
* Returns the stack position modifier, in pixels.
* @return stack position modifier
*/
public static float getStackOffset() { return stackOffset; }
/**
* Sets the stack position modifier.
* @param offset stack position modifier, in pixels
*/
public static void setStackOffset(float offset) { stackOffset = offset; }
/** Starting coordinates. */
private float x, y;
/** Start time (in ms). */
private int time;
/** Hit object type (TYPE_* bitmask). */
private int type;
/** Hit sound type (SOUND_* bitmask). */
private byte hitSound;
/** Hit sound addition (sampleSet, AdditionSampleSet, ?, ...). */
private byte[] addition;
/** Slider curve type (SLIDER_* constant). */
private char sliderType;
/** Slider coordinate lists. */
private float[] sliderX, sliderY;
/** Slider repeat count. */
private int repeat;
/** Slider pixel length. */
private float pixelLength;
/** Spinner end time (in ms). */
private int endTime;
/** Slider edge hit sound type (SOUND_* bitmask). */
private byte[] edgeHitSound;
/** Slider edge hit sound addition (sampleSet, AdditionSampleSet). */
private byte[][] edgeAddition;
/** Current index in combo color array. */
private int comboIndex;
/** Number to display in hit object. */
private int comboNumber;
/** Hit object index in the current stack. */
private int stack;
/**
* Initializes the OsuHitObject data type with container dimensions.
* @param width the container width
* @param height the container height
*/
public static void init(int width, int height) {
containerHeight = height;
int swidth = width;
int sheight = height;
if (swidth * 3 > sheight * 4)
swidth = sheight * 4 / 3;
else
sheight = swidth * 3 / 4;
xMultiplier = swidth / 640f;
yMultiplier = sheight / 480f;
//xMultiplier = swidth / MAX_X;
//yMultiplier = sheight / MAX_Y;
xOffset = (int) (width - MAX_X * xMultiplier) / 2;
yOffset = (int) (height - MAX_Y * yMultiplier) / 2;
}
/**
* Returns the X multiplier for coordinates.
*/
public static float getXMultiplier() { return xMultiplier; }
/**
* Returns the Y multiplier for coordinates.
*/
public static float getYMultiplier() { return yMultiplier; }
/**
* Returns the X offset for coordinates.
*/
public static int getXOffset() { return xOffset; }
/**
* Returns the Y offset for coordinates.
*/
public static int getYOffset() { return yOffset; }
/**
* Constructor.
* @param line the line to be parsed
*/
public OsuHitObject(String line) {
/**
* [OBJECT FORMATS]
* Circles:
* x,y,time,type,hitSound,addition
* 256,148,9466,1,2,0:0:0:0:
*
* Sliders:
* x,y,time,type,hitSound,sliderType|curveX:curveY|...,repeat,pixelLength,edgeHitsound,edgeAddition,addition
* 300,68,4591,2,0,B|372:100|332:172|420:192,2,180,2|2|2,0:0|0:0|0:0,0:0:0:0:
*
* Spinners:
* x,y,time,type,hitSound,endTime,addition
* 256,192,654,12,0,4029,0:0:0:0:
*
* NOTE: 'addition' -> sampl:add:cust:vol:hitsound (optional, defaults to "0:0:0:0:")
*/
String tokens[] = line.split(",");
// common fields
this.x = Float.parseFloat(tokens[0]);
this.y = Float.parseFloat(tokens[1]);
this.time = Integer.parseInt(tokens[2]);
this.type = Integer.parseInt(tokens[3]);
this.hitSound = Byte.parseByte(tokens[4]);
// type-specific fields
int additionIndex;
if ((type & OsuHitObject.TYPE_CIRCLE) > 0)
additionIndex = 5;
else if ((type & OsuHitObject.TYPE_SLIDER) > 0) {
additionIndex = 10;
// slider curve type and coordinates
String[] sliderTokens = tokens[5].split("\\|");
this.sliderType = sliderTokens[0].charAt(0);
this.sliderX = new float[sliderTokens.length - 1];
this.sliderY = new float[sliderTokens.length - 1];
for (int j = 1; j < sliderTokens.length; j++) {
String[] sliderXY = sliderTokens[j].split(":");
this.sliderX[j - 1] = Integer.parseInt(sliderXY[0]);
this.sliderY[j - 1] = Integer.parseInt(sliderXY[1]);
}
this.repeat = Integer.parseInt(tokens[6]);
this.pixelLength = Float.parseFloat(tokens[7]);
if (tokens.length > 8) {
String[] edgeHitSoundTokens = tokens[8].split("\\|");
this.edgeHitSound = new byte[edgeHitSoundTokens.length];
for (int j = 0; j < edgeHitSoundTokens.length; j++)
edgeHitSound[j] = Byte.parseByte(edgeHitSoundTokens[j]);
}
if (tokens.length > 9) {
String[] edgeAdditionTokens = tokens[9].split("\\|");
this.edgeAddition = new byte[edgeAdditionTokens.length][2];
for (int j = 0; j < edgeAdditionTokens.length; j++) {
String[] tedgeAddition = edgeAdditionTokens[j].split(":");
edgeAddition[j][0] = Byte.parseByte(tedgeAddition[0]);
edgeAddition[j][1] = Byte.parseByte(tedgeAddition[1]);
}
}
} else { //if ((type & OsuHitObject.TYPE_SPINNER) > 0) {
additionIndex = 6;
// some 'endTime' fields contain a ':' character (?)
int index = tokens[5].indexOf(':');
if (index != -1)
tokens[5] = tokens[5].substring(0, index);
this.endTime = Integer.parseInt(tokens[5]);
}
// addition
if (tokens.length > additionIndex) {
String[] additionTokens = tokens[additionIndex].split(":");
this.addition = new byte[additionTokens.length];
for (int j = 0; j < additionTokens.length; j++)
this.addition[j] = Byte.parseByte(additionTokens[j]);
}
}
/**
* Returns the raw starting x coordinate.
*/
public float getX() { return x; }
/**
* Returns the raw starting y coordinate.
*/
public float getY() { return y; }
/**
* Returns the scaled starting x coordinate.
*/
public float getScaledX() { return (x - stack * stackOffset) * xMultiplier + xOffset; }
/**
* Returns the scaled starting y coordinate.
*/
public float getScaledY() {
if (GameMod.HARD_ROCK.isActive())
return containerHeight - ((y - stack * stackOffset) * yMultiplier + yOffset);
else
return (y - stack * stackOffset) * yMultiplier + yOffset;
}
/**
* Returns the start time.
* @return the start time (in ms)
*/
public int getTime() { return time; }
/**
* Returns the hit object type.
* @return the object type (TYPE_* bitmask)
*/
public int getType() { return type; }
/**
* Returns the name of the hit object type.
*/
public String getTypeName() {
if (isCircle())
return CIRCLE;
else if (isSlider())
return SLIDER;
else if (isSpinner())
return SPINNER;
else
return UNKNOWN;
}
/**
* Returns the hit sound type.
* @return the sound type (SOUND_* bitmask)
*/
public byte getHitSoundType() { return hitSound; }
/**
* Returns the edge hit sound type.
* @param index the slider edge index (ignored for non-sliders)
* @return the sound type (SOUND_* bitmask)
*/
public byte getEdgeHitSoundType(int index) {
if (edgeHitSound != null)
return edgeHitSound[index];
else
return hitSound;
}
/**
* Returns the slider type.
* @return the slider type (SLIDER_* constant)
*/
public char getSliderType() { return sliderType; }
/**
* Returns a list of raw slider x coordinates.
*/
public float[] getSliderX() { return sliderX; }
/**
* Returns a list of raw slider y coordinates.
*/
public float[] getSliderY() { return sliderY; }
/**
* Returns a list of scaled slider x coordinates.
* Note that this method will create a new array.
*/
public float[] getScaledSliderX() {
if (sliderX == null)
return null;
float[] x = new float[sliderX.length];
for (int i = 0; i < x.length; i++)
x[i] = (sliderX[i] - stack * stackOffset) * xMultiplier + xOffset;
return x;
}
/**
* Returns a list of scaled slider y coordinates.
* Note that this method will create a new array.
*/
public float[] getScaledSliderY() {
if (sliderY == null)
return null;
float[] y = new float[sliderY.length];
if (GameMod.HARD_ROCK.isActive()) {
for (int i = 0; i < y.length; i++)
y[i] = containerHeight - ((sliderY[i] - stack * stackOffset) * yMultiplier + yOffset);
} else {
for (int i = 0; i < y.length; i++)
y[i] = (sliderY[i] - stack * stackOffset) * yMultiplier + yOffset;
}
return y;
}
/**
* Returns the slider repeat count.
* @return the repeat count
*/
public int getRepeatCount() { return repeat; }
/**
* Returns the slider pixel length.
* @return the pixel length
*/
public float getPixelLength() { return pixelLength; }
/**
* Returns the spinner end time.
* @return the end time (in ms)
*/
public int getEndTime() { return endTime; }
/**
* Sets the current index in the combo color array.
* @param comboIndex the combo index
*/
public void setComboIndex(int comboIndex) { this.comboIndex = comboIndex; }
/**
* Returns the current index in the combo color array.
* @return the combo index
*/
public int getComboIndex() { return comboIndex; }
/**
* Sets the number to display in the hit object.
* @param comboNumber the combo number
*/
public void setComboNumber(int comboNumber) { this.comboNumber = comboNumber; }
/**
* Returns the number to display in the hit object.
* @return the combo number
*/
public int getComboNumber() { return comboNumber; }
/**
* Returns whether or not the hit object is a circle.
* @return true if circle
*/
public boolean isCircle() { return (type & TYPE_CIRCLE) > 0; }
/**
* Returns whether or not the hit object is a slider.
* @return true if slider
*/
public boolean isSlider() { return (type & TYPE_SLIDER) > 0; }
/**
* Returns whether or not the hit object is a spinner.
* @return true if spinner
*/
public boolean isSpinner() { return (type & TYPE_SPINNER) > 0; }
/**
* Returns whether or not the hit object starts a new combo.
* @return true if new combo
*/
public boolean isNewCombo() { return (type & TYPE_NEWCOMBO) > 0; }
/**
* Returns the number of extra skips on the combo colors.
*/
public int getComboSkip() { return (type >> TYPE_NEWCOMBO); }
/**
* Returns the sample set at the given index.
* @param index the index (for sliders, ignored otherwise)
* @return the sample set, or 0 if none available
*/
public byte getSampleSet(int index) {
if (edgeAddition != null)
return edgeAddition[index][0];
if (addition != null)
return addition[0];
return 0;
}
/**
* Returns the 'addition' sample set at the given index.
* @param index the index (for sliders, ignored otherwise)
* @return the sample set, or 0 if none available
*/
public byte getAdditionSampleSet(int index) {
if (edgeAddition != null)
return edgeAddition[index][1];
if (addition != null)
return addition[1];
return 0;
}
/**
* Sets the hit object index in the current stack.
* @param stack index in the stack
*/
public void setStack(int stack) { this.stack = stack; }
/**
* Returns the hit object index in the current stack.
* @return index in the stack
*/
public int getStack() { return stack; }
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
NumberFormat nf = new DecimalFormat("###.#####");
// common fields
sb.append(nf.format(x)); sb.append(',');
sb.append(nf.format(y)); sb.append(',');
sb.append(time); sb.append(',');
sb.append(type); sb.append(',');
sb.append(hitSound); sb.append(',');
// type-specific fields
if (isCircle())
;
else if (isSlider()) {
sb.append(getSliderType());
sb.append('|');
for (int i = 0; i < sliderX.length; i++) {
sb.append(nf.format(sliderX[i])); sb.append(':');
sb.append(nf.format(sliderY[i])); sb.append('|');
}
sb.setCharAt(sb.length() - 1, ',');
sb.append(repeat); sb.append(',');
sb.append(pixelLength); sb.append(',');
if (edgeHitSound != null) {
for (int i = 0; i < edgeHitSound.length; i++) {
sb.append(edgeHitSound[i]); sb.append('|');
}
sb.setCharAt(sb.length() - 1, ',');
}
if (edgeAddition != null) {
for (int i = 0; i < edgeAddition.length; i++) {
sb.append(edgeAddition[i][0]); sb.append(':');
sb.append(edgeAddition[i][1]); sb.append('|');
}
sb.setCharAt(sb.length() - 1, ',');
}
} else if (isSpinner()) {
sb.append(endTime);
sb.append(',');
}
// addition
if (addition != null) {
for (int i = 0; i < addition.length; i++) {
sb.append(addition[i]);
sb.append(':');
}
} else
sb.setLength(sb.length() - 1);
return sb.toString();
}
}

View File

@ -1,742 +0,0 @@
//TODO rename
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014, 2015 Jeffrey Han
*
* opsu! is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu! is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu;
import itdelatrisu.opsu.db.OsuDB;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.newdawn.slick.Color;
import org.newdawn.slick.util.Log;
/**
* Parser for OSU files.
*/
public class OsuParser {
/** The string lookup database. */
private static HashMap<String, String> stringdb = new HashMap<String, String>();
/** The expected pattern for beatmap directories, used to find beatmap set IDs. */
private static final String DIR_MSID_PATTERN = "^\\d+ .*";
/** The current file being parsed. */
private static File currentFile;
/** The current directory number while parsing. */
private static int currentDirectoryIndex = -1;
/** The total number of directories to parse. */
private static int totalDirectories = -1;
/** Parser statuses. */
public enum Status { NONE, PARSING, CACHE, INSERTING };
/** The current status. */
private static Status status = Status.NONE;
// This class should not be instantiated.
private OsuParser() {}
/**
* Invokes parser for each OSU file in a root directory and
* adds the OsuFiles to a new OsuGroupList.
* @param root the root directory (search has depth 1)
*/
public static void parseAllFiles(File root) {
// create a new OsuGroupList
OsuGroupList.create();
// parse all directories
parseDirectories(root.listFiles());
}
/**
* Invokes parser for each directory in the given array and
* adds the OsuFiles to the existing OsuGroupList.
* @param dirs the array of directories to parse
* @return the last OsuGroupNode parsed, or null if none
*/
public static OsuGroupNode parseDirectories(File[] dirs) {
if (dirs == null)
return null;
// progress tracking
status = Status.PARSING;
currentDirectoryIndex = 0;
totalDirectories = dirs.length;
// get last modified map from database
Map<String, Long> map = OsuDB.getLastModifiedMap();
// OsuFile lists
List<ArrayList<OsuFile>> allOsuFiles = new LinkedList<ArrayList<OsuFile>>();
List<OsuFile> cachedOsuFiles = new LinkedList<OsuFile>(); // loaded from database
List<OsuFile> parsedOsuFiles = new LinkedList<OsuFile>(); // loaded from parser
// parse directories
OsuGroupNode lastNode = null;
for (File dir : dirs) {
currentDirectoryIndex++;
if (!dir.isDirectory())
continue;
// find all OSU files
File[] files = dir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".osu");
}
});
if (files.length < 1)
continue;
// create a new group entry
ArrayList<OsuFile> osuFiles = new ArrayList<OsuFile>();
for (File file : files) {
currentFile = file;
// check if beatmap is cached
String path = String.format("%s/%s", dir.getName(), file.getName());
if (map.containsKey(path)) {
// check last modified times
long lastModified = map.get(path);
if (lastModified == file.lastModified()) {
// add to cached beatmap list
OsuFile osu = new OsuFile(file);
osuFiles.add(osu);
cachedOsuFiles.add(osu);
continue;
} else
OsuDB.delete(dir.getName(), file.getName());
}
// Parse hit objects only when needed to save time/memory.
// Change boolean to 'true' to parse them immediately.
OsuFile osu = parseFile(file, dir, osuFiles, false);
// add to parsed beatmap list
if (osu != null) {
osuFiles.add(osu);
parsedOsuFiles.add(osu);
}
}
// add group entry if non-empty
if (!osuFiles.isEmpty()) {
osuFiles.trimToSize();
allOsuFiles.add(osuFiles);
}
// stop parsing files (interrupted)
if (Thread.interrupted())
break;
}
// load cached entries from database
if (!cachedOsuFiles.isEmpty()) {
status = Status.CACHE;
// Load array fields only when needed to save time/memory.
// Change flag to 'LOAD_ALL' to load them immediately.
OsuDB.load(cachedOsuFiles, OsuDB.LOAD_NONARRAY);
}
// add group entries to OsuGroupList
for (ArrayList<OsuFile> osuFiles : allOsuFiles) {
Collections.sort(osuFiles);
lastNode = OsuGroupList.get().addSongGroup(osuFiles);
}
// clear string DB
stringdb = new HashMap<String, String>();
// add beatmap entries to database
if (!parsedOsuFiles.isEmpty()) {
status = Status.INSERTING;
OsuDB.insert(parsedOsuFiles);
}
status = Status.NONE;
currentFile = null;
currentDirectoryIndex = -1;
totalDirectories = -1;
return lastNode;
}
/**
* Parses an OSU file.
* @param file the file to parse
* @param dir the directory containing the beatmap
* @param osuFiles the song group
* @param parseObjects if true, hit objects will be fully parsed now
* @return the new OsuFile object
*/
private static OsuFile parseFile(File file, File dir, ArrayList<OsuFile> osuFiles, boolean parseObjects) {
OsuFile osu = new OsuFile(file);
osu.timingPoints = new ArrayList<OsuTimingPoint>();
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))) {
String line = in.readLine();
String tokens[] = null;
while (line != null) {
line = line.trim();
if (!isValidLine(line)) {
line = in.readLine();
continue;
}
switch (line) {
case "[General]":
while ((line = in.readLine()) != null) {
line = line.trim();
if (!isValidLine(line))
continue;
if (line.charAt(0) == '[')
break;
if ((tokens = tokenize(line)) == null)
continue;
try {
switch (tokens[0]) {
case "AudioFilename":
File audioFileName = new File(dir, tokens[1]);
if (!osuFiles.isEmpty()) {
// if possible, reuse the same File object from another OsuFile in the group
File groupAudioFileName = osuFiles.get(0).audioFilename;
if (groupAudioFileName != null &&
tokens[1].equalsIgnoreCase(groupAudioFileName.getName()))
audioFileName = groupAudioFileName;
}
if (!audioFileName.isFile()) {
// try to find the file with a case-insensitive match
boolean match = false;
for (String s : dir.list()) {
if (s.equalsIgnoreCase(tokens[1])) {
audioFileName = new File(dir, s);
match = true;
break;
}
}
if (!match) {
Log.error(String.format("File not found: '%s'", tokens[1]));
return null;
}
}
osu.audioFilename = audioFileName;
break;
case "AudioLeadIn":
osu.audioLeadIn = Integer.parseInt(tokens[1]);
break;
// case "AudioHash": // deprecated
// osu.audioHash = tokens[1];
// break;
case "PreviewTime":
osu.previewTime = Integer.parseInt(tokens[1]);
break;
case "Countdown":
osu.countdown = Byte.parseByte(tokens[1]);
break;
case "SampleSet":
osu.sampleSet = getDBString(tokens[1]);
break;
case "StackLeniency":
osu.stackLeniency = Float.parseFloat(tokens[1]);
break;
case "Mode":
osu.mode = Byte.parseByte(tokens[1]);
/* Non-Opsu! standard files not implemented (obviously). */
if (osu.mode != OsuFile.MODE_OSU)
return null;
break;
case "LetterboxInBreaks":
osu.letterboxInBreaks = (Integer.parseInt(tokens[1]) == 1);
break;
case "WidescreenStoryboard":
osu.widescreenStoryboard = (Integer.parseInt(tokens[1]) == 1);
break;
case "EpilepsyWarning":
osu.epilepsyWarning = (Integer.parseInt(tokens[1]) == 1);
default:
break;
}
} catch (Exception e) {
Log.warn(String.format("Failed to read line '%s' for file '%s'.",
line, file.getAbsolutePath()), e);
}
}
break;
case "[Editor]":
while ((line = in.readLine()) != null) {
line = line.trim();
if (!isValidLine(line))
continue;
if (line.charAt(0) == '[')
break;
/* Not implemented. */
// if ((tokens = tokenize(line)) == null)
// continue;
// try {
// switch (tokens[0]) {
// case "Bookmarks":
// String[] bookmarks = tokens[1].split(",");
// osu.bookmarks = new int[bookmarks.length];
// for (int i = 0; i < bookmarks.length; i++)
// osu.bookmarks[i] = Integer.parseInt(bookmarks[i]);
// break;
// case "DistanceSpacing":
// osu.distanceSpacing = Float.parseFloat(tokens[1]);
// break;
// case "BeatDivisor":
// osu.beatDivisor = Byte.parseByte(tokens[1]);
// break;
// case "GridSize":
// osu.gridSize = Integer.parseInt(tokens[1]);
// break;
// case "TimelineZoom":
// osu.timelineZoom = Integer.parseInt(tokens[1]);
// break;
// default:
// break;
// }
// } catch (Exception e) {
// Log.warn(String.format("Failed to read editor line '%s' for file '%s'.",
// line, file.getAbsolutePath()), e);
// }
}
break;
case "[Metadata]":
while ((line = in.readLine()) != null) {
line = line.trim();
if (!isValidLine(line))
continue;
if (line.charAt(0) == '[')
break;
if ((tokens = tokenize(line)) == null)
continue;
try {
switch (tokens[0]) {
case "Title":
osu.title = getDBString(tokens[1]);
break;
case "TitleUnicode":
osu.titleUnicode = getDBString(tokens[1]);
break;
case "Artist":
osu.artist = getDBString(tokens[1]);
break;
case "ArtistUnicode":
osu.artistUnicode = getDBString(tokens[1]);
break;
case "Creator":
osu.creator = getDBString(tokens[1]);
break;
case "Version":
osu.version = getDBString(tokens[1]);
break;
case "Source":
osu.source = getDBString(tokens[1]);
break;
case "Tags":
osu.tags = getDBString(tokens[1].toLowerCase());
break;
case "BeatmapID":
osu.beatmapID = Integer.parseInt(tokens[1]);
break;
case "BeatmapSetID":
osu.beatmapSetID = Integer.parseInt(tokens[1]);
break;
}
} catch (Exception e) {
Log.warn(String.format("Failed to read metadata '%s' for file '%s'.",
line, file.getAbsolutePath()), e);
}
if (osu.beatmapSetID <= 0) { // try to determine MSID from directory name
if (dir != null && dir.isDirectory()) {
String dirName = dir.getName();
if (!dirName.isEmpty() && dirName.matches(DIR_MSID_PATTERN))
osu.beatmapSetID = Integer.parseInt(dirName.substring(0, dirName.indexOf(' ')));
}
}
}
break;
case "[Difficulty]":
while ((line = in.readLine()) != null) {
line = line.trim();
if (!isValidLine(line))
continue;
if (line.charAt(0) == '[')
break;
if ((tokens = tokenize(line)) == null)
continue;
try {
switch (tokens[0]) {
case "HPDrainRate":
osu.HPDrainRate = Float.parseFloat(tokens[1]);
break;
case "CircleSize":
osu.circleSize = Float.parseFloat(tokens[1]);
break;
case "OverallDifficulty":
osu.overallDifficulty = Float.parseFloat(tokens[1]);
break;
case "ApproachRate":
osu.approachRate = Float.parseFloat(tokens[1]);
break;
case "SliderMultiplier":
osu.sliderMultiplier = Float.parseFloat(tokens[1]);
break;
case "SliderTickRate":
osu.sliderTickRate = Float.parseFloat(tokens[1]);
break;
}
} catch (Exception e) {
Log.warn(String.format("Failed to read difficulty '%s' for file '%s'.",
line, file.getAbsolutePath()), e);
}
}
if (osu.approachRate == -1f) // not in old format
osu.approachRate = osu.overallDifficulty;
break;
case "[Events]":
while ((line = in.readLine()) != null) {
line = line.trim();
if (!isValidLine(line))
continue;
if (line.charAt(0) == '[')
break;
tokens = line.split(",");
switch (tokens[0]) {
case "0": // background
tokens[2] = tokens[2].replaceAll("^\"|\"$", "");
String ext = OsuParser.getExtension(tokens[2]);
if (ext.equals("jpg") || ext.equals("png"))
osu.bg = getDBString(tokens[2]);
break;
case "2": // break periods
try {
if (osu.breaks == null) // optional, create if needed
osu.breaks = new ArrayList<Integer>();
osu.breaks.add(Integer.parseInt(tokens[1]));
osu.breaks.add(Integer.parseInt(tokens[2]));
} catch (Exception e) {
Log.warn(String.format("Failed to read break period '%s' for file '%s'.",
line, file.getAbsolutePath()), e);
}
break;
default:
/* Not implemented. */
break;
}
}
if (osu.breaks != null)
osu.breaks.trimToSize();
break;
case "[TimingPoints]":
while ((line = in.readLine()) != null) {
line = line.trim();
if (!isValidLine(line))
continue;
if (line.charAt(0) == '[')
break;
try {
// parse timing point
OsuTimingPoint timingPoint = new OsuTimingPoint(line);
// calculate BPM
if (!timingPoint.isInherited()) {
int bpm = Math.round(60000 / timingPoint.getBeatLength());
if (osu.bpmMin == 0)
osu.bpmMin = osu.bpmMax = bpm;
else if (bpm < osu.bpmMin)
osu.bpmMin = bpm;
else if (bpm > osu.bpmMax)
osu.bpmMax = bpm;
}
osu.timingPoints.add(timingPoint);
} catch (Exception e) {
Log.warn(String.format("Failed to read timing point '%s' for file '%s'.",
line, file.getAbsolutePath()), e);
}
}
osu.timingPoints.trimToSize();
break;
case "[Colours]":
LinkedList<Color> colors = new LinkedList<Color>();
while ((line = in.readLine()) != null) {
line = line.trim();
if (!isValidLine(line))
continue;
if (line.charAt(0) == '[')
break;
if ((tokens = tokenize(line)) == null)
continue;
try {
switch (tokens[0]) {
case "Combo1":
case "Combo2":
case "Combo3":
case "Combo4":
case "Combo5":
case "Combo6":
case "Combo7":
case "Combo8":
String[] rgb = tokens[1].split(",");
colors.add(new Color(
Integer.parseInt(rgb[0]),
Integer.parseInt(rgb[1]),
Integer.parseInt(rgb[2])
));
default:
break;
}
} catch (Exception e) {
Log.warn(String.format("Failed to read color '%s' for file '%s'.",
line, file.getAbsolutePath()), e);
}
}
if (!colors.isEmpty())
osu.combo = colors.toArray(new Color[colors.size()]);
break;
case "[HitObjects]":
int type = 0;
while ((line = in.readLine()) != null) {
line = line.trim();
if (!isValidLine(line))
continue;
if (line.charAt(0) == '[')
break;
/* Only type counts parsed at this time. */
tokens = line.split(",");
try {
type = Integer.parseInt(tokens[3]);
if ((type & OsuHitObject.TYPE_CIRCLE) > 0)
osu.hitObjectCircle++;
else if ((type & OsuHitObject.TYPE_SLIDER) > 0)
osu.hitObjectSlider++;
else //if ((type & OsuHitObject.TYPE_SPINNER) > 0)
osu.hitObjectSpinner++;
} catch (Exception e) {
Log.warn(String.format("Failed to read hit object '%s' for file '%s'.",
line, file.getAbsolutePath()), e);
}
}
try {
// map length = last object end time (TODO: end on slider?)
if ((type & OsuHitObject.TYPE_SPINNER) > 0) {
// some 'endTime' fields contain a ':' character (?)
int index = tokens[5].indexOf(':');
if (index != -1)
tokens[5] = tokens[5].substring(0, index);
osu.endTime = Integer.parseInt(tokens[5]);
} else if (type != 0)
osu.endTime = Integer.parseInt(tokens[2]);
} catch (Exception e) {
Log.warn(String.format("Failed to read hit object end time '%s' for file '%s'.",
line, file.getAbsolutePath()), e);
}
break;
default:
line = in.readLine();
break;
}
}
} catch (IOException e) {
ErrorHandler.error(String.format("Failed to read file '%s'.", file.getAbsolutePath()), e, false);
}
// no associated audio file?
if (osu.audioFilename == null)
return null;
// if no custom colors, use the default color scheme
if (osu.combo == null)
osu.combo = Utils.DEFAULT_COMBO;
// parse hit objects now?
if (parseObjects)
parseHitObjects(osu);
osu.md5Hash = Utils.getMD5(file);
return osu;
}
/**
* Parses all hit objects in an OSU file.
* @param osu the OsuFile to parse
*/
public static void parseHitObjects(OsuFile osu) {
if (osu.objects != null) // already parsed
return;
osu.objects = new OsuHitObject[(osu.hitObjectCircle
+ osu.hitObjectSlider + osu.hitObjectSpinner)];
try (BufferedReader in = new BufferedReader(new FileReader(osu.getFile()))) {
String line = in.readLine();
while (line != null) {
line = line.trim();
if (!line.equals("[HitObjects]"))
line = in.readLine();
else
break;
}
if (line == null) {
Log.warn(String.format("No hit objects found in OsuFile '%s'.", osu.toString()));
return;
}
// combo info
int comboIndex = 0; // color index
int comboNumber = 1; // combo number
int objectIndex = 0;
boolean first = true;
while ((line = in.readLine()) != null && objectIndex < osu.objects.length) {
line = line.trim();
if (!isValidLine(line))
continue;
if (line.charAt(0) == '[')
break;
// lines must have at minimum 5 parameters
int tokenCount = line.length() - line.replace(",", "").length();
if (tokenCount < 4)
continue;
try {
// create a new OsuHitObject for each line
OsuHitObject hitObject = new OsuHitObject(line);
// set combo info
// - new combo: get next combo index, reset combo number
// - else: maintain combo index, increase combo number
if (hitObject.isNewCombo() || first) {
int skip = (hitObject.isSpinner() ? 0 : 1) + hitObject.getComboSkip();
for (int i = 0; i < skip; i++) {
comboIndex = (comboIndex + 1) % osu.combo.length;
comboNumber = 1;
}
first = false;
}
hitObject.setComboIndex(comboIndex);
hitObject.setComboNumber(comboNumber++);
osu.objects[objectIndex++] = hitObject;
} catch (Exception e) {
Log.warn(String.format("Failed to read hit object '%s' for OsuFile '%s'.",
line, osu.toString()), e);
}
}
} catch (IOException e) {
ErrorHandler.error(String.format("Failed to read file '%s'.", osu.getFile().getAbsolutePath()), e, false);
}
}
/**
* Returns false if the line is too short or commented.
*/
private static boolean isValidLine(String line) {
return (line.length() > 1 && !line.startsWith("//"));
}
/**
* Splits line into two strings: tag, value.
* If no ':' character is present, null will be returned.
*/
private static String[] tokenize(String line) {
int index = line.indexOf(':');
if (index == -1) {
Log.debug(String.format("Failed to tokenize line: '%s'.", line));
return null;
}
String[] tokens = new String[2];
tokens[0] = line.substring(0, index).trim();
tokens[1] = line.substring(index + 1).trim();
return tokens;
}
/**
* Returns the file extension of a file.
*/
public static String getExtension(String file) {
int i = file.lastIndexOf('.');
return (i != -1) ? file.substring(i + 1).toLowerCase() : "";
}
/**
* Returns the name of the current file being parsed, or null if none.
*/
public static String getCurrentFileName() {
if (status == Status.PARSING)
return (currentFile != null) ? currentFile.getName() : null;
else
return (status == Status.NONE) ? null : "";
}
/**
* Returns the progress of file parsing, or -1 if not parsing.
* @return the completion percent [0, 100] or -1
*/
public static int getParserProgress() {
if (currentDirectoryIndex == -1 || totalDirectories == -1)
return -1;
return currentDirectoryIndex * 100 / totalDirectories;
}
/**
* Returns the current parser status.
*/
public static Status getStatus() { return status; }
/**
* Returns the String object in the database for the given String.
* If none, insert the String into the database and return the original String.
* @param s the string to retrieve
* @return the string object
*/
public static String getDBString(String s) {
String DBString = stringdb.get(s);
if (DBString == null) {
stringdb.put(s, s);
return s;
} else
return DBString;
}
}

View File

@ -1,722 +0,0 @@
//TODO rename?
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014, 2015 Jeffrey Han
*
* opsu! is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu! is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu;
import itdelatrisu.opsu.audio.SoundController;
import java.nio.IntBuffer;
import java.util.Iterator;
import java.util.LinkedList;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Cursor;
import org.newdawn.slick.Animation;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.StateBasedGame;
/**
* Class primarily used for drawing UI components.
*/
public class UI {
/** Back button. */
private static MenuButton backButton;
/** Empty cursor. */
private static Cursor emptyCursor;
/** Last cursor coordinates. */
private static int lastX = -1, lastY = -1;
/** Cursor rotation angle. */
private static float cursorAngle = 0f;
/** Stores all previous cursor locations to display a trail. */
private static LinkedList<Integer>
cursorX = new LinkedList<Integer>(),
cursorY = new LinkedList<Integer>();
/** Time to show volume image, in milliseconds. */
private static final int VOLUME_DISPLAY_TIME = 1500;
/** Volume display elapsed time. */
private static int volumeDisplay = -1;
/** The current bar notification string. */
private static String barNotif;
/** The current bar notification timer. */
private static int barNotifTimer = -1;
/** Duration, in milliseconds, to display bar notifications. */
private static final int BAR_NOTIFICATION_TIME = 1250;
/** The current tooltip. */
private static String tooltip;
/** Whether or not to check the current tooltip for line breaks. */
private static boolean tooltipNewlines;
/** The current tooltip timer. */
private static int tooltipTimer = -1;
/** Duration, in milliseconds, to fade tooltips. */
private static final int TOOLTIP_FADE_TIME = 200;
// game-related variables
private static GameContainer container;
private static StateBasedGame game;
private static Input input;
// This class should not be instantiated.
private UI() {}
/**
* Initializes UI data.
* @param container the game container
* @param game the game object
* @throws SlickException
*/
public static void init(GameContainer container, StateBasedGame game)
throws SlickException {
UI.container = container;
UI.game = game;
UI.input = container.getInput();
// hide native cursor
try {
int min = Cursor.getMinCursorSize();
IntBuffer tmp = BufferUtils.createIntBuffer(min * min);
emptyCursor = new Cursor(min, min, min/2, min/2, 1, tmp, null);
hideCursor();
} catch (LWJGLException e) {
ErrorHandler.error("Failed to create hidden cursor.", e, true);
}
// back button
if (GameImage.MENU_BACK.getImages() != null) {
Animation back = GameImage.MENU_BACK.getAnimation(120);
backButton = new MenuButton(back, back.getWidth() / 2f, container.getHeight() - (back.getHeight() / 2f));
} else {
Image back = GameImage.MENU_BACK.getImage();
backButton = new MenuButton(back, back.getWidth() / 2f, container.getHeight() - (back.getHeight() / 2f));
}
backButton.setHoverExpand(MenuButton.Expand.UP_RIGHT);
}
/**
* Updates all UI components by a delta interval.
* @param delta the delta interval since the last call.
*/
public static void update(int delta) {
updateCursor(delta);
updateVolumeDisplay(delta);
updateBarNotification(delta);
if (tooltipTimer > 0)
tooltipTimer -= delta;
}
/**
* Draws the global UI components: cursor, FPS, volume bar, bar notifications.
* @param g the graphics context
*/
public static void draw(Graphics g) {
drawBarNotification(g);
drawVolume(g);
drawFPS();
drawCursor();
drawTooltip(g);
}
/**
* Draws the global UI components: cursor, FPS, volume bar, bar notifications.
* @param g the graphics context
* @param mouseX the mouse x coordinate
* @param mouseY the mouse y coordinate
* @param mousePressed whether or not the mouse button is pressed
*/
public static void draw(Graphics g, int mouseX, int mouseY, boolean mousePressed) {
drawBarNotification(g);
drawVolume(g);
drawFPS();
drawCursor(mouseX, mouseY, mousePressed);
drawTooltip(g);
}
/**
* Resets the necessary UI components upon entering a state.
*/
public static void enter() {
backButton.resetHover();
resetBarNotification();
resetCursorLocations();
resetTooltip();
}
/**
* Returns the 'menu-back' MenuButton.
*/
public static MenuButton getBackButton() { return backButton; }
/**
* Draws a tab image and text centered at a location.
* @param x the center x coordinate
* @param y the center y coordinate
* @param text the text to draw inside the tab
* @param selected whether the tab is selected (white) or not (red)
* @param isHover whether to include a hover effect (unselected only)
*/
public static void drawTab(float x, float y, String text, boolean selected, boolean isHover) {
Image tabImage = GameImage.MENU_TAB.getImage();
float tabTextX = x - (Utils.FONT_MEDIUM.getWidth(text) / 2);
float tabTextY = y - (tabImage.getHeight() / 2.5f);
Color filter, textColor;
if (selected) {
filter = Color.white;
textColor = Color.black;
} else {
filter = (isHover) ? Utils.COLOR_RED_HOVER : Color.red;
textColor = Color.white;
}
tabImage.drawCentered(x, y, filter);
Utils.FONT_MEDIUM.drawString(tabTextX, tabTextY, text, textColor);
}
/**
* Draws the cursor.
*/
public static void drawCursor() {
int state = game.getCurrentStateID();
boolean mousePressed =
(((state == Opsu.STATE_GAME || state == Opsu.STATE_GAMEPAUSEMENU) && Utils.isGameKeyPressed()) ||
((input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON) || input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON)) &&
!(state == Opsu.STATE_GAME && Options.isMouseDisabled())));
drawCursor(input.getMouseX(), input.getMouseY(), mousePressed);
}
/**
* Draws the cursor.
* @param mouseX the mouse x coordinate
* @param mouseY the mouse y coordinate
* @param mousePressed whether or not the mouse button is pressed
*/
public static void drawCursor(int mouseX, int mouseY, boolean mousePressed) {
// determine correct cursor image
// TODO: most beatmaps don't skin CURSOR_MIDDLE, so how to determine style?
Image cursor = null, cursorMiddle = null, cursorTrail = null;
boolean skinned = GameImage.CURSOR.hasSkinImage();
boolean newStyle = (skinned) ? true : Options.isNewCursorEnabled();
if (skinned || newStyle) {
cursor = GameImage.CURSOR.getImage();
cursorTrail = GameImage.CURSOR_TRAIL.getImage();
} else {
cursor = GameImage.CURSOR_OLD.getImage();
cursorTrail = GameImage.CURSOR_TRAIL_OLD.getImage();
}
if (newStyle)
cursorMiddle = GameImage.CURSOR_MIDDLE.getImage();
int removeCount = 0;
int FPSmod = (Options.getTargetFPS() / 60);
// TODO: use an image buffer
/*
if (newStyle) {
// new style: add all points between cursor movements
if (lastX < 0) {
lastX = mouseX;
lastY = mouseY;
return;
}
addCursorPoints(lastX, lastY, mouseX, mouseY);
lastX = mouseX;
lastY = mouseY;
removeCount = (cursorX.size() / (6 * FPSmod)) + 1;
} else {
// old style: sample one point at a time
cursorX.add(mouseX);
cursorY.add(mouseY);
int max = 10 * FPSmod;
if (cursorX.size() > max)
removeCount = cursorX.size() - max;
}*/
// remove points from the lists
for (int i = 0; i < removeCount && !cursorX.isEmpty(); i++) {
cursorX.remove();
cursorY.remove();
}
// draw a fading trail
float alpha = 0f;
float t = 2f / cursorX.size();
Iterator<Integer> iterX = cursorX.iterator();
Iterator<Integer> iterY = cursorY.iterator();
while (iterX.hasNext()) {
int cx = iterX.next();
int cy = iterY.next();
alpha += t;
cursorTrail.setAlpha(alpha);
// if (cx != x || cy != y)
cursorTrail.drawCentered(cx, cy);
}
cursorTrail.drawCentered(mouseX, mouseY);
// increase the cursor size if pressed
final float scale = 1.25f;
if (mousePressed) {
cursor = cursor.getScaledCopy(scale);
if (newStyle)
cursorMiddle = cursorMiddle.getScaledCopy(scale);
}
// draw the other components
if (newStyle)
cursor.setRotation(cursorAngle);
cursor.drawCentered(mouseX, mouseY);
if (newStyle)
cursorMiddle.drawCentered(mouseX, mouseY);
}
/**
* Adds all points between (x1, y1) and (x2, y2) to the cursor point lists.
* @author http://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#Java
*/
private static void addCursorPoints(int x1, int y1, int x2, int y2) {
// delta of exact value and rounded value of the dependent variable
int d = 0;
int dy = Math.abs(y2 - y1);
int dx = Math.abs(x2 - x1);
int dy2 = (dy << 1); // slope scaling factors to avoid floating
int dx2 = (dx << 1); // point
int ix = x1 < x2 ? 1 : -1; // increment direction
int iy = y1 < y2 ? 1 : -1;
int k = 5; // sample size
if (dy <= dx) {
for (int i = 0; ; i++) {
if (i == k) {
cursorX.add(x1);
cursorY.add(y1);
i = 0;
}
if (x1 == x2)
break;
x1 += ix;
d += dy2;
if (d > dx) {
y1 += iy;
d -= dx2;
}
}
} else {
for (int i = 0; ; i++) {
if (i == k) {
cursorX.add(x1);
cursorY.add(y1);
i = 0;
}
if (y1 == y2)
break;
y1 += iy;
d += dx2;
if (d > dy) {
x1 += ix;
d -= dy2;
}
}
}
}
/**
* Rotates the cursor by a degree determined by a delta interval.
* If the old style cursor is being used, this will do nothing.
* @param delta the delta interval since the last call
*/
private static void updateCursor(int delta) {
cursorAngle += delta / 40f;
cursorAngle %= 360;
}
/**
* Resets all cursor data and skins.
*/
public static void resetCursor() {
GameImage.CURSOR.destroySkinImage();
GameImage.CURSOR_MIDDLE.destroySkinImage();
GameImage.CURSOR_TRAIL.destroySkinImage();
cursorAngle = 0f;
GameImage.CURSOR.getImage().setRotation(0f);
}
/**
* Resets all cursor location data.
*/
private static void resetCursorLocations() {
lastX = lastY = -1;
cursorX.clear();
cursorY.clear();
}
/**
* Hides the cursor, if possible.
*/
public static void hideCursor() {
if (emptyCursor != null) {
try {
container.setMouseCursor(emptyCursor, 0, 0);
} catch (SlickException e) {
ErrorHandler.error("Failed to hide the cursor.", e, true);
}
}
}
/**
* Unhides the cursor.
*/
public static void showCursor() {
container.setDefaultMouseCursor();
}
/**
* Draws the FPS at the bottom-right corner of the game container.
* If the option is not activated, this will do nothing.
*/
public static void drawFPS() {
if (!Options.isFPSCounterEnabled())
return;
String fps = String.format("%dFPS", container.getFPS());
Utils.FONT_BOLD.drawString(
container.getWidth() * 0.997f - Utils.FONT_BOLD.getWidth(fps),
container.getHeight() * 0.997f - Utils.FONT_BOLD.getHeight(fps),
Integer.toString(container.getFPS()), Color.white
);
Utils.FONT_DEFAULT.drawString(
container.getWidth() * 0.997f - Utils.FONT_BOLD.getWidth("FPS"),
container.getHeight() * 0.997f - Utils.FONT_BOLD.getHeight("FPS"),
"FPS", Color.white
);
}
/**
* Draws the volume bar on the middle right-hand side of the game container.
* Only draws if the volume has recently been changed using with {@link #changeVolume(int)}.
* @param g the graphics context
*/
public static void drawVolume(Graphics g) {
if (volumeDisplay == -1)
return;
int width = container.getWidth(), height = container.getHeight();
Image img = GameImage.VOLUME.getImage();
// move image in/out
float xOffset = 0;
float ratio = (float) volumeDisplay / VOLUME_DISPLAY_TIME;
if (ratio <= 0.1f)
xOffset = img.getWidth() * (1 - (ratio * 10f));
else if (ratio >= 0.9f)
xOffset = img.getWidth() * (1 - ((1 - ratio) * 10f));
img.drawCentered(width - img.getWidth() / 2f + xOffset, height / 2f);
float barHeight = img.getHeight() * 0.9f;
float volume = Options.getMasterVolume();
g.setColor(Color.white);
g.fillRoundRect(
width - (img.getWidth() * 0.368f) + xOffset,
(height / 2f) - (img.getHeight() * 0.47f) + (barHeight * (1 - volume)),
img.getWidth() * 0.15f, barHeight * volume, 3
);
}
/**
* Updates volume display by a delta interval.
* @param delta the delta interval since the last call
*/
private static void updateVolumeDisplay(int delta) {
if (volumeDisplay == -1)
return;
volumeDisplay += delta;
if (volumeDisplay > VOLUME_DISPLAY_TIME)
volumeDisplay = -1;
}
/**
* Changes the master volume by a unit (positive or negative).
* @param units the number of units
*/
public static void changeVolume(int units) {
final float UNIT_OFFSET = 0.05f;
Options.setMasterVolume(container, Utils.getBoundedValue(Options.getMasterVolume(), UNIT_OFFSET * units, 0f, 1f));
if (volumeDisplay == -1)
volumeDisplay = 0;
else if (volumeDisplay >= VOLUME_DISPLAY_TIME / 10)
volumeDisplay = VOLUME_DISPLAY_TIME / 10;
}
/**
* Draws loading progress (OSZ unpacking, OsuFile parsing, sound loading)
* at the bottom of the screen.
*/
public static void drawLoadingProgress(Graphics g) {
String text, file;
int progress;
// determine current action
if ((file = OszUnpacker.getCurrentFileName()) != null) {
text = "Unpacking new beatmaps...";
progress = OszUnpacker.getUnpackerProgress();
} else if ((file = OsuParser.getCurrentFileName()) != null) {
text = (OsuParser.getStatus() == OsuParser.Status.INSERTING) ?
"Updating database..." : "Loading beatmaps...";
progress = OsuParser.getParserProgress();
} else if ((file = SoundController.getCurrentFileName()) != null) {
text = "Loading sounds...";
progress = SoundController.getLoadingProgress();
} else
return;
// draw loading info
float marginX = container.getWidth() * 0.02f, marginY = container.getHeight() * 0.02f;
float lineY = container.getHeight() - marginY;
int lineOffsetY = Utils.FONT_MEDIUM.getLineHeight();
if (Options.isLoadVerbose()) {
// verbose: display percentages and file names
Utils.FONT_MEDIUM.drawString(
marginX, lineY - (lineOffsetY * 2),
String.format("%s (%d%%)", text, progress), Color.white);
Utils.FONT_MEDIUM.drawString(marginX, lineY - lineOffsetY, file, Color.white);
} else {
// draw loading bar
Utils.FONT_MEDIUM.drawString(marginX, lineY - (lineOffsetY * 2), text, Color.white);
g.setColor(Color.white);
g.fillRoundRect(marginX, lineY - (lineOffsetY / 2f),
(container.getWidth() - (marginX * 2f)) * progress / 100f, lineOffsetY / 4f, 4
);
}
}
/**
* Draws a scroll bar.
* @param g the graphics context
* @param unitIndex the unit index
* @param totalUnits the total number of units
* @param maxShown the maximum number of units shown at one time
* @param unitBaseX the base x coordinate of the units
* @param unitBaseY the base y coordinate of the units
* @param unitWidth the width of a unit
* @param unitHeight the height of a unit
* @param unitOffsetY the y offset between units
* @param bgColor the scroll bar area background color (null if none)
* @param scrollbarColor the scroll bar color
* @param right whether or not to place the scroll bar on the right side of the unit
*/
public static void drawScrollbar(
Graphics g, int unitIndex, int totalUnits, int maxShown,
float unitBaseX, float unitBaseY, float unitWidth, float unitHeight, float unitOffsetY,
Color bgColor, Color scrollbarColor, boolean right
) {
float scrollbarWidth = container.getWidth() * 0.00347f;
float heightRatio = (float) (2.6701f * Math.exp(-0.81 * Math.log(totalUnits)));
float scrollbarHeight = container.getHeight() * heightRatio;
float scrollAreaHeight = unitHeight + unitOffsetY * (maxShown - 1);
float offsetY = (scrollAreaHeight - scrollbarHeight) * ((float) unitIndex / (totalUnits - maxShown));
float scrollbarX = unitBaseX + unitWidth - ((right) ? scrollbarWidth : 0);
if (bgColor != null) {
g.setColor(bgColor);
g.fillRect(scrollbarX, unitBaseY, scrollbarWidth, scrollAreaHeight);
}
g.setColor(scrollbarColor);
g.fillRect(scrollbarX, unitBaseY + offsetY, scrollbarWidth, scrollbarHeight);
}
/**
* Sets or updates a tooltip for drawing.
* Must be called with {@link #drawTooltip(Graphics)}.
* @param delta the delta interval since the last call
* @param s the tooltip text
* @param newlines whether to check for line breaks ('\n')
*/
public static void updateTooltip(int delta, String s, boolean newlines) {
if (s != null) {
tooltip = s;
tooltipNewlines = newlines;
if (tooltipTimer <= 0)
tooltipTimer = delta;
else
tooltipTimer += delta * 2;
if (tooltipTimer > TOOLTIP_FADE_TIME)
tooltipTimer = TOOLTIP_FADE_TIME;
}
}
/**
* Draws a tooltip, if any, near the current mouse coordinates,
* bounded by the container dimensions.
* @param g the graphics context
*/
public static void drawTooltip(Graphics g) {
if (tooltipTimer <= 0 || tooltip == null)
return;
int containerWidth = container.getWidth(), containerHeight = container.getHeight();
int margin = containerWidth / 100, textMarginX = 2;
int offset = GameImage.CURSOR_MIDDLE.getImage().getWidth() / 2;
int lineHeight = Utils.FONT_SMALL.getLineHeight();
int textWidth = textMarginX * 2, textHeight = lineHeight;
if (tooltipNewlines) {
String[] lines = tooltip.split("\\n");
int maxWidth = Utils.FONT_SMALL.getWidth(lines[0]);
for (int i = 1; i < lines.length; i++) {
int w = Utils.FONT_SMALL.getWidth(lines[i]);
if (w > maxWidth)
maxWidth = w;
}
textWidth += maxWidth;
textHeight += lineHeight * (lines.length - 1);
} else
textWidth += Utils.FONT_SMALL.getWidth(tooltip);
// get drawing coordinates
int x = input.getMouseX() + offset, y = input.getMouseY() + offset;
if (x + textWidth > containerWidth - margin)
x = containerWidth - margin - textWidth;
else if (x < margin)
x = margin;
if (y + textHeight > containerHeight - margin)
y = containerHeight - margin - textHeight;
else if (y < margin)
y = margin;
// draw tooltip text inside a filled rectangle
float alpha = (float) tooltipTimer / TOOLTIP_FADE_TIME;
float oldAlpha = Utils.COLOR_BLACK_ALPHA.a;
Utils.COLOR_BLACK_ALPHA.a = alpha;
g.setColor(Utils.COLOR_BLACK_ALPHA);
Utils.COLOR_BLACK_ALPHA.a = oldAlpha;
g.fillRect(x, y, textWidth, textHeight);
oldAlpha = Utils.COLOR_DARK_GRAY.a;
Utils.COLOR_DARK_GRAY.a = alpha;
g.setColor(Utils.COLOR_DARK_GRAY);
g.setLineWidth(1);
g.drawRect(x, y, textWidth, textHeight);
Utils.COLOR_DARK_GRAY.a = oldAlpha;
oldAlpha = Utils.COLOR_WHITE_ALPHA.a;
Utils.COLOR_WHITE_ALPHA.a = alpha;
Utils.FONT_SMALL.drawString(x + textMarginX, y, tooltip, Utils.COLOR_WHITE_ALPHA);
Utils.COLOR_WHITE_ALPHA.a = oldAlpha;
}
/**
* Resets the tooltip.
*/
public static void resetTooltip() {
tooltipTimer = -1;
tooltip = null;
}
/**
* Submits a bar notification for drawing.
* Must be called with {@link #drawBarNotification(Graphics)}.
* @param s the notification string
*/
public static void sendBarNotification(String s) {
if (s != null) {
barNotif = s;
barNotifTimer = 0;
}
}
/**
* Updates the bar notification by a delta interval.
* @param delta the delta interval since the last call
*/
private static void updateBarNotification(int delta) {
if (barNotifTimer > -1 && barNotifTimer < BAR_NOTIFICATION_TIME) {
barNotifTimer += delta;
if (barNotifTimer > BAR_NOTIFICATION_TIME)
barNotifTimer = BAR_NOTIFICATION_TIME;
}
}
/**
* Resets the bar notification.
*/
public static void resetBarNotification() {
barNotifTimer = -1;
barNotif = null;
}
/**
* Draws the notification sent from {@link #sendBarNotification(String)}.
* @param g the graphics context
*/
public static void drawBarNotification(Graphics g) {
if (barNotifTimer <= 0 || barNotifTimer >= BAR_NOTIFICATION_TIME)
return;
float alpha = 1f;
if (barNotifTimer >= BAR_NOTIFICATION_TIME * 0.9f)
alpha -= 1 - ((BAR_NOTIFICATION_TIME - barNotifTimer) / (BAR_NOTIFICATION_TIME * 0.1f));
int midX = container.getWidth() / 2, midY = container.getHeight() / 2;
float barHeight = Utils.FONT_LARGE.getLineHeight() * (1f + 0.6f * Math.min(barNotifTimer * 15f / BAR_NOTIFICATION_TIME, 1f));
float oldAlphaB = Utils.COLOR_BLACK_ALPHA.a, oldAlphaW = Utils.COLOR_WHITE_ALPHA.a;
Utils.COLOR_BLACK_ALPHA.a *= alpha;
Utils.COLOR_WHITE_ALPHA.a = alpha;
g.setColor(Utils.COLOR_BLACK_ALPHA);
g.fillRect(0, midY - barHeight / 2f, container.getWidth(), barHeight);
Utils.FONT_LARGE.drawString(
midX - Utils.FONT_LARGE.getWidth(barNotif) / 2f,
midY - Utils.FONT_LARGE.getLineHeight() / 2.2f,
barNotif, Utils.COLOR_WHITE_ALPHA);
Utils.COLOR_BLACK_ALPHA.a = oldAlphaB;
Utils.COLOR_WHITE_ALPHA.a = oldAlphaW;
}
/**
* Shows a confirmation dialog (used before exiting the game).
* @param message the message to display
* @return true if user selects "yes", false otherwise
*/
public static boolean showExitConfirmation(String message) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
ErrorHandler.error("Could not set system look and feel for exit confirmation.", e, true);
}
int n = JOptionPane.showConfirmDialog(null, message, "Warning",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
return (n != JOptionPane.YES_OPTION);
}
}

View File

@ -185,6 +185,9 @@ public class Beatmap implements Comparable<Beatmap> {
/** Slider border color. If null, the skin value is used. */
public Color sliderBorder;
/** md5 hash of this file */
public String md5Hash;
/**
* [HitObjects]
*/

View File

@ -590,6 +590,8 @@ public class BeatmapParser {
if (parseObjects)
parseHitObjects(beatmap);
beatmap.md5Hash = Utils.getMD5(file);
return beatmap;
}

View File

@ -28,6 +28,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
@ -58,6 +59,10 @@ public class BeatmapSetList {
/** Set of all beatmap set IDs for the parsed beatmaps. */
private HashSet<Integer> MSIDdb;
/** Map of all hash to Beatmap . */
public HashMap<String, Beatmap> beatmapHashesToFile;
/** Index of current expanded node (-1 if no node is expanded). */
private int expandedIndex;
@ -83,6 +88,7 @@ public class BeatmapSetList {
private BeatmapSetList() {
parsedNodes = new ArrayList<BeatmapSetNode>();
MSIDdb = new HashSet<Integer>();
beatmapHashesToFile = new HashMap<String, Beatmap>();
reset();
}
@ -117,6 +123,10 @@ public class BeatmapSetList {
int msid = beatmaps.get(0).beatmapSetID;
if (msid > 0)
MSIDdb.add(msid);
for(Beatmap f : beatmaps) {
beatmapHashesToFile.put(f.md5Hash, f);
}
return node;
}
@ -501,4 +511,8 @@ public class BeatmapSetList {
* @return true if id is in the list
*/
public boolean containsBeatmapSetID(int id) { return MSIDdb.contains(id); }
public Beatmap getFileFromBeatmapHash(String beatmapHash) {
return beatmapHashesToFile.get(beatmapHash);
}
}

View File

@ -43,7 +43,7 @@ public class BeatmapDB {
* Current database version.
* This value should be changed whenever the database format changes.
*/
private static final String DATABASE_VERSION = "2014-06-08";
private static final String DATABASE_VERSION = "2015-06-11";
/** Minimum batch size ratio ({@code batchSize/cacheSize}) to invoke batch loading. */
private static final float LOAD_BATCH_MIN_RATIO = 0.2f;
@ -96,7 +96,7 @@ public class BeatmapDB {
insertStmt = connection.prepareStatement(
"INSERT INTO beatmaps VALUES (" +
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " +
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
selectStmt = connection.prepareStatement("SELECT * FROM beatmaps WHERE dir = ? AND file = ?");
deleteMapStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ? AND file = ?");
@ -122,7 +122,8 @@ public class BeatmapDB {
"bpmMin INTEGER, bpmMax INTEGER, endTime INTEGER, " +
"audioFile TEXT, audioLeadIn INTEGER, previewTime INTEGER, countdown INTEGER, sampleSet TEXT, stackLeniency REAL, " +
"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" +
"); " +
"CREATE TABLE IF NOT EXISTS info (" +
"key TEXT NOT NULL UNIQUE, value TEXT" +
@ -340,6 +341,7 @@ public class BeatmapDB {
stmt.setString(38, beatmap.timingPointsToString());
stmt.setString(39, beatmap.breaksToString());
stmt.setString(40, beatmap.comboToString());
stmt.setString(41, beatmap.md5Hash);
} catch (SQLException e) {
throw e;
} catch (Exception e) {
@ -481,6 +483,7 @@ public class BeatmapDB {
if (bg != null)
beatmap.bg = new File(dir, BeatmapParser.getDBString(bg));
beatmap.sliderBorderFromString(rs.getString(37));
beatmap.md5Hash = BeatmapParser.getDBString(rs.getString(41));
} catch (SQLException e) {
throw e;
} catch (Exception e) {

View File

@ -1,584 +0,0 @@
//todo rename
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014, 2015 Jeffrey Han
*
* opsu! is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu! is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu.db;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.OsuFile;
import itdelatrisu.opsu.OsuParser;
import java.io.File;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.newdawn.slick.util.Log;
/**
* Handles connections and queries with the cached beatmap database.
*/
public class OsuDB {
/**
* Current database version.
* This value should be changed whenever the database format changes.
*/
private static final String DATABASE_VERSION = "2015-03-30";
/** Minimum batch size ratio ({@code batchSize/cacheSize}) to invoke batch loading. */
private static final float LOAD_BATCH_MIN_RATIO = 0.2f;
/** Minimum batch size to invoke batch insertion. */
private static final int INSERT_BATCH_MIN = 100;
/** OsuFile loading flags. */
public static final int LOAD_NONARRAY = 1, LOAD_ARRAY = 2, LOAD_ALL = 3;
/** Database connection. */
private static Connection connection;
/** Query statements. */
private static PreparedStatement insertStmt, selectStmt, deleteMapStmt, deleteGroupStmt, updateSizeStmt;
/** Current size of beatmap cache table. */
private static int cacheSize = -1;
// This class should not be instantiated.
private OsuDB() {}
/**
* Initializes the database connection.
*/
public static void init() {
// create a database connection
connection = DBController.createConnection(Options.OSU_DB.getPath());
if (connection == null)
return;
// create the database
createDatabase();
// prepare sql statements
try {
insertStmt = connection.prepareStatement(
"INSERT INTO beatmaps VALUES (" +
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " +
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
selectStmt = connection.prepareStatement("SELECT * FROM beatmaps WHERE dir = ? AND file = ?");
deleteMapStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ? AND file = ?");
deleteGroupStmt = connection.prepareStatement("DELETE FROM beatmaps WHERE dir = ?");
updateSizeStmt = connection.prepareStatement("REPLACE INTO info (key, value) VALUES ('size', ?)");
} catch (SQLException e) {
ErrorHandler.error("Failed to prepare beatmap statements.", e, true);
}
// retrieve the cache size
getCacheSize();
// check the database version
checkVersion();
}
/**
* Creates the database, if it does not exist.
*/
private static void createDatabase() {
try (Statement stmt = connection.createStatement()) {
String sql =
"CREATE TABLE IF NOT EXISTS beatmaps (" +
"dir TEXT, file TEXT, lastModified INTEGER, " +
"MID INTEGER, MSID INTEGER, " +
"title TEXT, titleUnicode TEXT, artist TEXT, artistUnicode TEXT, " +
"creator TEXT, version TEXT, source TEXT, tags TEXT, " +
"circles INTEGER, sliders INTEGER, spinners INTEGER, " +
"hp REAL, cs REAL, od REAL, ar REAL, sliderMultiplier REAL, sliderTickRate REAL, " +
"bpmMin INTEGER, bpmMax INTEGER, endTime INTEGER, " +
"audioFile TEXT, audioLeadIn INTEGER, previewTime INTEGER, countdown INTEGER, sampleSet TEXT, stackLeniency REAL, " +
"mode INTEGER, letterboxInBreaks BOOLEAN, widescreenStoryboard BOOLEAN, epilepsyWarning BOOLEAN, " +
"bg TEXT, timingPoints TEXT, breaks TEXT, combo TEXT," +
"md5hash TEXT" +
"); " +
"CREATE TABLE IF NOT EXISTS info (" +
"key TEXT NOT NULL UNIQUE, value TEXT" +
"); " +
"CREATE INDEX IF NOT EXISTS idx ON beatmaps (dir, file); " +
// extra optimizations
"PRAGMA locking_mode = EXCLUSIVE; " +
"PRAGMA journal_mode = WAL;";
stmt.executeUpdate(sql);
// set the version key, if empty
sql = String.format("INSERT OR IGNORE INTO info(key, value) VALUES('version', '%s')", DATABASE_VERSION);
stmt.executeUpdate(sql);
} catch (SQLException e) {
ErrorHandler.error("Could not create beatmap database.", e, true);
}
}
/**
* Checks the stored table version, clears the beatmap database if different
* from the current version, then updates the version field.
*/
private static void checkVersion() {
try (Statement stmt = connection.createStatement()) {
// get the stored version
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 (!version.equals(DATABASE_VERSION)) {
clearDatabase();
// update version
PreparedStatement ps = connection.prepareStatement("REPLACE INTO info (key, value) VALUES ('version', ?)");
ps.setString(1, DATABASE_VERSION);
ps.executeUpdate();
ps.close();
}
} catch (SQLException e) {
ErrorHandler.error("Beatmap database version checks failed.", e, true);
}
}
/**
* Retrieves the size of the beatmap cache from the 'info' table.
*/
private static void getCacheSize() {
try (Statement stmt = connection.createStatement()) {
String sql = "SELECT value FROM info WHERE key = 'size'";
ResultSet rs = stmt.executeQuery(sql);
try {
cacheSize = (rs.next()) ? Integer.parseInt(rs.getString(1)) : 0;
} catch (NumberFormatException e) {
cacheSize = 0;
}
rs.close();
} catch (SQLException e) {
ErrorHandler.error("Could not get beatmap cache size.", e, true);
}
}
/**
* Updates the size of the beatmap cache in the 'info' table.
*/
private static void updateCacheSize() {
if (connection == null)
return;
try {
updateSizeStmt.setString(1, Integer.toString(Math.max(cacheSize, 0)));
updateSizeStmt.executeUpdate();
} catch (SQLException e) {
ErrorHandler.error("Could not update beatmap cache size.", e, true);
}
}
/**
* Clears the database.
*/
public static void clearDatabase() {
if (connection == null)
return;
// drop the table, then recreate it
try (Statement stmt = connection.createStatement()) {
String sql = "DROP TABLE beatmaps";
stmt.executeUpdate(sql);
cacheSize = 0;
updateCacheSize();
} catch (SQLException e) {
ErrorHandler.error("Could not drop beatmap database.", e, true);
}
createDatabase();
}
/**
* Adds the OsuFile to the database.
* @param osu the OsuFile object
*/
public static void insert(OsuFile osu) {
if (connection == null)
return;
try {
setStatementFields(insertStmt, osu);
cacheSize += insertStmt.executeUpdate();
updateCacheSize();
} catch (SQLException e) {
ErrorHandler.error("Failed to add beatmap to database.", e, true);
}
}
/**
* Adds the OsuFiles to the database in a batch.
* @param batch a list of OsuFile objects
*/
public static void insert(List<OsuFile> batch) {
if (connection == null)
return;
try (Statement stmt = connection.createStatement()) {
// turn off auto-commit mode
boolean autoCommit = connection.getAutoCommit();
connection.setAutoCommit(false);
// drop indexes
boolean recreateIndexes = (batch.size() >= INSERT_BATCH_MIN);
if (recreateIndexes) {
String sql = "DROP INDEX IF EXISTS idx";
stmt.executeUpdate(sql);
}
// batch insert
for (OsuFile osu : batch) {
try {
setStatementFields(insertStmt, osu);
} catch (SQLException e) {
Log.error(String.format("Failed to insert map '%s' into database.", osu.getFile().getPath()), e);
continue;
}
insertStmt.addBatch();
}
int[] results = insertStmt.executeBatch();
for (int i = 0; i < results.length; i++) {
if (results[i] > 0)
cacheSize += results[i];
}
// re-create indexes
if (recreateIndexes) {
String sql = "CREATE INDEX idx ON beatmaps (dir, file)";
stmt.executeUpdate(sql);
}
// restore previous auto-commit mode
connection.commit();
connection.setAutoCommit(autoCommit);
// update cache size
updateCacheSize();
} catch (SQLException e) {
ErrorHandler.error("Failed to add beatmaps to database.", e, true);
}
}
/**
* Sets all statement fields using a given OsuFile object.
* @param stmt the statement to set fields for
* @param osu the OsuFile
* @throws SQLException
*/
private static void setStatementFields(PreparedStatement stmt, OsuFile osu)
throws SQLException {
try {
stmt.setString(1, osu.getFile().getParentFile().getName());
stmt.setString(2, osu.getFile().getName());
stmt.setLong(3, osu.getFile().lastModified());
stmt.setInt(4, osu.beatmapID);
stmt.setInt(5, osu.beatmapSetID);
stmt.setString(6, osu.title);
stmt.setString(7, osu.titleUnicode);
stmt.setString(8, osu.artist);
stmt.setString(9, osu.artistUnicode);
stmt.setString(10, osu.creator);
stmt.setString(11, osu.version);
stmt.setString(12, osu.source);
stmt.setString(13, osu.tags);
stmt.setInt(14, osu.hitObjectCircle);
stmt.setInt(15, osu.hitObjectSlider);
stmt.setInt(16, osu.hitObjectSpinner);
stmt.setFloat(17, osu.HPDrainRate);
stmt.setFloat(18, osu.circleSize);
stmt.setFloat(19, osu.overallDifficulty);
stmt.setFloat(20, osu.approachRate);
stmt.setFloat(21, osu.sliderMultiplier);
stmt.setFloat(22, osu.sliderTickRate);
stmt.setInt(23, osu.bpmMin);
stmt.setInt(24, osu.bpmMax);
stmt.setInt(25, osu.endTime);
stmt.setString(26, osu.audioFilename.getName());
stmt.setInt(27, osu.audioLeadIn);
stmt.setInt(28, osu.previewTime);
stmt.setByte(29, osu.countdown);
stmt.setString(30, osu.sampleSet);
stmt.setFloat(31, osu.stackLeniency);
stmt.setByte(32, osu.mode);
stmt.setBoolean(33, osu.letterboxInBreaks);
stmt.setBoolean(34, osu.widescreenStoryboard);
stmt.setBoolean(35, osu.epilepsyWarning);
stmt.setString(36, osu.bg);
stmt.setString(37, osu.timingPointsToString());
stmt.setString(38, osu.breaksToString());
stmt.setString(39, osu.comboToString());
stmt.setString(40, osu.md5Hash);
} catch (SQLException e) {
throw e;
} catch (Exception e) {
throw new SQLException(e);
}
}
/**
* Loads OsuFile fields from the database.
* @param osu the OsuFile object
* @param flag whether to load all fields (LOAD_ALL), non-array
* fields (LOAD_NONARRAY), or array fields (LOAD_ARRAY)
*/
public static void load(OsuFile osu, int flag) {
if (connection == null)
return;
try {
selectStmt.setString(1, osu.getFile().getParentFile().getName());
selectStmt.setString(2, osu.getFile().getName());
ResultSet rs = selectStmt.executeQuery();
if (rs.next()) {
if ((flag & LOAD_NONARRAY) > 0)
setOsuFileFields(rs, osu);
if ((flag & LOAD_ARRAY) > 0)
setOsuFileArrayFields(rs, osu);
}
rs.close();
} catch (SQLException e) {
ErrorHandler.error("Failed to load OsuFile from database.", e, true);
}
}
/**
* Loads OsuFile fields from the database in a batch.
* @param batch a list of OsuFile objects
* @param flag whether to load all fields (LOAD_ALL), non-array
* fields (LOAD_NONARRAY), or array fields (LOAD_ARRAY)
*/
public static void load(List<OsuFile> batch, int flag) {
if (connection == null)
return;
// batch size too small
int size = batch.size();
if (size < cacheSize * LOAD_BATCH_MIN_RATIO) {
for (OsuFile osu : batch)
load(osu, flag);
return;
}
try (Statement stmt = connection.createStatement()) {
// create map
HashMap<String, HashMap<String, OsuFile>> map = new HashMap<String, HashMap<String, OsuFile>>();
for (OsuFile osu : batch) {
String parent = osu.getFile().getParentFile().getName();
String name = osu.getFile().getName();
HashMap<String, OsuFile> m = map.get(parent);
if (m == null) {
m = new HashMap<String, OsuFile>();
map.put(parent, m);
}
m.put(name, osu);
}
// iterate through database to load OsuFiles
int count = 0;
stmt.setFetchSize(100);
String sql = "SELECT * FROM beatmaps";
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
String parent = rs.getString(1);
HashMap<String, OsuFile> m = map.get(parent);
if (m != null) {
String name = rs.getString(2);
OsuFile osu = m.get(name);
if (osu != null) {
try {
if ((flag & LOAD_NONARRAY) > 0)
setOsuFileFields(rs, osu);
if ((flag & LOAD_ARRAY) > 0)
setOsuFileArrayFields(rs, osu);
} catch (SQLException e) {
Log.error(String.format("Failed to load map '%s/%s' from database.", parent, name), e);
}
if (++count >= size)
break;
}
}
}
rs.close();
} catch (SQLException e) {
ErrorHandler.error("Failed to load OsuFiles from database.", e, true);
}
}
/**
* Sets all OsuFile non-array fields using a given result set.
* @param rs the result set containing the fields
* @param osu the OsuFile
* @throws SQLException
*/
private static void setOsuFileFields(ResultSet rs, OsuFile osu) throws SQLException {
try {
osu.beatmapID = rs.getInt(4);
osu.beatmapSetID = rs.getInt(5);
osu.title = OsuParser.getDBString(rs.getString(6));
osu.titleUnicode = OsuParser.getDBString(rs.getString(7));
osu.artist = OsuParser.getDBString(rs.getString(8));
osu.artistUnicode = OsuParser.getDBString(rs.getString(9));
osu.creator = OsuParser.getDBString(rs.getString(10));
osu.version = OsuParser.getDBString(rs.getString(11));
osu.source = OsuParser.getDBString(rs.getString(12));
osu.tags = OsuParser.getDBString(rs.getString(13));
osu.hitObjectCircle = rs.getInt(14);
osu.hitObjectSlider = rs.getInt(15);
osu.hitObjectSpinner = rs.getInt(16);
osu.HPDrainRate = rs.getFloat(17);
osu.circleSize = rs.getFloat(18);
osu.overallDifficulty = rs.getFloat(19);
osu.approachRate = rs.getFloat(20);
osu.sliderMultiplier = rs.getFloat(21);
osu.sliderTickRate = rs.getFloat(22);
osu.bpmMin = rs.getInt(23);
osu.bpmMax = rs.getInt(24);
osu.endTime = rs.getInt(25);
osu.audioFilename = new File(osu.getFile().getParentFile(), OsuParser.getDBString(rs.getString(26)));
osu.audioLeadIn = rs.getInt(27);
osu.previewTime = rs.getInt(28);
osu.countdown = rs.getByte(29);
osu.sampleSet = OsuParser.getDBString(rs.getString(30));
osu.stackLeniency = rs.getFloat(31);
osu.mode = rs.getByte(32);
osu.letterboxInBreaks = rs.getBoolean(33);
osu.widescreenStoryboard = rs.getBoolean(34);
osu.epilepsyWarning = rs.getBoolean(35);
osu.bg = OsuParser.getDBString(rs.getString(36));
osu.md5Hash = OsuParser.getDBString(rs.getString(40));
} catch (SQLException e) {
throw e;
} catch (Exception e) {
throw new SQLException(e);
}
}
/**
* Sets all OsuFile array fields using a given result set.
* @param rs the result set containing the fields
* @param osu the OsuFile
* @throws SQLException
*/
private static void setOsuFileArrayFields(ResultSet rs, OsuFile osu) throws SQLException {
try {
osu.timingPointsFromString(rs.getString(37));
osu.breaksFromString(rs.getString(38));
osu.comboFromString(rs.getString(39));
} catch (SQLException e) {
throw e;
} catch (Exception e) {
throw new SQLException(e);
}
}
/**
* Returns a map of file paths ({dir}/{file}) to last modified times, or
* null if any error occurred.
*/
public static Map<String, Long> getLastModifiedMap() {
if (connection == null)
return null;
try (Statement stmt = connection.createStatement()) {
Map<String, Long> map = new HashMap<String, Long>();
String sql = "SELECT dir, file, lastModified FROM beatmaps";
ResultSet rs = stmt.executeQuery(sql);
stmt.setFetchSize(100);
while (rs.next()) {
String path = String.format("%s/%s", rs.getString(1), rs.getString(2));
long lastModified = rs.getLong(3);
map.put(path, lastModified);
}
rs.close();
return map;
} catch (SQLException e) {
ErrorHandler.error("Failed to get last modified map from database.", e, true);
return null;
}
}
/**
* Deletes the beatmap entry from the database.
* @param dir the directory
* @param file the file
*/
public static void delete(String dir, String file) {
if (connection == null)
return;
try {
deleteMapStmt.setString(1, dir);
deleteMapStmt.setString(2, file);
cacheSize -= deleteMapStmt.executeUpdate();
updateCacheSize();
} catch (SQLException e) {
ErrorHandler.error("Failed to delete beatmap entry from database.", e, true);
}
}
/**
* Deletes the beatmap group entry from the database.
* @param dir the directory
*/
public static void delete(String dir) {
if (connection == null)
return;
try {
deleteGroupStmt.setString(1, dir);
cacheSize -= deleteGroupStmt.executeUpdate();
updateCacheSize();
} catch (SQLException e) {
ErrorHandler.error("Failed to delete beatmap group entry from database.", e, true);
}
}
/**
* Closes the connection to the database.
*/
public static void closeConnection() {
if (connection == null)
return;
try {
insertStmt.close();
selectStmt.close();
deleteMapStmt.close();
deleteGroupStmt.close();
updateSizeStmt.close();
connection.close();
connection = null;
} catch (SQLException e) {
ErrorHandler.error("Failed to close beatmap database.", e, true);
}
}
}

View File

@ -66,7 +66,7 @@ public class Circle implements GameObject {
*/
public static void init(GameContainer container, float circleSize) {
diameter = (108 - (circleSize * 8));
diameter = (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480)
diameter = (diameter * HitObject.getXMultiplier()); // convert from Osupixels (640x480)
int diameterInt = (int)diameter;
GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameterInt, diameterInt));
GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(diameterInt, diameterInt));

View File

@ -69,4 +69,9 @@ public interface GameObject {
* Updates the position of the hit object.
*/
public void updatePosition();
/**
* Resets the hit object so that it can be reused.
*/
public void reset();
}

View File

@ -1,79 +0,0 @@
//TODO rename
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014, 2015 Jeffrey Han
*
* opsu! is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu! is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu.objects;
import org.newdawn.slick.Graphics;
/**
* Hit object interface.
*/
public interface HitObject {
/**
* Draws the hit object to the graphics context.
* @param g the graphics context
* @param trackPosition the current track position
*/
public void draw(Graphics g, int trackPosition);
/**
* Updates the hit object.
* @param overlap true if the next object's start time has already passed
* @param delta the delta interval since the last call
* @param mouseX the x coordinate of the mouse
* @param mouseY the y coordinate of the mouse
* @param keyPressed whether or not a game key is currently pressed
* @param trackPosition the track position
* @return true if object ended
*/
public boolean update(boolean overlap, int delta, int mouseX, int mouseY, boolean keyPressed, int trackPosition);
/**
* Processes a mouse click.
* @param x the x coordinate of the mouse
* @param y the y coordinate of the mouse
* @param trackPosition the track position
* @return true if a hit result was processed
*/
public boolean mousePressed(int x, int y, int trackPosition);
/**
* Returns the coordinates of the hit object at a given track position.
* @param trackPosition the track position
* @return the [x,y] coordinates
*/
public float[] getPointAt(int trackPosition);
/**
* Returns the end time of the hit object.
* @return the end time, in milliseconds
*/
public int getEndTime();
/**
* Updates the position of the hit object.
*/
public void updatePosition();
/**
* Resets the hit object so that it can be reused.
*/
public void reset();
}

View File

@ -118,7 +118,7 @@ public class Slider implements GameObject {
containerHeight = container.getHeight();
diameter = (108 - (circleSize * 8));
diameter = (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480)
diameter = (diameter * HitObject.getXMultiplier()); // convert from Osupixels (640x480)
int diameterInt = (int)diameter;
followRadius = diameter / 2 * 3f;

View File

@ -21,9 +21,9 @@ package itdelatrisu.opsu.replay;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.GameMod;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.OsuFile;
import itdelatrisu.opsu.ScoreData;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.beatmap.Beatmap;
import itdelatrisu.opsu.io.OsuReader;
import itdelatrisu.opsu.io.OsuWriter;
@ -150,7 +150,7 @@ public class Replay {
* @param osu the OsuFile
* @return the ScoreData object
*/
public ScoreData getScoreData(OsuFile osu) {
public ScoreData getScoreData(Beatmap osu) {
if (scoreData != null)
return scoreData;
@ -186,7 +186,6 @@ public class Replay {
private void loadHeader(OsuReader reader) throws IOException {
this.mode = reader.readByte();
this.version = reader.readInt();
//System.out.println("Header:"+file.getName()+" "+mode+" "+version);
this.beatmapHash = reader.readString();
this.playerName = reader.readString();
this.replayHash = reader.readString();
@ -208,7 +207,6 @@ public class Replay {
* @throws IOException
*/
private void loadData(OsuReader reader) throws IOException {
//System.out.println("Load Data");
// life data
String[] lifeData = reader.readString().split(",");
List<LifeFrame> lifeFrameList = new ArrayList<LifeFrame>(lifeData.length);

View File

@ -2,9 +2,9 @@ package itdelatrisu.opsu.replay;
import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.OsuFile;
import itdelatrisu.opsu.OsuGroupList;
import itdelatrisu.opsu.ScoreData;
import itdelatrisu.opsu.beatmap.Beatmap;
import itdelatrisu.opsu.beatmap.BeatmapSetList;
import itdelatrisu.opsu.db.ScoreDB;
import java.io.File;
@ -16,7 +16,7 @@ public class ReplayImporter {
try {
Replay r = new Replay(replayToImport);
r.loadHeader();
OsuFile oFile = OsuGroupList.get().getFileFromBeatmapHash(r.beatmapHash);
Beatmap oFile = BeatmapSetList.get().getFileFromBeatmapHash(r.beatmapHash);
if(oFile != null){
File replaydir = Options.getReplayDir();
if (!replaydir.isDirectory()) {

View File

@ -618,21 +618,21 @@ public class Game extends BasicGameState {
if(replayIndex-1 >= 1 && replayIndex < replay.frames.length && trackPosition < replay.frames[replayIndex-1].getTime()){
replayIndex = 0;
while(objectIndex>=0){
hitObjects[objectIndex].reset();
gameObjects[objectIndex].reset();
objectIndex--;
}
// load the first timingPoint
if (!osu.timingPoints.isEmpty()) {
OsuTimingPoint timingPoint = osu.timingPoints.get(0);
// reset game data
resetGameData();
// load the first timingPoint for stacking
if (!beatmap.timingPoints.isEmpty()) {
TimingPoint timingPoint = beatmap.timingPoints.get(0);
if (!timingPoint.isInherited()) {
beatLengthBase = beatLength = timingPoint.getBeatLength();
HitSound.setDefaultSampleSet(timingPoint.getSampleType());
SoundController.setSampleVolume(timingPoint.getSampleVolume());
setBeatLength(timingPoint, true);
timingPointIndex++;
}
}
resetGameData();
}
// update and run replay frames
@ -777,8 +777,8 @@ public class Game extends BasicGameState {
boolean keyPressed = keys != ReplayFrame.KEY_NONE;
while (objectIndex < gameObjects.length && trackPosition > beatmap.objects[objectIndex].getTime()) {
// check if we've already passed the next object's start time
boolean overlap = (objectIndex + 1 < hitObjects.length &&
trackPosition > osu.objects[objectIndex + 1].getTime() - hitResultOffset[GameData.HIT_50]);
boolean overlap = (objectIndex + 1 < gameObjects.length &&
trackPosition > beatmap.objects[objectIndex + 1].getTime() - hitResultOffset[GameData.HIT_50]);
// update hit object and check completion status
if (gameObjects[objectIndex].update(overlap, delta, mouseX, mouseY, keyPressed, trackPosition))
@ -915,8 +915,15 @@ public class Game extends BasicGameState {
// skip button
if (skipButton.contains(x, y))
skipIntro();
// playback speed button
else if (playbackSpeed.getButton().contains(x, y)) {
playbackSpeed = playbackSpeed.next();
MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier());
}
if(y < 50){
float pos = (float)x / width * osu.endTime;
float pos = (float)x / width * beatmap.endTime;
System.out.println("Seek to"+pos);
MusicController.setPosition((int)pos);
}
@ -1067,6 +1074,7 @@ public class Game extends BasicGameState {
} else if (restart == Restart.REPLAY)
retries = 0;
gameObjects = new GameObject[beatmap.objects.length];
// reset game data
resetGameData();
@ -1079,14 +1087,6 @@ public class Game extends BasicGameState {
}
}
if (!osu.timingPoints.isEmpty()) {
OsuTimingPoint timingPoint = osu.timingPoints.get(0);
if (!timingPoint.isInherited()) {
beatLengthBase = beatLength = timingPoint.getBeatLength();
}
}
hitObjects = new HitObject[osu.objects.length];
// initialize object maps
Color[] combo = beatmap.getComboColors();
for (int i = 0; i < beatmap.objects.length; i++) {
@ -1094,7 +1094,7 @@ public class Game extends BasicGameState {
// is this the last note in the combo?
boolean comboEnd = false;
if (i + 1 >= osu.objects.length || osu.objects[i + 1].isNewCombo())
if (i + 1 < beatmap.objects.length && beatmap.objects[i + 1].isNewCombo())
comboEnd = true;
Color color = combo[hitObject.getComboIndex()];
@ -1145,7 +1145,6 @@ public class Game extends BasicGameState {
// load replay frames
if (isReplay) {
//System.out.println(replay.toString());
// load mods
previousMods = GameMod.getModState();
GameMod.loadModState(replay.mods);
@ -1310,9 +1309,6 @@ public class Game extends BasicGameState {
* Resets all game data and structures.
*/
public void resetGameData() {
//conflict
gameObjects = new GameObject[beatmap.objects.length];
//
data.clear();
objectIndex = 0;
breakIndex = 0;