Draw cursor location in "auto" mod.

Moves the cursor between hit objects and along hit object paths (slider curves, spinner circles).
- Added 'getPointAt(trackPosition)' and 'getEndTime()' methods to HitObject interface.
- Unhide default cursor for "auto" plays.

Other changes:
- Don't save replays for unranked plays ("auto", "relax", "autopilot" mods).
- For "auto" replays, don't parse replay frames: use default "auto" behavior instead.
- Fixed cursor location data not being reset upon entering states.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2015-03-17 14:48:13 -04:00
parent f23159d003
commit e6206e52d4
6 changed files with 170 additions and 11 deletions

View File

@ -173,6 +173,7 @@ public class UI {
public static void enter() { public static void enter() {
backButton.resetHover(); backButton.resetHover();
resetBarNotification(); resetBarNotification();
resetCursorLocations();
resetTooltip(); resetTooltip();
} }
@ -371,10 +372,16 @@ public class UI {
GameImage.CURSOR_MIDDLE.destroySkinImage(); GameImage.CURSOR_MIDDLE.destroySkinImage();
GameImage.CURSOR_TRAIL.destroySkinImage(); GameImage.CURSOR_TRAIL.destroySkinImage();
cursorAngle = 0f; cursorAngle = 0f;
GameImage.CURSOR.getImage().setRotation(0f);
}
/**
* Resets all cursor location data.
*/
private static void resetCursorLocations() {
lastX = lastY = -1; lastX = lastY = -1;
cursorX.clear(); cursorX.clear();
cursorY.clear(); cursorY.clear();
GameImage.CURSOR.getImage().setRotation(0f);
} }
/** /**

View File

@ -180,4 +180,10 @@ public class Circle implements HitObject {
return false; return false;
} }
@Override
public float[] getPointAt(int trackPosition) { return new float[] { x, y }; }
@Override
public int getEndTime() { return hitObject.getTime(); }
} }

View File

@ -49,4 +49,17 @@ public interface HitObject {
* @return true if a hit result was processed * @return true if a hit result was processed
*/ */
public boolean mousePressed(int x, int y); 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();
} }

View File

@ -458,6 +458,24 @@ public class Slider implements HitObject {
return false; 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. * Returns the t value based on the given track position.
* @param trackPosition the current track position * @param trackPosition the current track position

View File

@ -51,7 +51,9 @@ public class Spinner implements HitObject {
private static final int FADE_IN_TIME = 500; private static final int FADE_IN_TIME = 500;
/** PI constants. */ /** 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. */ /** The associated OsuHitObject. */
private OsuHitObject hitObject; private OsuHitObject hitObject;
@ -261,6 +263,30 @@ public class Spinner implements HitObject {
return false; 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. * Rotates the spinner by an angle.
* @param angle the angle to rotate (in radians) * @param angle the angle to rotate (in radians)

View File

@ -263,6 +263,67 @@ public class Game extends BasicGameState {
int firstObjectTime = osu.objects[0].getTime(); int firstObjectTime = osu.objects[0].getTime();
int timeDiff = firstObjectTime - trackPosition; 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 // "flashlight" mod: restricted view of hit objects around cursor
if (GameMod.FLASHLIGHT.isActive()) { if (GameMod.FLASHLIGHT.isActive()) {
// render hit objects offscreen // render hit objects offscreen
@ -281,6 +342,9 @@ public class Game extends BasicGameState {
if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) { if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) {
mouseX = pausedMouseX; mouseX = pausedMouseX;
mouseY = pausedMouseY; mouseY = pausedMouseY;
} else if (GameMod.AUTO.isActive()) {
mouseX = autoMouseX;
mouseY = autoMouseY;
} else if (isReplay) { } else if (isReplay) {
mouseX = replayX; mouseX = replayX;
mouseY = replayY; mouseY = replayY;
@ -445,7 +509,9 @@ public class Game extends BasicGameState {
cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY); cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY);
} }
if (!isReplay) if (GameMod.AUTO.isActive())
UI.draw(g, autoMouseX, autoMouseY, autoMousePress);
else if (!isReplay)
UI.draw(g); UI.draw(g);
else else
UI.draw(g, replayX, replayY, replayKeyPressed); UI.draw(g, replayX, replayY, replayKeyPressed);
@ -577,6 +643,7 @@ public class Game extends BasicGameState {
// go to ranking screen // go to ranking screen
else { else {
boolean unranked = (GameMod.AUTO.isActive() || GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive());
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data); ((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
if (isReplay) if (isReplay)
data.setReplay(replay); data.setReplay(replay);
@ -587,13 +654,13 @@ public class Game extends BasicGameState {
replayFrames.addFirst(ReplayFrame.getStartFrame(replaySkipTime)); replayFrames.addFirst(ReplayFrame.getStartFrame(replaySkipTime));
replayFrames.addFirst(ReplayFrame.getStartFrame(0)); replayFrames.addFirst(ReplayFrame.getStartFrame(0));
Replay r = data.getReplay(replayFrames.toArray(new ReplayFrame[replayFrames.size()])); Replay r = data.getReplay(replayFrames.toArray(new ReplayFrame[replayFrames.size()]));
if (r != null) if (r != null && !unranked)
r.save(); r.save();
} }
ScoreData score = data.getScoreData(osu); ScoreData score = data.getScoreData(osu);
// add score to database // add score to database
if (!GameMod.AUTO.isActive() && !GameMod.RELAX.isActive() && !GameMod.AUTOPILOT.isActive() && !isReplay) if (!unranked && !isReplay)
ScoreDB.addScore(score); ScoreDB.addScore(score);
game.enterState(Opsu.STATE_GAMERANKING, new FadeOutTransition(Color.black), new FadeInTransition(Color.black)); 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 // unhide cursor for "auto" mod and replays
if (isReplay) { if (GameMod.AUTO.isActive() || isReplay)
// unhide cursor
UI.showCursor(); UI.showCursor();
// load replay frames
if (isReplay) {
// load mods // load mods
previousMods = GameMod.getModState(); previousMods = GameMod.getModState();
GameMod.loadModState(replay.mods); GameMod.loadModState(replay.mods);
@ -1048,8 +1116,10 @@ public class Game extends BasicGameState {
} }
} }
}; };
replayThreadRunning = true; if (!GameMod.AUTO.isActive()) { // "auto" mod: ignore replay frames
replayThread.start(); replayThreadRunning = true;
replayThread.start();
}
} }
// initialize replay-recording structures // initialize replay-recording structures
@ -1073,10 +1143,13 @@ public class Game extends BasicGameState {
throws SlickException { throws SlickException {
// container.setMouseGrabbed(false); // container.setMouseGrabbed(false);
// re-hide cursor
if (GameMod.AUTO.isActive() || isReplay)
UI.hideCursor();
// replays // replays
if (isReplay) { if (isReplay) {
GameMod.loadModState(previousMods); GameMod.loadModState(previousMods);
UI.hideCursor();
killReplayThread(); killReplayThread();
} }
} }
@ -1331,4 +1404,20 @@ public class Game extends BasicGameState {
int cy = (int) ((y - OsuHitObject.getYOffset()) / OsuHitObject.getYMultiplier()); int cy = (int) ((y - OsuHitObject.getYOffset()) / OsuHitObject.getYMultiplier());
replayFrames.add(new ReplayFrame(timeDiff, time, cx, cy, lastKeysPressed)); 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;
}
} }