From 4eaf0b6a5482e4a8e075627cb3d231e7b0cecfd5 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Wed, 8 Apr 2015 02:05:12 -0400 Subject: [PATCH] Follow-up to #71. - Fixed a major bug where two hit result calculations were being performed for each slider. - Fixed a bug where hit circles/sliders were being drawn for a miss. - Sliders now only expand when held to the end (as in osu!). - Use the track position as the hit result start time for circles (instead of the object time). - Added a 'color' parameter to Curve.draw(), rather than keeping an extra reference to the slider Color object. - Renamed HitResultType enum to HitObjectType, and moved it into GameData. - Removed some overloaded methods (not really necessary...). - Other style changes. Signed-off-by: Jeffrey Han --- src/itdelatrisu/opsu/GameData.java | 155 +++++++----------- src/itdelatrisu/opsu/Utils.java | 6 +- src/itdelatrisu/opsu/objects/Circle.java | 9 +- .../opsu/objects/HitResultType.java | 10 -- src/itdelatrisu/opsu/objects/Slider.java | 22 ++- src/itdelatrisu/opsu/objects/Spinner.java | 3 +- .../objects/curves/CircumscribedCircle.java | 2 +- .../opsu/objects/curves/Curve.java | 7 +- .../opsu/objects/curves/LinearBezier.java | 2 +- 9 files changed, 88 insertions(+), 128 deletions(-) delete mode 100644 src/itdelatrisu/opsu/objects/HitResultType.java diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index 210b7188..5fd49caf 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -23,7 +23,6 @@ import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundEffect; import itdelatrisu.opsu.downloads.Updater; -import itdelatrisu.opsu.objects.HitResultType; import itdelatrisu.opsu.objects.curves.Curve; import itdelatrisu.opsu.replay.Replay; import itdelatrisu.opsu.replay.ReplayFrame; @@ -225,9 +224,10 @@ public class GameData { /** List containing recent hit error information. */ private LinkedBlockingDeque hitErrorList; - /** - * Hit result helper class. - */ + /** Hit object types, used for drawing results. */ + public enum HitObjectType { CIRCLE, SLIDERTICK, SLIDER_FIRST, SLIDER_LAST, SPINNER } + + /** Hit result helper class. */ private class OsuHitObjectResult { /** Object start time. */ public int time; @@ -242,7 +242,7 @@ public class GameData { public Color color; /** The type of the hit object. */ - public HitResultType hitResultType; + public HitObjectType hitResultType; /** Alpha level (for fading out). */ public float alpha = 1f; @@ -250,23 +250,8 @@ public class GameData { /** Slider curve. */ public Curve curve; - /** - * Constructor. - * @param time the result's starting track position - * @param result the hit result (HIT_* constants) - * @param x the center x coordinate - * @param y the center y coordinate - * @param color the color of the hit object - * @param hitResultType the type of the hit object - */ - public OsuHitObjectResult(int time, int result, float x, float y, Color color, HitResultType hitResultType) { - this.time = time; - this.result = result; - this.x = x; - this.y = y; - this.color = color; - this.hitResultType = hitResultType; - } + /** Whether or not to expand when animating. */ + public boolean expand; /** * Constructor. @@ -275,9 +260,11 @@ public class GameData { * @param x the center x coordinate * @param y the center y coordinate * @param color the color of the hit object - * @param curve the slider curve + * @param curve the slider curve (or null if not applicable) + * @param expand whether or not the hit result animation should expand (if applicable) */ - public OsuHitObjectResult(int time, int result, float x, float y, Color color, HitResultType hitResultType, Curve curve) { + public OsuHitObjectResult(int time, int result, float x, float y, Color color, + HitObjectType hitResultType, Curve curve, boolean expand) { this.time = time; this.result = result; this.x = x; @@ -285,6 +272,7 @@ public class GameData { this.color = color; this.hitResultType = hitResultType; this.curve = curve; + this.expand = expand; } } @@ -866,7 +854,7 @@ public class GameData { OsuHitObjectResult hitResult = iter.next(); if (hitResult.time + HITRESULT_TIME > trackPosition) { // spinner - if (hitResult.hitResultType == HitResultType.SPINNER && hitResult.result != HIT_MISS) { + if (hitResult.hitResultType == HitObjectType.SPINNER && hitResult.result != HIT_MISS) { Image spinnerOsu = GameImage.SPINNER_OSU.getImage(); spinnerOsu.setAlpha(hitResult.alpha); spinnerOsu.drawCentered(width / 2, height / 4); @@ -876,68 +864,58 @@ public class GameData { // hit lighting else if (Options.isHitLightingEnabled() && hitResult.result != HIT_MISS && hitResult.result != HIT_SLIDER30 && hitResult.result != HIT_SLIDER10) { - // soon add particle system to reflect original game - Image lighting = GameImage.LIGHTING.getImage(); + // TODO: add particle system + Image lighting = GameImage.LIGHTING.getImage(); lighting.setAlpha(hitResult.alpha); lighting.drawCentered(hitResult.x, hitResult.y, hitResult.color); } // hit animation - if (hitResult.hitResultType == HitResultType.CIRCLE - || hitResult.hitResultType == HitResultType.SLIDEREND - || hitResult.hitResultType == HitResultType.SLIDEREND_FIRSTOBJECT) { - float scale = Utils.easeOut( + if (hitResult.result != HIT_MISS && ( + hitResult.hitResultType == HitObjectType.CIRCLE || + hitResult.hitResultType == HitObjectType.SLIDER_FIRST || + hitResult.hitResultType == HitObjectType.SLIDER_LAST)) { + float scale = (!hitResult.expand) ? 1f : Utils.easeOut( Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_FADE_TIME), - 1f, - HITCIRCLE_ANIM_SCALE-1f, - HITCIRCLE_FADE_TIME + 1f, HITCIRCLE_ANIM_SCALE - 1f, HITCIRCLE_FADE_TIME ); - float alpha = Utils.easeOut( Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_FADE_TIME), - 1f, - -1f, - HITCIRCLE_FADE_TIME + 1f, -1f, HITCIRCLE_FADE_TIME ); + // slider curve if (hitResult.curve != null) { - float oldAlpha = Utils.COLOR_WHITE_FADE.a; - - Curve curve = hitResult.curve; + float oldWhiteAlpha = Utils.COLOR_WHITE_FADE.a; + float oldColorAlpha = hitResult.color.a; Utils.COLOR_WHITE_FADE.a = alpha; - curve.color.a = alpha; - curve.draw(); - - Utils.COLOR_WHITE_FADE.a = oldAlpha; + hitResult.color.a = alpha; + hitResult.curve.draw(hitResult.color); + Utils.COLOR_WHITE_FADE.a = oldWhiteAlpha; + hitResult.color.a = oldColorAlpha; } + // hit circles Image scaledHitCircle = GameImage.HITCIRCLE.getImage().getScaledCopy(scale); - scaledHitCircle.setAlpha(alpha); Image scaledHitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(scale); + scaledHitCircle.setAlpha(alpha); scaledHitCircleOverlay.setAlpha(alpha); - scaledHitCircle.drawCentered(hitResult.x, hitResult.y, hitResult.color); scaledHitCircleOverlay.drawCentered(hitResult.x, hitResult.y); } // hit result - if (hitResult.hitResultType == HitResultType.CIRCLE - || hitResult.hitResultType == HitResultType.SLIDEREND - || hitResult.hitResultType == HitResultType.SPINNER) { + if (hitResult.hitResultType == HitObjectType.CIRCLE || + hitResult.hitResultType == HitObjectType.SPINNER || + hitResult.curve != null) { float scale = Utils.easeBounce( Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_TEXT_BOUNCE_TIME), - 1f, - HITCIRCLE_TEXT_ANIM_SCALE - 1f, - HITCIRCLE_TEXT_BOUNCE_TIME + 1f, HITCIRCLE_TEXT_ANIM_SCALE - 1f, HITCIRCLE_TEXT_BOUNCE_TIME ); - float alpha = Utils.easeOut( Utils.clamp((trackPosition - hitResult.time) - HITCIRCLE_FADE_TIME, 0, HITCIRCLE_TEXT_FADE_TIME), - 1f, - -1f, - HITCIRCLE_TEXT_FADE_TIME + 1f, -1f, HITCIRCLE_TEXT_FADE_TIME ); - Image scaledHitResult = hitResults[hitResult.result].getScaledCopy(scale); scaledHitResult.setAlpha(alpha); scaledHitResult.drawCentered(hitResult.x, hitResult.y); @@ -1207,14 +1185,14 @@ public class GameData { if (!Options.isPerfectHitBurstEnabled()) ; // hide perfect hit results else - hitResultList.add(new OsuHitObjectResult(time, result, x, y, null, HitResultType.SLIDERTICK)); + hitResultList.add(new OsuHitObjectResult(time, result, x, y, null, HitObjectType.SLIDERTICK, null, false)); } } /** - * Handles a hit result. + * Handles a hit result and performs all associated calculations. * @param time the object start time - * @param result the hit result (HIT_* constants) + * @param result the base hit result (HIT_* constants) * @param x the x coordinate * @param y the y coordinate * @param color the combo color @@ -1222,10 +1200,10 @@ public class GameData { * @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 - * @return the hit result (HIT_* constants) + * @return the actual hit result (HIT_* constants) */ - private int hitRes(int time, int result, float x, float y, Color color, - boolean end, OsuHitObject hitObject, int repeat, HitResultType hitResultType) { + private int handleHitResult(int time, int result, float x, float y, Color color, + boolean end, OsuHitObject hitObject, int repeat, HitObjectType hitResultType) { int hitValue = 0; switch (result) { case HIT_300: @@ -1292,31 +1270,6 @@ public class GameData { return result; } - /** - * Handles a hit result. - * @param time the object start time - * @param result the hit result (HIT_* constants) - * @param x the x coordinate - * @param y the y coordinate - * @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 - */ - public void hitResult(int time, int result, float x, float y, Color color, - boolean end, OsuHitObject hitObject, int repeat, HitResultType hitResultType) { - result = hitRes(time, result, x, y, color, end, hitObject, repeat, hitResultType); - - if ((result == HIT_300 || result == HIT_300G || result == HIT_300K) - && !Options.isPerfectHitBurstEnabled()) - ; // hide perfect hit results - else if (result == HIT_MISS && (GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive())) - ; // "relax" and "autopilot" mods: hide misses - else - hitResultList.add(new OsuHitObjectResult(time, result, x, y, color, hitResultType)); - } - /** * Handles a slider hit result. * @param time the object start time @@ -1328,19 +1281,29 @@ public class GameData { * @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 + * @param curve the slider curve (or null if not applicable) + * @param expand whether or not the hit result animation should expand (if applicable) */ public void hitResult(int time, int result, float x, float y, Color color, - boolean end, OsuHitObject hitObject, int repeat, HitResultType hitResultType, Curve curve) { - result = hitRes(time, result, x, y, color, end, hitObject, repeat, hitResultType); + boolean end, OsuHitObject hitObject, int repeat, + HitObjectType hitResultType, Curve curve, boolean expand) { + result = handleHitResult(time, result, x, y, color, end, hitObject, repeat, hitResultType); - if ((result == HIT_300 || result == HIT_300G || result == HIT_300K) - && !Options.isPerfectHitBurstEnabled()) + if ((result == HIT_300 || result == HIT_300G || result == HIT_300K) && !Options.isPerfectHitBurstEnabled()) ; // hide perfect hit results else if (result == HIT_MISS && (GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive())) ; // "relax" and "autopilot" mods: hide misses - else - hitResultList.add(new OsuHitObjectResult(time, result, x, y, color, hitResultType, curve)); + else { + hitResultList.add(new OsuHitObjectResult(time, result, x, y, color, hitResultType, curve, expand)); + + // sliders: add the other curve endpoint for the hit animation + if (curve != null) { + boolean isFirst = (hitResultType == HitObjectType.SLIDER_FIRST); + float[] p = curve.pointAt((isFirst) ? 1f : 0f); + HitObjectType type = (isFirst) ? HitObjectType.SLIDER_LAST : HitObjectType.SLIDER_FIRST; + hitResultList.add(new OsuHitObjectResult(time, result, p[0], p[1], color, type, null, expand)); + } + } } /** diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index db200ee4..62397428 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -609,7 +609,7 @@ public class Utils { } /** - * Cubic ease out function + * Cubic ease out function. * @param t the current time * @param a the starting position * @param b the finishing position @@ -621,7 +621,7 @@ public class Utils { } /** - * Fake bounce ease function + * Fake bounce ease function. * @param t the current time * @param a the starting position * @param b the finishing position @@ -631,6 +631,6 @@ public class Utils { public static float easeBounce(float t, float a, float b, float d) { if (t < d / 2) return easeOut(t, a, b, d); - return easeOut(d-t, a, b, d); + return easeOut(d - t, a, b, d); } } diff --git a/src/itdelatrisu/opsu/objects/Circle.java b/src/itdelatrisu/opsu/objects/Circle.java index c8ee6c1b..5762c00a 100644 --- a/src/itdelatrisu/opsu/objects/Circle.java +++ b/src/itdelatrisu/opsu/objects/Circle.java @@ -19,6 +19,7 @@ package itdelatrisu.opsu.objects; import itdelatrisu.opsu.GameData; +import itdelatrisu.opsu.GameData.HitObjectType; import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.OsuHitObject; @@ -138,7 +139,7 @@ public class Circle implements HitObject { if (result > -1) { data.addHitError(hitObject.getTime(), x, y, timeDiff); - data.hitResult(hitObject.getTime(), result, this.x, this.y, color, comboEnd, hitObject, 0, HitResultType.CIRCLE); + data.hitResult(trackPosition, result, this.x, this.y, color, comboEnd, hitObject, 0, HitObjectType.CIRCLE, null, true); return true; } } @@ -154,17 +155,17 @@ public class Circle implements HitObject { if (overlap || 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, HitResultType.CIRCLE); + data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, 0, HitObjectType.CIRCLE, null, true); else // no more points can be scored, so send a miss - data.hitResult(time, GameData.HIT_MISS, x, y, null, comboEnd, hitObject, 0, HitResultType.CIRCLE); + data.hitResult(trackPosition, GameData.HIT_MISS, x, y, null, comboEnd, hitObject, 0, HitObjectType.CIRCLE, null, true); 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, HitResultType.CIRCLE); + data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, 0, HitObjectType.CIRCLE, null, true); return true; } } diff --git a/src/itdelatrisu/opsu/objects/HitResultType.java b/src/itdelatrisu/opsu/objects/HitResultType.java deleted file mode 100644 index 0f7f4311..00000000 --- a/src/itdelatrisu/opsu/objects/HitResultType.java +++ /dev/null @@ -1,10 +0,0 @@ -package itdelatrisu.opsu.objects; - -public enum HitResultType { - CIRCLE, - //SLIDERSTART, - SLIDERTICK, - SLIDEREND, - SLIDEREND_FIRSTOBJECT, - SPINNER -} diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index 2dbd40b3..9c53bbe3 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -19,6 +19,7 @@ package itdelatrisu.opsu.objects; import itdelatrisu.opsu.GameData; +import itdelatrisu.opsu.GameData.HitObjectType; import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.OsuFile; @@ -172,7 +173,7 @@ public class Slider implements HitObject { Utils.COLOR_WHITE_FADE.a = color.a = alpha; // curve - curve.draw(); + curve.draw(color); // ticks if (ticksT != null) { @@ -278,13 +279,20 @@ public class Slider implements HitObject { else result = GameData.HIT_MISS; - float[] lastPos = curve.pointAt(1); + float cx, cy; + HitObjectType type; + if (currentRepeats % 2 == 0) { // last circle + float[] lastPos = curve.pointAt(1); + cx = lastPos[0]; + cy = lastPos[1]; + type = HitObjectType.SLIDER_LAST; + } else { // first circle + cx = x; + cy = y; + type = HitObjectType.SLIDER_FIRST; + } data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result, - x, y, color, comboEnd, hitObject, currentRepeats + 1, - currentRepeats % 2 == 0 ? HitResultType.SLIDEREND_FIRSTOBJECT : HitResultType.SLIDEREND, curve); - data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result, - lastPos[0], lastPos[1], color, comboEnd, hitObject, currentRepeats + 1, - currentRepeats % 2 == 0 ? HitResultType.SLIDEREND : HitResultType.SLIDEREND_FIRSTOBJECT); + cx, cy, color, comboEnd, hitObject, currentRepeats + 1, type, curve, sliderClickedFinal); return result; } diff --git a/src/itdelatrisu/opsu/objects/Spinner.java b/src/itdelatrisu/opsu/objects/Spinner.java index 1cc6e290..22cdad97 100644 --- a/src/itdelatrisu/opsu/objects/Spinner.java +++ b/src/itdelatrisu/opsu/objects/Spinner.java @@ -19,6 +19,7 @@ package itdelatrisu.opsu.objects; import itdelatrisu.opsu.GameData; +import itdelatrisu.opsu.GameData.HitObjectType; import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.OsuHitObject; @@ -191,7 +192,7 @@ public class Spinner implements HitObject { result = GameData.HIT_MISS; data.hitResult(hitObject.getEndTime(), result, width / 2, height / 2, - Color.transparent, true, hitObject, 0, HitResultType.SPINNER); + Color.transparent, true, hitObject, 0, HitObjectType.SPINNER, null, true); return result; } diff --git a/src/itdelatrisu/opsu/objects/curves/CircumscribedCircle.java b/src/itdelatrisu/opsu/objects/curves/CircumscribedCircle.java index 74ac354d..f534db95 100644 --- a/src/itdelatrisu/opsu/objects/curves/CircumscribedCircle.java +++ b/src/itdelatrisu/opsu/objects/curves/CircumscribedCircle.java @@ -179,7 +179,7 @@ public class CircumscribedCircle extends Curve { } @Override - public void draw() { + public void draw(Color color) { Image hitCircle = GameImage.HITCIRCLE.getImage(); Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage(); for (int i = 0; i < step; i++) diff --git a/src/itdelatrisu/opsu/objects/curves/Curve.java b/src/itdelatrisu/opsu/objects/curves/Curve.java index c09de69f..ab3d23aa 100644 --- a/src/itdelatrisu/opsu/objects/curves/Curve.java +++ b/src/itdelatrisu/opsu/objects/curves/Curve.java @@ -28,9 +28,6 @@ import org.newdawn.slick.Color; * @author fluddokt (https://github.com/fluddokt) */ public abstract class Curve { - /** The color of this curve. */ - public Color color; - /** The associated OsuHitObject. */ protected OsuHitObject hitObject; @@ -51,7 +48,6 @@ public abstract class Curve { this.y = hitObject.getScaledY(); this.sliderX = hitObject.getScaledSliderX(); this.sliderY = hitObject.getScaledSliderY(); - this.color = color; } /** @@ -63,8 +59,9 @@ public abstract class Curve { /** * Draws the full curve to the graphics context. + * @param color the color filter */ - public abstract void draw(); + public abstract void draw(Color color); /** * Returns the angle of the first control point. diff --git a/src/itdelatrisu/opsu/objects/curves/LinearBezier.java b/src/itdelatrisu/opsu/objects/curves/LinearBezier.java index 21e3b82c..ca7dede2 100644 --- a/src/itdelatrisu/opsu/objects/curves/LinearBezier.java +++ b/src/itdelatrisu/opsu/objects/curves/LinearBezier.java @@ -164,7 +164,7 @@ public class LinearBezier extends Curve { } @Override - public void draw() { + public void draw(Color color) { Image hitCircle = GameImage.HITCIRCLE.getImage(); Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage(); for (int i = curve.length - 2; i >= 0; i--)