Follow-up to #99.
- Many code style changes. - Don't increment combo if missing the last slider circle. - Added player name in ranking screen. - Don't show null/default player names. - Only import replays with .osr extension. - Display loading status for importing replays. - Moved MD5InputStreamWrapper to package "opsu.io". Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
parent
7d08a7d391
commit
d860a30aed
|
@ -74,7 +74,7 @@ public class ErrorHandler {
|
|||
|
||||
/**
|
||||
* Displays an error popup and logs the given error.
|
||||
* @param error a descR of the error
|
||||
* @param error a description of the error
|
||||
* @param e the exception causing the error
|
||||
* @param report whether to ask to report the error
|
||||
*/
|
||||
|
|
|
@ -838,8 +838,9 @@ public class GameData {
|
|||
String.format("%s - %s [%s]", beatmap.getArtist(), beatmap.getTitle(), beatmap.version), Color.white);
|
||||
Utils.FONT_MEDIUM.drawString(marginX, marginY + Utils.FONT_LARGE.getLineHeight() - 6,
|
||||
String.format("Beatmap by %s", beatmap.creator), Color.white);
|
||||
String player = (scoreData.playerName == null) ? "" : String.format(" by %s", scoreData.playerName);
|
||||
Utils.FONT_MEDIUM.drawString(marginX, marginY + Utils.FONT_LARGE.getLineHeight() + Utils.FONT_MEDIUM.getLineHeight() - 10,
|
||||
String.format("Played on %s.", scoreData.getTimeString()), Color.white);
|
||||
String.format("Played%s on %s.", player, scoreData.getTimeString()), Color.white);
|
||||
|
||||
// mod icons
|
||||
int modWidth = GameMod.AUTO.getImage().getWidth();
|
||||
|
@ -1207,13 +1208,8 @@ public class GameData {
|
|||
}
|
||||
fullObjectCount++;
|
||||
}
|
||||
public void sliderFinalResult(int time, int hitSlider30, float x, float y,
|
||||
HitObject hitObject, int currentRepeats) {
|
||||
score += 30;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://osu.ppy.sh/wiki/Score
|
||||
* Returns the score for a hit based on the following score formula:
|
||||
* <p>
|
||||
* Score = Hit Value + Hit Value * (Combo * Difficulty * Mod) / 25
|
||||
|
@ -1224,26 +1220,31 @@ public class GameData {
|
|||
* <li><strong>Mod:</strong> mod multipliers
|
||||
* </ul>
|
||||
* @param hitValue the hit value
|
||||
* @param hitObject
|
||||
* @param hitObject the hit object
|
||||
* @return the score value
|
||||
* @see <a href="https://osu.ppy.sh/wiki/Score">https://osu.ppy.sh/wiki/Score</a>
|
||||
*/
|
||||
private int getScoreForHit(int hitValue, HitObject hitObject) {
|
||||
int comboMulti = Math.max(combo - 1, 0);
|
||||
if(hitObject.isSlider()){
|
||||
comboMulti += 1;
|
||||
}
|
||||
return (hitValue + (int)(hitValue * (comboMulti * difficultyMultiplier * GameMod.getScoreMultiplier()) / 25));
|
||||
int comboMultiplier = Math.max(combo - 1, 0);
|
||||
if (hitObject.isSlider())
|
||||
comboMultiplier++;
|
||||
return (hitValue + (int)(hitValue * (comboMultiplier * difficultyMultiplier * GameMod.getScoreMultiplier()) / 25));
|
||||
}
|
||||
|
||||
/**
|
||||
* https://osu.ppy.sh/wiki/Score#How_to_calculate_the_Difficulty_multiplier
|
||||
* Computes and stores the difficulty multiplier used in the score formula.
|
||||
* @param drainRate the raw HP drain rate value
|
||||
* @param circleSize the raw circle size value
|
||||
* @param overallDifficulty the raw overall difficulty value
|
||||
* @see <a href="https://osu.ppy.sh/wiki/Score#How_to_calculate_the_Difficulty_multiplier">https://osu.ppy.sh/wiki/Score#How_to_calculate_the_Difficulty_multiplier</a>
|
||||
*/
|
||||
public void calculateDifficultyMultiplier(float drainRate, float circleSize, float overallDifficulty) {
|
||||
//TODO THE LIES ( difficultyMultiplier )
|
||||
//*
|
||||
// TODO: find the actual formula (osu!wiki is wrong)
|
||||
// seems to be based on hit object density? (total objects / time)
|
||||
// 924 3x1/4 beat notes 0.14stars
|
||||
// 924 3x1beat 0.28stars
|
||||
// 912 3x1beat with 1 extra note 10 sec away 0.29stars
|
||||
|
||||
float sum = drainRate + circleSize + overallDifficulty; // typically 2~27
|
||||
if (sum <= 5f)
|
||||
difficultyMultiplier = 2;
|
||||
|
@ -1255,21 +1256,11 @@ public class GameData {
|
|||
difficultyMultiplier = 5;
|
||||
else //if (sum <= 30f)
|
||||
difficultyMultiplier = 6;
|
||||
//*/
|
||||
|
||||
/*
|
||||
924 3x1/4 beat notes 0.14stars
|
||||
924 3x1beat 0.28stars
|
||||
912 3x1beat wth 1 extra note 10 sec away 0.29stars
|
||||
|
||||
seems to be based on hitobject density? (Total Objects/Time)
|
||||
*/
|
||||
/*
|
||||
float mult = ((circleSize + overallDifficulty + drainRate) / 6) + 1.5f;
|
||||
System.out.println("diffuculty Multiplier : "+ mult);
|
||||
difficultyMultiplier = (int)mult;
|
||||
*/
|
||||
//float multiplier = ((circleSize + overallDifficulty + drainRate) / 6) + 1.5f;
|
||||
//difficultyMultiplier = (int) multiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a hit result and performs all associated calculations.
|
||||
* @param time the object start time
|
||||
|
@ -1279,12 +1270,13 @@ public class GameData {
|
|||
* @param color the combo color
|
||||
* @param end true if this is the last hit object in the combo
|
||||
* @param hitObject the hit object
|
||||
* @param repeat the current repeat number (for sliders, or 0 otherwise)
|
||||
* @param hitResultType the type of hit object for the result
|
||||
* @param repeat the current repeat number (for sliders, or 0 otherwise)
|
||||
* @param noIncrementCombo if the combo should not be incremented by this result
|
||||
* @return the actual hit result (HIT_* constants)
|
||||
*/
|
||||
private int handleHitResult(int time, int result, float x, float y, Color color,
|
||||
boolean end, HitObject hitObject, int repeat, HitObjectType hitResultType) {
|
||||
private int handleHitResult(int time, int result, float x, float y, Color color, boolean end,
|
||||
HitObject hitObject, HitObjectType hitResultType, int repeat, boolean noIncrementCombo) {
|
||||
// update health, score, and combo streak based on hit result
|
||||
int hitValue = 0;
|
||||
switch (result) {
|
||||
|
@ -1318,6 +1310,7 @@ public class GameData {
|
|||
|
||||
// calculate score and increment combo streak
|
||||
changeScore(getScoreForHit(hitValue, hitObject));
|
||||
if (!noIncrementCombo)
|
||||
incrementComboStreak();
|
||||
}
|
||||
hitResultCount[result]++;
|
||||
|
@ -1347,7 +1340,7 @@ public class GameData {
|
|||
}
|
||||
|
||||
/**
|
||||
* Handles a slider hit result.
|
||||
* Handles a hit result.
|
||||
* @param time the object start time
|
||||
* @param result the hit result (HIT_* constants)
|
||||
* @param x the x coordinate
|
||||
|
@ -1355,15 +1348,17 @@ public class GameData {
|
|||
* @param color the combo color
|
||||
* @param end true if this is the last hit object in the combo
|
||||
* @param hitObject the hit object
|
||||
* @param repeat the current repeat number (for sliders, or 0 otherwise)
|
||||
* @param hitResultType the type of hit object for the result
|
||||
* @param curve the slider curve (or null if not applicable)
|
||||
* @param expand whether or not the hit result animation should expand (if applicable)
|
||||
* @param repeat the current repeat number (for sliders, or 0 otherwise)
|
||||
* @param curve the slider curve (or null if not applicable)
|
||||
* @param sliderHeldToEnd whether or not the slider was held to the end (if applicable)
|
||||
*/
|
||||
public void hitResult(int time, int result, float x, float y, Color color,
|
||||
boolean end, HitObject hitObject, int repeat,
|
||||
HitObjectType hitResultType, Curve curve, boolean expand) {
|
||||
int hitResult = handleHitResult(time, result, x, y, color, end, hitObject, repeat, hitResultType);
|
||||
boolean end, HitObject hitObject, HitObjectType hitResultType,
|
||||
boolean expand, int repeat, Curve curve, boolean sliderHeldToEnd) {
|
||||
int hitResult = handleHitResult(time, result, x, y, color, end, hitObject,
|
||||
hitResultType, repeat, (curve != null && !sliderHeldToEnd));
|
||||
|
||||
if ((hitResult == HIT_300 || hitResult == HIT_300G || hitResult == HIT_300K) && !Options.isPerfectHitBurstEnabled())
|
||||
; // hide perfect hit results
|
||||
|
@ -1382,7 +1377,6 @@ public class GameData {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a ScoreData object encapsulating all game data.
|
||||
* If score data already exists, the existing object will be returned
|
||||
|
@ -1413,7 +1407,7 @@ public class GameData {
|
|||
scoreData.perfect = (comboMax == fullObjectCount);
|
||||
scoreData.mods = GameMod.getModState();
|
||||
scoreData.replayString = (replay == null) ? null : replay.getReplayFilename();
|
||||
scoreData.playerName = "OpsuPlayer"; //TODO GameDataPlayerName?
|
||||
scoreData.playerName = null; // TODO
|
||||
return scoreData;
|
||||
}
|
||||
|
||||
|
@ -1434,7 +1428,7 @@ public class GameData {
|
|||
replay = new Replay();
|
||||
replay.mode = Beatmap.MODE_OSU;
|
||||
replay.version = Updater.get().getBuildDate();
|
||||
replay.beatmapHash = (beatmap == null) ? "" : beatmap.md5Hash;//Utils.getMD5(beatmap.getFile());
|
||||
replay.beatmapHash = (beatmap == null) ? "" : beatmap.md5Hash;
|
||||
replay.playerName = ""; // TODO
|
||||
replay.replayHash = Long.toString(System.currentTimeMillis()); // TODO
|
||||
replay.hit300 = (short) hitResultCount[HIT_300];
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
package itdelatrisu.opsu;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class MD5InputStreamWrapper extends InputStream {
|
||||
|
||||
InputStream in;
|
||||
private boolean eof; // End Of File
|
||||
MessageDigest md;
|
||||
public MD5InputStreamWrapper(InputStream in) throws NoSuchAlgorithmException {
|
||||
this.in = in;
|
||||
md = MessageDigest.getInstance("MD5");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int readed = in.read();
|
||||
if(readed>=0)
|
||||
md.update((byte) readed);
|
||||
else
|
||||
eof=true;
|
||||
return readed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return in.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
in.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
in.mark(readlimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return in.markSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int readed = in.read(b, off, len);
|
||||
if(readed>=0)
|
||||
md.update(b, off, readed);
|
||||
else
|
||||
eof=true;
|
||||
|
||||
return readed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
return read(b, 0 ,b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
throw new RuntimeException("MD5 stream not resetable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
throw new RuntimeException("MD5 stream not skipable");
|
||||
}
|
||||
|
||||
public String getMD5() throws IOException {
|
||||
byte[] buf = null;
|
||||
if(!eof)
|
||||
buf = new byte[0x1000];
|
||||
while(!eof){
|
||||
read(buf);
|
||||
}
|
||||
|
||||
byte[] md5byte = md.digest();
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (byte b : md5byte)
|
||||
result.append(String.format("%02x", b));
|
||||
//System.out.println("MD5 stream md5 " + result.toString());
|
||||
return result.toString();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -33,7 +33,7 @@ public class OszUnpacker {
|
|||
/** The index of the current file being unpacked. */
|
||||
private static int fileIndex = -1;
|
||||
|
||||
/** The total number of directories to parse. */
|
||||
/** The total number of files to unpack. */
|
||||
private static File[] files;
|
||||
|
||||
// This class should not be instantiated.
|
||||
|
|
|
@ -63,6 +63,9 @@ public class ScoreData implements Comparable<ScoreData> {
|
|||
/** The replay string. */
|
||||
public String replayString;
|
||||
|
||||
/** The player name. */
|
||||
public String playerName;
|
||||
|
||||
/** Time since the score was achieved. */
|
||||
private String timeSince;
|
||||
|
||||
|
@ -78,9 +81,6 @@ public class ScoreData implements Comparable<ScoreData> {
|
|||
/** The tooltip string. */
|
||||
private String tooltip;
|
||||
|
||||
/** The players Name. */
|
||||
public String playerName;
|
||||
|
||||
/** Drawing values. */
|
||||
private static float baseX, baseY, buttonWidth, buttonHeight, buttonOffset;
|
||||
|
||||
|
@ -262,9 +262,10 @@ public class ScoreData implements Comparable<ScoreData> {
|
|||
);
|
||||
|
||||
// hit counts (custom: osu! shows user instead, above score)
|
||||
String player = (playerName == null) ? "" : String.format(" (%s)", playerName);
|
||||
Utils.FONT_SMALL.drawString(
|
||||
textX, y + textOffset + Utils.FONT_MEDIUM.getLineHeight(),
|
||||
String.format("300:%d 100:%d 50:%d Miss:%d Name:%s", hit300, hit100, hit50, miss, getPlayerName()),
|
||||
String.format("300:%d 100:%d 50:%d Miss:%d%s", hit300, hit100, hit50, miss, player),
|
||||
Color.white
|
||||
);
|
||||
|
||||
|
@ -335,11 +336,6 @@ public class ScoreData implements Comparable<ScoreData> {
|
|||
);
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
if(playerName == null)
|
||||
return "Null Name";
|
||||
return playerName;
|
||||
}
|
||||
@Override
|
||||
public int compareTo(ScoreData that) {
|
||||
if (this.score != that.score)
|
||||
|
|
|
@ -185,7 +185,7 @@ public class Beatmap implements Comparable<Beatmap> {
|
|||
/** Slider border color. If null, the skin value is used. */
|
||||
public Color sliderBorder;
|
||||
|
||||
/** md5 hash of this file */
|
||||
/** MD5 hash of this file. */
|
||||
public String md5Hash;
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
package itdelatrisu.opsu.beatmap;
|
||||
|
||||
import itdelatrisu.opsu.ErrorHandler;
|
||||
import itdelatrisu.opsu.MD5InputStreamWrapper;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.db.BeatmapDB;
|
||||
import itdelatrisu.opsu.io.MD5InputStreamWrapper;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
|
@ -68,6 +68,9 @@ public class BeatmapParser {
|
|||
/** The current status. */
|
||||
private static Status status = Status.NONE;
|
||||
|
||||
/** If no Provider supports a MessageDigestSpi implementation for the MD5 algorithm. */
|
||||
private static boolean hasNoMD5Algorithm = false;
|
||||
|
||||
// This class should not be instantiated.
|
||||
private BeatmapParser() {}
|
||||
|
||||
|
@ -209,15 +212,11 @@ public class BeatmapParser {
|
|||
Beatmap beatmap = new Beatmap(file);
|
||||
beatmap.timingPoints = new ArrayList<TimingPoint>();
|
||||
|
||||
try (InputStream inFileStream = new BufferedInputStream(new FileInputStream(file));){
|
||||
MD5InputStreamWrapper md5stream = null;
|
||||
try {
|
||||
md5stream = new MD5InputStreamWrapper(inFileStream);
|
||||
} catch (NoSuchAlgorithmException e1) {
|
||||
ErrorHandler.error("Failed to get MD5 hash stream.", e1, true);
|
||||
}
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(md5stream!=null?md5stream:inFileStream, "UTF-8"));
|
||||
|
||||
try (
|
||||
InputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
MD5InputStreamWrapper md5stream = (!hasNoMD5Algorithm) ? new MD5InputStreamWrapper(bis) : null;
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader((md5stream != null) ? md5stream : bis, "UTF-8"));
|
||||
) {
|
||||
String line = in.readLine();
|
||||
String tokens[] = null;
|
||||
while (line != null) {
|
||||
|
@ -594,6 +593,12 @@ public class BeatmapParser {
|
|||
beatmap.md5Hash = md5stream.getMD5();
|
||||
} catch (IOException e) {
|
||||
ErrorHandler.error(String.format("Failed to read file '%s'.", file.getAbsolutePath()), e, false);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
ErrorHandler.error("Failed to get MD5 hash stream.", e, true);
|
||||
|
||||
// retry without MD5
|
||||
hasNoMD5Algorithm = true;
|
||||
return parseFile(file, dir, beatmaps, parseObjects);
|
||||
}
|
||||
|
||||
// no associated audio file?
|
||||
|
|
|
@ -59,9 +59,8 @@ public class BeatmapSetList {
|
|||
/** Set of all beatmap set IDs for the parsed beatmaps. */
|
||||
private HashSet<Integer> MSIDdb;
|
||||
|
||||
/** Map of all hash to Beatmap . */
|
||||
public HashMap<String, Beatmap> beatmapHashesToFile;
|
||||
|
||||
/** Map of all MD5 hashes to beatmaps. */
|
||||
private HashMap<String, Beatmap> beatmapHashDB;
|
||||
|
||||
/** Index of current expanded node (-1 if no node is expanded). */
|
||||
private int expandedIndex;
|
||||
|
@ -88,7 +87,7 @@ public class BeatmapSetList {
|
|||
private BeatmapSetList() {
|
||||
parsedNodes = new ArrayList<BeatmapSetNode>();
|
||||
MSIDdb = new HashSet<Integer>();
|
||||
beatmapHashesToFile = new HashMap<String, Beatmap>();
|
||||
beatmapHashDB = new HashMap<String, Beatmap>();
|
||||
reset();
|
||||
}
|
||||
|
||||
|
@ -123,10 +122,12 @@ public class BeatmapSetList {
|
|||
int msid = beatmaps.get(0).beatmapSetID;
|
||||
if (msid > 0)
|
||||
MSIDdb.add(msid);
|
||||
for(Beatmap f : beatmaps) {
|
||||
beatmapHashesToFile.put(f.md5Hash, f);
|
||||
}
|
||||
|
||||
// add MD5 hashes to table
|
||||
for (Beatmap beatmap : beatmaps) {
|
||||
if (beatmap.md5Hash != null)
|
||||
beatmapHashDB.put(beatmap.md5Hash, beatmap);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
@ -512,7 +513,12 @@ public class BeatmapSetList {
|
|||
*/
|
||||
public boolean containsBeatmapSetID(int id) { return MSIDdb.contains(id); }
|
||||
|
||||
public Beatmap getFileFromBeatmapHash(String beatmapHash) {
|
||||
return beatmapHashesToFile.get(beatmapHash);
|
||||
/**
|
||||
* Returns the beatmap associated with the given hash.
|
||||
* @param beatmapHash the MD5 hash
|
||||
* @return the associated beatmap, or {@code null} if no match was found
|
||||
*/
|
||||
public Beatmap getBeatmapFromHash(String beatmapHash) {
|
||||
return beatmapHashDB.get(beatmapHash);
|
||||
}
|
||||
}
|
|
@ -483,7 +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));
|
||||
beatmap.md5Hash = rs.getString(41);
|
||||
} catch (SQLException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -97,12 +97,9 @@ public class ScoreDB {
|
|||
|
||||
// prepare sql statements
|
||||
try {
|
||||
|
||||
//TODO timestamp as primary key should prevent importing the same replay multiple times
|
||||
//but if for some magical reason two different replays has the same time stamp
|
||||
//it will fail, such as copying replays from another drive? which will reset
|
||||
//the last modified of the file.
|
||||
insertStmt = connection.prepareStatement(
|
||||
// TODO: There will be problems if multiple replays have the same
|
||||
// timestamp (e.g. when imported) due to timestamp being the primary key.
|
||||
"INSERT OR IGNORE INTO scores VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
selectMapStmt = connection.prepareStatement(
|
||||
|
@ -294,7 +291,6 @@ public class ScoreDB {
|
|||
stmt.setInt(17, data.mods);
|
||||
stmt.setString(18, data.replayString);
|
||||
stmt.setString(19, data.playerName);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
118
src/itdelatrisu/opsu/io/MD5InputStreamWrapper.java
Normal file
118
src/itdelatrisu/opsu/io/MD5InputStreamWrapper.java
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
*
|
||||
* opsu! is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* opsu! is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package itdelatrisu.opsu.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Wrapper for an InputStream that computes the MD5 hash while reading the stream.
|
||||
*/
|
||||
public class MD5InputStreamWrapper extends InputStream {
|
||||
/** The input stream. */
|
||||
private InputStream in;
|
||||
|
||||
/** Whether the end of stream has been reached. */
|
||||
private boolean eof = false;
|
||||
|
||||
/** A MessageDigest object that implements the MD5 digest algorithm. */
|
||||
private MessageDigest md;
|
||||
|
||||
/** The computed MD5 hash. */
|
||||
private String md5;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param in the input stream
|
||||
* @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the MD5 algorithm
|
||||
*/
|
||||
public MD5InputStreamWrapper(InputStream in) throws NoSuchAlgorithmException {
|
||||
this.in = in;
|
||||
this.md = MessageDigest.getInstance("MD5");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int bytesRead = in.read();
|
||||
if (bytesRead >= 0)
|
||||
md.update((byte) bytesRead);
|
||||
else
|
||||
eof = true;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException { return in.available(); }
|
||||
|
||||
@Override
|
||||
public void close() throws IOException { in.close(); }
|
||||
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) { in.mark(readlimit); }
|
||||
|
||||
@Override
|
||||
public boolean markSupported() { return in.markSupported(); }
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int bytesRead = in.read(b, off, len);
|
||||
if (bytesRead >= 0)
|
||||
md.update(b, off, bytesRead);
|
||||
else
|
||||
eof = true;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException { return read(b, 0, b.length); }
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
throw new RuntimeException("The reset() method is not implemented.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
throw new RuntimeException("The skip() method is not implemented.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MD5 hash of the input stream.
|
||||
* @throws IOException if the end of stream has not yet been reached and a call to {@link #read(byte[])} fails
|
||||
*/
|
||||
public String getMD5() throws IOException {
|
||||
if (md5 != null)
|
||||
return md5;
|
||||
|
||||
if (!eof) { // read the rest of the stream
|
||||
byte[] buf = new byte[0x1000];
|
||||
while (!eof)
|
||||
read(buf);
|
||||
}
|
||||
|
||||
byte[] md5byte = md.digest();
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (byte b : md5byte)
|
||||
result.append(String.format("%02x", b));
|
||||
md5 = result.toString();
|
||||
return md5;
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ public class Circle implements GameObject {
|
|||
/** The amount of time, in milliseconds, to fade in the circle. */
|
||||
private static final int FADE_IN_TIME = 375;
|
||||
|
||||
/** The diameter of Circle Hitobjects */
|
||||
/** The diameter of hit circles. */
|
||||
private static float diameter;
|
||||
|
||||
/** The associated HitObject. */
|
||||
|
@ -123,7 +123,6 @@ public class Circle implements GameObject {
|
|||
private int hitResult(int time) {
|
||||
int timeDiff = Math.abs(time);
|
||||
|
||||
|
||||
int[] hitResultOffset = game.getHitResultOffsets();
|
||||
int result = -1;
|
||||
if (timeDiff <= hitResultOffset[GameData.HIT_300])
|
||||
|
@ -148,7 +147,7 @@ public class Circle implements GameObject {
|
|||
|
||||
if (result > -1) {
|
||||
data.addHitError(hitObject.getTime(), x, y, timeDiff);
|
||||
data.hitResult(trackPosition, result, this.x, this.y, color, comboEnd, hitObject, 0, HitObjectType.CIRCLE, null, true);
|
||||
data.hitResult(trackPosition, result, this.x, this.y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -164,17 +163,17 @@ public class Circle implements GameObject {
|
|||
|
||||
if (trackPosition > time + hitResultOffset[GameData.HIT_50]) {
|
||||
if (isAutoMod) // "auto" mod: catch any missed notes due to lag
|
||||
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, 0, HitObjectType.CIRCLE, null, true);
|
||||
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
|
||||
|
||||
else // no more points can be scored, so send a miss
|
||||
data.hitResult(trackPosition, GameData.HIT_MISS, x, y, null, comboEnd, hitObject, 0, HitObjectType.CIRCLE, null, true);
|
||||
data.hitResult(trackPosition, GameData.HIT_MISS, x, y, null, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// "auto" mod: send a perfect hit result
|
||||
else if (isAutoMod) {
|
||||
if (Math.abs(trackPosition - time) < hitResultOffset[GameData.HIT_300]) {
|
||||
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, 0, HitObjectType.CIRCLE, null, true);
|
||||
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -199,7 +198,5 @@ public class Circle implements GameObject {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
|
||||
}
|
||||
public void reset() {}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,5 @@ public class DummyObject implements GameObject {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
|
||||
}
|
||||
public void reset() {}
|
||||
}
|
||||
|
|
|
@ -71,9 +71,7 @@ public interface GameObject {
|
|||
public void updatePosition();
|
||||
|
||||
/**
|
||||
* Resets the hit object so that it can be reused.
|
||||
* Resets all internal state so that the hit object can be reused.
|
||||
*/
|
||||
public void reset();
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -50,8 +50,10 @@ public class Slider implements GameObject {
|
|||
/** Rate at which slider ticks are placed. */
|
||||
private static float sliderTickRate = 1.0f;
|
||||
|
||||
/** Follow circle radius. */
|
||||
private static float followRadius;
|
||||
|
||||
/** The diameter of hit circles. */
|
||||
private static float diameter;
|
||||
|
||||
/** The amount of time, in milliseconds, to fade in the slider. */
|
||||
|
@ -81,8 +83,11 @@ public class Slider implements GameObject {
|
|||
/** The time duration of the slider including repeats, in milliseconds. */
|
||||
private float sliderTimeTotal = 0f;
|
||||
|
||||
/** Whether or not the result of the initial/final hit circles have been processed. */
|
||||
private boolean sliderClickedInitial = false, sliderClickedFinal = false;
|
||||
/** Whether or not the result of the initial hit circle has been processed. */
|
||||
private boolean sliderClickedInitial = false;
|
||||
|
||||
/** Whether or not the slider was held to the end. */
|
||||
private boolean sliderHeldToEnd = false;
|
||||
|
||||
/** Whether or not to show the follow circle. */
|
||||
private boolean followCircleActive = false;
|
||||
|
@ -105,8 +110,6 @@ public class Slider implements GameObject {
|
|||
/** Container dimensions. */
|
||||
private static int containerWidth, containerHeight;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the Slider data type with images and dimensions.
|
||||
* @param container the game container
|
||||
|
@ -122,6 +125,7 @@ public class Slider implements GameObject {
|
|||
int diameterInt = (int) diameter;
|
||||
|
||||
followRadius = diameter / 2 * 3f;
|
||||
|
||||
// slider ball
|
||||
if (GameImage.SLIDER_BALL.hasSkinImages() ||
|
||||
(!GameImage.SLIDER_BALL.hasSkinImage() && GameImage.SLIDER_BALL.getImages() != null))
|
||||
|
@ -326,8 +330,6 @@ public class Slider implements GameObject {
|
|||
80 - 10*3 - 50 - 0 (3 tick only)(0x)
|
||||
70 - 10*2 - 50 - 0 (2 tick only)(0x)
|
||||
60 - 10 - 50 - 0 (1 tick only)(0x)
|
||||
|
||||
|
||||
*/
|
||||
float tickRatio = (float) ticksHit / tickIntervals;
|
||||
|
||||
|
@ -354,7 +356,8 @@ public class Slider implements GameObject {
|
|||
type = HitObjectType.SLIDER_FIRST;
|
||||
}
|
||||
data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
|
||||
cx, cy, color, comboEnd, hitObject, currentRepeats + 1, type, curve, sliderClickedFinal);
|
||||
cx, cy, color, comboEnd, hitObject, type, sliderHeldToEnd,
|
||||
currentRepeats + 1, curve, sliderHeldToEnd);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -429,21 +432,17 @@ public class Slider implements GameObject {
|
|||
float[] c = curve.pointAt(getT(trackPosition, false));
|
||||
double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY);
|
||||
if (distance < followRadius)
|
||||
sliderClickedFinal = true;
|
||||
sliderHeldToEnd = true;
|
||||
}
|
||||
|
||||
// final circle hit
|
||||
if (sliderClickedFinal){
|
||||
if (sliderHeldToEnd)
|
||||
ticksHit++;
|
||||
data.sliderFinalResult(hitObject.getTime(), GameData.HIT_SLIDER30, this.x, this.y, hitObject, currentRepeats);
|
||||
}
|
||||
|
||||
// "auto" mod: always send a perfect hit result
|
||||
if (isAutoMod)
|
||||
ticksHit = tickIntervals;
|
||||
|
||||
//TODO missing the final shouldn't increment the combo
|
||||
|
||||
// calculate and send slider result
|
||||
hitResult();
|
||||
return true;
|
||||
|
@ -501,8 +500,8 @@ public class Slider implements GameObject {
|
|||
}
|
||||
|
||||
// held near end of slider
|
||||
if (!sliderClickedFinal && trackPosition > hitObject.getTime() + sliderTimeTotal - hitResultOffset[GameData.HIT_300])
|
||||
sliderClickedFinal = true;
|
||||
if (!sliderHeldToEnd && trackPosition > hitObject.getTime() + sliderTimeTotal - hitResultOffset[GameData.HIT_300])
|
||||
sliderHeldToEnd = true;
|
||||
} else {
|
||||
followCircleActive = false;
|
||||
|
||||
|
@ -563,12 +562,11 @@ public class Slider implements GameObject {
|
|||
@Override
|
||||
public void reset() {
|
||||
sliderClickedInitial = false;
|
||||
sliderClickedFinal = false;
|
||||
sliderHeldToEnd = false;
|
||||
followCircleActive = false;
|
||||
currentRepeats = 0;
|
||||
tickIndex = 0;
|
||||
ticksHit = 0;
|
||||
tickIntervals = 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -58,13 +58,14 @@ public class Spinner implements GameObject {
|
|||
AUTO_MULTIPLIER = 1 / 20f, // angle = 477/60f * delta/1000f * TWO_PI;
|
||||
SPUN_OUT_MULTIPLIER = 1 / 33.25f; // angle = 287/60f * delta/1000f * TWO_PI;
|
||||
|
||||
/** Maximum angle difference. */
|
||||
private static final float MAX_ANG_DIFF = DELTA_UPDATE_TIME * AUTO_MULTIPLIER; // ~95.3
|
||||
|
||||
/** PI constants. */
|
||||
private static final float
|
||||
TWO_PI = (float) (Math.PI * 2),
|
||||
HALF_PI = (float) (Math.PI / 2);
|
||||
|
||||
private static final float MAX_ANG_DIFF = DELTA_UPDATE_TIME * AUTO_MULTIPLIER; // ~95.3
|
||||
|
||||
/** The associated HitObject. */
|
||||
private HitObject hitObject;
|
||||
|
||||
|
@ -124,6 +125,7 @@ public class Spinner implements GameObject {
|
|||
public Spinner(HitObject hitObject, Game game, GameData data) {
|
||||
this.hitObject = hitObject;
|
||||
this.data = data;
|
||||
|
||||
/*
|
||||
1 beat = 731.707317073171ms
|
||||
RPM at frame X with spinner Y beats long
|
||||
|
@ -155,13 +157,13 @@ public class Spinner implements GameObject {
|
|||
// TODO not correct at all, but close enough?
|
||||
// <2sec ~ 12 ~ 200ms
|
||||
// >5sec ~ 48 ~ 800ms
|
||||
|
||||
final int minVel = 12;
|
||||
final int maxVel = 48;
|
||||
final int minTime = 2000;
|
||||
final int maxTime = 5000;
|
||||
maxStoredDeltaAngles = (int) Utils.clamp(
|
||||
(hitObject.getEndTime() - hitObject.getTime() - minTime) * (maxVel-minVel)/(maxTime-minTime) + minVel
|
||||
, minVel, maxVel);
|
||||
maxStoredDeltaAngles = (int) Utils.clamp((hitObject.getEndTime() - hitObject.getTime() - minTime)
|
||||
* (maxVel - minVel) / (maxTime - minTime) + minVel, minVel, maxVel);
|
||||
storedDeltaAngle = new float[maxStoredDeltaAngles];
|
||||
|
||||
// calculate rotations needed
|
||||
|
@ -245,7 +247,7 @@ public class Spinner implements GameObject {
|
|||
result = GameData.HIT_MISS;
|
||||
|
||||
data.hitResult(hitObject.getEndTime(), result, width / 2, height / 2,
|
||||
Color.transparent, true, hitObject, 0, HitObjectType.SPINNER, null, true);
|
||||
Color.transparent, true, hitObject, HitObjectType.SPINNER, true, 0, null, false);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -257,7 +259,6 @@ public class Spinner implements GameObject {
|
|||
|
||||
@Override
|
||||
public boolean update(boolean overlap, int delta, int mouseX, int mouseY, boolean keyPressed, int trackPosition) {
|
||||
|
||||
// end of spinner
|
||||
if (overlap || trackPosition > hitObject.getEndTime()) {
|
||||
hitResult();
|
||||
|
@ -289,13 +290,12 @@ public class Spinner implements GameObject {
|
|||
isSpinning = true;
|
||||
return false;
|
||||
}
|
||||
angleDiff = angle - lastAngle;
|
||||
if(Math.abs(angleDiff) > 0.01f){
|
||||
lastAngle = angle;
|
||||
}else{
|
||||
angleDiff = 0;
|
||||
}
|
||||
|
||||
angleDiff = angle - lastAngle;
|
||||
if (Math.abs(angleDiff) > 0.01f)
|
||||
lastAngle = angle;
|
||||
else
|
||||
angleDiff = 0;
|
||||
}
|
||||
|
||||
// make angleDiff the smallest angle change possible
|
||||
|
@ -305,7 +305,7 @@ public class Spinner implements GameObject {
|
|||
else if (angleDiff > Math.PI)
|
||||
angleDiff -= TWO_PI;
|
||||
|
||||
//may be a problem at higher frame rate due to float point round off
|
||||
// may be a problem at higher frame rate due to floating point round off
|
||||
if (isSpinning)
|
||||
deltaAngleOverflow += angleDiff;
|
||||
|
||||
|
@ -332,8 +332,8 @@ public class Spinner implements GameObject {
|
|||
rotate(rotationAngle);
|
||||
if (Math.abs(rotationAngle) > 0.00001f)
|
||||
data.changeHealth(DELTA_UPDATE_TIME * GameData.HP_DRAIN_MULTIPLIER);
|
||||
|
||||
}
|
||||
|
||||
//TODO may need to update 1 more time when the spinner ends?
|
||||
return false;
|
||||
}
|
||||
|
@ -380,21 +380,17 @@ public class Spinner implements GameObject {
|
|||
//TODO seems to give 1100 points per spin but also an extra 100 for some spinners
|
||||
if (newRotations > rotationsNeeded) { // extra rotations
|
||||
data.changeScore(1000);
|
||||
|
||||
SoundController.playSound(SoundEffect.SPINNERBONUS);
|
||||
}
|
||||
data.changeScore(100);
|
||||
SoundController.playSound(SoundEffect.SPINNERSPIN);
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
//The extra 100 for some spinners (mostly wrong)
|
||||
if (Math.floor(newRotations + 0.5f) > rotations + 0.5f) {
|
||||
if (newRotations + 0.5f > rotationsNeeded) { // extra rotations
|
||||
data.changeScore(100);
|
||||
}
|
||||
}
|
||||
//*/
|
||||
// extra 100 for some spinners (mostly wrong)
|
||||
// if (Math.floor(newRotations + 0.5f) > rotations + 0.5f) {
|
||||
// if (newRotations + 0.5f > rotationsNeeded) // extra rotations
|
||||
// data.changeScore(100);
|
||||
// }
|
||||
|
||||
rotations = newRotations;
|
||||
}
|
||||
|
@ -403,9 +399,8 @@ public class Spinner implements GameObject {
|
|||
public void reset() {
|
||||
deltaAngleIndex = 0;
|
||||
sumDeltaAngle = 0;
|
||||
for(int i=0; i<storedDeltaAngle.length; i++){
|
||||
for (int i = 0; i < storedDeltaAngle.length; i++)
|
||||
storedDeltaAngle[i] = 0;
|
||||
}
|
||||
drawRotation = 0;
|
||||
rotations = 0;
|
||||
deltaOverflow = 0;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package itdelatrisu.opsu.render;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
|
|
@ -56,6 +56,9 @@ public class Replay {
|
|||
/** The associated file. */
|
||||
private File file;
|
||||
|
||||
/** The associated score data. */
|
||||
private ScoreData scoreData;
|
||||
|
||||
/** Whether or not the replay data has been loaded from the file. */
|
||||
public boolean loaded = false;
|
||||
|
||||
|
@ -104,8 +107,6 @@ public class Replay {
|
|||
/** Seed. (?) */
|
||||
public int seed;
|
||||
|
||||
private ScoreData scoreData;
|
||||
|
||||
/** Seed string. */
|
||||
private static final String SEED_STRING = "-12345";
|
||||
|
||||
|
@ -137,45 +138,15 @@ public class Replay {
|
|||
loaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the replay header only.
|
||||
* @throws IOException failure to load the data
|
||||
*/
|
||||
public void loadHeader() throws IOException {
|
||||
OsuReader reader = new OsuReader(file);
|
||||
loadHeader(reader);
|
||||
reader.close();
|
||||
}
|
||||
/**
|
||||
* Returns a ScoreData object encapsulating all game data.
|
||||
* If score data already exists, the existing object will be returned
|
||||
* (i.e. this will not overwrite existing data).
|
||||
* @param osu the OsuFile
|
||||
* @return the ScoreData object
|
||||
*/
|
||||
public ScoreData getScoreData(Beatmap osu) {
|
||||
if (scoreData != null)
|
||||
return scoreData;
|
||||
|
||||
scoreData = new ScoreData();
|
||||
scoreData.timestamp = file.lastModified() / 1000L;
|
||||
scoreData.MID = osu.beatmapID;
|
||||
scoreData.MSID = osu.beatmapSetID;
|
||||
scoreData.title = osu.title;
|
||||
scoreData.artist = osu.artist;
|
||||
scoreData.creator = osu.creator;
|
||||
scoreData.version = osu.version;
|
||||
scoreData.hit300 = hit300;
|
||||
scoreData.hit100 = hit100;
|
||||
scoreData.hit50 = hit50;
|
||||
scoreData.geki = geki;
|
||||
scoreData.katu = katu;
|
||||
scoreData.miss = miss;
|
||||
scoreData.score = score;
|
||||
scoreData.combo = combo;
|
||||
scoreData.perfect = perfect;
|
||||
scoreData.mods = mods;
|
||||
scoreData.replayString = file!=null ? file.getName() : getReplayFilename();
|
||||
scoreData.playerName = playerName!=null ? playerName : "No Name";
|
||||
return scoreData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the replay header data.
|
||||
|
@ -260,6 +231,40 @@ public class Replay {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ScoreData object encapsulating all replay data.
|
||||
* If score data already exists, the existing object will be returned
|
||||
* (i.e. this will not overwrite existing data).
|
||||
* @param beatmap the beatmap
|
||||
* @return the ScoreData object
|
||||
*/
|
||||
public ScoreData getScoreData(Beatmap beatmap) {
|
||||
if (scoreData != null)
|
||||
return scoreData;
|
||||
|
||||
scoreData = new ScoreData();
|
||||
scoreData.timestamp = file.lastModified() / 1000L;
|
||||
scoreData.MID = beatmap.beatmapID;
|
||||
scoreData.MSID = beatmap.beatmapSetID;
|
||||
scoreData.title = beatmap.title;
|
||||
scoreData.artist = beatmap.artist;
|
||||
scoreData.creator = beatmap.creator;
|
||||
scoreData.version = beatmap.version;
|
||||
scoreData.hit300 = hit300;
|
||||
scoreData.hit100 = hit100;
|
||||
scoreData.hit50 = hit50;
|
||||
scoreData.geki = geki;
|
||||
scoreData.katu = katu;
|
||||
scoreData.miss = miss;
|
||||
scoreData.score = score;
|
||||
scoreData.combo = combo;
|
||||
scoreData.perfect = perfect;
|
||||
scoreData.mods = mods;
|
||||
scoreData.replayString = getReplayFilename();
|
||||
scoreData.playerName = playerName;
|
||||
return scoreData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the replay data to a file in the replays directory.
|
||||
*/
|
||||
|
|
|
@ -1,49 +1,119 @@
|
|||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
*
|
||||
* opsu! is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* opsu! is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package itdelatrisu.opsu.replay;
|
||||
|
||||
import itdelatrisu.opsu.ErrorHandler;
|
||||
import itdelatrisu.opsu.Options;
|
||||
import itdelatrisu.opsu.ScoreData;
|
||||
import itdelatrisu.opsu.beatmap.Beatmap;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
||||
import itdelatrisu.opsu.db.ScoreDB;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.newdawn.slick.util.Log;
|
||||
|
||||
/**
|
||||
* Importer for replay files.
|
||||
*/
|
||||
public class ReplayImporter {
|
||||
/** The index of the current file being imported. */
|
||||
private static int fileIndex = -1;
|
||||
|
||||
/** The total number of replays to import. */
|
||||
private static File[] files;
|
||||
|
||||
// This class should not be instantiated.
|
||||
private ReplayImporter() {}
|
||||
|
||||
/**
|
||||
* Invokes the importer for each OSR file in a directory, adding the replay
|
||||
* to the score database and moving the file into the replay directory.
|
||||
* @param dir the directory
|
||||
*/
|
||||
public static void importAllReplaysFromDir(File dir) {
|
||||
for (File replayToImport : dir.listFiles()) {
|
||||
try {
|
||||
Replay r = new Replay(replayToImport);
|
||||
r.loadHeader();
|
||||
Beatmap oFile = BeatmapSetList.get().getFileFromBeatmapHash(r.beatmapHash);
|
||||
if(oFile != null){
|
||||
File replaydir = Options.getReplayDir();
|
||||
if (!replaydir.isDirectory()) {
|
||||
if (!replaydir.mkdir()) {
|
||||
ErrorHandler.error("Failed to create replay directory.", null, false);
|
||||
// find all OSR files
|
||||
files = dir.listFiles(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.toLowerCase().endsWith(".osr");
|
||||
}
|
||||
});
|
||||
if (files == null || files.length < 1) {
|
||||
files = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// get replay directory
|
||||
File replayDir = Options.getReplayDir();
|
||||
if (!replayDir.isDirectory()) {
|
||||
if (!replayDir.mkdir()) {
|
||||
ErrorHandler.error(String.format("Failed to create replay directory '%s'.", replayDir.getAbsolutePath()), null, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
//ErrorHandler.error("Importing"+replayToImport+" forBeatmap:"+oFile, null, false);
|
||||
ScoreData data = r.getScoreData(oFile);
|
||||
File moveToFile = new File(replaydir, replayToImport.getName());
|
||||
if(
|
||||
!replayToImport.renameTo(moveToFile)
|
||||
){
|
||||
Log.warn("Rename Failed "+moveToFile);
|
||||
}
|
||||
data.replayString = replayToImport.getName().substring(0, replayToImport.getName().length()-4);
|
||||
ScoreDB.addScore(data);;
|
||||
} else {
|
||||
Log.warn("Could not find beatmap for replay "+replayToImport);
|
||||
}
|
||||
|
||||
// import OSRs
|
||||
for (File file : files) {
|
||||
fileIndex++;
|
||||
Replay r = new Replay(file);
|
||||
try {
|
||||
r.loadHeader();
|
||||
} catch (IOException e) {
|
||||
Log.warn("Failed to import replays ",e);
|
||||
ErrorHandler.error(String.format("Failed to import replay '%s'. The replay file could not be parsed.", file.getName()), e, false);
|
||||
continue;
|
||||
}
|
||||
Beatmap beatmap = BeatmapSetList.get().getBeatmapFromHash(r.beatmapHash);
|
||||
if (beatmap != null) {
|
||||
File moveToFile = new File(replayDir, String.format("%s.osr", r.getReplayFilename()));
|
||||
if (!file.renameTo(moveToFile)) {
|
||||
ErrorHandler.error(String.format("Failed to import replay '%s'. The replay file could not be moved to the replay directory.", file.getName()), null, false);
|
||||
//continue;
|
||||
}
|
||||
ScoreDB.addScore(r.getScoreData(beatmap));
|
||||
} else {
|
||||
ErrorHandler.error(String.format("Failed to import replay '%s'. The associated beatmap could not be found.", file.getName()), null, false);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
fileIndex = -1;
|
||||
files = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the current file being imported, or null if none.
|
||||
*/
|
||||
public static String getCurrentFileName() {
|
||||
if (files == null || fileIndex == -1)
|
||||
return null;
|
||||
|
||||
return files[fileIndex].getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the progress of replay importing, or -1 if not importing.
|
||||
* @return the completion percent [0, 100] or -1
|
||||
*/
|
||||
public static int getLoadingProgress() {
|
||||
if (files == null || fileIndex == -1)
|
||||
return -1;
|
||||
|
||||
return (fileIndex + 1) * 100 / files.length;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,9 @@ import itdelatrisu.opsu.Utils;
|
|||
import itdelatrisu.opsu.audio.MusicController;
|
||||
import itdelatrisu.opsu.audio.SoundController;
|
||||
import itdelatrisu.opsu.audio.SoundEffect;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapParser;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapSetNode;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapParser;
|
||||
import itdelatrisu.opsu.downloads.Download;
|
||||
import itdelatrisu.opsu.downloads.DownloadList;
|
||||
import itdelatrisu.opsu.downloads.DownloadNode;
|
||||
|
|
|
@ -217,18 +217,15 @@ public class Game extends BasicGameState {
|
|||
/** Playback speed (used in replays and "auto" mod). */
|
||||
private PlaybackSpeed playbackSpeed;
|
||||
|
||||
/** Whether the game is currently seeking to a replay position. */
|
||||
private boolean isSeeking;
|
||||
|
||||
// game-related variables
|
||||
private GameContainer container;
|
||||
private StateBasedGame game;
|
||||
private Input input;
|
||||
private int state;
|
||||
|
||||
private int width;
|
||||
|
||||
private int height;
|
||||
|
||||
private boolean seeking;
|
||||
|
||||
public Game(int state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
@ -240,8 +237,8 @@ public class Game extends BasicGameState {
|
|||
this.game = game;
|
||||
input = container.getInput();
|
||||
|
||||
width = container.getWidth();
|
||||
height = container.getHeight();
|
||||
int width = container.getWidth();
|
||||
int height = container.getHeight();
|
||||
|
||||
// create offscreen graphics
|
||||
offscreen = new Image(width, height);
|
||||
|
@ -617,13 +614,14 @@ public class Game extends BasicGameState {
|
|||
updateGame(replayX, replayY, delta, MusicController.getPosition(), lastKeysPressed);
|
||||
|
||||
//TODO probably should to disable sounds then reseek to the new position
|
||||
if(seeking && replayIndex-1 >= 1 && replayIndex < replay.frames.length && trackPosition < replay.frames[replayIndex-1].getTime()){
|
||||
if (isSeeking && replayIndex - 1 >= 1 && replayIndex < replay.frames.length &&
|
||||
trackPosition < replay.frames[replayIndex - 1].getTime()) {
|
||||
replayIndex = 0;
|
||||
while (objectIndex >= 0) {
|
||||
gameObjects[objectIndex].reset();
|
||||
objectIndex--;
|
||||
|
||||
}
|
||||
|
||||
// reset game data
|
||||
resetGameData();
|
||||
|
||||
|
@ -635,7 +633,7 @@ public class Game extends BasicGameState {
|
|||
timingPointIndex++;
|
||||
}
|
||||
}
|
||||
seeking = false;
|
||||
isSeeking = false;
|
||||
}
|
||||
|
||||
// update and run replay frames
|
||||
|
@ -925,10 +923,11 @@ public class Game extends BasicGameState {
|
|||
MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier());
|
||||
}
|
||||
|
||||
if(!GameMod.AUTO.isActive() && y < 50){
|
||||
float pos = (float)x / width * beatmap.endTime;
|
||||
// TODO
|
||||
else if (!GameMod.AUTO.isActive() && y < 50) {
|
||||
float pos = (float) x / container.getWidth() * beatmap.endTime;
|
||||
MusicController.setPosition((int) pos);
|
||||
seeking = true;
|
||||
isSeeking = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -1439,19 +1438,16 @@ public class Game extends BasicGameState {
|
|||
|
||||
// overallDifficulty (hit result time offsets)
|
||||
hitResultOffset = new int[GameData.HIT_MAX];
|
||||
/*
|
||||
float mult = 0.608f;
|
||||
hitResultOffset[GameData.HIT_300] = (int) ((128 - (overallDifficulty * 9.6))*mult);
|
||||
hitResultOffset[GameData.HIT_100] = (int) ((224 - (overallDifficulty * 12.8))*mult);
|
||||
hitResultOffset[GameData.HIT_50] = (int) ((320 - (overallDifficulty * 16))*mult);
|
||||
hitResultOffset[GameData.HIT_MISS] = (int) ((1000 - (overallDifficulty * 10))*mult);
|
||||
/*/
|
||||
hitResultOffset[GameData.HIT_300] = (int) (78 - (overallDifficulty * 6));
|
||||
hitResultOffset[GameData.HIT_100] = (int) (138 - (overallDifficulty * 8));
|
||||
hitResultOffset[GameData.HIT_50] = (int) (198 - (overallDifficulty * 10));
|
||||
hitResultOffset[GameData.HIT_MISS] = (int) (500 - (overallDifficulty * 10));
|
||||
//final float mult = 0.608f;
|
||||
//hitResultOffset[GameData.HIT_300] = (int) ((128 - (overallDifficulty * 9.6)) * mult);
|
||||
//hitResultOffset[GameData.HIT_100] = (int) ((224 - (overallDifficulty * 12.8)) * mult);
|
||||
//hitResultOffset[GameData.HIT_50] = (int) ((320 - (overallDifficulty * 16)) * mult);
|
||||
//hitResultOffset[GameData.HIT_MISS] = (int) ((1000 - (overallDifficulty * 10)) * mult);
|
||||
data.setHitResultOffset(hitResultOffset);
|
||||
//*/
|
||||
|
||||
// HPDrainRate (health change)
|
||||
data.setDrainRate(HPDrainRate);
|
||||
|
|
|
@ -32,8 +32,8 @@ import itdelatrisu.opsu.beatmap.BeatmapSetNode;
|
|||
import itdelatrisu.opsu.downloads.Updater;
|
||||
import itdelatrisu.opsu.states.ButtonMenu.MenuState;
|
||||
import itdelatrisu.opsu.ui.MenuButton;
|
||||
import itdelatrisu.opsu.ui.UI;
|
||||
import itdelatrisu.opsu.ui.MenuButton.Expand;
|
||||
import itdelatrisu.opsu.ui.UI;
|
||||
|
||||
import java.awt.Desktop;
|
||||
import java.io.IOException;
|
||||
|
|
|
@ -32,10 +32,10 @@ import itdelatrisu.opsu.audio.MusicController;
|
|||
import itdelatrisu.opsu.audio.SoundController;
|
||||
import itdelatrisu.opsu.audio.SoundEffect;
|
||||
import itdelatrisu.opsu.beatmap.Beatmap;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapParser;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapSetNode;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapSortOrder;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapParser;
|
||||
import itdelatrisu.opsu.db.BeatmapDB;
|
||||
import itdelatrisu.opsu.db.ScoreDB;
|
||||
import itdelatrisu.opsu.states.ButtonMenu.MenuState;
|
||||
|
|
|
@ -25,9 +25,9 @@ import itdelatrisu.opsu.OszUnpacker;
|
|||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.audio.MusicController;
|
||||
import itdelatrisu.opsu.audio.SoundController;
|
||||
import itdelatrisu.opsu.replay.ReplayImporter;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapParser;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapSetList;
|
||||
import itdelatrisu.opsu.replay.ReplayImporter;
|
||||
import itdelatrisu.opsu.ui.UI;
|
||||
|
||||
import java.io.File;
|
||||
|
|
|
@ -25,6 +25,7 @@ import itdelatrisu.opsu.OszUnpacker;
|
|||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.audio.SoundController;
|
||||
import itdelatrisu.opsu.beatmap.BeatmapParser;
|
||||
import itdelatrisu.opsu.replay.ReplayImporter;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.UIManager;
|
||||
|
@ -285,6 +286,9 @@ public class UI {
|
|||
text = (BeatmapParser.getStatus() == BeatmapParser.Status.INSERTING) ?
|
||||
"Updating database..." : "Loading beatmaps...";
|
||||
progress = BeatmapParser.getParserProgress();
|
||||
} else if ((file = ReplayImporter.getCurrentFileName()) != null) {
|
||||
text = "Importing replays...";
|
||||
progress = ReplayImporter.getLoadingProgress();
|
||||
} else if ((file = SoundController.getCurrentFileName()) != null) {
|
||||
text = "Loading sounds...";
|
||||
progress = SoundController.getLoadingProgress();
|
||||
|
|
|
@ -41,7 +41,6 @@ import org.lwjgl.openal.OpenALException;
|
|||
import org.newdawn.slick.util.Log;
|
||||
import org.newdawn.slick.util.ResourceLoader;
|
||||
|
||||
|
||||
/**
|
||||
* A generic tool to work on a supplied stream, pulling out PCM data and buffered it to OpenAL
|
||||
* as required.
|
||||
|
@ -409,7 +408,6 @@ public class OpenALStreamPlayer {
|
|||
|
||||
// hard reset
|
||||
if (Math.abs(thisPosition - dxTime / 1000f) > 1 / 2f) {
|
||||
//System.out.println("Time HARD Reset"+" "+thisPosition+" "+(dxTime / 1000f));
|
||||
syncPosition();
|
||||
dxTime = (thisTime - syncStartTime) * pitch;
|
||||
avgDiff = 0;
|
||||
|
@ -426,9 +424,12 @@ public class OpenALStreamPlayer {
|
|||
lastUpdatePosition = thisPosition;
|
||||
}
|
||||
|
||||
|
||||
return dxTime / 1000f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes the track position.
|
||||
*/
|
||||
private void syncPosition() {
|
||||
syncStartTime = getTime() - (long) (getALPosition() * 1000 / pitch);
|
||||
avgDiff = 0;
|
||||
|
|
Loading…
Reference in New Issue
Block a user