diff --git a/src/itdelatrisu/opsu/UI.java b/src/itdelatrisu/opsu/UI.java index 60108150..ab5e44d1 100644 --- a/src/itdelatrisu/opsu/UI.java +++ b/src/itdelatrisu/opsu/UI.java @@ -173,6 +173,7 @@ public class UI { public static void enter() { backButton.resetHover(); resetBarNotification(); + resetCursorLocations(); resetTooltip(); } @@ -371,10 +372,16 @@ public class UI { GameImage.CURSOR_MIDDLE.destroySkinImage(); GameImage.CURSOR_TRAIL.destroySkinImage(); cursorAngle = 0f; + GameImage.CURSOR.getImage().setRotation(0f); + } + + /** + * Resets all cursor location data. + */ + private static void resetCursorLocations() { lastX = lastY = -1; cursorX.clear(); cursorY.clear(); - GameImage.CURSOR.getImage().setRotation(0f); } /** diff --git a/src/itdelatrisu/opsu/objects/Circle.java b/src/itdelatrisu/opsu/objects/Circle.java index 40798e33..0b58afd6 100644 --- a/src/itdelatrisu/opsu/objects/Circle.java +++ b/src/itdelatrisu/opsu/objects/Circle.java @@ -180,4 +180,10 @@ public class Circle implements HitObject { return false; } + + @Override + public float[] getPointAt(int trackPosition) { return new float[] { x, y }; } + + @Override + public int getEndTime() { return hitObject.getTime(); } } \ No newline at end of file diff --git a/src/itdelatrisu/opsu/objects/HitObject.java b/src/itdelatrisu/opsu/objects/HitObject.java index 1f4f64b6..4a7dc04f 100644 --- a/src/itdelatrisu/opsu/objects/HitObject.java +++ b/src/itdelatrisu/opsu/objects/HitObject.java @@ -49,4 +49,17 @@ public interface HitObject { * @return true if a hit result was processed */ public boolean mousePressed(int x, int y); + + /** + * Returns the coordinates of the hit object at a given track position. + * @param trackPosition the track position + * @return the [x,y] coordinates + */ + public float[] getPointAt(int trackPosition); + + /** + * Returns the end time of the hit object. + * @return the end time, in milliseconds + */ + public int getEndTime(); } diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index b526cc9a..4a316086 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -458,6 +458,24 @@ public class Slider implements HitObject { return false; } + @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 { + int lastIndex = hitObject.getSliderX().length; + return new float[] { curve.getX(lastIndex), curve.getY(lastIndex) }; + } + } else + return curve.pointAt(getT(trackPosition, false)); + } + + @Override + public int getEndTime() { return hitObject.getTime() + (int) sliderTimeTotal; } + /** * Returns the t value based on the given track position. * @param trackPosition the current track position diff --git a/src/itdelatrisu/opsu/objects/Spinner.java b/src/itdelatrisu/opsu/objects/Spinner.java index d7849f2f..04ddaa60 100644 --- a/src/itdelatrisu/opsu/objects/Spinner.java +++ b/src/itdelatrisu/opsu/objects/Spinner.java @@ -51,7 +51,9 @@ public class Spinner implements HitObject { private static final int FADE_IN_TIME = 500; /** PI constants. */ - private static final float TWO_PI = (float) (Math.PI * 2); + private static final float + TWO_PI = (float) (Math.PI * 2), + HALF_PI = (float) (Math.PI / 2); /** The associated OsuHitObject. */ private OsuHitObject hitObject; @@ -261,6 +263,30 @@ public class Spinner implements HitObject { return false; } + @Override + public float[] getPointAt(int trackPosition) { + // get spinner time + int timeDiff; + float x = hitObject.getScaledX(), y = hitObject.getScaledY(); + if (trackPosition <= hitObject.getTime()) + timeDiff = 0; + else if (trackPosition >= hitObject.getEndTime()) + timeDiff = hitObject.getEndTime() - hitObject.getTime(); + else + timeDiff = trackPosition - hitObject.getTime(); + + // calculate point + float angle = timeDiff / 20f - HALF_PI; + final float r = height / 10f; + return new float[] { + (float) (x + r * Math.cos(angle)), + (float) (y + r * Math.sin(angle)) + }; + } + + @Override + public int getEndTime() { return hitObject.getEndTime(); } + /** * Rotates the spinner by an angle. * @param angle the angle to rotate (in radians) diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 20268349..d5598952 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -263,6 +263,67 @@ public class Game extends BasicGameState { int firstObjectTime = osu.objects[0].getTime(); int timeDiff = firstObjectTime - trackPosition; + // "auto" mod: move cursor automatically + int autoMouseX = width / 2, autoMouseY = height / 2; + boolean autoMousePress = false; + if (GameMod.AUTO.isActive()) { + float[] autoXY = null; + if (isLeadIn()) { + // lead-in + float progress = Math.max((float) (leadInTime - osu.audioLeadIn) / approachTime, 0f); + autoMouseY = (int) (height / (2f - progress)); + } else if (objectIndex == 0 && trackPosition < firstObjectTime) { + // before first object + timeDiff = firstObjectTime - trackPosition; + if (timeDiff < approachTime) { + float[] xy = hitObjects[0].getPointAt(trackPosition); + autoXY = getPointAt(autoMouseX, autoMouseY, xy[0], xy[1], 1f - ((float) timeDiff / approachTime)); + } + } else if (objectIndex < osu.objects.length) { + // normal object + int objectTime = osu.objects[objectIndex].getTime(); + if (trackPosition < objectTime) { + float[] xyStart = hitObjects[objectIndex - 1].getPointAt(trackPosition); + int startTime = hitObjects[objectIndex - 1].getEndTime(); + if (osu.breaks != null && breakIndex < osu.breaks.size()) { + // starting a break: keep cursor at previous hit object position + if (breakTime > 0 || objectTime > osu.breaks.get(breakIndex)) + autoXY = xyStart; + + // after a break ends: move startTime to break end time + else if (breakIndex > 1) { + int lastBreakEndTime = osu.breaks.get(breakIndex - 1); + if (objectTime > lastBreakEndTime && startTime < lastBreakEndTime) + startTime = lastBreakEndTime; + } + } + if (autoXY == null) { + float[] xyEnd = hitObjects[objectIndex].getPointAt(trackPosition); + int totalTime = objectTime - startTime; + autoXY = getPointAt(xyStart[0], xyStart[1], xyEnd[0], xyEnd[1], (float) (trackPosition - startTime) / totalTime); + + // hit circles: show a mouse press + int offset300 = hitResultOffset[GameData.HIT_300]; + if ((osu.objects[objectIndex].isCircle() && objectTime - trackPosition < offset300) || + (osu.objects[objectIndex - 1].isCircle() && trackPosition - osu.objects[objectIndex - 1].getTime() < offset300)) + autoMousePress = true; + } + } else { + autoXY = hitObjects[objectIndex].getPointAt(trackPosition); + autoMousePress = true; + } + } else { + // last object + autoXY = hitObjects[objectIndex - 1].getPointAt(trackPosition); + } + + // set mouse coordinates + if (autoXY != null) { + autoMouseX = (int) autoXY[0]; + autoMouseY = (int) autoXY[1]; + } + } + // "flashlight" mod: restricted view of hit objects around cursor if (GameMod.FLASHLIGHT.isActive()) { // render hit objects offscreen @@ -281,6 +342,9 @@ public class Game extends BasicGameState { if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) { mouseX = pausedMouseX; mouseY = pausedMouseY; + } else if (GameMod.AUTO.isActive()) { + mouseX = autoMouseX; + mouseY = autoMouseY; } else if (isReplay) { mouseX = replayX; mouseY = replayY; @@ -445,7 +509,9 @@ public class Game extends BasicGameState { cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY); } - if (!isReplay) + if (GameMod.AUTO.isActive()) + UI.draw(g, autoMouseX, autoMouseY, autoMousePress); + else if (!isReplay) UI.draw(g); else UI.draw(g, replayX, replayY, replayKeyPressed); @@ -577,6 +643,7 @@ public class Game extends BasicGameState { // go to ranking screen else { + boolean unranked = (GameMod.AUTO.isActive() || GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive()); ((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data); if (isReplay) data.setReplay(replay); @@ -587,13 +654,13 @@ public class Game extends BasicGameState { replayFrames.addFirst(ReplayFrame.getStartFrame(replaySkipTime)); replayFrames.addFirst(ReplayFrame.getStartFrame(0)); Replay r = data.getReplay(replayFrames.toArray(new ReplayFrame[replayFrames.size()])); - if (r != null) + if (r != null && !unranked) r.save(); } ScoreData score = data.getScoreData(osu); // add score to database - if (!GameMod.AUTO.isActive() && !GameMod.RELAX.isActive() && !GameMod.AUTOPILOT.isActive() && !isReplay) + if (!unranked && !isReplay) ScoreDB.addScore(score); game.enterState(Opsu.STATE_GAMERANKING, new FadeOutTransition(Color.black), new FadeInTransition(Color.black)); @@ -984,11 +1051,12 @@ public class Game extends BasicGameState { } } - // load replay frames - if (isReplay) { - // unhide cursor + // unhide cursor for "auto" mod and replays + if (GameMod.AUTO.isActive() || isReplay) UI.showCursor(); + // load replay frames + if (isReplay) { // load mods previousMods = GameMod.getModState(); GameMod.loadModState(replay.mods); @@ -1048,8 +1116,10 @@ public class Game extends BasicGameState { } } }; - replayThreadRunning = true; - replayThread.start(); + if (!GameMod.AUTO.isActive()) { // "auto" mod: ignore replay frames + replayThreadRunning = true; + replayThread.start(); + } } // initialize replay-recording structures @@ -1073,10 +1143,13 @@ public class Game extends BasicGameState { throws SlickException { // container.setMouseGrabbed(false); + // re-hide cursor + if (GameMod.AUTO.isActive() || isReplay) + UI.hideCursor(); + // replays if (isReplay) { GameMod.loadModState(previousMods); - UI.hideCursor(); killReplayThread(); } } @@ -1331,4 +1404,20 @@ public class Game extends BasicGameState { int cy = (int) ((y - OsuHitObject.getYOffset()) / OsuHitObject.getYMultiplier()); replayFrames.add(new ReplayFrame(timeDiff, time, cx, cy, lastKeysPressed)); } + + /** + * Returns the point at the t value between a start and end point. + * @param startX the starting x coordinate + * @param startY the starting y coordinate + * @param endX the ending x coordinate + * @param endY the ending y coordinate + * @param t the t value [0, 1] + * @return the [x,y] coordinates + */ + private float[] getPointAt(float startX, float startY, float endX, float endY, float t) { + float[] xy = new float[2]; + xy[0] = startX + (endX - startX) * t; + xy[1] = startY + (endY - startY) * t; + return xy; + } }