From 717605564d82094f12b3e8bde5a6c8cbe0091600 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Fri, 18 Jul 2014 15:11:57 -0400 Subject: [PATCH] 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 --- src/itdelatrisu/opsu/GameScore.java | 2 +- src/itdelatrisu/opsu/OsuHitObject.java | 270 ++++++++++++++++++++-- src/itdelatrisu/opsu/OsuParser.java | 136 ++++------- src/itdelatrisu/opsu/objects/Circle.java | 41 ++-- src/itdelatrisu/opsu/objects/Slider.java | 103 +++++---- src/itdelatrisu/opsu/objects/Spinner.java | 14 +- src/itdelatrisu/opsu/states/Game.java | 75 +++--- 7 files changed, 410 insertions(+), 231 deletions(-) diff --git a/src/itdelatrisu/opsu/GameScore.java b/src/itdelatrisu/opsu/GameScore.java index e1bf76b3..19cba2fd 100644 --- a/src/itdelatrisu/opsu/GameScore.java +++ b/src/itdelatrisu/opsu/GameScore.java @@ -500,7 +500,7 @@ public class GameScore { g.drawOval(circleX, symbolHeight, circleDiameter, circleDiameter); OsuFile osu = MusicController.getOsuFile(); - int firstObjectTime = osu.objects[0].time; + int firstObjectTime = osu.objects[0].getTime(); int trackPosition = MusicController.getPosition(); if (trackPosition > firstObjectTime) { // map progress (white) diff --git a/src/itdelatrisu/opsu/OsuHitObject.java b/src/itdelatrisu/opsu/OsuHitObject.java index 9e159023..471adc1f 100644 --- a/src/itdelatrisu/opsu/OsuHitObject.java +++ b/src/itdelatrisu/opsu/OsuHitObject.java @@ -32,7 +32,7 @@ public class OsuHitObject { TYPE_SPINNER = 8; /** - * Hit sound types. + * Hit sound types (bits). */ public static final byte SOUND_NORMAL = 0, @@ -53,39 +53,263 @@ public class OsuHitObject { /** * Max hit object coordinates. */ - public static final int + private static final int MAX_X = 512, MAX_Y = 384; - // parsed fields (coordinates are scaled) - public float x, y; // start coordinates - public int time; // start time, in ms - public int type; // hit object type - public byte hitSound; // hit sound type - public char sliderType; // slider curve type (sliders only) - public float[] sliderX; // slider x coordinate list (sliders only) - public float[] sliderY; // slider y coordinate list (sliders only) - public int repeat; // slider repeat count (sliders only) - public float pixelLength; // slider pixel length (sliders only) - public int endTime; // end time, in ms (spinners only) + /** + * 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 + + /** + * 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... // addition -> sampl:add:cust:vol:hitsound // edge_hitsound, edge_addition (sliders only) - // extra fields - public int comboIndex; // current index in Color array - public int comboNumber; // number to display in hit object + /** + * Current index in combo color array. + */ + 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) { - this.x = x; - this.y = y; - this.time = time; - this.type = type; - this.hitSound = hitSound; + private int comboNumber; + + /** + * Initializes the OsuHitObject data type with container dimensions. + * @param width the container width + * @param height the container height + */ + public static void init(int width, int height) { + 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; } } \ No newline at end of file diff --git a/src/itdelatrisu/opsu/OsuParser.java b/src/itdelatrisu/opsu/OsuParser.java index bd04266b..71797755 100644 --- a/src/itdelatrisu/opsu/OsuParser.java +++ b/src/itdelatrisu/opsu/OsuParser.java @@ -49,18 +49,6 @@ public class OsuParser { */ 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. private OsuParser() {} @@ -71,11 +59,8 @@ public class OsuParser { * @param height the container height */ public static void parseAllFiles(File root, int width, int height) { - // set coordinate modifiers - xMultiplier = (width * 0.6f) / OsuHitObject.MAX_X; - yMultiplier = (height * 0.6f) / OsuHitObject.MAX_Y; - xOffset = width / 5; - yOffset = height / 5; + // initialize hit objects + OsuHitObject.init(width, height); // progress tracking File[] folders = root.listFiles(); @@ -438,23 +423,7 @@ public class OsuParser { /** * Parses all hit objects in an OSU file. - * - * 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. + * @param osu the OsuFile to parse */ public static void parseHitObjects(OsuFile osu) { if (osu.objects != null) // already parsed @@ -467,72 +436,47 @@ public class OsuParser { String line = in.readLine(); while (line != null) { line = line.trim(); - if (!line.equals("[HitObjects]")) { + if (!line.equals("[HitObjects]")) line = in.readLine(); + else + break; + } + if (line == null) { + Log.warn(String.format("No hit objects found in OsuFile '%s'.", osu.toString())); + return; + } + + // combo info + int comboIndex = 0; // color index + int comboNumber = 1; // combo number + + int objectIndex = 0; + while ((line = in.readLine()) != null && objectIndex < osu.objects.length) { + line = line.trim(); + if (!isValidLine(line)) continue; + if (line.charAt(0) == '[') + break; + + // lines must have at minimum 5 parameters + int tokenCount = line.length() - line.replace(",", "").length(); + if (tokenCount < 4) + continue; + + // create a new OsuHitObject for each line + OsuHitObject hitObject = new OsuHitObject(line); + + // set combo info + // - new combo: get next combo index, reset combo number + // - else: maintain combo index, increase combo number + if (hitObject.isNewCombo()) { + comboIndex = (comboIndex + 1) % osu.combo.length; + comboNumber = 1; } + hitObject.setComboIndex(comboIndex); + hitObject.setComboNumber(comboNumber++); - int i = 0; // object index - int comboIndex = 0; // color index - int comboNumber = 1; // combo number - String tokens[], sliderTokens[], sliderXY[]; - - while ((line = in.readLine()) != null && i < osu.objects.length) { - line = line.trim(); - if (!isValidLine(line)) - continue; - if (line.charAt(0) == '[') - 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]); - osu.objects[i].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); - osu.objects[i].endTime = Integer.parseInt(tokens[5]); - /* 'addition' not implemented. */ - } - - // set combo info - // - new combo: get next combo index, reset combo number - // - else: maintain combo index, increase combo number - if ((osu.objects[i].type & OsuHitObject.TYPE_NEWCOMBO) > 0) { - comboIndex = (comboIndex + 1) % osu.combo.length; - comboNumber = 1; - } - osu.objects[i].comboIndex = comboIndex; - osu.objects[i].comboNumber = comboNumber++; - - i++; - } - break; + osu.objects[objectIndex++] = hitObject; } } catch (IOException e) { Log.error(String.format("Failed to read file '%s'.", osu.getFile().getAbsolutePath()), e); diff --git a/src/itdelatrisu/opsu/objects/Circle.java b/src/itdelatrisu/opsu/objects/Circle.java index 0277a9d1..11222a80 100644 --- a/src/itdelatrisu/opsu/objects/Circle.java +++ b/src/itdelatrisu/opsu/objects/Circle.java @@ -93,16 +93,16 @@ public class Circle { * Draws the circle to the graphics context. * @param trackPosition the current track position */ - public void draw(int trackPosition) { - int timeDiff = hitObject.time - trackPosition; + public void draw(int trackPosition) { + int timeDiff = hitObject.getTime() - trackPosition; if (timeDiff >= 0) { + float x = hitObject.getX(), y = hitObject.getY(); float approachScale = 1 + (timeDiff * 2f / game.getApproachTime()); - Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), - hitObject.x, hitObject.y, color); - Utils.drawCentered(GameImage.HITCIRCLE_OVERLAY.getImage(), hitObject.x, hitObject.y, Color.white); - Utils.drawCentered(GameImage.HITCIRCLE.getImage(), hitObject.x, hitObject.y, color); - score.drawSymbolNumber(hitObject.comboNumber, hitObject.x, hitObject.y, + Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), x, y, color); + Utils.drawCentered(GameImage.HITCIRCLE_OVERLAY.getImage(), x, y, Color.white); + Utils.drawCentered(GameImage.HITCIRCLE.getImage(), x, y, color); + score.drawSymbolNumber(hitObject.getComboNumber(), x, y, 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 */ 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; if (distance < circleRadius) { - int result = hitResult(hitObject.time); + int result = hitResult(hitObject.getTime()); if (result > -1) { - score.hitResult(hitObject.time, result, hitObject.x, hitObject.y, - color, comboEnd, hitObject.hitSound + score.hitResult( + hitObject.getTime(), result, + hitObject.getX(), hitObject.getY(), + color, comboEnd, hitObject.getHitSoundType() ); return true; } @@ -159,26 +161,27 @@ public class Circle { * @return true if a hit result (miss) was processed */ 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[] hitResultOffset = game.getHitResultOffsets(); 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 - score.hitResult(hitObject.time, GameScore.HIT_300, - hitObject.x, hitObject.y, color, comboEnd, hitObject.hitSound); + score.hitResult(time, GameScore.HIT_300, x, y, color, comboEnd, hitSound); else // no more points can be scored, so send a miss - score.hitResult(hitObject.time, GameScore.HIT_MISS, - hitObject.x, hitObject.y, null, comboEnd, hitObject.hitSound); + score.hitResult(time, GameScore.HIT_MISS, x, y, null, comboEnd, hitSound); return true; } // "auto" mod: send a perfect hit result else if (isAutoMod) { - if (Math.abs(trackPosition - hitObject.time) < hitResultOffset[GameScore.HIT_300]) { - score.hitResult(hitObject.time, GameScore.HIT_300, - hitObject.x, hitObject.y, color, comboEnd, hitObject.hitSound); + if (Math.abs(trackPosition - time) < hitResultOffset[GameScore.HIT_300]) { + score.hitResult(time, GameScore.HIT_300, x, y, color, comboEnd, hitSound); return true; } } diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index 7ff5e78f..37408fc3 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -155,8 +155,8 @@ public class Slider { * Constructor. */ public Bezier() { - this.order = hitObject.sliderX.length + 1; - this.step = 5 / hitObject.pixelLength; + this.order = hitObject.getSliderX().length + 1; + this.step = 5 / hitObject.getPixelLength(); // calculate curve points for drawing int N = (int) (1 / step); @@ -172,7 +172,7 @@ public class Slider { curveY[N] = getY(order - 1); // calculate angles (if needed) - if (hitObject.repeat > 1) { + if (hitObject.getRepeatCount() > 1) { float[] c1 = pointAt(0f); float[] c2 = pointAt(step); 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. */ 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. */ 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 */ 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 hitCircle = GameImage.HITCIRCLE.getImage(); @@ -358,36 +360,35 @@ public class Slider { } // end circle - int lastIndex = hitObject.sliderX.length - 1; - Utils.drawCentered(hitCircleOverlay, hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex], Color.white); - Utils.drawCentered(hitCircle, hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex], color); + int lastIndex = sliderX.length - 1; + Utils.drawCentered(hitCircleOverlay, sliderX[lastIndex], sliderY[lastIndex], Color.white); + Utils.drawCentered(hitCircle, sliderX[lastIndex], sliderY[lastIndex], color); // start circle - Utils.drawCentered(hitCircleOverlay, hitObject.x, hitObject.y, Color.white); - Utils.drawCentered(hitCircle, hitObject.x, hitObject.y, color); + Utils.drawCentered(hitCircleOverlay, x, y, Color.white); + Utils.drawCentered(hitCircle, x, y, color); if (sliderClicked) ; // don't draw current combo number if already clicked else - score.drawSymbolNumber(hitObject.comboNumber, hitObject.x, hitObject.y, + score.drawSymbolNumber(hitObject.getComboNumber(), x, y, hitCircle.getWidth() * 0.40f / score.getDefaultSymbolImage(0).getHeight()); // repeats - if (hitObject.repeat - 1 > currentRepeats) { + if (hitObject.getRepeatCount() - 1 > currentRepeats) { Image arrow = GameImage.REVERSEARROW.getImage(); if (currentRepeats % 2 == 0) { // last circle arrow.setRotation(bezier.getEndAngle()); - arrow.drawCentered(hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex]); + arrow.drawCentered(sliderX[lastIndex], sliderY[lastIndex]); } else { // first circle arrow.setRotation(bezier.getStartAngle()); - arrow.drawCentered(hitObject.x, hitObject.y); + arrow.drawCentered(x, y); } } if (timeDiff >= 0) { // approach circle float approachScale = 1 + (timeDiff * 2f / game.getApproachTime()); - Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), - hitObject.x, hitObject.y, color); + Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), x, y, color); } else { float[] c = bezier.pointAt(getT(trackPosition, false)); @@ -407,7 +408,7 @@ public class Slider { * @return the hit result (GameScore.HIT_* constants) */ public int hitResult() { - int lastIndex = hitObject.sliderX.length - 1; + int lastIndex = hitObject.getSliderX().length - 1; float tickRatio = (float) ticksHit / tickIntervals; int result; @@ -421,12 +422,12 @@ public class Slider { result = GameScore.HIT_MISS; if (currentRepeats % 2 == 0) // last circle - score.hitResult(hitObject.time + (int) sliderTimeTotal, result, - hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex], - color, comboEnd, hitObject.hitSound); + score.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result, + hitObject.getSliderX()[lastIndex], hitObject.getSliderY()[lastIndex], + color, comboEnd, hitObject.getHitSoundType()); else // first circle - score.hitResult(hitObject.time + (int) sliderTimeTotal, result, - hitObject.x, hitObject.y, color, comboEnd, hitObject.hitSound); + score.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result, + hitObject.getX(), hitObject.getY(), color, comboEnd, hitObject.getHitSoundType()); return result; } @@ -442,11 +443,11 @@ public class Slider { if (sliderClicked) // first circle already processed 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; if (distance < circleRadius) { int trackPosition = MusicController.getPosition(); - int timeDiff = Math.abs(trackPosition - hitObject.time); + int timeDiff = Math.abs(trackPosition - hitObject.getTime()); int[] hitResultOffset = game.getHitResultOffsets(); int result = -1; @@ -459,8 +460,8 @@ public class Slider { if (result > -1) { sliderClicked = true; - score.sliderTickResult(hitObject.time, result, - hitObject.x, hitObject.y, hitObject.hitSound); + score.sliderTickResult(hitObject.getTime(), result, + hitObject.getX(), hitObject.getY(), hitObject.getHitSoundType()); return true; } } @@ -476,15 +477,17 @@ public class Slider { * @return true if slider ended */ public boolean update(boolean overlap, int delta, int mouseX, int mouseY) { + int repeatCount = hitObject.getRepeatCount(); + // slider time and tick calculations if (sliderTimeTotal == 0f) { // slider time - this.sliderTime = game.getBeatLength() * (hitObject.pixelLength / sliderMultiplier) / 100f; - this.sliderTimeTotal = sliderTime * hitObject.repeat; + this.sliderTime = game.getBeatLength() * (hitObject.getPixelLength() / sliderMultiplier) / 100f; + this.sliderTimeTotal = sliderTime * repeatCount; // ticks 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) { this.ticksT = new float[tickCount]; float tickTOffset = 1f / (tickCount + 1); @@ -494,37 +497,40 @@ public class Slider { } } + byte hitSound = hitObject.getHitSoundType(); int trackPosition = MusicController.getPosition(); int[] hitResultOffset = game.getHitResultOffsets(); - int lastIndex = hitObject.sliderX.length - 1; + int lastIndex = hitObject.getSliderX().length - 1; boolean isAutoMod = GameMod.AUTO.isActive(); if (!sliderClicked) { + int time = hitObject.getTime(); + // start circle time passed - if (trackPosition > hitObject.time + hitResultOffset[GameScore.HIT_50]) { + if (trackPosition > time + hitResultOffset[GameScore.HIT_50]) { sliderClicked = true; if (isAutoMod) { // "auto" mod: catch any missed notes due to lag ticksHit++; - score.sliderTickResult(hitObject.time, GameScore.HIT_SLIDER30, - hitObject.x, hitObject.y, hitObject.hitSound); + score.sliderTickResult(time, GameScore.HIT_SLIDER30, + hitObject.getX(), hitObject.getY(), hitSound); } else - score.sliderTickResult(hitObject.time, GameScore.HIT_MISS, - hitObject.x, hitObject.y, hitObject.hitSound); + score.sliderTickResult(time, GameScore.HIT_MISS, + hitObject.getX(), hitObject.getY(), hitSound); } // "auto" mod: send a perfect hit result else if (isAutoMod) { - if (Math.abs(trackPosition - hitObject.time) < hitResultOffset[GameScore.HIT_300]) { + if (Math.abs(trackPosition - time) < hitResultOffset[GameScore.HIT_300]) { ticksHit++; sliderClicked = true; - score.sliderTickResult(hitObject.time, GameScore.HIT_SLIDER30, - hitObject.x, hitObject.y, hitObject.hitSound); + score.sliderTickResult(time, GameScore.HIT_SLIDER30, + hitObject.getX(), hitObject.getY(), hitSound); } } } // end of slider - if (overlap || trackPosition > hitObject.time + sliderTimeTotal) { + if (overlap || trackPosition > hitObject.getTime() + sliderTimeTotal) { tickIntervals++; // "auto" mod: send a perfect hit result @@ -533,7 +539,7 @@ public class Slider { // check if cursor pressed and within end circle 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; if (distance < followCircleRadius) ticksHit++; @@ -546,7 +552,7 @@ public class Slider { // repeats boolean isNewRepeat = false; - if (hitObject.repeat - 1 > currentRepeats) { + if (repeatCount - 1 > currentRepeats) { float t = getT(trackPosition, true); if (Math.floor(t) > currentRepeats) { currentRepeats++; @@ -558,8 +564,8 @@ public class Slider { // ticks boolean isNewTick = false; if (ticksT != null && - tickIntervals < (ticksT.length * (currentRepeats + 1)) + hitObject.repeat && - tickIntervals < (ticksT.length * hitObject.repeat) + hitObject.repeat) { + tickIntervals < (ticksT.length * (currentRepeats + 1)) + repeatCount && + tickIntervals < (ticksT.length * repeatCount) + repeatCount) { float t = getT(trackPosition, true); if (t - Math.floor(t) >= ticksT[tickIndex]) { tickIntervals++; @@ -582,11 +588,10 @@ public class Slider { ticksHit++; if (currentRepeats % 2 > 0) // last circle score.sliderTickResult(trackPosition, GameScore.HIT_SLIDER30, - hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex], - hitObject.hitSound); + hitObject.getSliderX()[lastIndex], hitObject.getSliderY()[lastIndex], hitSound); else // first circle score.sliderTickResult(trackPosition, GameScore.HIT_SLIDER30, - c[0], c[1], hitObject.hitSound); + c[0], c[1], hitSound); } // held during new tick @@ -599,7 +604,7 @@ public class Slider { followCircleActive = false; if (isNewRepeat) - score.sliderTickResult(trackPosition, GameScore.HIT_MISS, 0, 0, hitObject.hitSound); + score.sliderTickResult(trackPosition, GameScore.HIT_MISS, 0, 0, hitSound); if (isNewTick) 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] */ public float getT(int trackPosition, boolean raw) { - float t = (trackPosition - hitObject.time) / sliderTime; + float t = (trackPosition - hitObject.getTime()) / sliderTime; if (raw) return t; else { diff --git a/src/itdelatrisu/opsu/objects/Spinner.java b/src/itdelatrisu/opsu/objects/Spinner.java index 83eacd33..20a6c170 100644 --- a/src/itdelatrisu/opsu/objects/Spinner.java +++ b/src/itdelatrisu/opsu/objects/Spinner.java @@ -78,7 +78,8 @@ public class Spinner { Image spinnerCircle = GameImage.SPINNER_CIRCLE.getImage(); 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)); } @@ -94,7 +95,7 @@ public class Spinner { // calculate rotations needed 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 */ public void draw(int trackPosition, Graphics g) { - int timeDiff = hitObject.time - trackPosition; + int timeDiff = hitObject.getTime() - trackPosition; boolean spinnerComplete = (rotations >= rotationsNeeded); // TODO: draw "OSU!" image after spinner ends @@ -126,8 +127,9 @@ public class Spinner { spinnerMetreSub.draw(0, height - spinnerMetreSub.getHeight()); // main spinner elements + float approachScale = 1 - ((float) timeDiff / (hitObject.getTime() - hitObject.getEndTime())); 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); if (spinnerComplete) { @@ -158,7 +160,7 @@ public class Spinner { else 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); return result; } @@ -177,7 +179,7 @@ public class Spinner { return true; // end of spinner - if (trackPosition > hitObject.endTime) { + if (trackPosition > hitObject.getEndTime()) { hitResult(); return true; } diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 276bc1e5..3314cee7 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -316,8 +316,8 @@ public class Game extends BasicGameState { // skip beginning if (objectIndex == 0 && - osu.objects[0].time - SKIP_OFFSET > 5000 && - trackPosition < osu.objects[0].time - SKIP_OFFSET) + osu.objects[0].getTime() - SKIP_OFFSET > 5000 && + trackPosition < osu.objects[0].getTime() - SKIP_OFFSET) skipButton.draw(); if (isLeadIn()) @@ -325,7 +325,7 @@ public class Game extends BasicGameState { // countdown 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 >= 1500) { 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 Stack stack = new Stack(); - 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); while (!stack.isEmpty()) { int i = stack.pop(); OsuHitObject hitObject = osu.objects[i]; - if ((hitObject.type & OsuHitObject.TYPE_CIRCLE) > 0) + if (hitObject.isCircle()) circles.get(i).draw(trackPosition); - else if ((hitObject.type & OsuHitObject.TYPE_SLIDER) > 0) + else if (hitObject.isSlider()) 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 spinners.get(i).draw(trackPosition, g); else @@ -487,7 +487,7 @@ public class Game extends BasicGameState { // song beginning if (objectIndex == 0) { - if (trackPosition < osu.objects[0].time) + if (trackPosition < osu.objects[0].getTime()) return; // nothing to do here } @@ -544,20 +544,20 @@ public class Game extends BasicGameState { } // 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]; // check if we've already passed the next object's start time 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 boolean done = false; - if ((hitObject.type & OsuHitObject.TYPE_CIRCLE) > 0) + if (hitObject.isCircle()) 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()); - else if ((hitObject.type & OsuHitObject.TYPE_SPINNER) > 0) + else if (hitObject.isSpinner()) done = spinners.get(objectIndex).update(overlap, delta, input.getMouseX(), input.getMouseY()); // increment object index? @@ -586,7 +586,7 @@ public class Game extends BasicGameState { // pause game int trackPosition = MusicController.getPosition(); if (pauseTime < 0 && breakTime <= 0 && - trackPosition >= osu.objects[0].time && + trackPosition >= osu.objects[0].getTime() && !GameMod.AUTO.isActive()) { pausedMouseX = input.getMouseX(); pausedMouseY = input.getMouseY(); @@ -642,7 +642,7 @@ public class Game extends BasicGameState { // skip to checkpoint MusicController.setPosition(checkpoint); while (objectIndex < osu.objects.length && - osu.objects[objectIndex++].time <= MusicController.getPosition()) + osu.objects[objectIndex++].getTime() <= MusicController.getPosition()) ; objectIndex--; } catch (SlickException e) { @@ -692,14 +692,14 @@ public class Game extends BasicGameState { return; // circles - if ((hitObject.type & OsuHitObject.TYPE_CIRCLE) > 0) { + if (hitObject.isCircle()) { boolean hit = circles.get(objectIndex).mousePressed(x, y); if (hit) objectIndex++; } // sliders - else if ((hitObject.type & OsuHitObject.TYPE_SLIDER) > 0) + else if (hitObject.isSlider()) sliders.get(objectIndex).mousePressed(x, y); } @@ -733,15 +733,15 @@ public class Game extends BasicGameState { // is this the last note in the combo? boolean comboEnd = false; - if (i + 1 < osu.objects.length && - (osu.objects[i + 1].type & OsuHitObject.TYPE_NEWCOMBO) > 0) + if (i + 1 < osu.objects.length && osu.objects[i + 1].isNewCombo()) comboEnd = true; - if ((hitObject.type & OsuHitObject.TYPE_CIRCLE) > 0) { - circles.put(i, new Circle(hitObject, this, score, osu.combo[hitObject.comboIndex], comboEnd)); - } else if ((hitObject.type & OsuHitObject.TYPE_SLIDER) > 0) { - sliders.put(i, new Slider(hitObject, this, score, osu.combo[hitObject.comboIndex], comboEnd)); - } else if ((hitObject.type & OsuHitObject.TYPE_SPINNER) > 0) { + Color color = osu.combo[hitObject.getComboIndex()]; + if (hitObject.isCircle()) { + circles.put(i, new Circle(hitObject, this, score, color, comboEnd)); + } else if (hitObject.isSlider()) { + sliders.put(i, new Slider(hitObject, this, score, color, comboEnd)); + } else if (hitObject.isSpinner()) { spinners.put(i, new Spinner(hitObject, this, score)); } } @@ -794,15 +794,16 @@ public class Game extends BasicGameState { * @return true if skipped, false otherwise */ private boolean skipIntro() { + int firstObjectTime = osu.objects[0].getTime(); int trackPosition = MusicController.getPosition(); if (objectIndex == 0 && - osu.objects[0].time - SKIP_OFFSET > 4000 && - trackPosition < osu.objects[0].time - SKIP_OFFSET) { + firstObjectTime - SKIP_OFFSET > 4000 && + trackPosition < firstObjectTime - SKIP_OFFSET) { if (isLeadIn()) { leadInTime = 0; MusicController.resume(); } - MusicController.setPosition(osu.objects[0].time - SKIP_OFFSET); + MusicController.setPosition(firstObjectTime - SKIP_OFFSET); SoundController.playSound(SoundController.SOUND_MENUHIT); return true; } @@ -883,16 +884,6 @@ public class Game extends BasicGameState { float overallDifficulty = osu.overallDifficulty; 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 if (GameMod.HARD_ROCK.isActive()) { circleSize = Math.min(circleSize * 1.4f, 10); @@ -909,6 +900,16 @@ public class Game extends BasicGameState { 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 Circle.init(container, circleSize); Slider.init(container, circleSize, osu);