@Override public float[] getPointAt(int trackPosition) { if (trackPosition <= hitObject.getTime()) return new float[] {x, y}; else if (trackPosition >= hitObject.getTime() + sliderTimeTotal) { if (hitObject.getRepeatCount() % 2 == 0) return new float[] {x, y}; else return curve.pointAt(1); } else return curve.pointAt(getT(trackPosition, false)); }
/** * Calculates the slider hit result. * * @return the hit result (GameData.HIT_* constants) */ private int hitResult() { /* time scoredelta score-hit-initial-tick= unaccounted (1/4 - 1) 396 - 300 - 30 46 (1+1/4 - 2) 442 - 300 - 30 - 10 (2+1/4 - 3) 488 - 300 - 30 - 2*10 896 (408)5x (3+1/4 - 4) 534 - 300 - 30 - 3*10 (4+1/4 - 5) 580 - 300 - 30 - 4*10 (5+1/4 - 6) 626 - 300 - 30 - 5*10 (6+1/4 - 7) 672 - 300 - 30 - 6*10 difficultyMulti = 3 (+36 per combo) score = (t)ticks(10) * nticks + (h)hitValue (c)combo (hitValue/25 * difficultyMultiplier*(combo-1)) (i)initialHit (30) + (f)finalHit(30) + s t h c i f 626 - 10*5 - 300 - 276(-216 - 30 - 30) (all)(7x) 240 - 10*5 - 100 - 90 (-60 <- 30>) (no final or initial)(6x) 218 - 10*4 - 100 - 78 (-36 - 30) (4 tick no initial)(5x) 196 - 10*3 - 100 - 66 (-24 - 30 ) (3 tick no initial)(4x) 112 - 10*2 - 50 - 42 (-12 - 30 ) (2 tick no initial)(3x) 96 - 10 - 50 - 36 ( -6 - 30 ) (1 tick no initial)(2x) 206 - 10*4 - 100 - 66 (-36 - 30 ) (4 tick no initial)(4x) 184 - 10*3 - 100 - 54 (-24 - 30 ) (3 tick no initial)(3x) 90 - 10 - 50 - 30 ( - 30 ) (1 tick no initial)(0x) 194 - 10*4 - 100 - 54 (-24 - 30 ) (4 tick no initial)(3x) 170 - 10*4 - 100 - 30 ( - 30 ) (4 tick no final)(0x) 160 - 10*3 - 100 - 30 ( - 30 ) (3 tick no final)(0x) 100 - 10*2 - 50 - 30 ( - 30 ) (2 tick no final)(0x) 198 - 10*5 - 100 - 48 (-36 ) (no initial and final)(5x) 110 - 50 - ( - 30 - 30 ) (final and initial no tick)(0x) 80 - 50 - ( <- 30> ) (only final or initial)(0x) 140 - 10*4 - 100 - 0 (4 ticks only)(0x) 80 - 10*3 - 50 - 0 (3 tick only)(0x) 70 - 10*2 - 50 - 0 (2 tick only)(0x) 60 - 10 - 50 - 0 (1 tick only)(0x) */ float tickRatio = (float) ticksHit / tickIntervals; int result; if (tickRatio >= 1.0f) result = GameData.HIT_300; else if (tickRatio >= 0.5f) result = GameData.HIT_100; else if (tickRatio > 0f) result = GameData.HIT_50; else result = GameData.HIT_MISS; 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, cx, cy, color, comboEnd, hitObject, type, sliderHeldToEnd, currentRepeats + 1, curve, sliderHeldToEnd); return result; }
@Override public boolean update( boolean overlap, int delta, int mouseX, int mouseY, boolean keyPressed, int trackPosition) { int repeatCount = hitObject.getRepeatCount(); int[] hitResultOffset = game.getHitResultOffsets(); boolean isAutoMod = GameMod.AUTO.isActive(); if (!sliderClickedInitial) { int time = hitObject.getTime(); // start circle time passed if (trackPosition > time + hitResultOffset[GameData.HIT_50]) { sliderClickedInitial = true; if (isAutoMod) { // "auto" mod: catch any missed notes due to lag ticksHit++; data.sliderTickResult(time, GameData.HIT_SLIDER30, x, y, hitObject, currentRepeats); } else data.sliderTickResult(time, GameData.HIT_MISS, x, y, hitObject, currentRepeats); } // "auto" mod: send a perfect hit result else if (isAutoMod) { if (Math.abs(trackPosition - time) < hitResultOffset[GameData.HIT_300]) { ticksHit++; sliderClickedInitial = true; data.sliderTickResult(time, GameData.HIT_SLIDER30, x, y, hitObject, currentRepeats); } } // "relax" mod: click automatically else if (GameMod.RELAX.isActive() && trackPosition >= time) mousePressed(mouseX, mouseY, trackPosition); } // end of slider if (trackPosition > hitObject.getTime() + sliderTimeTotal) { tickIntervals++; // check if cursor pressed and within end circle if (keyPressed || GameMod.RELAX.isActive()) { float[] c = curve.pointAt(getT(trackPosition, false)); double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY); if (distance < followRadius) sliderHeldToEnd = true; } // final circle hit if (sliderHeldToEnd) ticksHit++; // "auto" mod: always send a perfect hit result if (isAutoMod) ticksHit = tickIntervals; // calculate and send slider result hitResult(); return true; } // repeats boolean isNewRepeat = false; if (repeatCount - 1 > currentRepeats) { float t = getT(trackPosition, true); if (Math.floor(t) > currentRepeats) { currentRepeats++; tickIntervals++; isNewRepeat = true; } } // ticks boolean isNewTick = false; if (ticksT != null && tickIntervals < (ticksT.length * (currentRepeats + 1)) + repeatCount && tickIntervals < (ticksT.length * repeatCount) + repeatCount) { float t = getT(trackPosition, true); if (t - Math.floor(t) >= ticksT[tickIndex]) { tickIntervals++; tickIndex = (tickIndex + 1) % ticksT.length; isNewTick = true; } } // holding slider... float[] c = curve.pointAt(getT(trackPosition, false)); double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY); if (((keyPressed || GameMod.RELAX.isActive()) && distance < followRadius) || isAutoMod) { // mouse pressed and within follow circle followCircleActive = true; data.changeHealth(delta * GameData.HP_DRAIN_MULTIPLIER); // held during new repeat if (isNewRepeat) { ticksHit++; if (currentRepeats % 2 > 0) { // last circle int lastIndex = hitObject.getSliderX().length; data.sliderTickResult( trackPosition, GameData.HIT_SLIDER30, curve.getX(lastIndex), curve.getY(lastIndex), hitObject, currentRepeats); } else // first circle data.sliderTickResult( trackPosition, GameData.HIT_SLIDER30, c[0], c[1], hitObject, currentRepeats); } // held during new tick if (isNewTick) { ticksHit++; data.sliderTickResult( trackPosition, GameData.HIT_SLIDER10, c[0], c[1], hitObject, currentRepeats); } // held near end of slider if (!sliderHeldToEnd && trackPosition > hitObject.getTime() + sliderTimeTotal - hitResultOffset[GameData.HIT_300]) sliderHeldToEnd = true; } else { followCircleActive = false; if (isNewRepeat) data.sliderTickResult(trackPosition, GameData.HIT_MISS, 0, 0, hitObject, currentRepeats); if (isNewTick) data.sliderTickResult(trackPosition, GameData.HIT_MISS, 0, 0, hitObject, currentRepeats); } return false; }
@Override public void draw(Graphics g, int trackPosition) { int timeDiff = hitObject.getTime() - trackPosition; float scale = timeDiff / (float) game.getApproachTime(); float fadeinScale = (timeDiff - game.getApproachTime() + FADE_IN_TIME) / (float) FADE_IN_TIME; float approachScale = 1 + scale * 3; float alpha = Utils.clamp(1 - fadeinScale, 0, 1); boolean overlayAboveNumber = Options.getSkin().isHitCircleOverlayAboveNumber(); float oldAlpha = Utils.COLOR_WHITE_FADE.a; Utils.COLOR_WHITE_FADE.a = color.a = alpha; Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage(); Image hitCircle = GameImage.HITCIRCLE.getImage(); float[] endPos = curve.pointAt(1); curve.draw(color); color.a = alpha; // end circle hitCircle.drawCentered(endPos[0], endPos[1], color); hitCircleOverlay.drawCentered(endPos[0], endPos[1], Utils.COLOR_WHITE_FADE); // start circle hitCircle.drawCentered(x, y, color); if (!overlayAboveNumber) hitCircleOverlay.drawCentered(x, y, Utils.COLOR_WHITE_FADE); // ticks if (ticksT != null) { Image tick = GameImage.SLIDER_TICK.getImage(); for (int i = 0; i < ticksT.length; i++) { float[] c = curve.pointAt(ticksT[i]); tick.drawCentered(c[0], c[1], Utils.COLOR_WHITE_FADE); } } if (sliderClickedInitial) ; // don't draw current combo number if already clicked else data.drawSymbolNumber( hitObject.getComboNumber(), x, y, hitCircle.getWidth() * 0.40f / data.getDefaultSymbolImage(0).getHeight(), alpha); if (overlayAboveNumber) hitCircleOverlay.drawCentered(x, y, Utils.COLOR_WHITE_FADE); // repeats for (int tcurRepeat = currentRepeats; tcurRepeat <= currentRepeats + 1; tcurRepeat++) { if (hitObject.getRepeatCount() - 1 > tcurRepeat) { Image arrow = GameImage.REVERSEARROW.getImage(); if (tcurRepeat != currentRepeats) { if (sliderTime == 0) continue; float t = Math.max(getT(trackPosition, true), 0); arrow.setAlpha((float) (t - Math.floor(t))); } else arrow.setAlpha(1f); if (tcurRepeat % 2 == 0) { // last circle arrow.setRotation(curve.getEndAngle()); arrow.drawCentered(endPos[0], endPos[1]); } else { // first circle arrow.setRotation(curve.getStartAngle()); arrow.drawCentered(x, y); } } } if (timeDiff >= 0) { // approach circle GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale).drawCentered(x, y, color); } else { // Since update() might not have run before drawing during a replay, the // slider time may not have been calculated, which causes NAN numbers and flicker. if (sliderTime == 0) return; float[] c = curve.pointAt(getT(trackPosition, false)); float[] c2 = curve.pointAt(getT(trackPosition, false) + 0.01f); float t = getT(trackPosition, false); // float dis = hitObject.getPixelLength() * HitObject.getXMultiplier() * (t - (int) t); // Image sliderBallFrame = sliderBallImages[(int) (dis / (diameter * Math.PI) * 30) % // sliderBallImages.length]; Image sliderBallFrame = sliderBallImages[(int) (t * sliderTime * 60 / 1000) % sliderBallImages.length]; float angle = (float) (Math.atan2(c2[1] - c[1], c2[0] - c[0]) * 180 / Math.PI); sliderBallFrame.setRotation(angle); sliderBallFrame.drawCentered(c[0], c[1]); // follow circle if (followCircleActive) { GameImage.SLIDER_FOLLOWCIRCLE.getImage().drawCentered(c[0], c[1]); // "flashlight" mod: dim the screen if (GameMod.FLASHLIGHT.isActive()) { float oldAlphaBlack = Utils.COLOR_BLACK_ALPHA.a; Utils.COLOR_BLACK_ALPHA.a = 0.75f; g.setColor(Utils.COLOR_BLACK_ALPHA); g.fillRect(0, 0, containerWidth, containerHeight); Utils.COLOR_BLACK_ALPHA.a = oldAlphaBlack; } } } Utils.COLOR_WHITE_FADE.a = oldAlpha; }