Hit object refactoring.

- Moved the bulk of hit object parsing into the OsuHitObject constructor, and made all fields private.  Only combo-related data is still set by OsuParser.
- Added 'isCircle()', 'isSlider()', 'isSpinner()', and 'isNewCombo()' methods for convenience.

Other changes:
- Fixed difficulty overrides are no longer affected by game mods.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2014-07-18 15:11:57 -04:00
parent 2380b11f48
commit 717605564d
7 changed files with 410 additions and 231 deletions

View File

@ -500,7 +500,7 @@ public class GameScore {
g.drawOval(circleX, symbolHeight, circleDiameter, circleDiameter); g.drawOval(circleX, symbolHeight, circleDiameter, circleDiameter);
OsuFile osu = MusicController.getOsuFile(); OsuFile osu = MusicController.getOsuFile();
int firstObjectTime = osu.objects[0].time; int firstObjectTime = osu.objects[0].getTime();
int trackPosition = MusicController.getPosition(); int trackPosition = MusicController.getPosition();
if (trackPosition > firstObjectTime) { if (trackPosition > firstObjectTime) {
// map progress (white) // map progress (white)

View File

@ -32,7 +32,7 @@ public class OsuHitObject {
TYPE_SPINNER = 8; TYPE_SPINNER = 8;
/** /**
* Hit sound types. * Hit sound types (bits).
*/ */
public static final byte public static final byte
SOUND_NORMAL = 0, SOUND_NORMAL = 0,
@ -53,39 +53,263 @@ public class OsuHitObject {
/** /**
* Max hit object coordinates. * Max hit object coordinates.
*/ */
public static final int private static final int
MAX_X = 512, MAX_X = 512,
MAX_Y = 384; MAX_Y = 384;
// parsed fields (coordinates are scaled) /**
public float x, y; // start coordinates * The x and y multipliers for hit object coordinates.
public int time; // start time, in ms */
public int type; // hit object type private static float xMultiplier, yMultiplier;
public byte hitSound; // hit sound type
public char sliderType; // slider curve type (sliders only) /**
public float[] sliderX; // slider x coordinate list (sliders only) * The x and y offsets for hit object coordinates.
public float[] sliderY; // slider y coordinate list (sliders only) */
public int repeat; // slider repeat count (sliders only) private static int
public float pixelLength; // slider pixel length (sliders only) xOffset, // offset right of border
public int endTime; // end time, in ms (spinners only) yOffset; // offset below health bar
/**
* Starting coordinates (scaled).
*/
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;
/**
* Slider curve type (SLIDER_* constant).
*/
private char sliderType;
/**
* Slider coordinate lists (scaled).
*/
private float[] sliderX, sliderY;
/**
* Slider repeat count.
*/
private int repeat;
/**
* Slider pixel length.
*/
private float pixelLength;
/**
* Spinner end time (in ms).
*/
private int endTime;
// additional v10+ parameters not implemented... // additional v10+ parameters not implemented...
// addition -> sampl:add:cust:vol:hitsound // addition -> sampl:add:cust:vol:hitsound
// edge_hitsound, edge_addition (sliders only) // edge_hitsound, edge_addition (sliders only)
// extra fields /**
public int comboIndex; // current index in Color array * Current index in combo color array.
public int comboNumber; // number to display in hit object */
private int comboIndex;
/** /**
* Constructor with all required fields. * Number to display in hit object.
*/ */
public OsuHitObject(float x, float y, int time, int type, byte hitSound) { private int comboNumber;
this.x = x;
this.y = y; /**
this.time = time; * Initializes the OsuHitObject data type with container dimensions.
this.type = type; * @param width the container width
this.hitSound = hitSound; * @param height the container height
*/
public static void init(int width, int height) {
xMultiplier = (width * 0.6f) / MAX_X;
yMultiplier = (height * 0.6f) / MAX_Y;
xOffset = width / 5;
yOffset = height / 5;
} }
/**
* 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' is optional, and defaults to "0:0:0:0:".
*/
String tokens[] = line.split(",");
// common fields
this.x = Integer.parseInt(tokens[0]) * xMultiplier + xOffset;
this.y = Integer.parseInt(tokens[1]) * yMultiplier + yOffset;
this.time = Integer.parseInt(tokens[2]);
this.type = Integer.parseInt(tokens[3]);
this.hitSound = Byte.parseByte(tokens[4]);
// type-specific fields
if ((type & OsuHitObject.TYPE_CIRCLE) > 0) {
/* 'addition' not implemented. */
} else if ((type & OsuHitObject.TYPE_SLIDER) > 0) {
// 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]) * xMultiplier + xOffset;
this.sliderY[j - 1] = Integer.parseInt(sliderXY[1]) * yMultiplier + yOffset;
}
this.repeat = Integer.parseInt(tokens[6]);
this.pixelLength = Float.parseFloat(tokens[7]);
/* edge fields and 'addition' not implemented. */
} else { //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);
this.endTime = Integer.parseInt(tokens[5]);
/* 'addition' not implemented. */
}
}
/**
* Returns the starting x coordinate.
* @return the x coordinate
*/
public float getX() { return x; }
/**
* Returns the starting y coordinate.
* @return the y coordinate
*/
public float getY() { return y; }
/**
* 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 hit sound type.
* @return the sound type (SOUND_* bitmask)
*/
public byte getHitSoundType() { return hitSound; }
/**
* Returns the slider type.
* @return the slider type (SLIDER_* constant)
*/
public char getSliderType() { return sliderType; }
/**
* Returns a list of slider x coordinates.
* @return the slider x coordinates
*/
public float[] getSliderX() { return sliderX; }
/**
* Returns a list of slider y coordinates.
* @return the slider y coordinates
*/
public float[] getSliderY() { return sliderY; }
/**
* 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; }
} }

View File

@ -49,18 +49,6 @@ public class OsuParser {
*/ */
private static int totalDirectories = -1; private static int totalDirectories = -1;
/**
* 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
// This class should not be instantiated. // This class should not be instantiated.
private OsuParser() {} private OsuParser() {}
@ -71,11 +59,8 @@ public class OsuParser {
* @param height the container height * @param height the container height
*/ */
public static void parseAllFiles(File root, int width, int height) { public static void parseAllFiles(File root, int width, int height) {
// set coordinate modifiers // initialize hit objects
xMultiplier = (width * 0.6f) / OsuHitObject.MAX_X; OsuHitObject.init(width, height);
yMultiplier = (height * 0.6f) / OsuHitObject.MAX_Y;
xOffset = width / 5;
yOffset = height / 5;
// progress tracking // progress tracking
File[] folders = root.listFiles(); File[] folders = root.listFiles();
@ -438,23 +423,7 @@ public class OsuParser {
/** /**
* Parses all hit objects in an OSU file. * Parses all hit objects in an OSU file.
* * @param osu the OsuFile to parse
* Object formats:
* - Circles [1]:
* x,y,time,type,hitSound,addition
* 256,148,9466,1,2,0:0:0:0:
*
* - Sliders [2]:
* 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 [8]:
* x,y,time,type,hitSound,endTime,addition
* 256,192,654,12,0,4029,0:0:0:0:
*
* Notes:
* - 'addition' is optional, and defaults to "0:0:0:0:".
* - Field descriptions are located in OsuHitObject.java.
*/ */
public static void parseHitObjects(OsuFile osu) { public static void parseHitObjects(OsuFile osu) {
if (osu.objects != null) // already parsed if (osu.objects != null) // already parsed
@ -467,72 +436,47 @@ public class OsuParser {
String line = in.readLine(); String line = in.readLine();
while (line != null) { while (line != null) {
line = line.trim(); line = line.trim();
if (!line.equals("[HitObjects]")) { if (!line.equals("[HitObjects]"))
line = in.readLine(); line = in.readLine();
continue; else
break;
}
if (line == null) {
Log.warn(String.format("No hit objects found in OsuFile '%s'.", osu.toString()));
return;
} }
int i = 0; // object index // combo info
int comboIndex = 0; // color index int comboIndex = 0; // color index
int comboNumber = 1; // combo number int comboNumber = 1; // combo number
String tokens[], sliderTokens[], sliderXY[];
while ((line = in.readLine()) != null && i < osu.objects.length) { int objectIndex = 0;
while ((line = in.readLine()) != null && objectIndex < osu.objects.length) {
line = line.trim(); line = line.trim();
if (!isValidLine(line)) if (!isValidLine(line))
continue; continue;
if (line.charAt(0) == '[') if (line.charAt(0) == '[')
break; break;
// create a new OsuHitObject for each line
tokens = line.split(",");
if (tokens.length < 5)
continue;
float scaledX = Integer.parseInt(tokens[0]) * xMultiplier + xOffset;
float scaledY = Integer.parseInt(tokens[1]) * yMultiplier + yOffset;
int type = Integer.parseInt(tokens[3]);
osu.objects[i] = new OsuHitObject(
scaledX, scaledY, Integer.parseInt(tokens[2]),
type, Byte.parseByte(tokens[4])
);
if ((type & OsuHitObject.TYPE_CIRCLE) > 0) {
/* 'addition' not implemented. */
} else if ((type & OsuHitObject.TYPE_SLIDER) > 0) {
// slider curve type and coordinates
sliderTokens = tokens[5].split("\\|");
osu.objects[i].sliderType = sliderTokens[0].charAt(0);
osu.objects[i].sliderX = new float[sliderTokens.length-1];
osu.objects[i].sliderY = new float[sliderTokens.length-1];
for (int j = 1; j < sliderTokens.length; j++) {
sliderXY = sliderTokens[j].split(":");
osu.objects[i].sliderX[j-1] = Integer.parseInt(sliderXY[0]) * xMultiplier + xOffset;
osu.objects[i].sliderY[j-1] = Integer.parseInt(sliderXY[1]) * yMultiplier + yOffset;
}
osu.objects[i].repeat = Integer.parseInt(tokens[6]); // lines must have at minimum 5 parameters
osu.objects[i].pixelLength = Float.parseFloat(tokens[7]); int tokenCount = line.length() - line.replace(",", "").length();
/* edge fields and 'addition' not implemented. */ if (tokenCount < 4)
} else { //if ((type & OsuHitObject.TYPE_SPINNER) > 0) { continue;
// some 'endTime' fields contain a ':' character (?)
int index = tokens[5].indexOf(':'); // create a new OsuHitObject for each line
if (index != -1) OsuHitObject hitObject = new OsuHitObject(line);
tokens[5] = tokens[5].substring(0, index);
osu.objects[i].endTime = Integer.parseInt(tokens[5]);
/* 'addition' not implemented. */
}
// set combo info // set combo info
// - new combo: get next combo index, reset combo number // - new combo: get next combo index, reset combo number
// - else: maintain combo index, increase combo number // - else: maintain combo index, increase combo number
if ((osu.objects[i].type & OsuHitObject.TYPE_NEWCOMBO) > 0) { if (hitObject.isNewCombo()) {
comboIndex = (comboIndex + 1) % osu.combo.length; comboIndex = (comboIndex + 1) % osu.combo.length;
comboNumber = 1; comboNumber = 1;
} }
osu.objects[i].comboIndex = comboIndex; hitObject.setComboIndex(comboIndex);
osu.objects[i].comboNumber = comboNumber++; hitObject.setComboNumber(comboNumber++);
i++; osu.objects[objectIndex++] = hitObject;
}
break;
} }
} catch (IOException e) { } catch (IOException e) {
Log.error(String.format("Failed to read file '%s'.", osu.getFile().getAbsolutePath()), e); Log.error(String.format("Failed to read file '%s'.", osu.getFile().getAbsolutePath()), e);

View File

@ -94,15 +94,15 @@ public class Circle {
* @param trackPosition the current track position * @param trackPosition the current track position
*/ */
public void draw(int trackPosition) { public void draw(int trackPosition) {
int timeDiff = hitObject.time - trackPosition; int timeDiff = hitObject.getTime() - trackPosition;
if (timeDiff >= 0) { if (timeDiff >= 0) {
float x = hitObject.getX(), y = hitObject.getY();
float approachScale = 1 + (timeDiff * 2f / game.getApproachTime()); float approachScale = 1 + (timeDiff * 2f / game.getApproachTime());
Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), x, y, color);
hitObject.x, hitObject.y, color); Utils.drawCentered(GameImage.HITCIRCLE_OVERLAY.getImage(), x, y, Color.white);
Utils.drawCentered(GameImage.HITCIRCLE_OVERLAY.getImage(), hitObject.x, hitObject.y, Color.white); Utils.drawCentered(GameImage.HITCIRCLE.getImage(), x, y, color);
Utils.drawCentered(GameImage.HITCIRCLE.getImage(), hitObject.x, hitObject.y, color); score.drawSymbolNumber(hitObject.getComboNumber(), x, y,
score.drawSymbolNumber(hitObject.comboNumber, hitObject.x, hitObject.y,
GameImage.HITCIRCLE.getImage().getWidth() * 0.40f / score.getDefaultSymbolImage(0).getHeight()); GameImage.HITCIRCLE.getImage().getWidth() * 0.40f / score.getDefaultSymbolImage(0).getHeight());
} }
} }
@ -139,13 +139,15 @@ public class Circle {
* @return true if a hit result was processed * @return true if a hit result was processed
*/ */
public boolean mousePressed(int x, int y) { public boolean mousePressed(int x, int y) {
double distance = Math.hypot(hitObject.x - x, hitObject.y - y); double distance = Math.hypot(hitObject.getX() - x, hitObject.getY() - y);
int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2; int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2;
if (distance < circleRadius) { if (distance < circleRadius) {
int result = hitResult(hitObject.time); int result = hitResult(hitObject.getTime());
if (result > -1) { if (result > -1) {
score.hitResult(hitObject.time, result, hitObject.x, hitObject.y, score.hitResult(
color, comboEnd, hitObject.hitSound hitObject.getTime(), result,
hitObject.getX(), hitObject.getY(),
color, comboEnd, hitObject.getHitSoundType()
); );
return true; return true;
} }
@ -159,26 +161,27 @@ public class Circle {
* @return true if a hit result (miss) was processed * @return true if a hit result (miss) was processed
*/ */
public boolean update(boolean overlap) { public boolean update(boolean overlap) {
int time = hitObject.getTime();
float x = hitObject.getX(), y = hitObject.getY();
byte hitSound = hitObject.getHitSoundType();
int trackPosition = MusicController.getPosition(); int trackPosition = MusicController.getPosition();
int[] hitResultOffset = game.getHitResultOffsets(); int[] hitResultOffset = game.getHitResultOffsets();
boolean isAutoMod = GameMod.AUTO.isActive(); boolean isAutoMod = GameMod.AUTO.isActive();
if (overlap || trackPosition > hitObject.time + hitResultOffset[GameScore.HIT_50]) { if (overlap || trackPosition > time + hitResultOffset[GameScore.HIT_50]) {
if (isAutoMod) // "auto" mod: catch any missed notes due to lag if (isAutoMod) // "auto" mod: catch any missed notes due to lag
score.hitResult(hitObject.time, GameScore.HIT_300, score.hitResult(time, GameScore.HIT_300, x, y, color, comboEnd, hitSound);
hitObject.x, hitObject.y, color, comboEnd, hitObject.hitSound);
else // no more points can be scored, so send a miss else // no more points can be scored, so send a miss
score.hitResult(hitObject.time, GameScore.HIT_MISS, score.hitResult(time, GameScore.HIT_MISS, x, y, null, comboEnd, hitSound);
hitObject.x, hitObject.y, null, comboEnd, hitObject.hitSound);
return true; return true;
} }
// "auto" mod: send a perfect hit result // "auto" mod: send a perfect hit result
else if (isAutoMod) { else if (isAutoMod) {
if (Math.abs(trackPosition - hitObject.time) < hitResultOffset[GameScore.HIT_300]) { if (Math.abs(trackPosition - time) < hitResultOffset[GameScore.HIT_300]) {
score.hitResult(hitObject.time, GameScore.HIT_300, score.hitResult(time, GameScore.HIT_300, x, y, color, comboEnd, hitSound);
hitObject.x, hitObject.y, color, comboEnd, hitObject.hitSound);
return true; return true;
} }
} }

View File

@ -155,8 +155,8 @@ public class Slider {
* Constructor. * Constructor.
*/ */
public Bezier() { public Bezier() {
this.order = hitObject.sliderX.length + 1; this.order = hitObject.getSliderX().length + 1;
this.step = 5 / hitObject.pixelLength; this.step = 5 / hitObject.getPixelLength();
// calculate curve points for drawing // calculate curve points for drawing
int N = (int) (1 / step); int N = (int) (1 / step);
@ -172,7 +172,7 @@ public class Slider {
curveY[N] = getY(order - 1); curveY[N] = getY(order - 1);
// calculate angles (if needed) // calculate angles (if needed)
if (hitObject.repeat > 1) { if (hitObject.getRepeatCount() > 1) {
float[] c1 = pointAt(0f); float[] c1 = pointAt(0f);
float[] c2 = pointAt(step); float[] c2 = pointAt(step);
startAngle = (float) (Math.atan2(c2[1] - c1[1], c2[0] - c1[0]) * 180 / Math.PI); startAngle = (float) (Math.atan2(c2[1] - c1[1], c2[0] - c1[0]) * 180 / Math.PI);
@ -186,14 +186,14 @@ public class Slider {
* Returns the x coordinate of the control point at index i. * Returns the x coordinate of the control point at index i.
*/ */
private float getX(int i) { private float getX(int i) {
return (i == 0) ? hitObject.x : hitObject.sliderX[i - 1]; return (i == 0) ? hitObject.getX() : hitObject.getSliderX()[i - 1];
} }
/** /**
* Returns the y coordinate of the control point at index i. * Returns the y coordinate of the control point at index i.
*/ */
private float getY(int i) { private float getY(int i) {
return (i == 0) ? hitObject.y : hitObject.sliderY[i - 1]; return (i == 0) ? hitObject.getY() : hitObject.getSliderY()[i - 1];
} }
/** /**
@ -341,7 +341,9 @@ public class Slider {
* @param currentObject true if this is the current hit object * @param currentObject true if this is the current hit object
*/ */
public void draw(int trackPosition, boolean currentObject) { public void draw(int trackPosition, boolean currentObject) {
int timeDiff = hitObject.time - trackPosition; float x = hitObject.getX(), y = hitObject.getY();
float[] sliderX = hitObject.getSliderX(), sliderY = hitObject.getSliderY();
int timeDiff = hitObject.getTime() - trackPosition;
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage(); Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
Image hitCircle = GameImage.HITCIRCLE.getImage(); Image hitCircle = GameImage.HITCIRCLE.getImage();
@ -358,36 +360,35 @@ public class Slider {
} }
// end circle // end circle
int lastIndex = hitObject.sliderX.length - 1; int lastIndex = sliderX.length - 1;
Utils.drawCentered(hitCircleOverlay, hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex], Color.white); Utils.drawCentered(hitCircleOverlay, sliderX[lastIndex], sliderY[lastIndex], Color.white);
Utils.drawCentered(hitCircle, hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex], color); Utils.drawCentered(hitCircle, sliderX[lastIndex], sliderY[lastIndex], color);
// start circle // start circle
Utils.drawCentered(hitCircleOverlay, hitObject.x, hitObject.y, Color.white); Utils.drawCentered(hitCircleOverlay, x, y, Color.white);
Utils.drawCentered(hitCircle, hitObject.x, hitObject.y, color); Utils.drawCentered(hitCircle, x, y, color);
if (sliderClicked) if (sliderClicked)
; // don't draw current combo number if already clicked ; // don't draw current combo number if already clicked
else else
score.drawSymbolNumber(hitObject.comboNumber, hitObject.x, hitObject.y, score.drawSymbolNumber(hitObject.getComboNumber(), x, y,
hitCircle.getWidth() * 0.40f / score.getDefaultSymbolImage(0).getHeight()); hitCircle.getWidth() * 0.40f / score.getDefaultSymbolImage(0).getHeight());
// repeats // repeats
if (hitObject.repeat - 1 > currentRepeats) { if (hitObject.getRepeatCount() - 1 > currentRepeats) {
Image arrow = GameImage.REVERSEARROW.getImage(); Image arrow = GameImage.REVERSEARROW.getImage();
if (currentRepeats % 2 == 0) { // last circle if (currentRepeats % 2 == 0) { // last circle
arrow.setRotation(bezier.getEndAngle()); arrow.setRotation(bezier.getEndAngle());
arrow.drawCentered(hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex]); arrow.drawCentered(sliderX[lastIndex], sliderY[lastIndex]);
} else { // first circle } else { // first circle
arrow.setRotation(bezier.getStartAngle()); arrow.setRotation(bezier.getStartAngle());
arrow.drawCentered(hitObject.x, hitObject.y); arrow.drawCentered(x, y);
} }
} }
if (timeDiff >= 0) { if (timeDiff >= 0) {
// approach circle // approach circle
float approachScale = 1 + (timeDiff * 2f / game.getApproachTime()); float approachScale = 1 + (timeDiff * 2f / game.getApproachTime());
Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), x, y, color);
hitObject.x, hitObject.y, color);
} else { } else {
float[] c = bezier.pointAt(getT(trackPosition, false)); float[] c = bezier.pointAt(getT(trackPosition, false));
@ -407,7 +408,7 @@ public class Slider {
* @return the hit result (GameScore.HIT_* constants) * @return the hit result (GameScore.HIT_* constants)
*/ */
public int hitResult() { public int hitResult() {
int lastIndex = hitObject.sliderX.length - 1; int lastIndex = hitObject.getSliderX().length - 1;
float tickRatio = (float) ticksHit / tickIntervals; float tickRatio = (float) ticksHit / tickIntervals;
int result; int result;
@ -421,12 +422,12 @@ public class Slider {
result = GameScore.HIT_MISS; result = GameScore.HIT_MISS;
if (currentRepeats % 2 == 0) // last circle if (currentRepeats % 2 == 0) // last circle
score.hitResult(hitObject.time + (int) sliderTimeTotal, result, score.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex], hitObject.getSliderX()[lastIndex], hitObject.getSliderY()[lastIndex],
color, comboEnd, hitObject.hitSound); color, comboEnd, hitObject.getHitSoundType());
else // first circle else // first circle
score.hitResult(hitObject.time + (int) sliderTimeTotal, result, score.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
hitObject.x, hitObject.y, color, comboEnd, hitObject.hitSound); hitObject.getX(), hitObject.getY(), color, comboEnd, hitObject.getHitSoundType());
return result; return result;
} }
@ -442,11 +443,11 @@ public class Slider {
if (sliderClicked) // first circle already processed if (sliderClicked) // first circle already processed
return false; return false;
double distance = Math.hypot(hitObject.x - x, hitObject.y - y); double distance = Math.hypot(hitObject.getX() - x, hitObject.getY() - y);
int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2; int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2;
if (distance < circleRadius) { if (distance < circleRadius) {
int trackPosition = MusicController.getPosition(); int trackPosition = MusicController.getPosition();
int timeDiff = Math.abs(trackPosition - hitObject.time); int timeDiff = Math.abs(trackPosition - hitObject.getTime());
int[] hitResultOffset = game.getHitResultOffsets(); int[] hitResultOffset = game.getHitResultOffsets();
int result = -1; int result = -1;
@ -459,8 +460,8 @@ public class Slider {
if (result > -1) { if (result > -1) {
sliderClicked = true; sliderClicked = true;
score.sliderTickResult(hitObject.time, result, score.sliderTickResult(hitObject.getTime(), result,
hitObject.x, hitObject.y, hitObject.hitSound); hitObject.getX(), hitObject.getY(), hitObject.getHitSoundType());
return true; return true;
} }
} }
@ -476,15 +477,17 @@ public class Slider {
* @return true if slider ended * @return true if slider ended
*/ */
public boolean update(boolean overlap, int delta, int mouseX, int mouseY) { public boolean update(boolean overlap, int delta, int mouseX, int mouseY) {
int repeatCount = hitObject.getRepeatCount();
// slider time and tick calculations // slider time and tick calculations
if (sliderTimeTotal == 0f) { if (sliderTimeTotal == 0f) {
// slider time // slider time
this.sliderTime = game.getBeatLength() * (hitObject.pixelLength / sliderMultiplier) / 100f; this.sliderTime = game.getBeatLength() * (hitObject.getPixelLength() / sliderMultiplier) / 100f;
this.sliderTimeTotal = sliderTime * hitObject.repeat; this.sliderTimeTotal = sliderTime * repeatCount;
// ticks // ticks
float tickLengthDiv = 100f * sliderMultiplier / sliderTickRate / game.getTimingPointMultiplier(); float tickLengthDiv = 100f * sliderMultiplier / sliderTickRate / game.getTimingPointMultiplier();
int tickCount = (int) Math.ceil(hitObject.pixelLength / tickLengthDiv) - 1; int tickCount = (int) Math.ceil(hitObject.getPixelLength() / tickLengthDiv) - 1;
if (tickCount > 0) { if (tickCount > 0) {
this.ticksT = new float[tickCount]; this.ticksT = new float[tickCount];
float tickTOffset = 1f / (tickCount + 1); float tickTOffset = 1f / (tickCount + 1);
@ -494,37 +497,40 @@ public class Slider {
} }
} }
byte hitSound = hitObject.getHitSoundType();
int trackPosition = MusicController.getPosition(); int trackPosition = MusicController.getPosition();
int[] hitResultOffset = game.getHitResultOffsets(); int[] hitResultOffset = game.getHitResultOffsets();
int lastIndex = hitObject.sliderX.length - 1; int lastIndex = hitObject.getSliderX().length - 1;
boolean isAutoMod = GameMod.AUTO.isActive(); boolean isAutoMod = GameMod.AUTO.isActive();
if (!sliderClicked) { if (!sliderClicked) {
int time = hitObject.getTime();
// start circle time passed // start circle time passed
if (trackPosition > hitObject.time + hitResultOffset[GameScore.HIT_50]) { if (trackPosition > time + hitResultOffset[GameScore.HIT_50]) {
sliderClicked = true; sliderClicked = true;
if (isAutoMod) { // "auto" mod: catch any missed notes due to lag if (isAutoMod) { // "auto" mod: catch any missed notes due to lag
ticksHit++; ticksHit++;
score.sliderTickResult(hitObject.time, GameScore.HIT_SLIDER30, score.sliderTickResult(time, GameScore.HIT_SLIDER30,
hitObject.x, hitObject.y, hitObject.hitSound); hitObject.getX(), hitObject.getY(), hitSound);
} else } else
score.sliderTickResult(hitObject.time, GameScore.HIT_MISS, score.sliderTickResult(time, GameScore.HIT_MISS,
hitObject.x, hitObject.y, hitObject.hitSound); hitObject.getX(), hitObject.getY(), hitSound);
} }
// "auto" mod: send a perfect hit result // "auto" mod: send a perfect hit result
else if (isAutoMod) { else if (isAutoMod) {
if (Math.abs(trackPosition - hitObject.time) < hitResultOffset[GameScore.HIT_300]) { if (Math.abs(trackPosition - time) < hitResultOffset[GameScore.HIT_300]) {
ticksHit++; ticksHit++;
sliderClicked = true; sliderClicked = true;
score.sliderTickResult(hitObject.time, GameScore.HIT_SLIDER30, score.sliderTickResult(time, GameScore.HIT_SLIDER30,
hitObject.x, hitObject.y, hitObject.hitSound); hitObject.getX(), hitObject.getY(), hitSound);
} }
} }
} }
// end of slider // end of slider
if (overlap || trackPosition > hitObject.time + sliderTimeTotal) { if (overlap || trackPosition > hitObject.getTime() + sliderTimeTotal) {
tickIntervals++; tickIntervals++;
// "auto" mod: send a perfect hit result // "auto" mod: send a perfect hit result
@ -533,7 +539,7 @@ public class Slider {
// check if cursor pressed and within end circle // check if cursor pressed and within end circle
else if (Utils.isGameKeyPressed()) { else if (Utils.isGameKeyPressed()) {
double distance = Math.hypot(hitObject.sliderX[lastIndex] - mouseX, hitObject.sliderY[lastIndex] - mouseY); double distance = Math.hypot(hitObject.getSliderX()[lastIndex] - mouseX, hitObject.getSliderY()[lastIndex] - mouseY);
int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2; int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2;
if (distance < followCircleRadius) if (distance < followCircleRadius)
ticksHit++; ticksHit++;
@ -546,7 +552,7 @@ public class Slider {
// repeats // repeats
boolean isNewRepeat = false; boolean isNewRepeat = false;
if (hitObject.repeat - 1 > currentRepeats) { if (repeatCount - 1 > currentRepeats) {
float t = getT(trackPosition, true); float t = getT(trackPosition, true);
if (Math.floor(t) > currentRepeats) { if (Math.floor(t) > currentRepeats) {
currentRepeats++; currentRepeats++;
@ -558,8 +564,8 @@ public class Slider {
// ticks // ticks
boolean isNewTick = false; boolean isNewTick = false;
if (ticksT != null && if (ticksT != null &&
tickIntervals < (ticksT.length * (currentRepeats + 1)) + hitObject.repeat && tickIntervals < (ticksT.length * (currentRepeats + 1)) + repeatCount &&
tickIntervals < (ticksT.length * hitObject.repeat) + hitObject.repeat) { tickIntervals < (ticksT.length * repeatCount) + repeatCount) {
float t = getT(trackPosition, true); float t = getT(trackPosition, true);
if (t - Math.floor(t) >= ticksT[tickIndex]) { if (t - Math.floor(t) >= ticksT[tickIndex]) {
tickIntervals++; tickIntervals++;
@ -582,11 +588,10 @@ public class Slider {
ticksHit++; ticksHit++;
if (currentRepeats % 2 > 0) // last circle if (currentRepeats % 2 > 0) // last circle
score.sliderTickResult(trackPosition, GameScore.HIT_SLIDER30, score.sliderTickResult(trackPosition, GameScore.HIT_SLIDER30,
hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex], hitObject.getSliderX()[lastIndex], hitObject.getSliderY()[lastIndex], hitSound);
hitObject.hitSound);
else // first circle else // first circle
score.sliderTickResult(trackPosition, GameScore.HIT_SLIDER30, score.sliderTickResult(trackPosition, GameScore.HIT_SLIDER30,
c[0], c[1], hitObject.hitSound); c[0], c[1], hitSound);
} }
// held during new tick // held during new tick
@ -599,7 +604,7 @@ public class Slider {
followCircleActive = false; followCircleActive = false;
if (isNewRepeat) if (isNewRepeat)
score.sliderTickResult(trackPosition, GameScore.HIT_MISS, 0, 0, hitObject.hitSound); score.sliderTickResult(trackPosition, GameScore.HIT_MISS, 0, 0, hitSound);
if (isNewTick) if (isNewTick)
score.sliderTickResult(trackPosition, GameScore.HIT_MISS, 0, 0, (byte) -1); score.sliderTickResult(trackPosition, GameScore.HIT_MISS, 0, 0, (byte) -1);
} }
@ -614,7 +619,7 @@ public class Slider {
* @return the t value: raw [0, repeats] or looped [0, 1] * @return the t value: raw [0, repeats] or looped [0, 1]
*/ */
public float getT(int trackPosition, boolean raw) { public float getT(int trackPosition, boolean raw) {
float t = (trackPosition - hitObject.time) / sliderTime; float t = (trackPosition - hitObject.getTime()) / sliderTime;
if (raw) if (raw)
return t; return t;
else { else {

View File

@ -78,7 +78,8 @@ public class Spinner {
Image spinnerCircle = GameImage.SPINNER_CIRCLE.getImage(); Image spinnerCircle = GameImage.SPINNER_CIRCLE.getImage();
GameImage.SPINNER_CIRCLE.setImage(spinnerCircle.getScaledCopy(height * 9 / 10, height * 9 / 10)); GameImage.SPINNER_CIRCLE.setImage(spinnerCircle.getScaledCopy(height * 9 / 10, height * 9 / 10));
GameImage.SPINNER_APPROACHCIRCLE.setImage(GameImage.SPINNER_APPROACHCIRCLE.getImage().getScaledCopy(spinnerCircle.getWidth(), spinnerCircle.getHeight())); GameImage.SPINNER_APPROACHCIRCLE.setImage(GameImage.SPINNER_APPROACHCIRCLE.getImage().getScaledCopy(
spinnerCircle.getWidth(), spinnerCircle.getHeight()));
GameImage.SPINNER_METRE.setImage(GameImage.SPINNER_METRE.getImage().getScaledCopy(width, height)); GameImage.SPINNER_METRE.setImage(GameImage.SPINNER_METRE.getImage().getScaledCopy(width, height));
} }
@ -94,7 +95,7 @@ public class Spinner {
// calculate rotations needed // calculate rotations needed
float spinsPerMinute = 100 + (score.getDifficulty() * 15); float spinsPerMinute = 100 + (score.getDifficulty() * 15);
rotationsNeeded = spinsPerMinute * (hitObject.endTime - hitObject.time) / 60000f; rotationsNeeded = spinsPerMinute * (hitObject.getEndTime() - hitObject.getTime()) / 60000f;
} }
/** /**
@ -103,7 +104,7 @@ public class Spinner {
* @param g the graphics context * @param g the graphics context
*/ */
public void draw(int trackPosition, Graphics g) { public void draw(int trackPosition, Graphics g) {
int timeDiff = hitObject.time - trackPosition; int timeDiff = hitObject.getTime() - trackPosition;
boolean spinnerComplete = (rotations >= rotationsNeeded); boolean spinnerComplete = (rotations >= rotationsNeeded);
// TODO: draw "OSU!" image after spinner ends // TODO: draw "OSU!" image after spinner ends
@ -126,8 +127,9 @@ public class Spinner {
spinnerMetreSub.draw(0, height - spinnerMetreSub.getHeight()); spinnerMetreSub.draw(0, height - spinnerMetreSub.getHeight());
// main spinner elements // main spinner elements
float approachScale = 1 - ((float) timeDiff / (hitObject.getTime() - hitObject.getEndTime()));
GameImage.SPINNER_CIRCLE.getImage().drawCentered(width / 2, height / 2); GameImage.SPINNER_CIRCLE.getImage().drawCentered(width / 2, height / 2);
GameImage.SPINNER_APPROACHCIRCLE.getImage().getScaledCopy(1 - ((float) timeDiff / (hitObject.time - hitObject.endTime))).drawCentered(width / 2, height / 2); GameImage.SPINNER_APPROACHCIRCLE.getImage().getScaledCopy(approachScale).drawCentered(width / 2, height / 2);
GameImage.SPINNER_SPIN.getImage().drawCentered(width / 2, height * 3 / 4); GameImage.SPINNER_SPIN.getImage().drawCentered(width / 2, height * 3 / 4);
if (spinnerComplete) { if (spinnerComplete) {
@ -158,7 +160,7 @@ public class Spinner {
else else
result = GameScore.HIT_MISS; result = GameScore.HIT_MISS;
score.hitResult(hitObject.endTime, result, width / 2, height / 2, score.hitResult(hitObject.getEndTime(), result, width / 2, height / 2,
Color.transparent, true, (byte) -1); Color.transparent, true, (byte) -1);
return result; return result;
} }
@ -177,7 +179,7 @@ public class Spinner {
return true; return true;
// end of spinner // end of spinner
if (trackPosition > hitObject.endTime) { if (trackPosition > hitObject.getEndTime()) {
hitResult(); hitResult();
return true; return true;
} }

View File

@ -316,8 +316,8 @@ public class Game extends BasicGameState {
// skip beginning // skip beginning
if (objectIndex == 0 && if (objectIndex == 0 &&
osu.objects[0].time - SKIP_OFFSET > 5000 && osu.objects[0].getTime() - SKIP_OFFSET > 5000 &&
trackPosition < osu.objects[0].time - SKIP_OFFSET) trackPosition < osu.objects[0].getTime() - SKIP_OFFSET)
skipButton.draw(); skipButton.draw();
if (isLeadIn()) if (isLeadIn())
@ -325,7 +325,7 @@ public class Game extends BasicGameState {
// countdown // countdown
if (osu.countdown > 0) { // TODO: implement half/double rate settings if (osu.countdown > 0) { // TODO: implement half/double rate settings
int timeDiff = osu.objects[0].time - trackPosition; int timeDiff = osu.objects[0].getTime() - trackPosition;
if (timeDiff >= 500 && timeDiff < 3000) { if (timeDiff >= 500 && timeDiff < 3000) {
if (timeDiff >= 1500) { if (timeDiff >= 1500) {
GameImage.COUNTDOWN_READY.getImage().drawCentered(width / 2, height / 2); GameImage.COUNTDOWN_READY.getImage().drawCentered(width / 2, height / 2);
@ -368,18 +368,18 @@ public class Game extends BasicGameState {
// draw hit objects in reverse order, or else overlapping objects are unreadable // draw hit objects in reverse order, or else overlapping objects are unreadable
Stack<Integer> stack = new Stack<Integer>(); Stack<Integer> stack = new Stack<Integer>();
for (int i = objectIndex; i < osu.objects.length && osu.objects[i].time < trackPosition + approachTime; i++) for (int i = objectIndex; i < osu.objects.length && osu.objects[i].getTime() < trackPosition + approachTime; i++)
stack.add(i); stack.add(i);
while (!stack.isEmpty()) { while (!stack.isEmpty()) {
int i = stack.pop(); int i = stack.pop();
OsuHitObject hitObject = osu.objects[i]; OsuHitObject hitObject = osu.objects[i];
if ((hitObject.type & OsuHitObject.TYPE_CIRCLE) > 0) if (hitObject.isCircle())
circles.get(i).draw(trackPosition); circles.get(i).draw(trackPosition);
else if ((hitObject.type & OsuHitObject.TYPE_SLIDER) > 0) else if (hitObject.isSlider())
sliders.get(i).draw(trackPosition, stack.isEmpty()); sliders.get(i).draw(trackPosition, stack.isEmpty());
else if ((hitObject.type & OsuHitObject.TYPE_SPINNER) > 0) { else if (hitObject.isSpinner()) {
if (stack.isEmpty()) // only draw spinner at objectIndex if (stack.isEmpty()) // only draw spinner at objectIndex
spinners.get(i).draw(trackPosition, g); spinners.get(i).draw(trackPosition, g);
else else
@ -487,7 +487,7 @@ public class Game extends BasicGameState {
// song beginning // song beginning
if (objectIndex == 0) { if (objectIndex == 0) {
if (trackPosition < osu.objects[0].time) if (trackPosition < osu.objects[0].getTime())
return; // nothing to do here return; // nothing to do here
} }
@ -544,20 +544,20 @@ public class Game extends BasicGameState {
} }
// update objects (loop in unlikely event of any skipped indexes) // update objects (loop in unlikely event of any skipped indexes)
while (objectIndex < osu.objects.length && trackPosition > osu.objects[objectIndex].time) { while (objectIndex < osu.objects.length && trackPosition > osu.objects[objectIndex].getTime()) {
OsuHitObject hitObject = osu.objects[objectIndex]; OsuHitObject hitObject = osu.objects[objectIndex];
// check if we've already passed the next object's start time // check if we've already passed the next object's start time
boolean overlap = (objectIndex + 1 < osu.objects.length && boolean overlap = (objectIndex + 1 < osu.objects.length &&
trackPosition > osu.objects[objectIndex + 1].time - hitResultOffset[GameScore.HIT_300]); trackPosition > osu.objects[objectIndex + 1].getTime() - hitResultOffset[GameScore.HIT_300]);
// check completion status of the hit object // check completion status of the hit object
boolean done = false; boolean done = false;
if ((hitObject.type & OsuHitObject.TYPE_CIRCLE) > 0) if (hitObject.isCircle())
done = circles.get(objectIndex).update(overlap); done = circles.get(objectIndex).update(overlap);
else if ((hitObject.type & OsuHitObject.TYPE_SLIDER) > 0) else if (hitObject.isSlider())
done = sliders.get(objectIndex).update(overlap, delta, input.getMouseX(), input.getMouseY()); done = sliders.get(objectIndex).update(overlap, delta, input.getMouseX(), input.getMouseY());
else if ((hitObject.type & OsuHitObject.TYPE_SPINNER) > 0) else if (hitObject.isSpinner())
done = spinners.get(objectIndex).update(overlap, delta, input.getMouseX(), input.getMouseY()); done = spinners.get(objectIndex).update(overlap, delta, input.getMouseX(), input.getMouseY());
// increment object index? // increment object index?
@ -586,7 +586,7 @@ public class Game extends BasicGameState {
// pause game // pause game
int trackPosition = MusicController.getPosition(); int trackPosition = MusicController.getPosition();
if (pauseTime < 0 && breakTime <= 0 && if (pauseTime < 0 && breakTime <= 0 &&
trackPosition >= osu.objects[0].time && trackPosition >= osu.objects[0].getTime() &&
!GameMod.AUTO.isActive()) { !GameMod.AUTO.isActive()) {
pausedMouseX = input.getMouseX(); pausedMouseX = input.getMouseX();
pausedMouseY = input.getMouseY(); pausedMouseY = input.getMouseY();
@ -642,7 +642,7 @@ public class Game extends BasicGameState {
// skip to checkpoint // skip to checkpoint
MusicController.setPosition(checkpoint); MusicController.setPosition(checkpoint);
while (objectIndex < osu.objects.length && while (objectIndex < osu.objects.length &&
osu.objects[objectIndex++].time <= MusicController.getPosition()) osu.objects[objectIndex++].getTime() <= MusicController.getPosition())
; ;
objectIndex--; objectIndex--;
} catch (SlickException e) { } catch (SlickException e) {
@ -692,14 +692,14 @@ public class Game extends BasicGameState {
return; return;
// circles // circles
if ((hitObject.type & OsuHitObject.TYPE_CIRCLE) > 0) { if (hitObject.isCircle()) {
boolean hit = circles.get(objectIndex).mousePressed(x, y); boolean hit = circles.get(objectIndex).mousePressed(x, y);
if (hit) if (hit)
objectIndex++; objectIndex++;
} }
// sliders // sliders
else if ((hitObject.type & OsuHitObject.TYPE_SLIDER) > 0) else if (hitObject.isSlider())
sliders.get(objectIndex).mousePressed(x, y); sliders.get(objectIndex).mousePressed(x, y);
} }
@ -733,15 +733,15 @@ public class Game extends BasicGameState {
// is this the last note in the combo? // is this the last note in the combo?
boolean comboEnd = false; boolean comboEnd = false;
if (i + 1 < osu.objects.length && if (i + 1 < osu.objects.length && osu.objects[i + 1].isNewCombo())
(osu.objects[i + 1].type & OsuHitObject.TYPE_NEWCOMBO) > 0)
comboEnd = true; comboEnd = true;
if ((hitObject.type & OsuHitObject.TYPE_CIRCLE) > 0) { Color color = osu.combo[hitObject.getComboIndex()];
circles.put(i, new Circle(hitObject, this, score, osu.combo[hitObject.comboIndex], comboEnd)); if (hitObject.isCircle()) {
} else if ((hitObject.type & OsuHitObject.TYPE_SLIDER) > 0) { circles.put(i, new Circle(hitObject, this, score, color, comboEnd));
sliders.put(i, new Slider(hitObject, this, score, osu.combo[hitObject.comboIndex], comboEnd)); } else if (hitObject.isSlider()) {
} else if ((hitObject.type & OsuHitObject.TYPE_SPINNER) > 0) { sliders.put(i, new Slider(hitObject, this, score, color, comboEnd));
} else if (hitObject.isSpinner()) {
spinners.put(i, new Spinner(hitObject, this, score)); spinners.put(i, new Spinner(hitObject, this, score));
} }
} }
@ -794,15 +794,16 @@ public class Game extends BasicGameState {
* @return true if skipped, false otherwise * @return true if skipped, false otherwise
*/ */
private boolean skipIntro() { private boolean skipIntro() {
int firstObjectTime = osu.objects[0].getTime();
int trackPosition = MusicController.getPosition(); int trackPosition = MusicController.getPosition();
if (objectIndex == 0 && if (objectIndex == 0 &&
osu.objects[0].time - SKIP_OFFSET > 4000 && firstObjectTime - SKIP_OFFSET > 4000 &&
trackPosition < osu.objects[0].time - SKIP_OFFSET) { trackPosition < firstObjectTime - SKIP_OFFSET) {
if (isLeadIn()) { if (isLeadIn()) {
leadInTime = 0; leadInTime = 0;
MusicController.resume(); MusicController.resume();
} }
MusicController.setPosition(osu.objects[0].time - SKIP_OFFSET); MusicController.setPosition(firstObjectTime - SKIP_OFFSET);
SoundController.playSound(SoundController.SOUND_MENUHIT); SoundController.playSound(SoundController.SOUND_MENUHIT);
return true; return true;
} }
@ -883,16 +884,6 @@ public class Game extends BasicGameState {
float overallDifficulty = osu.overallDifficulty; float overallDifficulty = osu.overallDifficulty;
float HPDrainRate = osu.HPDrainRate; float HPDrainRate = osu.HPDrainRate;
// fixed difficulty overrides
if (Options.getFixedCS() > 0f)
circleSize = Options.getFixedCS();
if (Options.getFixedAR() > 0f)
approachRate = Options.getFixedAR();
if (Options.getFixedOD() > 0f)
overallDifficulty = Options.getFixedOD();
if (Options.getFixedHP() > 0f)
HPDrainRate = Options.getFixedHP();
// "Hard Rock" modifiers // "Hard Rock" modifiers
if (GameMod.HARD_ROCK.isActive()) { if (GameMod.HARD_ROCK.isActive()) {
circleSize = Math.min(circleSize * 1.4f, 10); circleSize = Math.min(circleSize * 1.4f, 10);
@ -909,6 +900,16 @@ public class Game extends BasicGameState {
HPDrainRate /= 2f; HPDrainRate /= 2f;
} }
// fixed difficulty overrides
if (Options.getFixedCS() > 0f)
circleSize = Options.getFixedCS();
if (Options.getFixedAR() > 0f)
approachRate = Options.getFixedAR();
if (Options.getFixedOD() > 0f)
overallDifficulty = Options.getFixedOD();
if (Options.getFixedHP() > 0f)
HPDrainRate = Options.getFixedHP();
// initialize objects // initialize objects
Circle.init(container, circleSize); Circle.init(container, circleSize);
Slider.init(container, circleSize, osu); Slider.init(container, circleSize, osu);