Follow-up to #211: fix slider hit results not appearing; code cleanup.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2016-12-19 16:33:20 -05:00
parent d111fc0efe
commit e161d0180e
5 changed files with 106 additions and 82 deletions

View File

@ -153,10 +153,10 @@ public class GameData {
HIT_SLIDER_REPEAT = 10, // not a hit result
HIT_ANIMATION_RESULT = 11; // not a hit result
/** Hit result-related images (indexed by HIT_* constants). */
/** Hit result-related images (indexed by HIT_* constants to HIT_MAX). */
private Image[] hitResults;
/** Counts of each hit result so far. */
/** Counts of each hit result so far (indexed by HIT_* constants to HIT_MAX). */
private int[] hitResultCount;
/** Total objects including slider hits/ticks (for determining Full Combo status). */
@ -192,7 +192,7 @@ public class GameData {
/** Current x coordinate of the combo burst image (for sliding animation). */
private float comboBurstX;
/** Time offsets for obtaining each hit result (indexed by HIT_* constants). */
/** Time offsets for obtaining each hit result (indexed by HIT_* constants to HIT_MAX). */
private int[] hitResultOffset;
/** List of hit result objects associated with hit objects. */
@ -232,7 +232,7 @@ public class GameData {
private LinkedBlockingDeque<HitErrorInfo> hitErrorList;
/** Hit object types, used for drawing results. */
public enum HitObjectType { CIRCLE, SLIDERTICK, SLIDER_FIRST, SLIDER_CURVE, SLIDER_LAST, SPINNER }
public enum HitObjectType { CIRCLE, SLIDERTICK, SLIDER_FIRST, SLIDER_LAST, SPINNER }
/** Hit result helper class. */
private class HitObjectResult {
@ -892,7 +892,7 @@ public class GameData {
lighting.drawCentered(hitResult.x, hitResult.y, hitResult.color);
}
// hit animations, only draw when the 'hidden' mod is not enabled
// hit animations (only draw when the "Hidden" mod is not enabled)
if (!GameMod.HIDDEN.isActive()) {
drawHitAnimations(hitResult, trackPosition);
}
@ -900,8 +900,9 @@ public class GameData {
// hit result
if (!hitResult.hideResult && (
hitResult.hitResultType == HitObjectType.CIRCLE ||
hitResult.hitResultType == HitObjectType.SPINNER ||
hitResult.curve != null)) {
hitResult.hitResultType == HitObjectType.SLIDER_FIRST ||
hitResult.hitResultType == HitObjectType.SLIDER_LAST ||
hitResult.hitResultType == HitObjectType.SPINNER)) {
float scaleProgress = AnimationEquation.IN_OUT_BOUNCE.calc(
(float) Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_TEXT_BOUNCE_TIME) / HITCIRCLE_TEXT_BOUNCE_TIME);
float scale = 1f + (HITCIRCLE_TEXT_ANIM_SCALE - 1f) * scaleProgress;
@ -923,43 +924,42 @@ public class GameData {
}
/**
* Draw the hit animations: circles, reversearrows, slider curves fading out and/or expanding
* @param hitResult the hitresult which holds information about the kind of animation to draw
* Draw the hit animations:
* circles, reverse arrows, slider curves (fading out and/or expanding).
* @param hitResult the hit result
* @param trackPosition the current track position (in ms)
*/
private void drawHitAnimations(HitObjectResult hitResult, int trackPosition) {
if (hitResult.hitResultType == HitObjectType.SLIDER_CURVE && hitResult.curve != null) {
// fade out slider curve
if (hitResult.result != HIT_SLIDER_REPEAT && hitResult.curve != null) {
float progress = AnimationEquation.OUT_CUBIC.calc(
(float) Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_FADE_TIME) / HITCIRCLE_FADE_TIME);
float alpha = 1f - progress;
// slider curve
float oldWhiteAlpha = Colors.WHITE_FADE.a;
float oldColorAlpha = hitResult.color.a;
Colors.WHITE_FADE.a = alpha;
hitResult.color.a = alpha;
Colors.WHITE_FADE.a = hitResult.color.a = alpha;
hitResult.curve.draw(hitResult.color);
Colors.WHITE_FADE.a = oldWhiteAlpha;
hitResult.color.a = oldColorAlpha;
return;
}
// miss, don't draw an animation
if (hitResult.result == HIT_MISS) {
return;
}
if (hitResult.hitResultType != HitObjectType.CIRCLE
&& hitResult.hitResultType != HitObjectType.SLIDER_FIRST
&& hitResult.hitResultType != HitObjectType.SLIDER_LAST) {
// not a circle?
if (hitResult.hitResultType != HitObjectType.CIRCLE &&
hitResult.hitResultType != HitObjectType.SLIDER_FIRST &&
hitResult.hitResultType != HitObjectType.SLIDER_LAST) {
return;
}
// circles
// hit circles
float progress = AnimationEquation.OUT_CUBIC.calc(
(float) Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_FADE_TIME) / HITCIRCLE_FADE_TIME);
float scale = (!hitResult.expand) ? 1f : 1f + (HITCIRCLE_ANIM_SCALE - 1f) * progress;
float alpha = 1f - progress;
if (hitResult.result == HIT_SLIDER_REPEAT) {
// repeats
Image scaledRepeat = GameImage.REVERSEARROW.getImage().getScaledCopy(scale);
@ -973,7 +973,6 @@ public class GameData {
scaledRepeat.rotate(ang);
scaledRepeat.drawCentered(hitResult.x, hitResult.y, hitResult.color);
}
// hit circles
Image scaledHitCircle = GameImage.HITCIRCLE.getImage().getScaledCopy(scale);
Image scaledHitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(scale);
scaledHitCircle.setAlpha(alpha);
@ -1210,15 +1209,28 @@ public class GameData {
health = 0f;
}
public void sendRepeatSliderResult(int time, float x, float y, Color color, Curve curve, HitObjectType type) {
/**
* Handles a slider repeat result (animation only: arrow).
* @param time the repeat time
* @param x the x coordinate
* @param y the y coordinate
* @param color the arrow color
* @param curve the slider curve
* @param type the hit object type
*/
public void sendSliderRepeatResult(int time, float x, float y, Color color, Curve curve, HitObjectType type) {
hitResultList.add(new HitObjectResult(time, HIT_SLIDER_REPEAT, x, y, color, type, curve, true, true));
}
public void sendSliderCurveResult(int time, Color color, Curve curve) {
hitResultList.add(new HitObjectResult(time, HIT_SLIDER_REPEAT, 0f, 0f, color, HitObjectType.SLIDER_CURVE, curve, true, true));
}
public void sendAnimationResult(int time, float x, float y, Color color, boolean expand) {
/**
* Handles a slider start result (animation only: initial circle).
* @param time the hit time
* @param x the x coordinate
* @param y the y coordinate
* @param color the slider color
* @param expand whether or not the hit result animation should expand
*/
public void sendSliderStartResult(int time, float x, float y, Color color, boolean expand) {
hitResultList.add(new HitObjectResult(time, HIT_ANIMATION_RESULT, x, y, color, HitObjectType.CIRCLE, null, expand, true));
}
@ -1231,7 +1243,7 @@ public class GameData {
* @param hitObject the hit object
* @param repeat the current repeat number
*/
public void sliderTickResult(int time, int result, float x, float y, HitObject hitObject, int repeat) {
public void sendSliderTickResult(int time, int result, float x, float y, HitObject hitObject, int repeat) {
int hitValue = 0;
switch (result) {
case HIT_SLIDER30:
@ -1412,11 +1424,12 @@ public class GameData {
* @param curve the slider curve (or null if not applicable)
* @param sliderHeldToEnd whether or not the slider was held to the end (if applicable)
*/
public void hitResult(int time, int result, float x, float y, Color color,
public void sendHitResult(
int time, int result, float x, float y, Color color,
boolean end, HitObject hitObject, HitObjectType hitResultType,
boolean expand, int repeat, Curve curve, boolean sliderHeldToEnd) {
int hitResult = handleHitResult(time, result, x, y, color, end, hitObject,
hitResultType, repeat, (curve != null && !sliderHeldToEnd));
boolean expand, int repeat, Curve curve, boolean sliderHeldToEnd
) {
int hitResult = handleHitResult(time, result, x, y, color, end, hitObject, hitResultType, repeat, (curve != null && !sliderHeldToEnd));
if (hitResult == HIT_MISS && (GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive()))
return; // "relax" and "autopilot" mods: hide misses

View File

@ -63,6 +63,7 @@ import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.newdawn.slick.Animation;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Input;
import org.newdawn.slick.state.StateBasedGame;
@ -157,6 +158,14 @@ public class Utils {
anim.draw(x - (anim.getWidth() / 2f), y - (anim.getHeight() / 2f));
}
/**
* Returns the luminance of a color.
* @param c the color
*/
public static float getLuminance(Color c) {
return 0.299f*c.r + 0.587f*c.g + 0.114f*c.b;
}
/**
* Clamps a value between a lower and upper bound.
* @param val the value to clamp

View File

@ -156,7 +156,7 @@ public class Circle implements GameObject {
if (result > -1) {
data.addHitError(hitObject.getTime(), x, y, timeDiff);
data.hitResult(trackPosition, result, this.x, this.y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
data.sendHitResult(trackPosition, result, this.x, this.y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
return true;
}
}
@ -172,17 +172,17 @@ public class Circle implements GameObject {
if (trackPosition > time + hitResultOffset[GameData.HIT_50]) {
if (isAutoMod) // "auto" mod: catch any missed notes due to lag
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
data.sendHitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
else // no more points can be scored, so send a miss
data.hitResult(trackPosition, GameData.HIT_MISS, x, y, null, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
data.sendHitResult(trackPosition, GameData.HIT_MISS, x, y, null, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
return true;
}
// "auto" mod: send a perfect hit result
else if (isAutoMod) {
if (Math.abs(trackPosition - time) < hitResultOffset[GameData.HIT_300]) {
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
data.sendHitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
return true;
}
}

View File

@ -180,7 +180,7 @@ public class Slider implements GameObject {
@Override
public void draw(Graphics g, int trackPosition) {
int timeDiff = hitObject.getTime() - trackPosition;
final int repeats = hitObject.getRepeatCount();
final int repeatCount = hitObject.getRepeatCount();
final int approachTime = game.getApproachTime();
final int fadeInTime = game.getFadeInTime();
float scale = timeDiff / (float) approachTime;
@ -198,7 +198,7 @@ public class Slider implements GameObject {
float oldWhiteFadeAlpha = Colors.WHITE_FADE.a;
float sliderAlpha = 1f;
if (GameMod.HIDDEN.isActive() && trackPosition > hitObject.getTime()) {
// "hidden" mod: fade out sliders
// "Hidden" mod: fade out sliders
Colors.WHITE_FADE.a = color.a = sliderAlpha =
Math.max(0f, 1f - ((float) (trackPosition - hitObject.getTime()) / (getEndTime() - hitObject.getTime())) * 1.05f);
}
@ -206,8 +206,8 @@ public class Slider implements GameObject {
float curveInterval = Options.isSliderSnaking() ? alpha : 1f;
curve.draw(color,curveInterval);
// end circle, only draw if ball still has to go there
if (curveInterval == 1f && currentRepeats < repeats - (repeats % 2 == 0 ? 1 : 0)) {
// end circle (only draw if ball still has to go there)
if (curveInterval == 1f && currentRepeats < repeatCount - (repeatCount % 2 == 0 ? 1 : 0)) {
Color circleColor = new Color(color);
Color overlayColor = new Color(Colors.WHITE_FADE);
if (currentRepeats == 0) {
@ -233,7 +233,7 @@ public class Slider implements GameObject {
}
// start circle, only draw if ball still has to go there
if (!sliderClickedInitial || currentRepeats < repeats - (repeats % 2 == 1 ? 1 : 0)) {
if (!sliderClickedInitial || currentRepeats < repeatCount - (repeatCount % 2 == 1 ? 1 : 0)) {
hitCircle.drawCentered(x, y, firstCircleColor);
if (!overlayAboveNumber || sliderClickedInitial)
hitCircleOverlay.drawCentered(x, y, startCircleOverlayColor);
@ -256,7 +256,7 @@ public class Slider implements GameObject {
}
}
// draw combonumber and overlay if not initially clicked
// draw combo number and overlay if not initially clicked
if (!sliderClickedInitial) {
data.drawSymbolNumber(hitObject.getComboNumber(), x, y,
hitCircle.getWidth() * 0.40f / data.getDefaultSymbolImage(0).getHeight(), alpha);
@ -269,11 +269,11 @@ public class Slider implements GameObject {
// repeats
if (curveInterval == 1.0f) {
for (int tcurRepeat = currentRepeats; tcurRepeat <= currentRepeats + 1 && tcurRepeat < repeats - 1; tcurRepeat++) {
for (int tcurRepeat = currentRepeats; tcurRepeat <= currentRepeats + 1 && tcurRepeat < repeatCount - 1; tcurRepeat++) {
Image arrow = GameImage.REVERSEARROW.getImage();
// bouncing animation
//arrow = arrow.getScaledCopy((float) (1 + 0.2d * ((trackPosition + sliderTime * tcurRepeat) % 292) / 292));
float colorLuminance = 0.299f*color.r + 0.587f*color.g + 0.114f*color.b;
float colorLuminance = Utils.getLuminance(color);
Color arrowColor = colorLuminance < 0.8f ? Color.white : Color.black;
if (tcurRepeat == 0) {
arrow.setAlpha(Options.isSliderSnaking() ? decorationsAlpha : 1f);
@ -469,19 +469,21 @@ public class Slider implements GameObject {
float cx, cy;
HitObjectType type;
if (currentRepeats % 2 == 0) { // last circle
if (currentRepeats % 2 == 0) {
// last circle
Vec2f lastPos = curve.pointAt(1);
cx = lastPos.x;
cy = lastPos.y;
type = HitObjectType.SLIDER_LAST;
} else { // first circle
} else {
// first circle
cx = x;
cy = y;
type = HitObjectType.SLIDER_FIRST;
}
data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
data.sendHitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
cx, cy, color, comboEnd, hitObject, type, sliderHeldToEnd,
currentRepeats + 1, null, sliderHeldToEnd);
currentRepeats + 1, curve, sliderHeldToEnd);
return result;
}
@ -500,17 +502,17 @@ public class Slider implements GameObject {
if (timeDiff < hitResultOffset[GameData.HIT_50]) {
result = GameData.HIT_SLIDER30;
ticksHit++;
data.sendAnimationResult(trackPosition, this.x, this.y, color, true);
data.sendSliderStartResult(trackPosition, this.x, this.y, color, true);
} else if (timeDiff < hitResultOffset[GameData.HIT_MISS]) {
result = GameData.HIT_MISS;
data.sendAnimationResult(trackPosition, this.x, this.y, color, false);
data.sendSliderStartResult(trackPosition, this.x, this.y, color, false);
}
//else not a hit
if (result > -1) {
data.addHitError(hitObject.getTime(), x,y,trackPosition - hitObject.getTime());
sliderClickedInitial = true;
data.sliderTickResult(hitObject.getTime(), result, this.x, this.y, hitObject, currentRepeats);
data.sendSliderTickResult(hitObject.getTime(), result, this.x, this.y, hitObject, currentRepeats);
return true;
}
}
@ -531,11 +533,11 @@ public class Slider implements GameObject {
sliderClickedInitial = true;
if (isAutoMod) { // "auto" mod: catch any missed notes due to lag
ticksHit++;
data.sliderTickResult(time, GameData.HIT_SLIDER30, x, y, hitObject, currentRepeats);
data.sendAnimationResult(time, x, y, color, true);
data.sendSliderTickResult(time, GameData.HIT_SLIDER30, x, y, hitObject, currentRepeats);
data.sendSliderStartResult(time, x, y, color, true);
} else {
data.sliderTickResult(time, GameData.HIT_MISS, x, y, hitObject, currentRepeats);
data.sendAnimationResult(trackPosition, x, y, color, false);
data.sendSliderTickResult(time, GameData.HIT_MISS, x, y, hitObject, currentRepeats);
data.sendSliderStartResult(trackPosition, x, y, color, false);
}
}
@ -544,8 +546,8 @@ public class Slider implements GameObject {
if (Math.abs(trackPosition - time) < hitResultOffset[GameData.HIT_300]) {
ticksHit++;
sliderClickedInitial = true;
data.sliderTickResult(time, GameData.HIT_SLIDER30, x, y, hitObject, currentRepeats);
data.sendAnimationResult(time, x, y, color, true);
data.sendSliderTickResult(time, GameData.HIT_SLIDER30, x, y, hitObject, currentRepeats);
data.sendSliderStartResult(time, x, y, color, true);
}
}
@ -578,8 +580,6 @@ public class Slider implements GameObject {
// calculate and send slider result
hitResult();
// send 'curve fade out' hit result
data.sendSliderCurveResult(getEndTime(), color, curve);
return true;
}
@ -629,29 +629,31 @@ public class Slider implements GameObject {
HitObjectType type;
float posX, posY;
if (currentRepeats % 2 > 0) { // last circle
if (currentRepeats % 2 > 0) {
// last circle
type = HitObjectType.SLIDER_LAST;
Vec2f endPos = curve.pointAt(1f);
posX = endPos.x;
posY = endPos.y;
} else { // first circle
} else {
// first circle
type = HitObjectType.SLIDER_FIRST;
posX = this.x;
posY = this.y;
}
data.sliderTickResult(trackPosition, GameData.HIT_SLIDER30,
data.sendSliderTickResult(trackPosition, GameData.HIT_SLIDER30,
posX, posY, hitObject, currentRepeats);
// send hit result, to fade out reversearrow
float colorLuminance = 0.299f*color.r + 0.587f*color.g + 0.114f*color.b;
// fade out reverse arrow
float colorLuminance = Utils.getLuminance(color);
Color arrowColor = colorLuminance < 0.8f ? Color.white : Color.black;
data.sendRepeatSliderResult(trackPosition, posX, posY, arrowColor, curve, type);
data.sendSliderRepeatResult(trackPosition, posX, posY, arrowColor, curve, type);
}
// held during new tick
if (isNewTick) {
ticksHit++;
data.sliderTickResult(trackPosition, GameData.HIT_SLIDER10,
data.sendSliderTickResult(trackPosition, GameData.HIT_SLIDER10,
c.x, c.y, hitObject, currentRepeats);
}
@ -662,9 +664,9 @@ public class Slider implements GameObject {
followCircleActive = false;
if (isNewRepeat)
data.sliderTickResult(trackPosition, GameData.HIT_MISS, 0, 0, hitObject, currentRepeats);
data.sendSliderTickResult(trackPosition, GameData.HIT_MISS, 0, 0, hitObject, currentRepeats);
if (isNewTick)
data.sliderTickResult(trackPosition, GameData.HIT_MISS, 0, 0, hitObject, currentRepeats);
data.sendSliderTickResult(trackPosition, GameData.HIT_MISS, 0, 0, hitObject, currentRepeats);
}
return false;

View File

@ -252,7 +252,7 @@ public class Spinner implements GameObject {
else
result = GameData.HIT_MISS;
data.hitResult(hitObject.getEndTime(), result, width / 2, height / 2,
data.sendHitResult(hitObject.getEndTime(), result, width / 2, height / 2,
Color.transparent, true, hitObject, HitObjectType.SPINNER, true, 0, null, false);
return result;
}