From e161d0180e21f0d46e8960d351b2deebd0286f7a Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Mon, 19 Dec 2016 16:33:20 -0500 Subject: [PATCH] Follow-up to #211: fix slider hit results not appearing; code cleanup. Signed-off-by: Jeffrey Han --- src/itdelatrisu/opsu/GameData.java | 103 ++++++++++++---------- src/itdelatrisu/opsu/Utils.java | 9 ++ src/itdelatrisu/opsu/objects/Circle.java | 8 +- src/itdelatrisu/opsu/objects/Slider.java | 66 +++++++------- src/itdelatrisu/opsu/objects/Spinner.java | 2 +- 5 files changed, 106 insertions(+), 82 deletions(-) diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index e3c1f3f7..ec94eefa 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -140,23 +140,23 @@ public class GameData { /** Hit result types. */ public static final int - HIT_MISS = 0, - HIT_50 = 1, - HIT_100 = 2, - HIT_300 = 3, - HIT_100K = 4, // 100-Katu - HIT_300K = 5, // 300-Katu - HIT_300G = 6, // Geki - HIT_SLIDER10 = 7, - HIT_SLIDER30 = 8, - HIT_MAX = 9, // not a hit result - HIT_SLIDER_REPEAT = 10, // not a hit result + HIT_MISS = 0, + HIT_50 = 1, + HIT_100 = 2, + HIT_300 = 3, + HIT_100K = 4, // 100-Katu + HIT_300K = 5, // 300-Katu + HIT_300G = 6, // Geki + HIT_SLIDER10 = 7, + HIT_SLIDER30 = 8, + HIT_MAX = 9, // not a hit result + HIT_SLIDER_REPEAT = 10, // not a hit result HIT_ANIMATION_RESULT = 11; // not a hit result - /** Hit result-related images (indexed by HIT_* constants). */ + /** Hit result-related images (indexed by HIT_* constants to HIT_MAX). */ private Image[] hitResults; - /** Counts of each hit result so far. */ + /** Counts of each hit result so far (indexed by HIT_* constants to HIT_MAX). */ private int[] hitResultCount; /** Total objects including slider hits/ticks (for determining Full Combo status). */ @@ -192,7 +192,7 @@ public class GameData { /** Current x coordinate of the combo burst image (for sliding animation). */ private float comboBurstX; - /** Time offsets for obtaining each hit result (indexed by HIT_* constants). */ + /** Time offsets for obtaining each hit result (indexed by HIT_* constants to HIT_MAX). */ private int[] hitResultOffset; /** List of hit result objects associated with hit objects. */ @@ -232,7 +232,7 @@ public class GameData { private LinkedBlockingDeque hitErrorList; /** Hit object types, used for drawing results. */ - public enum HitObjectType { CIRCLE, SLIDERTICK, SLIDER_FIRST, SLIDER_CURVE, SLIDER_LAST, SPINNER } + public enum HitObjectType { CIRCLE, SLIDERTICK, SLIDER_FIRST, SLIDER_LAST, SPINNER } /** Hit result helper class. */ private class HitObjectResult { @@ -892,16 +892,17 @@ public class GameData { lighting.drawCentered(hitResult.x, hitResult.y, hitResult.color); } - // hit animations, only draw when the 'hidden' mod is not enabled + // hit animations (only draw when the "Hidden" mod is not enabled) if (!GameMod.HIDDEN.isActive()) { drawHitAnimations(hitResult, trackPosition); } // hit result if (!hitResult.hideResult && ( - hitResult.hitResultType == HitObjectType.CIRCLE || - hitResult.hitResultType == HitObjectType.SPINNER || - hitResult.curve != null)) { + hitResult.hitResultType == HitObjectType.CIRCLE || + hitResult.hitResultType == HitObjectType.SLIDER_FIRST || + hitResult.hitResultType == HitObjectType.SLIDER_LAST || + hitResult.hitResultType == HitObjectType.SPINNER)) { float scaleProgress = AnimationEquation.IN_OUT_BOUNCE.calc( (float) Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_TEXT_BOUNCE_TIME) / HITCIRCLE_TEXT_BOUNCE_TIME); float scale = 1f + (HITCIRCLE_TEXT_ANIM_SCALE - 1f) * scaleProgress; @@ -923,43 +924,42 @@ public class GameData { } /** - * Draw the hit animations: circles, reversearrows, slider curves fading out and/or expanding - * @param hitResult the hitresult which holds information about the kind of animation to draw + * Draw the hit animations: + * circles, reverse arrows, slider curves (fading out and/or expanding). + * @param hitResult the hit result * @param trackPosition the current track position (in ms) */ private void drawHitAnimations(HitObjectResult hitResult, int trackPosition) { - if (hitResult.hitResultType == HitObjectType.SLIDER_CURVE && hitResult.curve != null) { + // fade out slider curve + if (hitResult.result != HIT_SLIDER_REPEAT && hitResult.curve != null) { float progress = AnimationEquation.OUT_CUBIC.calc( (float) Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_FADE_TIME) / HITCIRCLE_FADE_TIME); float alpha = 1f - progress; - - // slider curve float oldWhiteAlpha = Colors.WHITE_FADE.a; float oldColorAlpha = hitResult.color.a; - Colors.WHITE_FADE.a = alpha; - hitResult.color.a = alpha; + Colors.WHITE_FADE.a = hitResult.color.a = alpha; hitResult.curve.draw(hitResult.color); Colors.WHITE_FADE.a = oldWhiteAlpha; hitResult.color.a = oldColorAlpha; - return; } + // miss, don't draw an animation if (hitResult.result == HIT_MISS) { return; } - if (hitResult.hitResultType != HitObjectType.CIRCLE - && hitResult.hitResultType != HitObjectType.SLIDER_FIRST - && hitResult.hitResultType != HitObjectType.SLIDER_LAST) { + // not a circle? + if (hitResult.hitResultType != HitObjectType.CIRCLE && + hitResult.hitResultType != HitObjectType.SLIDER_FIRST && + hitResult.hitResultType != HitObjectType.SLIDER_LAST) { return; } - // circles + // hit circles float progress = AnimationEquation.OUT_CUBIC.calc( (float) Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_FADE_TIME) / HITCIRCLE_FADE_TIME); float scale = (!hitResult.expand) ? 1f : 1f + (HITCIRCLE_ANIM_SCALE - 1f) * progress; float alpha = 1f - progress; - if (hitResult.result == HIT_SLIDER_REPEAT) { // repeats Image scaledRepeat = GameImage.REVERSEARROW.getImage().getScaledCopy(scale); @@ -973,7 +973,6 @@ public class GameData { scaledRepeat.rotate(ang); scaledRepeat.drawCentered(hitResult.x, hitResult.y, hitResult.color); } - // hit circles Image scaledHitCircle = GameImage.HITCIRCLE.getImage().getScaledCopy(scale); Image scaledHitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(scale); scaledHitCircle.setAlpha(alpha); @@ -1210,15 +1209,28 @@ public class GameData { health = 0f; } - public void sendRepeatSliderResult(int time, float x, float y, Color color, Curve curve, HitObjectType type) { + /** + * Handles a slider repeat result (animation only: arrow). + * @param time the repeat time + * @param x the x coordinate + * @param y the y coordinate + * @param color the arrow color + * @param curve the slider curve + * @param type the hit object type + */ + public void sendSliderRepeatResult(int time, float x, float y, Color color, Curve curve, HitObjectType type) { hitResultList.add(new HitObjectResult(time, HIT_SLIDER_REPEAT, x, y, color, type, curve, true, true)); } - public void sendSliderCurveResult(int time, Color color, Curve curve) { - hitResultList.add(new HitObjectResult(time, HIT_SLIDER_REPEAT, 0f, 0f, color, HitObjectType.SLIDER_CURVE, curve, true, true)); - } - - public void sendAnimationResult(int time, float x, float y, Color color, boolean expand) { + /** + * Handles a slider start result (animation only: initial circle). + * @param time the hit time + * @param x the x coordinate + * @param y the y coordinate + * @param color the slider color + * @param expand whether or not the hit result animation should expand + */ + public void sendSliderStartResult(int time, float x, float y, Color color, boolean expand) { hitResultList.add(new HitObjectResult(time, HIT_ANIMATION_RESULT, x, y, color, HitObjectType.CIRCLE, null, expand, true)); } @@ -1231,7 +1243,7 @@ public class GameData { * @param hitObject the hit object * @param repeat the current repeat number */ - public void sliderTickResult(int time, int result, float x, float y, HitObject hitObject, int repeat) { + public void sendSliderTickResult(int time, int result, float x, float y, HitObject hitObject, int repeat) { int hitValue = 0; switch (result) { case HIT_SLIDER30: @@ -1412,11 +1424,12 @@ public class GameData { * @param curve the slider curve (or null if not applicable) * @param sliderHeldToEnd whether or not the slider was held to the end (if applicable) */ - public void hitResult(int time, int result, float x, float y, Color color, - boolean end, HitObject hitObject, HitObjectType hitResultType, - boolean expand, int repeat, Curve curve, boolean sliderHeldToEnd) { - int hitResult = handleHitResult(time, result, x, y, color, end, hitObject, - hitResultType, repeat, (curve != null && !sliderHeldToEnd)); + public void sendHitResult( + int time, int result, float x, float y, Color color, + boolean end, HitObject hitObject, HitObjectType hitResultType, + boolean expand, int repeat, Curve curve, boolean sliderHeldToEnd + ) { + int hitResult = handleHitResult(time, result, x, y, color, end, hitObject, hitResultType, repeat, (curve != null && !sliderHeldToEnd)); if (hitResult == HIT_MISS && (GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive())) return; // "relax" and "autopilot" mods: hide misses diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index b77bd903..2c833b92 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -63,6 +63,7 @@ import org.lwjgl.BufferUtils; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.GL11; import org.newdawn.slick.Animation; +import org.newdawn.slick.Color; import org.newdawn.slick.GameContainer; import org.newdawn.slick.Input; import org.newdawn.slick.state.StateBasedGame; @@ -157,6 +158,14 @@ public class Utils { anim.draw(x - (anim.getWidth() / 2f), y - (anim.getHeight() / 2f)); } + /** + * Returns the luminance of a color. + * @param c the color + */ + public static float getLuminance(Color c) { + return 0.299f*c.r + 0.587f*c.g + 0.114f*c.b; + } + /** * Clamps a value between a lower and upper bound. * @param val the value to clamp diff --git a/src/itdelatrisu/opsu/objects/Circle.java b/src/itdelatrisu/opsu/objects/Circle.java index 15e94524..ee2e7432 100644 --- a/src/itdelatrisu/opsu/objects/Circle.java +++ b/src/itdelatrisu/opsu/objects/Circle.java @@ -156,7 +156,7 @@ public class Circle implements GameObject { if (result > -1) { data.addHitError(hitObject.getTime(), x, y, timeDiff); - data.hitResult(trackPosition, result, this.x, this.y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false); + data.sendHitResult(trackPosition, result, this.x, this.y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false); return true; } } @@ -172,17 +172,17 @@ public class Circle implements GameObject { if (trackPosition > time + hitResultOffset[GameData.HIT_50]) { if (isAutoMod) // "auto" mod: catch any missed notes due to lag - data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false); + data.sendHitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false); else // no more points can be scored, so send a miss - data.hitResult(trackPosition, GameData.HIT_MISS, x, y, null, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false); + data.sendHitResult(trackPosition, GameData.HIT_MISS, x, y, null, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false); return true; } // "auto" mod: send a perfect hit result else if (isAutoMod) { if (Math.abs(trackPosition - time) < hitResultOffset[GameData.HIT_300]) { - data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false); + data.sendHitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false); return true; } } diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index 2258204c..e70fbca7 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -180,7 +180,7 @@ public class Slider implements GameObject { @Override public void draw(Graphics g, int trackPosition) { int timeDiff = hitObject.getTime() - trackPosition; - final int repeats = hitObject.getRepeatCount(); + final int repeatCount = hitObject.getRepeatCount(); final int approachTime = game.getApproachTime(); final int fadeInTime = game.getFadeInTime(); float scale = timeDiff / (float) approachTime; @@ -198,7 +198,7 @@ public class Slider implements GameObject { float oldWhiteFadeAlpha = Colors.WHITE_FADE.a; float sliderAlpha = 1f; if (GameMod.HIDDEN.isActive() && trackPosition > hitObject.getTime()) { - // "hidden" mod: fade out sliders + // "Hidden" mod: fade out sliders Colors.WHITE_FADE.a = color.a = sliderAlpha = Math.max(0f, 1f - ((float) (trackPosition - hitObject.getTime()) / (getEndTime() - hitObject.getTime())) * 1.05f); } @@ -206,8 +206,8 @@ public class Slider implements GameObject { float curveInterval = Options.isSliderSnaking() ? alpha : 1f; curve.draw(color,curveInterval); - // end circle, only draw if ball still has to go there - if (curveInterval == 1f && currentRepeats < repeats - (repeats % 2 == 0 ? 1 : 0)) { + // end circle (only draw if ball still has to go there) + if (curveInterval == 1f && currentRepeats < repeatCount - (repeatCount % 2 == 0 ? 1 : 0)) { Color circleColor = new Color(color); Color overlayColor = new Color(Colors.WHITE_FADE); if (currentRepeats == 0) { @@ -233,7 +233,7 @@ public class Slider implements GameObject { } // start circle, only draw if ball still has to go there - if (!sliderClickedInitial || currentRepeats < repeats - (repeats % 2 == 1 ? 1 : 0)) { + if (!sliderClickedInitial || currentRepeats < repeatCount - (repeatCount % 2 == 1 ? 1 : 0)) { hitCircle.drawCentered(x, y, firstCircleColor); if (!overlayAboveNumber || sliderClickedInitial) hitCircleOverlay.drawCentered(x, y, startCircleOverlayColor); @@ -256,7 +256,7 @@ public class Slider implements GameObject { } } - // draw combonumber and overlay if not initially clicked + // draw combo number and overlay if not initially clicked if (!sliderClickedInitial) { data.drawSymbolNumber(hitObject.getComboNumber(), x, y, hitCircle.getWidth() * 0.40f / data.getDefaultSymbolImage(0).getHeight(), alpha); @@ -269,11 +269,11 @@ public class Slider implements GameObject { // repeats if (curveInterval == 1.0f) { - for (int tcurRepeat = currentRepeats; tcurRepeat <= currentRepeats + 1 && tcurRepeat < repeats - 1; tcurRepeat++) { + for (int tcurRepeat = currentRepeats; tcurRepeat <= currentRepeats + 1 && tcurRepeat < repeatCount - 1; tcurRepeat++) { Image arrow = GameImage.REVERSEARROW.getImage(); // bouncing animation //arrow = arrow.getScaledCopy((float) (1 + 0.2d * ((trackPosition + sliderTime * tcurRepeat) % 292) / 292)); - float colorLuminance = 0.299f*color.r + 0.587f*color.g + 0.114f*color.b; + float colorLuminance = Utils.getLuminance(color); Color arrowColor = colorLuminance < 0.8f ? Color.white : Color.black; if (tcurRepeat == 0) { arrow.setAlpha(Options.isSliderSnaking() ? decorationsAlpha : 1f); @@ -469,19 +469,21 @@ public class Slider implements GameObject { float cx, cy; HitObjectType type; - if (currentRepeats % 2 == 0) { // last circle + if (currentRepeats % 2 == 0) { + // last circle Vec2f lastPos = curve.pointAt(1); cx = lastPos.x; cy = lastPos.y; type = HitObjectType.SLIDER_LAST; - } else { // first circle + } else { + // first circle cx = x; cy = y; type = HitObjectType.SLIDER_FIRST; } - data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result, + data.sendHitResult(hitObject.getTime() + (int) sliderTimeTotal, result, cx, cy, color, comboEnd, hitObject, type, sliderHeldToEnd, - currentRepeats + 1, null, sliderHeldToEnd); + currentRepeats + 1, curve, sliderHeldToEnd); return result; } @@ -500,17 +502,17 @@ public class Slider implements GameObject { if (timeDiff < hitResultOffset[GameData.HIT_50]) { result = GameData.HIT_SLIDER30; ticksHit++; - data.sendAnimationResult(trackPosition, this.x, this.y, color, true); + data.sendSliderStartResult(trackPosition, this.x, this.y, color, true); } else if (timeDiff < hitResultOffset[GameData.HIT_MISS]) { result = GameData.HIT_MISS; - data.sendAnimationResult(trackPosition, this.x, this.y, color, false); + data.sendSliderStartResult(trackPosition, this.x, this.y, color, false); } //else not a hit if (result > -1) { data.addHitError(hitObject.getTime(), x,y,trackPosition - hitObject.getTime()); sliderClickedInitial = true; - data.sliderTickResult(hitObject.getTime(), result, this.x, this.y, hitObject, currentRepeats); + data.sendSliderTickResult(hitObject.getTime(), result, this.x, this.y, hitObject, currentRepeats); return true; } } @@ -531,11 +533,11 @@ public class Slider implements GameObject { sliderClickedInitial = true; if (isAutoMod) { // "auto" mod: catch any missed notes due to lag ticksHit++; - data.sliderTickResult(time, GameData.HIT_SLIDER30, x, y, hitObject, currentRepeats); - data.sendAnimationResult(time, x, y, color, true); + data.sendSliderTickResult(time, GameData.HIT_SLIDER30, x, y, hitObject, currentRepeats); + data.sendSliderStartResult(time, x, y, color, true); } else { - data.sliderTickResult(time, GameData.HIT_MISS, x, y, hitObject, currentRepeats); - data.sendAnimationResult(trackPosition, x, y, color, false); + data.sendSliderTickResult(time, GameData.HIT_MISS, x, y, hitObject, currentRepeats); + data.sendSliderStartResult(trackPosition, x, y, color, false); } } @@ -544,8 +546,8 @@ public class Slider implements GameObject { if (Math.abs(trackPosition - time) < hitResultOffset[GameData.HIT_300]) { ticksHit++; sliderClickedInitial = true; - data.sliderTickResult(time, GameData.HIT_SLIDER30, x, y, hitObject, currentRepeats); - data.sendAnimationResult(time, x, y, color, true); + data.sendSliderTickResult(time, GameData.HIT_SLIDER30, x, y, hitObject, currentRepeats); + data.sendSliderStartResult(time, x, y, color, true); } } @@ -578,8 +580,6 @@ public class Slider implements GameObject { // calculate and send slider result hitResult(); - // send 'curve fade out' hit result - data.sendSliderCurveResult(getEndTime(), color, curve); return true; } @@ -629,29 +629,31 @@ public class Slider implements GameObject { HitObjectType type; float posX, posY; - if (currentRepeats % 2 > 0) { // last circle + if (currentRepeats % 2 > 0) { + // last circle type = HitObjectType.SLIDER_LAST; Vec2f endPos = curve.pointAt(1f); posX = endPos.x; posY = endPos.y; - } else { // first circle + } else { + // first circle type = HitObjectType.SLIDER_FIRST; posX = this.x; posY = this.y; } - data.sliderTickResult(trackPosition, GameData.HIT_SLIDER30, + data.sendSliderTickResult(trackPosition, GameData.HIT_SLIDER30, posX, posY, hitObject, currentRepeats); - // send hit result, to fade out reversearrow - float colorLuminance = 0.299f*color.r + 0.587f*color.g + 0.114f*color.b; + // fade out reverse arrow + float colorLuminance = Utils.getLuminance(color); Color arrowColor = colorLuminance < 0.8f ? Color.white : Color.black; - data.sendRepeatSliderResult(trackPosition, posX, posY, arrowColor, curve, type); + data.sendSliderRepeatResult(trackPosition, posX, posY, arrowColor, curve, type); } // held during new tick if (isNewTick) { ticksHit++; - data.sliderTickResult(trackPosition, GameData.HIT_SLIDER10, + data.sendSliderTickResult(trackPosition, GameData.HIT_SLIDER10, c.x, c.y, hitObject, currentRepeats); } @@ -662,9 +664,9 @@ public class Slider implements GameObject { followCircleActive = false; if (isNewRepeat) - data.sliderTickResult(trackPosition, GameData.HIT_MISS, 0, 0, hitObject, currentRepeats); + data.sendSliderTickResult(trackPosition, GameData.HIT_MISS, 0, 0, hitObject, currentRepeats); if (isNewTick) - data.sliderTickResult(trackPosition, GameData.HIT_MISS, 0, 0, hitObject, currentRepeats); + data.sendSliderTickResult(trackPosition, GameData.HIT_MISS, 0, 0, hitObject, currentRepeats); } return false; diff --git a/src/itdelatrisu/opsu/objects/Spinner.java b/src/itdelatrisu/opsu/objects/Spinner.java index e82fa6a3..aee631b7 100644 --- a/src/itdelatrisu/opsu/objects/Spinner.java +++ b/src/itdelatrisu/opsu/objects/Spinner.java @@ -252,7 +252,7 @@ public class Spinner implements GameObject { else result = GameData.HIT_MISS; - data.hitResult(hitObject.getEndTime(), result, width / 2, height / 2, + data.sendHitResult(hitObject.getEndTime(), result, width / 2, height / 2, Color.transparent, true, hitObject, HitObjectType.SPINNER, true, 0, null, false); return result; }