diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index 63f0bd2f..6573cc65 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -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: *

* Score = Hit Value + Hit Value * (Combo * Difficulty * Mod) / 25 @@ -1256,10 +1257,15 @@ public class GameData { *

  • Mod: mod multipliers * * @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. diff --git a/src/itdelatrisu/opsu/OsuFile.java b/src/itdelatrisu/opsu/OsuFile.java deleted file mode 100644 index dd7c9436..00000000 --- a/src/itdelatrisu/opsu/OsuFile.java +++ /dev/null @@ -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 . - */ - -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 { - /** 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 bgImageMap = new HashMap(); - - /** 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 breaks; - - /** - * [TimingPoints] - */ - - /** All timing points. */ - public ArrayList 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(); - } - - /** - * 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(); - 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(); - 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 colors = new LinkedList(); - 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()]); - } -} \ No newline at end of file diff --git a/src/itdelatrisu/opsu/OsuGroupList.java b/src/itdelatrisu/opsu/OsuGroupList.java deleted file mode 100644 index 54aa09ba..00000000 --- a/src/itdelatrisu/opsu/OsuGroupList.java +++ /dev/null @@ -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 . - */ - -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 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 nodes; - - /** Set of all beatmap set IDs for the parsed beatmaps. */ - private HashSet MSIDdb; - - /** Map of all hash to OsuFile . */ - public HashMap 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(); - MSIDdb = new HashSet(); - beatmapHashesToFile = new HashMap(); - 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 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 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 terms = new LinkedList(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 condType = new LinkedList(); - LinkedList condOperator = new LinkedList(); - LinkedList condValue = new LinkedList(); - - Iterator 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(); - 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 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 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. - *

    - * 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); - } -} \ No newline at end of file diff --git a/src/itdelatrisu/opsu/OsuHitObject.java b/src/itdelatrisu/opsu/OsuHitObject.java deleted file mode 100644 index 4b62bdce..00000000 --- a/src/itdelatrisu/opsu/OsuHitObject.java +++ /dev/null @@ -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 . - */ - -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(); - } -} diff --git a/src/itdelatrisu/opsu/OsuParser.java b/src/itdelatrisu/opsu/OsuParser.java deleted file mode 100644 index 715e90d4..00000000 --- a/src/itdelatrisu/opsu/OsuParser.java +++ /dev/null @@ -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 . - */ - -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 stringdb = new HashMap(); - - /** 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 map = OsuDB.getLastModifiedMap(); - - // OsuFile lists - List> allOsuFiles = new LinkedList>(); - List cachedOsuFiles = new LinkedList(); // loaded from database - List parsedOsuFiles = new LinkedList(); // 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 osuFiles = new ArrayList(); - 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 osuFiles : allOsuFiles) { - Collections.sort(osuFiles); - lastNode = OsuGroupList.get().addSongGroup(osuFiles); - } - - // clear string DB - stringdb = new HashMap(); - - // 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 osuFiles, boolean parseObjects) { - OsuFile osu = new OsuFile(file); - osu.timingPoints = new ArrayList(); - - 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(); - 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 colors = new LinkedList(); - 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; - } -} \ No newline at end of file diff --git a/src/itdelatrisu/opsu/UI.java b/src/itdelatrisu/opsu/UI.java deleted file mode 100644 index 1f7aa07d..00000000 --- a/src/itdelatrisu/opsu/UI.java +++ /dev/null @@ -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 . - */ - -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 - cursorX = new LinkedList(), - cursorY = new LinkedList(); - - /** 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 iterX = cursorX.iterator(); - Iterator 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); - } -} diff --git a/src/itdelatrisu/opsu/beatmap/Beatmap.java b/src/itdelatrisu/opsu/beatmap/Beatmap.java index ad8b5476..7216b0de 100644 --- a/src/itdelatrisu/opsu/beatmap/Beatmap.java +++ b/src/itdelatrisu/opsu/beatmap/Beatmap.java @@ -184,7 +184,10 @@ public class Beatmap implements Comparable { /** Slider border color. If null, the skin value is used. */ public Color sliderBorder; - + + /** md5 hash of this file */ + public String md5Hash; + /** * [HitObjects] */ diff --git a/src/itdelatrisu/opsu/beatmap/BeatmapParser.java b/src/itdelatrisu/opsu/beatmap/BeatmapParser.java index 369ea19e..86d3dcb2 100644 --- a/src/itdelatrisu/opsu/beatmap/BeatmapParser.java +++ b/src/itdelatrisu/opsu/beatmap/BeatmapParser.java @@ -589,6 +589,8 @@ public class BeatmapParser { // parse hit objects now? if (parseObjects) parseHitObjects(beatmap); + + beatmap.md5Hash = Utils.getMD5(file); return beatmap; } diff --git a/src/itdelatrisu/opsu/beatmap/BeatmapSetList.java b/src/itdelatrisu/opsu/beatmap/BeatmapSetList.java index 7c5aab81..80362838 100644 --- a/src/itdelatrisu/opsu/beatmap/BeatmapSetList.java +++ b/src/itdelatrisu/opsu/beatmap/BeatmapSetList.java @@ -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; @@ -57,6 +58,10 @@ public class BeatmapSetList { /** Set of all beatmap set IDs for the parsed beatmaps. */ private HashSet MSIDdb; + + /** Map of all hash to Beatmap . */ + public HashMap 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(); MSIDdb = new HashSet(); + beatmapHashesToFile = new HashMap(); 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); + } } \ No newline at end of file diff --git a/src/itdelatrisu/opsu/db/BeatmapDB.java b/src/itdelatrisu/opsu/db/BeatmapDB.java index 3f85373e..57508975 100644 --- a/src/itdelatrisu/opsu/db/BeatmapDB.java +++ b/src/itdelatrisu/opsu/db/BeatmapDB.java @@ -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) { diff --git a/src/itdelatrisu/opsu/db/OsuDB.java b/src/itdelatrisu/opsu/db/OsuDB.java deleted file mode 100644 index d632daa3..00000000 --- a/src/itdelatrisu/opsu/db/OsuDB.java +++ /dev/null @@ -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 . - */ - -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 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 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> map = new HashMap>(); - for (OsuFile osu : batch) { - String parent = osu.getFile().getParentFile().getName(); - String name = osu.getFile().getName(); - HashMap m = map.get(parent); - if (m == null) { - m = new HashMap(); - 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 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 getLastModifiedMap() { - if (connection == null) - return null; - - try (Statement stmt = connection.createStatement()) { - Map map = new HashMap(); - 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); - } - } -} diff --git a/src/itdelatrisu/opsu/objects/Circle.java b/src/itdelatrisu/opsu/objects/Circle.java index cb9bada1..7d3637d9 100644 --- a/src/itdelatrisu/opsu/objects/Circle.java +++ b/src/itdelatrisu/opsu/objects/Circle.java @@ -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)); diff --git a/src/itdelatrisu/opsu/objects/GameObject.java b/src/itdelatrisu/opsu/objects/GameObject.java index ca1ab8e5..5ce189c4 100644 --- a/src/itdelatrisu/opsu/objects/GameObject.java +++ b/src/itdelatrisu/opsu/objects/GameObject.java @@ -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(); } diff --git a/src/itdelatrisu/opsu/objects/HitObject.java b/src/itdelatrisu/opsu/objects/HitObject.java deleted file mode 100644 index d4602e2d..00000000 --- a/src/itdelatrisu/opsu/objects/HitObject.java +++ /dev/null @@ -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 . - */ - -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(); -} diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index ac50e429..c70b4377 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -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; diff --git a/src/itdelatrisu/opsu/replay/Replay.java b/src/itdelatrisu/opsu/replay/Replay.java index 2e34cd12..712e920f 100644 --- a/src/itdelatrisu/opsu/replay/Replay.java +++ b/src/itdelatrisu/opsu/replay/Replay.java @@ -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 lifeFrameList = new ArrayList(lifeData.length); diff --git a/src/itdelatrisu/opsu/replay/ReplayImporter.java b/src/itdelatrisu/opsu/replay/ReplayImporter.java index 1893f32c..51437aa4 100644 --- a/src/itdelatrisu/opsu/replay/ReplayImporter.java +++ b/src/itdelatrisu/opsu/replay/ReplayImporter.java @@ -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()) { diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index acb371c4..0b80a774 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -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(); @@ -1078,14 +1086,6 @@ public class Game extends BasicGameState { timingPointIndex++; } } - - 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(); @@ -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;