Reformatting of #20, and disable the hit error bar by default.

Added an option (in the "Custom" tab) to enable the bar.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2015-02-15 01:40:01 -05:00
parent b7f86f1962
commit 4826798fba
8 changed files with 138 additions and 113 deletions

View File

@ -39,6 +39,9 @@ public class GameData {
/** Delta multiplier for steady HP drain. */ /** Delta multiplier for steady HP drain. */
public static final float HP_DRAIN_MULTIPLIER = 1 / 200f; public static final float HP_DRAIN_MULTIPLIER = 1 / 200f;
/** Time, in milliseconds, for a hit error tick to fade. */
private static final int HIT_ERROR_FADE_TIME = 5000;
/** Letter grades. */ /** Letter grades. */
public enum Grade { public enum Grade {
NULL (null, null), NULL (null, null),
@ -150,29 +153,44 @@ public class GameData {
/** Current x coordinate of the combo burst image (for sliding animation). */ /** Current x coordinate of the combo burst image (for sliding animation). */
private int comboBurstX; private int comboBurstX;
/** Time offsets for obtaining each hit result (indexed by HIT_* constants). */
private int[] hitResultOffset;
/** List of hit result objects associated with hit objects. */ /** List of hit result objects associated with hit objects. */
private LinkedList<OsuHitObjectResult> hitResultList; private LinkedList<OsuHitObjectResult> hitResultList;
/** /**
* class to store Hit Error information. * Class to store hit error information.
* @author fluddokt * @author fluddokt
*/ */
class HitErrorInfo { private class HitErrorInfo {
int time, x, y, timeDiff; /** The correct hit time. */
private int time;
/** The coordinates of the hit. */
@SuppressWarnings("unused")
private int x, y;
/** The difference between the correct and actual hit times. */
private int timeDiff;
/**
* Constructor.
* @param time the correct hit time
* @param x the x coordinate of the hit
* @param y the y coordinate of the hit
* @param timeDiff the difference between the correct and actual hit times
*/
public HitErrorInfo(int time, int x, int y, int timeDiff) { public HitErrorInfo(int time, int x, int y, int timeDiff) {
this.time = time; this.time = time;
this.x = x; this.x = x;
this.y = y; this.y = y;
this.timeDiff = timeDiff; this.timeDiff = timeDiff;
} }
} }
/** /** List containing recent hit error information. */
* List of stored Hit Error Info private LinkedList<HitErrorInfo> hitErrorList;
*/
private LinkedList<HitErrorInfo> hitErrorList = new LinkedList<HitErrorInfo>();
/** /**
* Hit result helper class. * Hit result helper class.
@ -246,10 +264,6 @@ public class GameData {
/** Container dimensions. */ /** Container dimensions. */
private int width, height; private int width, height;
/** Time offsets for obtaining each hit result (indexed by HIT_* constants). */
private int[] hitResultOffset;
/** /**
* Constructor for gameplay. * Constructor for gameplay.
* @param width container width * @param width container width
@ -302,13 +316,13 @@ public class GameData {
healthDisplay = 100f; healthDisplay = 100f;
hitResultCount = new int[HIT_MAX]; hitResultCount = new int[HIT_MAX];
hitResultList = new LinkedList<OsuHitObjectResult>(); hitResultList = new LinkedList<OsuHitObjectResult>();
hitErrorList = new LinkedList<HitErrorInfo>();
fullObjectCount = 0; fullObjectCount = 0;
combo = 0; combo = 0;
comboMax = 0; comboMax = 0;
comboEnd = 0; comboEnd = 0;
comboBurstIndex = -1; comboBurstIndex = -1;
scoreData = null; scoreData = null;
hitErrorList.clear();
} }
/** /**
@ -390,10 +404,11 @@ public class GameData {
public void setDifficulty(float difficulty) { this.difficulty = difficulty; } public void setDifficulty(float difficulty) { this.difficulty = difficulty; }
public float getDifficulty() { return difficulty; } public float getDifficulty() { return difficulty; }
/** Sets the HitResultOffset */ /**
public void setHitResultOffset(int[] hitResultOffset) {this.hitResultOffset = hitResultOffset; } * Sets the array of hit result offsets.
*/
public void setHitResultOffset(int[] hitResultOffset) { this.hitResultOffset = hitResultOffset; }
/** /**
* Draws a number with defaultSymbols. * Draws a number with defaultSymbols.
* @param n the number to draw * @param n the number to draw
@ -446,7 +461,7 @@ public class GameData {
/** /**
* Draws game elements: * Draws game elements:
* scorebar, score, score percentage, map progress circle, * scorebar, score, score percentage, map progress circle,
* mod icons, combo count, combo burst, and grade. * mod icons, combo count, combo burst, hit error bar, and grade.
* @param g the graphics context * @param g the graphics context
* @param breakPeriod if true, will not draw scorebar and combo elements, and will draw grade * @param breakPeriod if true, will not draw scorebar and combo elements, and will draw grade
* @param firstObject true if the first hit object's start time has not yet passed * @param firstObject true if the first hit object's start time has not yet passed
@ -547,50 +562,46 @@ public class GameData {
// combo count // combo count
if (combo > 0) // 0 isn't a combo if (combo > 0) // 0 isn't a combo
drawSymbolString(String.format("%dx", combo), 10, height - 10 - symbolHeight, 1.0f, false); drawSymbolString(String.format("%dx", combo), 10, height - 10 - symbolHeight, 1.0f, false);
//* // hit error bar
//Draw Hit Error bar if (Options.isHitErrorBarEnabled()) {
final int fadeDelay = 5000; // draw bar
int hitErrorY = 30; int hitErrorY = 30;
Iterator<HitErrorInfo> iter2 = hitErrorList.iterator(); g.setColor(Color.black);
g.setColor(Color.black); g.fillRect(width / 2f - 3 - hitResultOffset[HIT_50],
g.fillRect(width / 2f - 3 - hitResultOffset[HIT_50], height - marginX - hitErrorY - 10,
height - marginX - hitErrorY - 10, hitResultOffset[HIT_50] * 2, 20);
hitResultOffset[HIT_50] * 2, g.setColor(Utils.COLOR_LIGHT_ORANGE);
20); g.fillRect(width / 2f - 3 - hitResultOffset[HIT_50],
g.setColor(Utils.COLOR_LIGHT_ORANGE); height - marginX - hitErrorY - 3,
g.fillRect(width / 2f - 3 - hitResultOffset[HIT_50], hitResultOffset[HIT_50] * 2, 6);
height - marginX - hitErrorY - 3, g.setColor(Utils.COLOR_LIGHT_GREEN);
hitResultOffset[HIT_50] * 2, g.fillRect(width / 2f - 3 - hitResultOffset[HIT_100],
6); height - marginX - hitErrorY - 3,
g.setColor(Utils.COLOR_LIGHT_GREEN); hitResultOffset[HIT_100] * 2, 6);
g.fillRect(width / 2f - 3 - hitResultOffset[HIT_100], g.setColor(Utils.COLOR_LIGHT_BLUE);
height - marginX - hitErrorY - 3, g.fillRect(width / 2f - 3 - hitResultOffset[HIT_300],
hitResultOffset[HIT_100] * 2, height - marginX - hitErrorY - 3,
6); hitResultOffset[HIT_300] * 2, 6);
g.setColor(Utils.COLOR_LIGHT_BLUE); g.setColor(Color.white);
g.fillRect(width / 2f - 3 - hitResultOffset[HIT_300], g.drawRect(width / 2f - 3, height - marginX - hitErrorY - 10, 6, 20);
height- marginX - hitErrorY - 3,
hitResultOffset[HIT_300] * 2, // draw ticks
6); Color white = new Color(Color.white);
g.setColor(Color.white); Iterator<HitErrorInfo> iter = hitErrorList.iterator();
g.drawRect(width / 2f - 3, height - marginX - hitErrorY - 10, 6, 20); while (iter.hasNext()) {
while (iter2.hasNext()) { HitErrorInfo info = iter.next();
HitErrorInfo info = iter2.next(); int time = info.time;
int time = info.time; if (Math.abs(info.timeDiff) < hitResultOffset[GameData.HIT_50] &&
if (Math.abs(info.timeDiff) < hitResultOffset[GameData.HIT_50] time + HIT_ERROR_FADE_TIME > trackPosition) {
&& time + fadeDelay > trackPosition) { float alpha = 1 - ((float) (trackPosition - time) / HIT_ERROR_FADE_TIME);
float alpha = 1 - ((float) (trackPosition - time) / fadeDelay); white.a = alpha;
Color col = new Color(Color.white); g.setColor(white);
col.a = alpha; g.fillRect(width / 2 + info.timeDiff - 1, height - marginX - hitErrorY - 10, 2, 20);
g.setColor(col); } else
g.fillRect(width / 2 + info.timeDiff - 1, height - marginX iter.remove();
- hitErrorY - 10, 2, 20);
} else {
iter2.remove();
} }
} }
//*/
} else { } else {
// grade // grade
Grade grade = getGrade(); Grade grade = getGrade();
@ -602,9 +613,6 @@ public class GameData {
); );
} }
} }
} }
/** /**
@ -728,7 +736,7 @@ public class GameData {
Image scaledLighting1 = GameImage.LIGHTING1.getImage().getScaledCopy(scale); Image scaledLighting1 = GameImage.LIGHTING1.getImage().getScaledCopy(scale);
scaledLighting.setAlpha(hitResult.alpha); scaledLighting.setAlpha(hitResult.alpha);
scaledLighting1.setAlpha(hitResult.alpha); scaledLighting1.setAlpha(hitResult.alpha);
scaledLighting.draw(hitResult.x - (scaledLighting.getWidth() / 2f), scaledLighting.draw(hitResult.x - (scaledLighting.getWidth() / 2f),
hitResult.y - (scaledLighting.getHeight() / 2f), hitResult.color); hitResult.y - (scaledLighting.getHeight() / 2f), hitResult.color);
scaledLighting1.draw(hitResult.x - (scaledLighting1.getWidth() / 2f), scaledLighting1.draw(hitResult.x - (scaledLighting1.getWidth() / 2f),
@ -1088,15 +1096,15 @@ public class GameData {
* @return true if gameplay, false if score viewing * @return true if gameplay, false if score viewing
*/ */
public boolean isGameplay() { return gameplay; } public boolean isGameplay() { return gameplay; }
/** /**
* add a Hit Error Info to the list. * Adds the hit into the list of hit error information.
* @param time when it should have been hit * @param time the correct hit time
* @param x, y the location that it hit * @param x the x coordinate of the hit
* @param timeDiff the difference from the time from when it was actually hit * @param y the y coordinate of the hit
* @param timeDiff the difference between the correct and actual hit times
*/ */
public void addHitError(int time, int x, int y, int timeDiff) { public void addHitError(int time, int x, int y, int timeDiff) {
hitErrorList.add(new HitErrorInfo(time, x, y, timeDiff)); hitErrorList.add(new HitErrorInfo(time, x, y, timeDiff));
} }
} }

View File

@ -363,6 +363,13 @@ public class Options {
@Override @Override
public void click(GameContainer container) { themeSongEnabled = !themeSongEnabled; } public void click(GameContainer container) { themeSongEnabled = !themeSongEnabled; }
},
SHOW_HIT_ERROR_BAR ("Show Hit Error Bar", "Displays hit accuracy information at the bottom of the screen.") {
@Override
public String getValueString() { return showHitErrorBar ? "Yes" : "No"; }
@Override
public void click(GameContainer container) { showHitErrorBar = !showHitErrorBar; }
}; };
/** Option name. */ /** Option name. */
@ -432,7 +439,6 @@ public class Options {
RES_2560_1440 (2560, 1440), RES_2560_1440 (2560, 1440),
RES_2560_1600 (2560, 1600), RES_2560_1600 (2560, 1600),
RES_3840_2160 (3840, 2160); RES_3840_2160 (3840, 2160);
/** Screen dimensions. */ /** Screen dimensions. */
private int width, height; private int width, height;
@ -535,6 +541,9 @@ public class Options {
/** Whether or not to play the theme song. */ /** Whether or not to play the theme song. */
private static boolean themeSongEnabled = true; private static boolean themeSongEnabled = true;
/** Whether or not to show the hit error bar. */
private static boolean showHitErrorBar = false;
/** Fixed difficulty overrides. */ /** Fixed difficulty overrides. */
private static float private static float
fixedCS = 0f, fixedHP = 0f, fixedCS = 0f, fixedHP = 0f,
@ -765,7 +774,7 @@ public class Options {
* Returns whether or not to play the theme song. * Returns whether or not to play the theme song.
* @return true if enabled * @return true if enabled
*/ */
public static boolean isThemSongEnabled() { return themeSongEnabled; } public static boolean isThemeSongEnabled() { return themeSongEnabled; }
/** /**
* Sets the track checkpoint time, if within bounds. * Sets the track checkpoint time, if within bounds.
@ -780,6 +789,12 @@ public class Options {
return false; return false;
} }
/**
* Returns whether or not to show the hit error bar.
* @return true if enabled
*/
public static boolean isHitErrorBarEnabled() { return showHitErrorBar; }
/** /**
* Returns the left game key. * Returns the left game key.
* @return the left key code * @return the left key code
@ -1042,6 +1057,9 @@ public class Options {
case "PerfectHit": case "PerfectHit":
showPerfectHit = Boolean.parseBoolean(value); showPerfectHit = Boolean.parseBoolean(value);
break; break;
case "HitErrorBar":
showHitErrorBar = Boolean.parseBoolean(value);
break;
case "FixedCS": case "FixedCS":
fixedCS = Float.parseFloat(value); fixedCS = Float.parseFloat(value);
break; break;
@ -1145,6 +1163,8 @@ public class Options {
writer.newLine(); writer.newLine();
writer.write(String.format("PerfectHit = %b", showPerfectHit)); writer.write(String.format("PerfectHit = %b", showPerfectHit));
writer.newLine(); writer.newLine();
writer.write(String.format("HitErrorBar = %b", showHitErrorBar));
writer.newLine();
writer.write(String.format(Locale.US, "FixedCS = %.1f", fixedCS)); writer.write(String.format(Locale.US, "FixedCS = %.1f", fixedCS));
writer.newLine(); writer.newLine();
writer.write(String.format(Locale.US, "FixedHP = %.1f", fixedHP)); writer.write(String.format(Locale.US, "FixedHP = %.1f", fixedHP));

View File

@ -277,7 +277,7 @@ public class OsuFile implements Comparable<OsuFile> {
else else
swidth = (int) (height * bgImage.getWidth() / (float) bgImage.getHeight()); swidth = (int) (height * bgImage.getWidth() / (float) bgImage.getHeight());
} else { } else {
//fill image to screen while keeping aspect ratio // fill screen while maintaining aspect ratio
if (bgImage.getWidth() / (float) bgImage.getHeight() > width / (float) height) // x > y if (bgImage.getWidth() / (float) bgImage.getHeight() > width / (float) height) // x > y
swidth = (int) (height * bgImage.getWidth() / (float) bgImage.getHeight()); swidth = (int) (height * bgImage.getWidth() / (float) bgImage.getHeight());
else else

View File

@ -321,6 +321,22 @@ public class Utils {
return val; return val;
} }
/**
* Clamps a value between a lower and upper bound.
* @param val the value to clamp
* @param low the lower bound
* @param high the upper bound
* @return the clamped value
* @author fluddokt
*/
public static float clamp(float val, int low, int high) {
if (val < low)
return low;
if (val > high)
return high;
return val;
}
/** /**
* Draws the cursor. * Draws the cursor.
*/ */
@ -875,20 +891,4 @@ public class Utils {
list.add(str); list.add(str);
return list; return list;
} }
/**
* Clamps the value to be in between low and high
* @param val
* @param low
* @param high
* @return val clamped between low and high
* @author fluddokt (https://github.com/fluddokt)
*/
public static float clamp(float val, int low, int high) {
if(val < low)
return low;
if(val > high)
return high;
return val;
}
} }

View File

@ -96,7 +96,7 @@ public class Circle implements HitObject {
Utils.COLOR_WHITE_FADE.a = alpha; Utils.COLOR_WHITE_FADE.a = alpha;
Utils.drawCentered(GameImage.HITCIRCLE.getImage(), x, y, color); Utils.drawCentered(GameImage.HITCIRCLE.getImage(), x, y, color);
Utils.drawCentered(GameImage.HITCIRCLE_OVERLAY.getImage(), x, y, Utils.COLOR_WHITE_FADE); Utils.drawCentered(GameImage.HITCIRCLE_OVERLAY.getImage(), x, y, Utils.COLOR_WHITE_FADE);
color.a = oldAlpha; color.a = oldAlpha;
Utils.COLOR_WHITE_FADE.a = 1f; Utils.COLOR_WHITE_FADE.a = 1f;
data.drawSymbolNumber(hitObject.getComboNumber(), x, y, data.drawSymbolNumber(hitObject.getComboNumber(), x, y,
@ -135,7 +135,7 @@ public class Circle implements HitObject {
int trackPosition = MusicController.getPosition(); int trackPosition = MusicController.getPosition();
int timeDiff = trackPosition - hitObject.getTime(); int timeDiff = trackPosition - hitObject.getTime();
int result = hitResult(timeDiff); int result = hitResult(timeDiff);
if (result > -1) { if (result > -1) {
data.addHitError(hitObject.getTime(), x, y, timeDiff); data.addHitError(hitObject.getTime(), x, y, timeDiff);
data.hitResult( data.hitResult(

View File

@ -141,23 +141,21 @@ public class Slider implements HitObject {
this.curve = new LinearBezier(hitObject, color); this.curve = new LinearBezier(hitObject, color);
} }
@SuppressWarnings("deprecation")
@Override @Override
public void draw(int trackPosition, boolean currentObject, Graphics g) { public void draw(int trackPosition, boolean currentObject, Graphics g) {
int timeDiff = hitObject.getTime() - trackPosition; int timeDiff = hitObject.getTime() - trackPosition;
float scale = timeDiff / (float) game.getApproachTime();
float approachScale = 1 + scale * 3;
float x = hitObject.getX(), y = hitObject.getY();
float oldAlpha = color.a; float oldAlpha = color.a;
float oldAlphaFade = Utils.COLOR_WHITE_FADE.a; float oldAlphaFade = Utils.COLOR_WHITE_FADE.a;
float alpha = (1 - scale);
float x = hitObject.getX(), y = hitObject.getY(); color.a = Utils.clamp(alpha * 0.5f, 0, 1);
float scale = timeDiff / (float)game.getApproachTime();
float approachScale = 1 + scale*3 ;
float alpha = (1 - scale);//(approachScale > 3.3f) ? 0f : 1f - (approachScale - 1f) / 2.7f;
color.a = Utils.clamp(alpha* 0.5f, 0, 1);
alpha = Utils.clamp(alpha, 0, 1); alpha = Utils.clamp(alpha, 0, 1);
Utils.COLOR_WHITE_FADE.a = alpha; Utils.COLOR_WHITE_FADE.a = alpha;
// curve // curve
curve.draw(); curve.draw();
@ -169,11 +167,10 @@ public class Slider implements HitObject {
tick.drawCentered(c[0], c[1]); tick.drawCentered(c[0], c[1]);
} }
} }
color.a = alpha;
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage(); Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
Image hitCircle = GameImage.HITCIRCLE.getImage(); Image hitCircle = GameImage.HITCIRCLE.getImage();
color.a = alpha;
// end circle // end circle
float[] endPos = curve.pointAt(1); float[] endPos = curve.pointAt(1);
@ -216,28 +213,27 @@ public class Slider implements HitObject {
if (timeDiff >= 0) { if (timeDiff >= 0) {
// approach circle // approach circle
color.a = 1 - scale; color.a = 1 - scale;
Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), x, y, color); Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), x, y, color);
} else { } else {
float[] c = curve.pointAt(getT(trackPosition, false)); float[] c = curve.pointAt(getT(trackPosition, false));
float[] c2 = curve.pointAt(getT(trackPosition, false) + 0.01f); float[] c2 = curve.pointAt(getT(trackPosition, false) + 0.01f);
// slider ball // slider ball
sliderBall.updateNoDraw();//TODO ..... I can't thing of anything else // TODO: deprecated method
//It might be better to make your own animation class or something // TODO 2: update the animation based on the distance traveled?
//also it might be better to update the animation based on the distance traveled sliderBall.updateNoDraw();
Image sliderBallFrame = sliderBall.getCurrentFrame(); Image sliderBallFrame = sliderBall.getCurrentFrame();
float angle = (float) (Math.atan2(c2[1] - c[1], c2[0] - c[0]) * 180 / Math.PI) float angle = (float) (Math.atan2(c2[1] - c[1], c2[0] - c[0]) * 180 / Math.PI);
+ (currentRepeats % 2 == 1 ? 180 : 0); if (currentRepeats % 2 == 1)
angle += 180;
sliderBallFrame.setRotation(angle); sliderBallFrame.setRotation(angle);
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]);
} }
} }
/** /**
* Calculates the slider hit result. * Calculates the slider hit result.

View File

@ -83,7 +83,8 @@ public class OptionsMenu extends BasicGameState {
GameOption.FIXED_HP, GameOption.FIXED_HP,
GameOption.FIXED_AR, GameOption.FIXED_AR,
GameOption.FIXED_OD, GameOption.FIXED_OD,
GameOption.CHECKPOINT GameOption.CHECKPOINT,
GameOption.SHOW_HIT_ERROR_BAR
}); });
/** Total number of tabs. */ /** Total number of tabs. */

View File

@ -126,7 +126,7 @@ public class Splash extends BasicGameState {
// initialize song list // initialize song list
if (OsuGroupList.get().size() > 0) { if (OsuGroupList.get().size() > 0) {
OsuGroupList.get().init(); OsuGroupList.get().init();
if (Options.isThemSongEnabled()) if (Options.isThemeSongEnabled())
MusicController.playThemeSong(); MusicController.playThemeSong();
else else
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).setFocus(OsuGroupList.get().getRandomNode(), -1, true); ((SongMenu) game.getState(Opsu.STATE_SONGMENU)).setFocus(OsuGroupList.get().getRandomNode(), -1, true);