Merge remote-tracking branch 'org/master' into ReplayTest
Conflicts: src/itdelatrisu/opsu/objects/HitObject.java src/itdelatrisu/opsu/states/Game.java
This commit is contained in:
commit
2877d9bc3d
BIN
res/alpha.png
Normal file
BIN
res/alpha.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -133,6 +133,14 @@ public class ErrorHandler {
|
||||||
sb.append(timestamp);
|
sb.append(timestamp);
|
||||||
sb.append('\n');
|
sb.append('\n');
|
||||||
}
|
}
|
||||||
|
sb.append("**OS:** ");
|
||||||
|
sb.append(System.getProperty("os.name"));
|
||||||
|
sb.append(" (");
|
||||||
|
sb.append(System.getProperty("os.arch"));
|
||||||
|
sb.append(")\n");
|
||||||
|
sb.append("**JRE:** ");
|
||||||
|
sb.append(System.getProperty("java.version"));
|
||||||
|
sb.append('\n');
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
sb.append("**Error:** `");
|
sb.append("**Error:** `");
|
||||||
sb.append(error);
|
sb.append(error);
|
||||||
|
|
|
@ -917,22 +917,22 @@ public class GameData {
|
||||||
* @param hit100 the number of 100s
|
* @param hit100 the number of 100s
|
||||||
* @param hit50 the number of 50s
|
* @param hit50 the number of 50s
|
||||||
* @param miss the number of misses
|
* @param miss the number of misses
|
||||||
|
* @param silver whether or not a silver SS/S should be awarded (if applicable)
|
||||||
* @return the current Grade
|
* @return the current Grade
|
||||||
*/
|
*/
|
||||||
public static Grade getGrade(int hit300, int hit100, int hit50, int miss) {
|
public static Grade getGrade(int hit300, int hit100, int hit50, int miss, boolean silver) {
|
||||||
int objectCount = hit300 + hit100 + hit50 + miss;
|
int objectCount = hit300 + hit100 + hit50 + miss;
|
||||||
if (objectCount < 1) // avoid division by zero
|
if (objectCount < 1) // avoid division by zero
|
||||||
return Grade.NULL;
|
return Grade.NULL;
|
||||||
|
|
||||||
// TODO: silvers
|
|
||||||
float percent = getScorePercent(hit300, hit100, hit50, miss);
|
float percent = getScorePercent(hit300, hit100, hit50, miss);
|
||||||
float hit300ratio = hit300 * 100f / objectCount;
|
float hit300ratio = hit300 * 100f / objectCount;
|
||||||
float hit50ratio = hit50 * 100f / objectCount;
|
float hit50ratio = hit50 * 100f / objectCount;
|
||||||
boolean noMiss = (miss == 0);
|
boolean noMiss = (miss == 0);
|
||||||
if (percent >= 100f)
|
if (percent >= 100f)
|
||||||
return Grade.SS;
|
return (silver) ? Grade.SSH : Grade.SS;
|
||||||
else if (hit300ratio >= 90f && hit50ratio < 1.0f && noMiss)
|
else if (hit300ratio >= 90f && hit50ratio < 1.0f && noMiss)
|
||||||
return Grade.S;
|
return (silver) ? Grade.SH : Grade.S;
|
||||||
else if ((hit300ratio >= 80f && noMiss) || hit300ratio >= 90f)
|
else if ((hit300ratio >= 80f && noMiss) || hit300ratio >= 90f)
|
||||||
return Grade.A;
|
return Grade.A;
|
||||||
else if ((hit300ratio >= 70f && noMiss) || hit300ratio >= 80f)
|
else if ((hit300ratio >= 70f && noMiss) || hit300ratio >= 80f)
|
||||||
|
@ -950,7 +950,8 @@ public class GameData {
|
||||||
private Grade getGrade() {
|
private Grade getGrade() {
|
||||||
return getGrade(
|
return getGrade(
|
||||||
hitResultCount[HIT_300], hitResultCount[HIT_100],
|
hitResultCount[HIT_300], hitResultCount[HIT_100],
|
||||||
hitResultCount[HIT_50], hitResultCount[HIT_MISS]
|
hitResultCount[HIT_50], hitResultCount[HIT_MISS],
|
||||||
|
(GameMod.HIDDEN.isActive() || GameMod.FLASHLIGHT.isActive())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1031,6 +1032,11 @@ public class GameData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current combo streak.
|
||||||
|
*/
|
||||||
|
public int getComboStreak() { return combo; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increases the combo streak by one.
|
* Increases the combo streak by one.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -327,7 +327,9 @@ public enum GameImage {
|
||||||
protected Image process_sub(Image img, int w, int h) {
|
protected Image process_sub(Image img, int w, int h) {
|
||||||
return REPOSITORY.process_sub(img, w, h);
|
return REPOSITORY.process_sub(img, w, h);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
// TODO: ensure this image hasn't been modified (checksum?)
|
||||||
|
ALPHA_MAP ("alpha", "png", false, false);
|
||||||
|
|
||||||
/** Image file types. */
|
/** Image file types. */
|
||||||
private static final byte
|
private static final byte
|
||||||
|
|
|
@ -47,11 +47,11 @@ public enum GameMod {
|
||||||
// "Nightcore", "uguuuuuuuu"),
|
// "Nightcore", "uguuuuuuuu"),
|
||||||
HIDDEN (Category.HARD, 3, GameImage.MOD_HIDDEN, "HD", 8, Input.KEY_F, 1.06f, false,
|
HIDDEN (Category.HARD, 3, GameImage.MOD_HIDDEN, "HD", 8, Input.KEY_F, 1.06f, false,
|
||||||
"Hidden", "Play with no approach circles and fading notes for a slight score advantage."),
|
"Hidden", "Play with no approach circles and fading notes for a slight score advantage."),
|
||||||
FLASHLIGHT (Category.HARD, 4, GameImage.MOD_FLASHLIGHT, "FL", 1024, Input.KEY_G, 1.12f, false,
|
FLASHLIGHT (Category.HARD, 4, GameImage.MOD_FLASHLIGHT, "FL", 1024, Input.KEY_G, 1.12f,
|
||||||
"Flashlight", "Restricted view area."),
|
"Flashlight", "Restricted view area."),
|
||||||
RELAX (Category.SPECIAL, 0, GameImage.MOD_RELAX, "RL", 128, Input.KEY_Z, 0f,
|
RELAX (Category.SPECIAL, 0, GameImage.MOD_RELAX, "RL", 128, Input.KEY_Z, 0f,
|
||||||
"Relax", "You don't need to click.\nGive your clicking/tapping finger a break from the heat of things.\n**UNRANKED**"),
|
"Relax", "You don't need to click.\nGive your clicking/tapping finger a break from the heat of things.\n**UNRANKED**"),
|
||||||
AUTOPILOT (Category.SPECIAL, 1, GameImage.MOD_AUTOPILOT, "AP", 8192, Input.KEY_X, 0f, false,
|
AUTOPILOT (Category.SPECIAL, 1, GameImage.MOD_AUTOPILOT, "AP", 8192, Input.KEY_X, 0f,
|
||||||
"Relax2", "Automatic cursor movement - just follow the rhythm.\n**UNRANKED**"),
|
"Relax2", "Automatic cursor movement - just follow the rhythm.\n**UNRANKED**"),
|
||||||
SPUN_OUT (Category.SPECIAL, 2, GameImage.MOD_SPUN_OUT, "SO", 4096, Input.KEY_V, 0.9f,
|
SPUN_OUT (Category.SPECIAL, 2, GameImage.MOD_SPUN_OUT, "SO", 4096, Input.KEY_V, 0.9f,
|
||||||
"SpunOut", "Spinners will be automatically completed."),
|
"SpunOut", "Spinners will be automatically completed."),
|
||||||
|
@ -355,17 +355,16 @@ public enum GameMod {
|
||||||
scoreMultiplier = -1f;
|
scoreMultiplier = -1f;
|
||||||
|
|
||||||
if (checkInverse) {
|
if (checkInverse) {
|
||||||
boolean b = (this == SUDDEN_DEATH || this == NO_FAIL || this == RELAX || this == AUTOPILOT);
|
|
||||||
if (AUTO.isActive()) {
|
if (AUTO.isActive()) {
|
||||||
if (this == AUTO) {
|
if (this == AUTO) {
|
||||||
SPUN_OUT.active = false;
|
SPUN_OUT.active = false;
|
||||||
SUDDEN_DEATH.active = false;
|
SUDDEN_DEATH.active = false;
|
||||||
RELAX.active = false;
|
RELAX.active = false;
|
||||||
AUTOPILOT.active = false;
|
AUTOPILOT.active = false;
|
||||||
} else if (b)
|
} else if (this == SPUN_OUT || this == SUDDEN_DEATH || this == RELAX || this == AUTOPILOT)
|
||||||
this.active = false;
|
this.active = false;
|
||||||
}
|
}
|
||||||
if (active && b) {
|
if (active && (this == SUDDEN_DEATH || this == NO_FAIL || this == RELAX || this == AUTOPILOT)) {
|
||||||
SUDDEN_DEATH.active = false;
|
SUDDEN_DEATH.active = false;
|
||||||
NO_FAIL.active = false;
|
NO_FAIL.active = false;
|
||||||
RELAX.active = false;
|
RELAX.active = false;
|
||||||
|
@ -384,6 +383,12 @@ public enum GameMod {
|
||||||
else
|
else
|
||||||
EASY.active = false;
|
EASY.active = false;
|
||||||
}
|
}
|
||||||
|
if (HALF_TIME.isActive() && DOUBLE_TIME.isActive()) {
|
||||||
|
if (this == HALF_TIME)
|
||||||
|
DOUBLE_TIME.active = false;
|
||||||
|
else
|
||||||
|
HALF_TIME.active = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -187,11 +187,12 @@ public class ScoreData implements Comparable<ScoreData> {
|
||||||
/**
|
/**
|
||||||
* Returns letter grade based on score data,
|
* Returns letter grade based on score data,
|
||||||
* or Grade.NULL if no objects have been processed.
|
* or Grade.NULL if no objects have been processed.
|
||||||
* @see GameData#getGrade(int, int, int, int)
|
* @see GameData#getGrade(int, int, int, int, boolean)
|
||||||
*/
|
*/
|
||||||
public Grade getGrade() {
|
public Grade getGrade() {
|
||||||
if (grade == null)
|
if (grade == null)
|
||||||
grade = GameData.getGrade(hit300, hit100, hit50, miss);
|
grade = GameData.getGrade(hit300, hit100, hit50, miss,
|
||||||
|
((mods & GameMod.HIDDEN.getBit()) > 0 || (mods & GameMod.FLASHLIGHT.getBit()) > 0));
|
||||||
return grade;
|
return grade;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -24,8 +24,14 @@ import itdelatrisu.opsu.OsuFile;
|
||||||
import itdelatrisu.opsu.OsuParser;
|
import itdelatrisu.opsu.OsuParser;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.nio.IntBuffer;
|
import java.nio.IntBuffer;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.sound.sampled.AudioFileFormat;
|
||||||
|
import javax.sound.sampled.AudioSystem;
|
||||||
|
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||||
|
|
||||||
import org.lwjgl.BufferUtils;
|
import org.lwjgl.BufferUtils;
|
||||||
import org.lwjgl.openal.AL;
|
import org.lwjgl.openal.AL;
|
||||||
|
@ -35,6 +41,7 @@ import org.newdawn.slick.MusicListener;
|
||||||
import org.newdawn.slick.SlickException;
|
import org.newdawn.slick.SlickException;
|
||||||
import org.newdawn.slick.openal.Audio;
|
import org.newdawn.slick.openal.Audio;
|
||||||
import org.newdawn.slick.openal.SoundStore;
|
import org.newdawn.slick.openal.SoundStore;
|
||||||
|
import org.tritonus.share.sampled.file.TAudioFileFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for all music.
|
* Controller for all music.
|
||||||
|
@ -252,6 +259,30 @@ public class MusicController {
|
||||||
return (trackExists() && position >= 0 && player.setPosition(position / 1000f));
|
return (trackExists() && position >= 0 && player.setPosition(position / 1000f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the duration of the current track, in milliseconds.
|
||||||
|
* Currently only works for MP3s.
|
||||||
|
* @return the duration, or -1 if no track exists, else the {@code endTime}
|
||||||
|
* field of the OsuFile loaded
|
||||||
|
* @author Tom Brito (http://stackoverflow.com/a/3056161)
|
||||||
|
*/
|
||||||
|
public static int getDuration() {
|
||||||
|
if (!trackExists() || lastOsu == null)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (lastOsu.audioFilename.getName().endsWith(".mp3")) {
|
||||||
|
try {
|
||||||
|
AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(lastOsu.audioFilename);
|
||||||
|
if (fileFormat instanceof TAudioFileFormat) {
|
||||||
|
Map<?, ?> properties = ((TAudioFileFormat) fileFormat).properties();
|
||||||
|
Long microseconds = (Long) properties.get("duration");
|
||||||
|
return (int) (microseconds / 1000);
|
||||||
|
}
|
||||||
|
} catch (UnsupportedAudioFileException | IOException e) {}
|
||||||
|
}
|
||||||
|
return lastOsu.endTime;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays the current track.
|
* Plays the current track.
|
||||||
* @param loop whether or not to loop the track
|
* @param loop whether or not to loop the track
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package itdelatrisu.opsu;
|
package itdelatrisu.opsu.io;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
|
@ -16,7 +16,7 @@
|
||||||
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package itdelatrisu.opsu;
|
package itdelatrisu.opsu.io;
|
||||||
|
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
|
@ -178,4 +178,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(); }
|
||||||
}
|
}
|
|
@ -51,4 +51,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, int trackPosition);
|
public boolean mousePressed(int x, int y, int trackPosition);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,6 +92,9 @@ public class Slider implements HitObject {
|
||||||
/** Number of ticks hit and tick intervals so far. */
|
/** Number of ticks hit and tick intervals so far. */
|
||||||
private int ticksHit = 0, tickIntervals = 1;
|
private int ticksHit = 0, tickIntervals = 1;
|
||||||
|
|
||||||
|
/** Container dimensions. */
|
||||||
|
private static int containerWidth, containerHeight;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the Slider data type with images and dimensions.
|
* Initializes the Slider data type with images and dimensions.
|
||||||
* @param container the game container
|
* @param container the game container
|
||||||
|
@ -99,6 +102,9 @@ public class Slider implements HitObject {
|
||||||
* @param osu the associated OsuFile object
|
* @param osu the associated OsuFile object
|
||||||
*/
|
*/
|
||||||
public static void init(GameContainer container, float circleSize, OsuFile osu) {
|
public static void init(GameContainer container, float circleSize, OsuFile osu) {
|
||||||
|
containerWidth = container.getWidth();
|
||||||
|
containerHeight = container.getHeight();
|
||||||
|
|
||||||
int diameter = (int) (104 - (circleSize * 8));
|
int diameter = (int) (104 - (circleSize * 8));
|
||||||
diameter = (int) (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480)
|
diameter = (int) (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480)
|
||||||
|
|
||||||
|
@ -188,7 +194,7 @@ public class Slider implements HitObject {
|
||||||
color.a = oldAlpha;
|
color.a = oldAlpha;
|
||||||
|
|
||||||
// repeats
|
// repeats
|
||||||
for(int tcurRepeat = currentRepeats; tcurRepeat<=currentRepeats+1; tcurRepeat++){
|
for (int tcurRepeat = currentRepeats; tcurRepeat <= currentRepeats + 1; tcurRepeat++) {
|
||||||
if (hitObject.getRepeatCount() - 1 > tcurRepeat) {
|
if (hitObject.getRepeatCount() - 1 > tcurRepeat) {
|
||||||
Image arrow = GameImage.REVERSEARROW.getImage();
|
Image arrow = GameImage.REVERSEARROW.getImage();
|
||||||
if (tcurRepeat != currentRepeats) {
|
if (tcurRepeat != currentRepeats) {
|
||||||
|
@ -233,8 +239,18 @@ public class Slider implements HitObject {
|
||||||
sliderBallFrame.drawCentered(c[0], c[1]);
|
sliderBallFrame.drawCentered(c[0], c[1]);
|
||||||
|
|
||||||
// follow circle
|
// follow circle
|
||||||
if (followCircleActive)
|
if (followCircleActive) {
|
||||||
GameImage.SLIDER_FOLLOWCIRCLE.getImage().drawCentered(c[0], c[1]);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,6 +457,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
|
||||||
|
|
|
@ -50,8 +50,15 @@ public class Spinner implements HitObject {
|
||||||
/** The amount of time, in milliseconds, to fade in the spinner. */
|
/** The amount of time, in milliseconds, to fade in the spinner. */
|
||||||
private static final int FADE_IN_TIME = 500;
|
private static final int FADE_IN_TIME = 500;
|
||||||
|
|
||||||
|
/** Angle mod multipliers: "auto" (477rpm), "spun out" (287rpm) */
|
||||||
|
private static final float
|
||||||
|
AUTO_MULTIPLIER = 1 / 20f, // angle = 477/60f * delta/1000f * TWO_PI;
|
||||||
|
SPUN_OUT_MULTIPLIER = 1 / 33.25f; // angle = 287/60f * delta/1000f * TWO_PI;
|
||||||
|
|
||||||
/** 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;
|
||||||
|
@ -174,8 +181,7 @@ public class Spinner implements HitObject {
|
||||||
// TODO: verify ratios
|
// TODO: verify ratios
|
||||||
int result;
|
int result;
|
||||||
float ratio = rotations / rotationsNeeded;
|
float ratio = rotations / rotationsNeeded;
|
||||||
if (ratio >= 1.0f ||
|
if (ratio >= 1.0f || GameMod.AUTO.isActive() || GameMod.AUTOPILOT.isActive() || GameMod.SPUN_OUT.isActive()) {
|
||||||
GameMod.AUTO.isActive() || GameMod.SPUN_OUT.isActive()) {
|
|
||||||
result = GameData.HIT_300;
|
result = GameData.HIT_300;
|
||||||
SoundController.playSound(SoundEffect.SPINNEROSU);
|
SoundController.playSound(SoundEffect.SPINNEROSU);
|
||||||
} else if (ratio >= 0.9f)
|
} else if (ratio >= 0.9f)
|
||||||
|
@ -210,14 +216,12 @@ public class Spinner implements HitObject {
|
||||||
// http://osu.ppy.sh/wiki/FAQ#Spinners
|
// http://osu.ppy.sh/wiki/FAQ#Spinners
|
||||||
float angle;
|
float angle;
|
||||||
if (GameMod.AUTO.isActive()) {
|
if (GameMod.AUTO.isActive()) {
|
||||||
// "auto" mod (fast: 477rpm)
|
|
||||||
lastAngle = 0;
|
lastAngle = 0;
|
||||||
angle = delta / 20f; // angle = 477/60f * delta/1000f * TWO_PI;
|
angle = delta * AUTO_MULTIPLIER;
|
||||||
isSpinning = true;
|
isSpinning = true;
|
||||||
} else if (GameMod.SPUN_OUT.isActive()) {
|
} else if (GameMod.SPUN_OUT.isActive() || GameMod.AUTOPILOT.isActive()) {
|
||||||
// "spun out" mod (slow: 287rpm)
|
|
||||||
lastAngle = 0;
|
lastAngle = 0;
|
||||||
angle = delta / 33.25f; // angle = 287/60f * delta/1000f * TWO_PI;
|
angle = delta * SPUN_OUT_MULTIPLIER;
|
||||||
isSpinning = true;
|
isSpinning = true;
|
||||||
} else {
|
} else {
|
||||||
angle = (float) Math.atan2(mouseY - (height / 2), mouseX - (width / 2));
|
angle = (float) Math.atan2(mouseY - (height / 2), mouseX - (width / 2));
|
||||||
|
@ -260,6 +264,31 @@ 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 multiplier = (GameMod.AUTO.isActive()) ? AUTO_MULTIPLIER : SPUN_OUT_MULTIPLIER;
|
||||||
|
float angle = (timeDiff * multiplier) - 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)
|
||||||
|
|
|
@ -20,9 +20,9 @@ package itdelatrisu.opsu.replay;
|
||||||
|
|
||||||
import itdelatrisu.opsu.ErrorHandler;
|
import itdelatrisu.opsu.ErrorHandler;
|
||||||
import itdelatrisu.opsu.Options;
|
import itdelatrisu.opsu.Options;
|
||||||
import itdelatrisu.opsu.OsuReader;
|
|
||||||
import itdelatrisu.opsu.OsuWriter;
|
|
||||||
import itdelatrisu.opsu.Utils;
|
import itdelatrisu.opsu.Utils;
|
||||||
|
import itdelatrisu.opsu.io.OsuReader;
|
||||||
|
import itdelatrisu.opsu.io.OsuWriter;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
|
@ -50,7 +50,6 @@ import java.io.FileNotFoundException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.lwjgl.input.Keyboard;
|
import org.lwjgl.input.Keyboard;
|
||||||
import org.lwjgl.opengl.Display;
|
import org.lwjgl.opengl.Display;
|
||||||
|
@ -189,6 +188,21 @@ public class Game extends BasicGameState {
|
||||||
/** The list of current replay frames (for recording replays). */
|
/** The list of current replay frames (for recording replays). */
|
||||||
private LinkedList<ReplayFrame> replayFrames;
|
private LinkedList<ReplayFrame> replayFrames;
|
||||||
|
|
||||||
|
/** The offscreen image rendered to. */
|
||||||
|
private Image offscreen;
|
||||||
|
|
||||||
|
/** The offscreen graphics. */
|
||||||
|
private Graphics gOffscreen;
|
||||||
|
|
||||||
|
/** The current flashlight area radius. */
|
||||||
|
private int flashlightRadius;
|
||||||
|
|
||||||
|
/** The cursor coordinates using the "auto" or "relax" mods. */
|
||||||
|
private int autoMouseX = 0, autoMouseY = 0;
|
||||||
|
|
||||||
|
/** Whether or not the cursor should be pressed using the "auto" mod. */
|
||||||
|
private boolean autoMousePressed;
|
||||||
|
|
||||||
// game-related variables
|
// game-related variables
|
||||||
private GameContainer container;
|
private GameContainer container;
|
||||||
private StateBasedGame game;
|
private StateBasedGame game;
|
||||||
|
@ -209,6 +223,11 @@ public class Game extends BasicGameState {
|
||||||
int width = container.getWidth();
|
int width = container.getWidth();
|
||||||
int height = container.getHeight();
|
int height = container.getHeight();
|
||||||
|
|
||||||
|
// create offscreen graphics
|
||||||
|
offscreen = new Image(width, height);
|
||||||
|
gOffscreen = offscreen.getGraphics();
|
||||||
|
gOffscreen.setBackground(Color.black);
|
||||||
|
|
||||||
// create the associated GameData object
|
// create the associated GameData object
|
||||||
data = new GameData(width, height);
|
data = new GameData(width, height);
|
||||||
}
|
}
|
||||||
|
@ -218,9 +237,15 @@ public class Game extends BasicGameState {
|
||||||
throws SlickException {
|
throws SlickException {
|
||||||
int width = container.getWidth();
|
int width = container.getWidth();
|
||||||
int height = container.getHeight();
|
int height = container.getHeight();
|
||||||
|
g.setBackground(Color.black);
|
||||||
|
|
||||||
|
// "flashlight" mod: initialize offscreen graphics
|
||||||
|
if (GameMod.FLASHLIGHT.isActive()) {
|
||||||
|
gOffscreen.clear();
|
||||||
|
Graphics.setCurrent(gOffscreen);
|
||||||
|
}
|
||||||
|
|
||||||
// background
|
// background
|
||||||
g.setBackground(Color.black);
|
|
||||||
float dimLevel = Options.getBackgroundDim();
|
float dimLevel = Options.getBackgroundDim();
|
||||||
if (Options.isDefaultPlayfieldForced() || !osu.drawBG(width, height, dimLevel, false)) {
|
if (Options.isDefaultPlayfieldForced() || !osu.drawBG(width, height, dimLevel, false)) {
|
||||||
Image playfield = GameImage.PLAYFIELD.getImage();
|
Image playfield = GameImage.PLAYFIELD.getImage();
|
||||||
|
@ -229,6 +254,9 @@ public class Game extends BasicGameState {
|
||||||
playfield.setAlpha(1f);
|
playfield.setAlpha(1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (GameMod.FLASHLIGHT.isActive())
|
||||||
|
Graphics.setCurrent(g);
|
||||||
|
|
||||||
int trackPosition = MusicController.getPosition();
|
int trackPosition = MusicController.getPosition();
|
||||||
if (pauseTime > -1) // returning from pause screen
|
if (pauseTime > -1) // returning from pause screen
|
||||||
trackPosition = pauseTime;
|
trackPosition = pauseTime;
|
||||||
|
@ -237,160 +265,234 @@ 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;
|
||||||
|
|
||||||
// checkpoint
|
// "auto" and "autopilot" mods: move cursor automatically
|
||||||
if (checkpointLoaded) {
|
// TODO: this should really be in update(), not render()
|
||||||
int checkpoint = Options.getCheckpoint();
|
autoMouseX = width / 2;
|
||||||
String checkpointText = String.format(
|
autoMouseY = height / 2;
|
||||||
"Playing from checkpoint at %02d:%02d.",
|
autoMousePressed = false;
|
||||||
TimeUnit.MILLISECONDS.toMinutes(checkpoint),
|
if (GameMod.AUTO.isActive() || GameMod.AUTOPILOT.isActive()) {
|
||||||
TimeUnit.MILLISECONDS.toSeconds(checkpoint) -
|
float[] autoXY = null;
|
||||||
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(checkpoint))
|
if (isLeadIn()) {
|
||||||
);
|
// lead-in
|
||||||
Utils.FONT_MEDIUM.drawString(
|
float progress = Math.max((float) (leadInTime - osu.audioLeadIn) / approachTime, 0f);
|
||||||
(width - Utils.FONT_MEDIUM.getWidth(checkpointText)) / 2,
|
autoMouseY = (int) (height / (2f - progress));
|
||||||
height - 15 - Utils.FONT_MEDIUM.getLineHeight(),
|
} else if (objectIndex == 0 && trackPosition < firstObjectTime) {
|
||||||
checkpointText, Color.white
|
// 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))
|
||||||
|
autoMousePressed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
autoXY = hitObjects[objectIndex].getPointAt(trackPosition);
|
||||||
|
autoMousePressed = 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
|
||||||
|
Graphics.setCurrent(gOffscreen);
|
||||||
|
int trackPos = (isLeadIn()) ? (leadInTime - Options.getMusicOffset()) * -1 : trackPosition;
|
||||||
|
drawHitObjects(gOffscreen, trackPos);
|
||||||
|
|
||||||
|
// restore original graphics context
|
||||||
|
gOffscreen.flush();
|
||||||
|
Graphics.setCurrent(g);
|
||||||
|
|
||||||
|
// draw alpha map around cursor
|
||||||
|
g.setDrawMode(Graphics.MODE_ALPHA_MAP);
|
||||||
|
g.clearAlphaMap();
|
||||||
|
int mouseX, mouseY;
|
||||||
|
if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) {
|
||||||
|
mouseX = pausedMouseX;
|
||||||
|
mouseY = pausedMouseY;
|
||||||
|
} else if (GameMod.AUTO.isActive() || GameMod.AUTOPILOT.isActive()) {
|
||||||
|
mouseX = autoMouseX;
|
||||||
|
mouseY = autoMouseY;
|
||||||
|
} else if (isReplay) {
|
||||||
|
mouseX = replayX;
|
||||||
|
mouseY = replayY;
|
||||||
|
} else {
|
||||||
|
mouseX = input.getMouseX();
|
||||||
|
mouseY = input.getMouseY();
|
||||||
|
}
|
||||||
|
int alphaRadius = flashlightRadius * 256 / 215;
|
||||||
|
int alphaX = mouseX - alphaRadius / 2;
|
||||||
|
int alphaY = mouseY - alphaRadius / 2;
|
||||||
|
GameImage.ALPHA_MAP.getImage().draw(alphaX, alphaY, alphaRadius, alphaRadius);
|
||||||
|
|
||||||
|
// blend offscreen image
|
||||||
|
g.setDrawMode(Graphics.MODE_ALPHA_BLEND);
|
||||||
|
g.setClip(alphaX, alphaY, alphaRadius, alphaRadius);
|
||||||
|
g.drawImage(offscreen, 0, 0);
|
||||||
|
g.clearClip();
|
||||||
|
g.setDrawMode(Graphics.MODE_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// break periods
|
// break periods
|
||||||
if (osu.breaks != null && breakIndex < osu.breaks.size()) {
|
if (osu.breaks != null && breakIndex < osu.breaks.size() && breakTime > 0) {
|
||||||
if (breakTime > 0) {
|
int endTime = osu.breaks.get(breakIndex);
|
||||||
int endTime = osu.breaks.get(breakIndex);
|
int breakLength = endTime - breakTime;
|
||||||
int breakLength = endTime - breakTime;
|
|
||||||
|
|
||||||
// letterbox effect (black bars on top/bottom)
|
// letterbox effect (black bars on top/bottom)
|
||||||
if (osu.letterboxInBreaks && breakLength >= 4000) {
|
if (osu.letterboxInBreaks && breakLength >= 4000) {
|
||||||
g.setColor(Color.black);
|
g.setColor(Color.black);
|
||||||
g.fillRect(0, 0, width, height * 0.125f);
|
g.fillRect(0, 0, width, height * 0.125f);
|
||||||
g.fillRect(0, height * 0.875f, width, height * 0.125f);
|
g.fillRect(0, height * 0.875f, width, height * 0.125f);
|
||||||
}
|
|
||||||
|
|
||||||
data.drawGameElements(g, true, objectIndex == 0);
|
|
||||||
|
|
||||||
if (breakLength >= 8000 &&
|
|
||||||
trackPosition - breakTime > 2000 &&
|
|
||||||
trackPosition - breakTime < 5000) {
|
|
||||||
// show break start
|
|
||||||
if (data.getHealth() >= 50) {
|
|
||||||
GameImage.SECTION_PASS.getImage().drawCentered(width / 2f, height / 2f);
|
|
||||||
if (!breakSound) {
|
|
||||||
SoundController.playSound(SoundEffect.SECTIONPASS);
|
|
||||||
breakSound = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GameImage.SECTION_FAIL.getImage().drawCentered(width / 2f, height / 2f);
|
|
||||||
if (!breakSound) {
|
|
||||||
SoundController.playSound(SoundEffect.SECTIONFAIL);
|
|
||||||
breakSound = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (breakLength >= 4000) {
|
|
||||||
// show break end (flash twice for 500ms)
|
|
||||||
int endTimeDiff = endTime - trackPosition;
|
|
||||||
if ((endTimeDiff > 1500 && endTimeDiff < 2000) ||
|
|
||||||
(endTimeDiff > 500 && endTimeDiff < 1000)) {
|
|
||||||
Image arrow = GameImage.WARNINGARROW.getImage();
|
|
||||||
arrow.setRotation(0);
|
|
||||||
arrow.draw(width * 0.15f, height * 0.15f);
|
|
||||||
arrow.draw(width * 0.15f, height * 0.75f);
|
|
||||||
arrow.setRotation(180);
|
|
||||||
arrow.draw(width * 0.75f, height * 0.15f);
|
|
||||||
arrow.draw(width * 0.75f, height * 0.75f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GameMod.AUTO.isActive())
|
|
||||||
GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f);
|
|
||||||
if (!isReplay)
|
|
||||||
UI.draw(g);
|
|
||||||
else
|
|
||||||
UI.draw(g, replayX, replayY, replayKeyPressed);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// game elements
|
data.drawGameElements(g, true, objectIndex == 0);
|
||||||
data.drawGameElements(g, false, objectIndex == 0);
|
|
||||||
|
|
||||||
// skip beginning
|
if (breakLength >= 8000 &&
|
||||||
if (objectIndex == 0 &&
|
trackPosition - breakTime > 2000 &&
|
||||||
trackPosition < osu.objects[0].getTime() - SKIP_OFFSET)
|
trackPosition - breakTime < 5000) {
|
||||||
skipButton.draw();
|
// show break start
|
||||||
|
if (data.getHealth() >= 50) {
|
||||||
// show retries
|
GameImage.SECTION_PASS.getImage().drawCentered(width / 2f, height / 2f);
|
||||||
if (retries >= 2 && timeDiff >= -1000) {
|
if (!breakSound) {
|
||||||
int retryHeight = Math.max(
|
SoundController.playSound(SoundEffect.SECTIONPASS);
|
||||||
GameImage.SCOREBAR_BG.getImage().getHeight(),
|
breakSound = true;
|
||||||
GameImage.SCOREBAR_KI.getImage().getHeight()
|
}
|
||||||
);
|
} else {
|
||||||
float oldAlpha = Utils.COLOR_WHITE_FADE.a;
|
GameImage.SECTION_FAIL.getImage().drawCentered(width / 2f, height / 2f);
|
||||||
if (timeDiff < -500)
|
if (!breakSound) {
|
||||||
Utils.COLOR_WHITE_FADE.a = (1000 + timeDiff) / 500f;
|
SoundController.playSound(SoundEffect.SECTIONFAIL);
|
||||||
Utils.FONT_MEDIUM.drawString(
|
breakSound = true;
|
||||||
2 + (width / 100), retryHeight,
|
|
||||||
String.format("%d retries and counting...", retries),
|
|
||||||
Utils.COLOR_WHITE_FADE
|
|
||||||
);
|
|
||||||
Utils.COLOR_WHITE_FADE.a = oldAlpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLeadIn())
|
|
||||||
trackPosition = (leadInTime - Options.getMusicOffset()) * -1; // render approach circles during song lead-in
|
|
||||||
|
|
||||||
// countdown
|
|
||||||
if (osu.countdown > 0) { // TODO: implement half/double rate settings
|
|
||||||
timeDiff = firstObjectTime - trackPosition;
|
|
||||||
if (timeDiff >= 500 && timeDiff < 3000) {
|
|
||||||
if (timeDiff >= 1500) {
|
|
||||||
GameImage.COUNTDOWN_READY.getImage().drawCentered(width / 2, height / 2);
|
|
||||||
if (!countdownReadySound) {
|
|
||||||
SoundController.playSound(SoundEffect.READY);
|
|
||||||
countdownReadySound = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (timeDiff < 2000) {
|
} else if (breakLength >= 4000) {
|
||||||
GameImage.COUNTDOWN_3.getImage().draw(0, 0);
|
// show break end (flash twice for 500ms)
|
||||||
if (!countdown3Sound) {
|
int endTimeDiff = endTime - trackPosition;
|
||||||
SoundController.playSound(SoundEffect.COUNT3);
|
if ((endTimeDiff > 1500 && endTimeDiff < 2000) ||
|
||||||
countdown3Sound = true;
|
(endTimeDiff > 500 && endTimeDiff < 1000)) {
|
||||||
}
|
Image arrow = GameImage.WARNINGARROW.getImage();
|
||||||
}
|
arrow.setRotation(0);
|
||||||
if (timeDiff < 1500) {
|
arrow.draw(width * 0.15f, height * 0.15f);
|
||||||
GameImage.COUNTDOWN_2.getImage().draw(width - GameImage.COUNTDOWN_2.getImage().getWidth(), 0);
|
arrow.draw(width * 0.15f, height * 0.75f);
|
||||||
if (!countdown2Sound) {
|
arrow.setRotation(180);
|
||||||
SoundController.playSound(SoundEffect.COUNT2);
|
arrow.draw(width * 0.75f, height * 0.15f);
|
||||||
countdown2Sound = true;
|
arrow.draw(width * 0.75f, height * 0.75f);
|
||||||
}
|
|
||||||
}
|
|
||||||
if (timeDiff < 1000) {
|
|
||||||
GameImage.COUNTDOWN_1.getImage().drawCentered(width / 2, height / 2);
|
|
||||||
if (!countdown1Sound) {
|
|
||||||
SoundController.playSound(SoundEffect.COUNT1);
|
|
||||||
countdown1Sound = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (timeDiff >= -500 && timeDiff < 500) {
|
|
||||||
Image go = GameImage.COUNTDOWN_GO.getImage();
|
|
||||||
go.setAlpha((timeDiff < 0) ? 1 - (timeDiff / -1000f) : 1);
|
|
||||||
go.drawCentered(width / 2, height / 2);
|
|
||||||
if (!countdownGoSound) {
|
|
||||||
SoundController.playSound(SoundEffect.GO);
|
|
||||||
countdownGoSound = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw hit objects in reverse order, or else overlapping objects are unreadable
|
// non-break
|
||||||
Stack<Integer> stack = new Stack<Integer>();
|
else {
|
||||||
for (int i = objectIndex; i < hitObjects.length && osu.objects[i].getTime() < trackPosition + approachTime; i++)
|
// game elements
|
||||||
stack.add(i);
|
data.drawGameElements(g, false, objectIndex == 0);
|
||||||
|
|
||||||
while (!stack.isEmpty())
|
// skip beginning
|
||||||
hitObjects[stack.pop()].draw(g, trackPosition);
|
if (objectIndex == 0 &&
|
||||||
|
trackPosition < osu.objects[0].getTime() - SKIP_OFFSET)
|
||||||
|
skipButton.draw();
|
||||||
|
|
||||||
// draw OsuHitObjectResult objects
|
// show retries
|
||||||
data.drawHitResults(trackPosition);
|
if (retries >= 2 && timeDiff >= -1000) {
|
||||||
|
int retryHeight = Math.max(
|
||||||
|
GameImage.SCOREBAR_BG.getImage().getHeight(),
|
||||||
|
GameImage.SCOREBAR_KI.getImage().getHeight()
|
||||||
|
);
|
||||||
|
float oldAlpha = Utils.COLOR_WHITE_FADE.a;
|
||||||
|
if (timeDiff < -500)
|
||||||
|
Utils.COLOR_WHITE_FADE.a = (1000 + timeDiff) / 500f;
|
||||||
|
Utils.FONT_MEDIUM.drawString(
|
||||||
|
2 + (width / 100), retryHeight,
|
||||||
|
String.format("%d retries and counting...", retries),
|
||||||
|
Utils.COLOR_WHITE_FADE
|
||||||
|
);
|
||||||
|
Utils.COLOR_WHITE_FADE.a = oldAlpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLeadIn())
|
||||||
|
trackPosition = (leadInTime - Options.getMusicOffset()) * -1; // render approach circles during song lead-in
|
||||||
|
|
||||||
|
// countdown
|
||||||
|
if (osu.countdown > 0) { // TODO: implement half/double rate settings
|
||||||
|
timeDiff = firstObjectTime - trackPosition;
|
||||||
|
if (timeDiff >= 500 && timeDiff < 3000) {
|
||||||
|
if (timeDiff >= 1500) {
|
||||||
|
GameImage.COUNTDOWN_READY.getImage().drawCentered(width / 2, height / 2);
|
||||||
|
if (!countdownReadySound) {
|
||||||
|
SoundController.playSound(SoundEffect.READY);
|
||||||
|
countdownReadySound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (timeDiff < 2000) {
|
||||||
|
GameImage.COUNTDOWN_3.getImage().draw(0, 0);
|
||||||
|
if (!countdown3Sound) {
|
||||||
|
SoundController.playSound(SoundEffect.COUNT3);
|
||||||
|
countdown3Sound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (timeDiff < 1500) {
|
||||||
|
GameImage.COUNTDOWN_2.getImage().draw(width - GameImage.COUNTDOWN_2.getImage().getWidth(), 0);
|
||||||
|
if (!countdown2Sound) {
|
||||||
|
SoundController.playSound(SoundEffect.COUNT2);
|
||||||
|
countdown2Sound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (timeDiff < 1000) {
|
||||||
|
GameImage.COUNTDOWN_1.getImage().drawCentered(width / 2, height / 2);
|
||||||
|
if (!countdown1Sound) {
|
||||||
|
SoundController.playSound(SoundEffect.COUNT1);
|
||||||
|
countdown1Sound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (timeDiff >= -500 && timeDiff < 500) {
|
||||||
|
Image go = GameImage.COUNTDOWN_GO.getImage();
|
||||||
|
go.setAlpha((timeDiff < 0) ? 1 - (timeDiff / -1000f) : 1);
|
||||||
|
go.drawCentered(width / 2, height / 2);
|
||||||
|
if (!countdownGoSound) {
|
||||||
|
SoundController.playSound(SoundEffect.GO);
|
||||||
|
countdownGoSound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw hit objects
|
||||||
|
if (!GameMod.FLASHLIGHT.isActive())
|
||||||
|
drawHitObjects(g, trackPosition);
|
||||||
|
}
|
||||||
|
|
||||||
if (GameMod.AUTO.isActive())
|
if (GameMod.AUTO.isActive())
|
||||||
GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f);
|
GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f);
|
||||||
|
@ -411,7 +513,11 @@ public class Game extends BasicGameState {
|
||||||
cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY);
|
cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isReplay)
|
if (GameMod.AUTO.isActive())
|
||||||
|
UI.draw(g, autoMouseX, autoMouseY, autoMousePressed);
|
||||||
|
else if (GameMod.AUTOPILOT.isActive())
|
||||||
|
UI.draw(g, autoMouseX, autoMouseY, Utils.isGameKeyPressed());
|
||||||
|
else if (!isReplay)
|
||||||
UI.draw(g);
|
UI.draw(g);
|
||||||
else
|
else
|
||||||
UI.draw(g, replayX, replayY, replayKeyPressed);
|
UI.draw(g, replayX, replayY, replayKeyPressed);
|
||||||
|
@ -429,7 +535,11 @@ public class Game extends BasicGameState {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int trackPosition = MusicController.getPosition();
|
int trackPosition = MusicController.getPosition();
|
||||||
if (!isReplay) {
|
if (GameMod.AUTO.isActive() || GameMod.AUTOPILOT.isActive()) {
|
||||||
|
mouseX = autoMouseX;
|
||||||
|
mouseY = autoMouseY;
|
||||||
|
frameAndRun(mouseX, mouseY, lastKeysPressed, trackPosition);
|
||||||
|
} else if (!isReplay) {
|
||||||
mouseX = input.getMouseX();
|
mouseX = input.getMouseX();
|
||||||
mouseY = input.getMouseY();
|
mouseY = input.getMouseY();
|
||||||
frameAndRun(mouseX, mouseY, lastKeysPressed, trackPosition);
|
frameAndRun(mouseX, mouseY, lastKeysPressed, trackPosition);
|
||||||
|
@ -455,6 +565,61 @@ public class Game extends BasicGameState {
|
||||||
// addReplayFrame(mouseX, mouseY, keysPressed, trackPosition);
|
// addReplayFrame(mouseX, mouseY, keysPressed, trackPosition);
|
||||||
skipButton.hoverUpdate(delta, mouseX, mouseY);
|
skipButton.hoverUpdate(delta, mouseX, mouseY);
|
||||||
|
|
||||||
|
// "flashlight" mod: calculate visible area radius
|
||||||
|
if (GameMod.FLASHLIGHT.isActive()) {
|
||||||
|
int width = container.getWidth(), height = container.getHeight();
|
||||||
|
boolean firstObject = (objectIndex == 0 && trackPosition < osu.objects[0].getTime());
|
||||||
|
if (isLeadIn()) {
|
||||||
|
// lead-in: expand area
|
||||||
|
float progress = Math.max((float) (leadInTime - osu.audioLeadIn) / approachTime, 0f);
|
||||||
|
flashlightRadius = width - (int) ((width - (height * 2 / 3)) * progress);
|
||||||
|
} else if (firstObject) {
|
||||||
|
// before first object: shrink area
|
||||||
|
int timeDiff = osu.objects[0].getTime() - trackPosition;
|
||||||
|
flashlightRadius = width;
|
||||||
|
if (timeDiff < approachTime) {
|
||||||
|
float progress = (float) timeDiff / approachTime;
|
||||||
|
flashlightRadius -= (width - (height * 2 / 3)) * (1 - progress);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// gameplay: size based on combo
|
||||||
|
int targetRadius;
|
||||||
|
int combo = data.getComboStreak();
|
||||||
|
if (combo < 100)
|
||||||
|
targetRadius = height * 2 / 3;
|
||||||
|
else if (combo < 200)
|
||||||
|
targetRadius = height / 2;
|
||||||
|
else
|
||||||
|
targetRadius = height / 3;
|
||||||
|
if (osu.breaks != null && breakIndex < osu.breaks.size() && breakTime > 0) {
|
||||||
|
// breaks: expand at beginning, shrink at end
|
||||||
|
flashlightRadius = targetRadius;
|
||||||
|
int endTime = osu.breaks.get(breakIndex);
|
||||||
|
int breakLength = endTime - breakTime;
|
||||||
|
if (breakLength > approachTime * 3) {
|
||||||
|
float progress = 1f;
|
||||||
|
if (trackPosition - breakTime < approachTime)
|
||||||
|
progress = (float) (trackPosition - breakTime) / approachTime;
|
||||||
|
else if (endTime - trackPosition < approachTime)
|
||||||
|
progress = (float) (endTime - trackPosition) / approachTime;
|
||||||
|
flashlightRadius += (width - flashlightRadius) * progress;
|
||||||
|
}
|
||||||
|
} else if (flashlightRadius != targetRadius) {
|
||||||
|
// radius size change
|
||||||
|
float radiusDiff = height * delta / 2000f;
|
||||||
|
if (flashlightRadius > targetRadius) {
|
||||||
|
flashlightRadius -= radiusDiff;
|
||||||
|
if (flashlightRadius < targetRadius)
|
||||||
|
flashlightRadius = targetRadius;
|
||||||
|
} else {
|
||||||
|
flashlightRadius += radiusDiff;
|
||||||
|
if (flashlightRadius > targetRadius)
|
||||||
|
flashlightRadius = targetRadius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// returning from pause screen: must click previous mouse position
|
// returning from pause screen: must click previous mouse position
|
||||||
if (pauseTime > -1) {
|
if (pauseTime > -1) {
|
||||||
|
@ -505,6 +670,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);
|
||||||
|
@ -515,13 +681,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()]), osu);
|
Replay r = data.getReplay(replayFrames.toArray(new ReplayFrame[replayFrames.size()]), osu);
|
||||||
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));
|
||||||
|
@ -810,13 +976,23 @@ public class Game extends BasicGameState {
|
||||||
if (GameMod.AUTO.isActive() || GameMod.RELAX.isActive())
|
if (GameMod.AUTO.isActive() || GameMod.RELAX.isActive())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// "autopilot" mod: ignore actual cursor coordinates
|
||||||
|
int cx, cy;
|
||||||
|
if (GameMod.AUTOPILOT.isActive()) {
|
||||||
|
cx = autoMouseX;
|
||||||
|
cy = autoMouseY;
|
||||||
|
} else {
|
||||||
|
cx = x;
|
||||||
|
cy = y;
|
||||||
|
}
|
||||||
|
|
||||||
// circles
|
// circles
|
||||||
if (hitObject.isCircle() && hitObjects[objectIndex].mousePressed(x, y, trackPosition))
|
if (hitObject.isCircle() && hitObjects[objectIndex].mousePressed(cx, cy, trackPosition))
|
||||||
objectIndex++; // circle hit
|
objectIndex++; // circle hit
|
||||||
|
|
||||||
// sliders
|
// sliders
|
||||||
else if (hitObject.isSlider())
|
else if (hitObject.isSlider())
|
||||||
hitObjects[objectIndex].mousePressed(x, y, trackPosition);
|
hitObjects[objectIndex].mousePressed(cx, cy, trackPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -837,9 +1013,7 @@ public class Game extends BasicGameState {
|
||||||
public void keyReleased(int key, char c) {
|
public void keyReleased(int key, char c) {
|
||||||
if (!isReplay && (key == Options.getGameKeyLeft() || key == Options.getGameKeyRight())) {
|
if (!isReplay && (key == Options.getGameKeyLeft() || key == Options.getGameKeyRight())) {
|
||||||
lastKeysPressed &= ~((key == Options.getGameKeyLeft()) ? ReplayFrame.KEY_K1 : ReplayFrame.KEY_K2);
|
lastKeysPressed &= ~((key == Options.getGameKeyLeft()) ? ReplayFrame.KEY_K1 : ReplayFrame.KEY_K2);
|
||||||
int mouseX = input.getMouseX();
|
frameAndRun(input.getMouseX(), input.getMouseY(), lastKeysPressed, MusicController.getPosition());
|
||||||
int mouseY = input.getMouseY();
|
|
||||||
frameAndRun(mouseX, mouseY, lastKeysPressed, MusicController.getPosition());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -920,11 +1094,13 @@ 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();
|
||||||
|
|
||||||
|
lastReplayTime = 0;
|
||||||
|
// load replay frames
|
||||||
|
if (isReplay) {
|
||||||
// load mods
|
// load mods
|
||||||
previousMods = GameMod.getModState();
|
previousMods = GameMod.getModState();
|
||||||
GameMod.loadModState(replay.mods);
|
GameMod.loadModState(replay.mods);
|
||||||
|
@ -953,7 +1129,6 @@ public class Game extends BasicGameState {
|
||||||
|
|
||||||
// initialize replay-recording structures
|
// initialize replay-recording structures
|
||||||
else {
|
else {
|
||||||
lastReplayTime = 0;
|
|
||||||
lastKeysPressed = ReplayFrame.KEY_NONE;
|
lastKeysPressed = ReplayFrame.KEY_NONE;
|
||||||
replaySkipTime = -1;
|
replaySkipTime = -1;
|
||||||
replayFrames = new LinkedList<ReplayFrame>();
|
replayFrames = new LinkedList<ReplayFrame>();
|
||||||
|
@ -972,13 +1147,34 @@ 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws hit objects and hit results.
|
||||||
|
* @param g the graphics context
|
||||||
|
* @param trackPosition the track position
|
||||||
|
*/
|
||||||
|
private void drawHitObjects(Graphics g, int trackPosition) {
|
||||||
|
// draw hit objects in reverse order, or else overlapping objects are unreadable
|
||||||
|
Stack<Integer> stack = new Stack<Integer>();
|
||||||
|
for (int i = objectIndex; i < hitObjects.length && osu.objects[i].getTime() < trackPosition + approachTime; i++)
|
||||||
|
stack.add(i);
|
||||||
|
|
||||||
|
while (!stack.isEmpty())
|
||||||
|
hitObjects[stack.pop()].draw(g, trackPosition);
|
||||||
|
|
||||||
|
// draw OsuHitObjectResult objects
|
||||||
|
data.drawHitResults(trackPosition);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads all required data from an OsuFile.
|
* Loads all required data from an OsuFile.
|
||||||
* @param osu the OsuFile to load
|
* @param osu the OsuFile to load
|
||||||
|
@ -1016,6 +1212,9 @@ public class Game extends BasicGameState {
|
||||||
deaths = 0;
|
deaths = 0;
|
||||||
deathTime = -1;
|
deathTime = -1;
|
||||||
replayFrames = null;
|
replayFrames = null;
|
||||||
|
autoMouseX = 0;
|
||||||
|
autoMouseY = 0;
|
||||||
|
autoMousePressed = false;
|
||||||
|
|
||||||
System.gc();
|
System.gc();
|
||||||
}
|
}
|
||||||
|
@ -1216,16 +1415,31 @@ public class Game extends BasicGameState {
|
||||||
private ReplayFrame addReplayFrame(int x, int y, int keys, int time) {
|
private ReplayFrame addReplayFrame(int x, int y, int keys, int time) {
|
||||||
int timeDiff = time - lastReplayTime;
|
int timeDiff = time - lastReplayTime;
|
||||||
lastReplayTime = time;
|
lastReplayTime = time;
|
||||||
int cx = unscaleX(x);
|
int cx = (int) ((x - OsuHitObject.getXOffset()) / OsuHitObject.getXMultiplier());
|
||||||
int cy = unscaleY(y);
|
int cy = (int) ((y - OsuHitObject.getYOffset()) / OsuHitObject.getYMultiplier());
|
||||||
ReplayFrame tFrame = new ReplayFrame(timeDiff, time, cx, cy, keys);
|
ReplayFrame tFrame = new ReplayFrame(timeDiff, time, cx, cy, keys);
|
||||||
replayFrames.add(tFrame);
|
if(replayFrames != null)
|
||||||
|
replayFrames.add(tFrame);
|
||||||
return tFrame;
|
return tFrame;
|
||||||
}
|
}
|
||||||
private int unscaleX(int x){
|
|
||||||
return (int) ((x - OsuHitObject.getXOffset()) / OsuHitObject.getXMultiplier());
|
/**
|
||||||
}
|
* Returns the point at the t value between a start and end point.
|
||||||
private int unscaleY(int y){
|
* @param startX the starting x coordinate
|
||||||
return (int) ((y - OsuHitObject.getYOffset()) / OsuHitObject.getYMultiplier());
|
* @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) {
|
||||||
|
// "autopilot" mod: move quicker between objects
|
||||||
|
if (GameMod.AUTOPILOT.isActive())
|
||||||
|
t = Utils.clamp(t * 2f, 0f, 1f);
|
||||||
|
|
||||||
|
float[] xy = new float[2];
|
||||||
|
xy[0] = startX + (endX - startX) * t;
|
||||||
|
xy[1] = startY + (endY - startY) * t;
|
||||||
|
return xy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,7 +239,7 @@ public class MainMenu extends BasicGameState {
|
||||||
g.fillRoundRect(musicBarX, musicBarY, musicBarWidth, musicBarHeight, 4);
|
g.fillRoundRect(musicBarX, musicBarY, musicBarWidth, musicBarHeight, 4);
|
||||||
g.setColor(Color.white);
|
g.setColor(Color.white);
|
||||||
if (!MusicController.isTrackLoading() && osu != null) {
|
if (!MusicController.isTrackLoading() && osu != null) {
|
||||||
float musicBarPosition = Math.min((float) MusicController.getPosition() / osu.endTime, 1f);
|
float musicBarPosition = Math.min((float) MusicController.getPosition() / MusicController.getDuration(), 1f);
|
||||||
g.fillRoundRect(musicBarX, musicBarY, musicBarWidth * musicBarPosition, musicBarHeight, 4);
|
g.fillRoundRect(musicBarX, musicBarY, musicBarWidth * musicBarPosition, musicBarHeight, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,8 +430,7 @@ public class MainMenu extends BasicGameState {
|
||||||
if (MusicController.isPlaying()) {
|
if (MusicController.isPlaying()) {
|
||||||
if (musicPositionBarContains(x, y)) {
|
if (musicPositionBarContains(x, y)) {
|
||||||
float pos = (x - musicBarX) / musicBarWidth;
|
float pos = (x - musicBarX) / musicBarWidth;
|
||||||
OsuFile osu = MusicController.getOsuFile();
|
MusicController.setPosition((int) (pos * MusicController.getDuration()));
|
||||||
MusicController.setPosition((int) (pos * osu.endTime));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user