@Override public boolean mousePressed(int x, int y, int trackPosition) { if (sliderClickedInitial) // first circle already processed return false; double distance = Math.hypot(this.x - x, this.y - y); if (distance < diameter / 2) { int timeDiff = Math.abs(trackPosition - hitObject.getTime()); int[] hitResultOffset = game.getHitResultOffsets(); int result = -1; if (timeDiff < hitResultOffset[GameData.HIT_50]) { result = GameData.HIT_SLIDER30; ticksHit++; } else if (timeDiff < hitResultOffset[GameData.HIT_MISS]) result = GameData.HIT_MISS; // 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); return true; } } return false; }
/** * Constructor. * * @param hitObject the associated HitObject * @param game the associated Game object * @param data the associated GameData object * @param color the color of this slider * @param comboEnd true if this is the last hit object in the combo */ public Slider(HitObject hitObject, Game game, GameData data, Color color, boolean comboEnd) { this.hitObject = hitObject; this.game = game; this.data = data; this.color = color; this.comboEnd = comboEnd; updatePosition(); // slider time calculations this.sliderTime = game.getBeatLength() * (hitObject.getPixelLength() / sliderMultiplier) / 100f; this.sliderTimeTotal = sliderTime * hitObject.getRepeatCount(); // ticks float tickLengthDiv = 100f * sliderMultiplier / sliderTickRate / game.getTimingPointMultiplier(); int tickCount = (int) Math.ceil(hitObject.getPixelLength() / tickLengthDiv) - 1; if (tickCount > 0) { this.ticksT = new float[tickCount]; float tickTOffset = 1f / (tickCount + 1); float t = tickTOffset; for (int i = 0; i < tickCount; i++, t += tickTOffset) ticksT[i] = t; } }
@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; }