Spinner Test2

Improves Score accuracy
(still mostly wrong)

inital replay seek
This commit is contained in:
fd
2015-04-05 11:24:05 -04:00
parent fa9accfc88
commit 40e67cedc9
17 changed files with 348 additions and 92 deletions

View File

@@ -36,6 +36,8 @@ public class Circle implements HitObject {
/** The amount of time, in milliseconds, to fade in the circle. */
private static final int FADE_IN_TIME = 375;
private static float diameter;
/** The associated OsuHitObject. */
private OsuHitObject hitObject;
@@ -54,17 +56,19 @@ public class Circle implements HitObject {
/** Whether or not the circle result ends the combo streak. */
private boolean comboEnd;
/**
* Initializes the Circle data type with map modifiers, images, and dimensions.
* @param container the game container
* @param circleSize the map's circleSize value
*/
public static void init(GameContainer container, float circleSize) {
int diameter = (int) (104 - (circleSize * 8));
diameter = (int) (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480)
GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameter, diameter));
GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(diameter, diameter));
GameImage.APPROACHCIRCLE.setImage(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(diameter, diameter));
diameter = (108 - (circleSize * 8));
diameter = (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480)
int diameterInt = (int)diameter;
GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameterInt, diameterInt));
GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(diameterInt, diameterInt));
GameImage.APPROACHCIRCLE.setImage(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(diameterInt, diameterInt));
}
/**
@@ -113,16 +117,19 @@ public class Circle implements HitObject {
private int hitResult(int time) {
int timeDiff = Math.abs(time);
int[] hitResultOffset = game.getHitResultOffsets();
int result = -1;
if (timeDiff < hitResultOffset[GameData.HIT_300])
if (timeDiff <= hitResultOffset[GameData.HIT_300])
result = GameData.HIT_300;
else if (timeDiff < hitResultOffset[GameData.HIT_100])
else if (timeDiff <= hitResultOffset[GameData.HIT_100])
result = GameData.HIT_100;
else if (timeDiff < hitResultOffset[GameData.HIT_50])
else if (timeDiff <= hitResultOffset[GameData.HIT_50])
result = GameData.HIT_50;
else if (timeDiff < hitResultOffset[GameData.HIT_MISS])
else if (timeDiff <= hitResultOffset[GameData.HIT_MISS]){
result = GameData.HIT_MISS;
System.out.println(timeDiff);
}
//else not a hit
return result;
@@ -131,8 +138,7 @@ public class Circle implements HitObject {
@Override
public boolean mousePressed(int x, int y, int trackPosition) {
double distance = Math.hypot(this.x - x, this.y - y);
int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2;
if (distance < circleRadius) {
if (distance < diameter/2) {
int timeDiff = trackPosition - hitObject.getTime();
int result = hitResult(timeDiff);
@@ -152,7 +158,7 @@ public class Circle implements HitObject {
int[] hitResultOffset = game.getHitResultOffsets();
boolean isAutoMod = GameMod.AUTO.isActive();
if (overlap || trackPosition > time + hitResultOffset[GameData.HIT_50]) {
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, 0);
@@ -187,4 +193,9 @@ public class Circle implements HitObject {
this.x = hitObject.getScaledX();
this.y = hitObject.getScaledY();
}
@Override
public void reset() {
}
}

View File

@@ -63,4 +63,9 @@ public class DummyObject implements HitObject {
this.x = hitObject.getScaledX();
this.y = hitObject.getScaledY();
}
@Override
public void reset() {
}
}

View File

@@ -69,4 +69,9 @@ public interface HitObject {
* Updates the position of the hit object.
*/
public void updatePosition();
/**
* Resets the hit object so that it can be reused.
*/
public void reset();
}

View File

@@ -46,6 +46,10 @@ public class Slider implements HitObject {
/** Rate at which slider ticks are placed. */
private static float sliderTickRate = 1.0f;
private static float followRadius;
private static float diameter;
/** The amount of time, in milliseconds, to fade in the slider. */
private static final int FADE_IN_TIME = 375;
@@ -97,6 +101,8 @@ public class Slider implements HitObject {
/** Container dimensions. */
private static int containerWidth, containerHeight;
/**
* Initializes the Slider data type with images and dimensions.
@@ -107,10 +113,13 @@ public class Slider implements HitObject {
public static void init(GameContainer container, float circleSize, OsuFile osu) {
containerWidth = container.getWidth();
containerHeight = container.getHeight();
int diameter = (int) (104 - (circleSize * 8));
diameter = (int) (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480)
diameter = (108 - (circleSize * 8));
diameter = (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480)
int diameterInt = (int)diameter;
followRadius = diameter / 2 * 3f;
// slider ball
if (GameImage.SLIDER_BALL.hasSkinImages() ||
(!GameImage.SLIDER_BALL.hasSkinImage() && GameImage.SLIDER_BALL.getImages() != null))
@@ -118,11 +127,11 @@ public class Slider implements HitObject {
else
sliderBallImages = new Image[]{ GameImage.SLIDER_BALL.getImage() };
for (int i = 0; i < sliderBallImages.length; i++)
sliderBallImages[i] = sliderBallImages[i].getScaledCopy(diameter * 118 / 128, diameter * 118 / 128);
sliderBallImages[i] = sliderBallImages[i].getScaledCopy(diameterInt * 118 / 128, diameterInt * 118 / 128);
GameImage.SLIDER_FOLLOWCIRCLE.setImage(GameImage.SLIDER_FOLLOWCIRCLE.getImage().getScaledCopy(diameter * 259 / 128, diameter * 259 / 128));
GameImage.REVERSEARROW.setImage(GameImage.REVERSEARROW.getImage().getScaledCopy(diameter, diameter));
GameImage.SLIDER_TICK.setImage(GameImage.SLIDER_TICK.getImage().getScaledCopy(diameter / 4, diameter / 4));
GameImage.SLIDER_FOLLOWCIRCLE.setImage(GameImage.SLIDER_FOLLOWCIRCLE.getImage().getScaledCopy(diameterInt * 259 / 128, diameterInt * 259 / 128));
GameImage.REVERSEARROW.setImage(GameImage.REVERSEARROW.getImage().getScaledCopy(diameterInt, diameterInt));
GameImage.SLIDER_TICK.setImage(GameImage.SLIDER_TICK.getImage().getScaledCopy(diameterInt / 4, diameterInt / 4));
sliderMultiplier = osu.sliderMultiplier;
sliderTickRate = osu.sliderTickRate;
@@ -266,6 +275,55 @@ public class Slider implements HitObject {
* @return the hit result (GameData.HIT_* constants)
*/
private int hitResult() {
/*
time scoredelta score-hit-initial-tick= unaccounted
(1/4 - 1) 396 - 300 - 30 46
(1+1/4 - 2) 442 - 300 - 30 - 10
(2+1/4 - 3) 488 - 300 - 30 - 2*10 896 (408)5x
(3+1/4 - 4) 534 - 300 - 30 - 3*10
(4+1/4 - 5) 580 - 300 - 30 - 4*10
(5+1/4 - 6) 626 - 300 - 30 - 5*10
(6+1/4 - 7) 672 - 300 - 30 - 6*10
difficultyMulti = 3 (+36 per combo)
score =
(t)ticks(10) * nticks +
(h)hitValue
(c)combo (hitValue/25 * difficultyMultiplier*(combo-1))
(i)initialHit (30) +
(f)finalHit(30) +
s t h c i f
626 - 10*5 - 300 - 276(-216 - 30 - 30) (all)(7x)
240 - 10*5 - 100 - 90 (-60 <- 30>) (no final or initial)(6x)
218 - 10*4 - 100 - 78 (-36 - 30) (4 tick no initial)(5x)
196 - 10*3 - 100 - 66 (-24 - 30 ) (3 tick no initial)(4x)
112 - 10*2 - 50 - 42 (-12 - 30 ) (2 tick no initial)(3x)
96 - 10 - 50 - 36 ( -6 - 30 ) (1 tick no initial)(2x)
206 - 10*4 - 100 - 66 (-36 - 30 ) (4 tick no initial)(4x)
184 - 10*3 - 100 - 54 (-24 - 30 ) (3 tick no initial)(3x)
90 - 10 - 50 - 30 ( - 30 ) (1 tick no initial)(0x)
194 - 10*4 - 100 - 54 (-24 - 30 ) (4 tick no initial)(3x)
170 - 10*4 - 100 - 30 ( - 30 ) (4 tick no final)(0x)
160 - 10*3 - 100 - 30 ( - 30 ) (3 tick no final)(0x)
100 - 10*2 - 50 - 30 ( - 30 ) (2 tick no final)(0x)
198 - 10*5 - 100 - 48 (-36 ) (no initial and final)(5x)
110 - 50 - ( - 30 - 30 ) (final and initial no tick)(0x)
80 - 50 - ( <- 30> ) (only final or initial)(0x)
140 - 10*4 - 100 - 0 (4 ticks only)(0x)
80 - 10*3 - 50 - 0 (3 tick only)(0x)
70 - 10*2 - 50 - 0 (2 tick only)(0x)
60 - 10 - 50 - 0 (1 tick only)(0x)
*/
float tickRatio = (float) ticksHit / tickIntervals;
int result;
@@ -277,7 +335,7 @@ public class Slider implements HitObject {
result = GameData.HIT_50;
else
result = GameData.HIT_MISS;
if (currentRepeats % 2 == 0) { // last circle
float[] lastPos = curve.pointAt(1);
data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
@@ -296,8 +354,7 @@ public class Slider implements HitObject {
return false;
double distance = Math.hypot(this.x - x, this.y - y);
int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2;
if (distance < circleRadius) {
if (distance < diameter / 2) {
int timeDiff = Math.abs(trackPosition - hitObject.getTime());
int[] hitResultOffset = game.getHitResultOffsets();
@@ -353,26 +410,29 @@ public class Slider implements HitObject {
}
// end of slider
if (overlap || trackPosition > hitObject.getTime() + sliderTimeTotal) {
if (trackPosition > hitObject.getTime() + sliderTimeTotal) {
tickIntervals++;
// check if cursor pressed and within end circle
if (keyPressed || GameMod.RELAX.isActive()) {
float[] c = curve.pointAt(getT(trackPosition, false));
double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY);
int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2;
if (distance < followCircleRadius)
if (distance < followRadius) //TODO IDK Magic numbers
sliderClickedFinal = true;
}
// final circle hit
if (sliderClickedFinal)
if (sliderClickedFinal){
ticksHit++;
data.sliderFinalResult(hitObject.getTime(), GameData.HIT_SLIDER30, this.x, this.y, hitObject, currentRepeats);
}
// "auto" mod: always send a perfect hit result
if (isAutoMod)
ticksHit = tickIntervals;
//TODO missing the final shouldn't increment the combo
// calculate and send slider result
hitResult();
return true;
@@ -405,8 +465,7 @@ public class Slider implements HitObject {
// holding slider...
float[] c = curve.pointAt(getT(trackPosition, false));
double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY);
int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2;
if (((keyPressed || GameMod.RELAX.isActive()) && distance < followCircleRadius) || isAutoMod) {
if (((keyPressed || GameMod.RELAX.isActive()) && distance < followRadius) || isAutoMod) {
// mouse pressed and within follow circle
followCircleActive = true;
data.changeHealth(delta * GameData.HP_DRAIN_MULTIPLIER);
@@ -487,4 +546,16 @@ public class Slider implements HitObject {
return (floor % 2 == 0) ? t - floor : floor + 1 - t;
}
}
@Override
public void reset() {
sliderClickedInitial = false;
sliderClickedFinal = false;
followCircleActive = false;
currentRepeats = 0;
tickIndex = 0;
ticksHit = 0;
tickIntervals = 1;
}
}

View File

@@ -40,12 +40,13 @@ public class Spinner implements HitObject {
private static int width, height;
/** The number of rotation velocities to store. */
// note: currently takes about 200ms to spin up (4 * 50)
private static final int MAX_ROTATION_VELOCITIES = 50;
// note: currently takes about 200ms to spin up (1000/60 * 12)
private static final int MAX_ROTATION_VELOCITIES = 12;
/** The amount of time, in milliseconds, before another velocity is stored. */
private static final int DELTA_UPDATE_TIME = 16;
private static final float DELTA_UPDATE_TIME = 1000 / 60f;
/** The amount of time, in milliseconds, to fade in the spinner. */
private static final int FADE_IN_TIME = 500;
@@ -59,6 +60,8 @@ public class Spinner implements HitObject {
TWO_PI = (float) (Math.PI * 2),
HALF_PI = (float) (Math.PI / 2);
private static final float MAX_ANG_DIFF = DELTA_UPDATE_TIME * 477 / 60 / 1000 * TWO_PI; // ~95.3
/** The associated OsuHitObject. */
private OsuHitObject hitObject;
@@ -78,19 +81,23 @@ public class Spinner implements HitObject {
private float rotationsNeeded;
/** The remaining amount of time that was not used. */
private int deltaOverflow;
private float deltaOverflow;
/** The sum of all the velocities in storedVelocities. */
private float sumVelocity = 0f;
private float sumDeltaAngle = 0f;
/** Array holding the most recent rotation velocities. */
private float[] storedVelocities = new float[MAX_ROTATION_VELOCITIES];
private float[] storedDeltaAngle = new float[MAX_ROTATION_VELOCITIES];
/** True if the mouse cursor is pressed. */
private boolean isSpinning;
/** Current index of the stored velocities in rotations/second. */
private int velocityIndex = 0;
private int deltaAngleIndex = 0;
private float deltaAngleOverflow = 0;
private int drawnRPM = 0;
/**
* Initializes the Spinner data type with images and dimensions.
@@ -112,8 +119,9 @@ public class Spinner implements HitObject {
this.data = data;
// calculate rotations needed
float spinsPerMinute = 100 + (data.getDifficulty() * 15);
float spinsPerMinute = 94 + (data.getDifficulty() * 15);
rotationsNeeded = spinsPerMinute * (hitObject.getEndTime() - hitObject.getTime()) / 60000f;
System.out.println("rotationsNeeded "+rotationsNeeded);
}
@Override
@@ -135,12 +143,11 @@ public class Spinner implements HitObject {
Utils.COLOR_BLACK_ALPHA.a = oldAlpha;
// rpm
int rpm = Math.abs(Math.round(sumVelocity / storedVelocities.length * 60));
Image rpmImg = GameImage.SPINNER_RPM.getImage();
rpmImg.setAlpha(alpha);
rpmImg.drawCentered(width / 2f, height - rpmImg.getHeight() / 2f);
if (timeDiff < 0)
data.drawSymbolString(Integer.toString(rpm), (width + rpmImg.getWidth() * 0.95f) / 2f,
data.drawSymbolString(Integer.toString(drawnRPM), (width + rpmImg.getWidth() * 0.95f) / 2f,
height - data.getScoreSymbolImage('0').getHeight() * 1.025f, 1f, 1f, true);
// spinner meter (subimage)
@@ -196,7 +203,11 @@ public class Spinner implements HitObject {
}
@Override
public boolean mousePressed(int x, int y, int trackPosition) { return false; } // not used
public boolean mousePressed(int x, int y, int trackPosition) {
lastAngle = (float) Math.atan2(x - (height / 2), y - (width / 2));
System.out.println("lastAngle:"+lastAngle);
return false;
}
@Override
public boolean update(boolean overlap, int delta, int mouseX, int mouseY, boolean keyPressed, int trackPosition) {
@@ -211,22 +222,25 @@ public class Spinner implements HitObject {
if (isSpinning && !(keyPressed || GameMod.RELAX.isActive()))
isSpinning = false;
System.out.println("Spinner update "+mouseX+" "+mouseY+" "+deltaOverflow);
// spin automatically
// http://osu.ppy.sh/wiki/FAQ#Spinners
deltaOverflow += delta;
while (deltaOverflow >= DELTA_UPDATE_TIME) {
// spin automatically
// http://osu.ppy.sh/wiki/FAQ#Spinners
float angle;
float angle = 0;
if (deltaOverflow >= DELTA_UPDATE_TIME){
if (GameMod.AUTO.isActive()) {
lastAngle = 0;
angle = delta * AUTO_MULTIPLIER;
angle = deltaOverflow * AUTO_MULTIPLIER;
isSpinning = true;
} else if (GameMod.SPUN_OUT.isActive() || GameMod.AUTOPILOT.isActive()) {
lastAngle = 0;
angle = delta * SPUN_OUT_MULTIPLIER;
angle = deltaOverflow * SPUN_OUT_MULTIPLIER;
isSpinning = true;
} else {
angle = (float) Math.atan2(mouseY - (height / 2), mouseX - (width / 2));
// set initial angle to current mouse position to skip first click
if (!isSpinning && (keyPressed || GameMod.RELAX.isActive())) {
lastAngle = angle;
@@ -234,33 +248,45 @@ public class Spinner implements HitObject {
return false;
}
}
// make angleDiff the smallest angle change possible
// (i.e. 1/4 rotation instead of 3/4 rotation)
float angleDiff = angle - lastAngle;
lastAngle = angle;
if (angleDiff < -Math.PI)
angleDiff += TWO_PI;
else if (angleDiff > Math.PI)
angleDiff -= TWO_PI;
System.out.println("AngleDiff "+angleDiff);
// spin caused by the cursor
float cursorVelocity = 0;
if (isSpinning)
cursorVelocity = Utils.clamp(angleDiff / TWO_PI / delta * 1000, -8f, 8f);
sumVelocity -= storedVelocities[velocityIndex];
sumVelocity += cursorVelocity;
storedVelocities[velocityIndex++] = cursorVelocity;
velocityIndex %= storedVelocities.length;
deltaOverflow -= DELTA_UPDATE_TIME;
deltaAngleOverflow += angleDiff;
}
while (deltaOverflow >= DELTA_UPDATE_TIME) {
System.out.println("Spinner update2 "+mouseX+" "+mouseY+" "+deltaAngleOverflow+" "+deltaOverflow);
float rotationAngle = sumVelocity / storedVelocities.length * TWO_PI * delta / 1000;
// spin caused by the cursor
float deltaAngle = 0;
if (isSpinning){
deltaAngle = deltaAngleOverflow * DELTA_UPDATE_TIME / deltaOverflow;
deltaAngleOverflow -= deltaAngle;
deltaAngle = Utils.clamp(deltaAngle, -MAX_ANG_DIFF, MAX_ANG_DIFF);
}
sumDeltaAngle -= storedDeltaAngle[deltaAngleIndex];
sumDeltaAngle += deltaAngle;
storedDeltaAngle[deltaAngleIndex++] = deltaAngle;
deltaAngleIndex %= storedDeltaAngle.length;
deltaOverflow -= DELTA_UPDATE_TIME;
float rotationAngle = sumDeltaAngle / MAX_ROTATION_VELOCITIES;
rotationAngle = Utils.clamp(rotationAngle, -MAX_ANG_DIFF, MAX_ANG_DIFF);//*0.9650f;
float rotationPerSec = rotationAngle * (1000/DELTA_UPDATE_TIME) / TWO_PI;
drawnRPM = (int)(Math.abs(rotationPerSec * 60));
System.out.println("Ang DIFF:"+deltaAngle+" "+rotations+" "+angle+" "+lastAngle+" "+rotationAngle+" "+sumDeltaAngle+" "+MAX_ANG_DIFF);
rotate(rotationAngle);
if (Math.abs(rotationAngle) > 0.00001f)
data.changeHealth(delta * GameData.HP_DRAIN_MULTIPLIER);
lastAngle = angle;
data.changeHealth(DELTA_UPDATE_TIME * GameData.HP_DRAIN_MULTIPLIER);
}
return false;
}
@@ -304,15 +330,37 @@ public class Spinner implements HitObject {
// added one whole rotation...
if (Math.floor(newRotations) > rotations) {
//TODO seems to give 1100 points per spin but also an extra 100 for some spinners
if (newRotations > rotationsNeeded) { // extra rotations
data.changeScore(1000);
SoundController.playSound(SoundEffect.SPINNERBONUS);
} else {
}
data.changeScore(100);
SoundController.playSound(SoundEffect.SPINNERSPIN);
}
//*
if (Math.floor(newRotations + 0.5f) > rotations + 0.5f) {
if (newRotations + 0.5f > rotationsNeeded) { // extra rotations
data.changeScore(100);
SoundController.playSound(SoundEffect.SPINNERSPIN);
}
}
//*/
rotations = newRotations;
}
@Override
public void reset() {
deltaAngleIndex = 0;
sumDeltaAngle = 0;
for(int i=0; i<storedDeltaAngle.length; i++){
storedDeltaAngle[i] = 0;
}
drawRotation = 0;
rotations = 0;
deltaOverflow = 0;
isSpinning = false;
}
}