Follow-up to #99.

- Many code style changes.
- Don't increment combo if missing the last slider circle.
- Added player name in ranking screen.
- Don't show null/default player names.
- Only import replays with .osr extension.
- Display loading status for importing replays.
- Moved MD5InputStreamWrapper to package "opsu.io".

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han
2015-06-29 19:22:38 -05:00
parent 7d08a7d391
commit d860a30aed
29 changed files with 513 additions and 426 deletions

View File

@@ -38,9 +38,9 @@ public class Circle implements GameObject {
/** The amount of time, in milliseconds, to fade in the circle. */
private static final int FADE_IN_TIME = 375;
/** The diameter of Circle Hitobjects */
/** The diameter of hit circles. */
private static float diameter;
/** The associated HitObject. */
private HitObject hitObject;
@@ -65,9 +65,9 @@ public class Circle implements GameObject {
* @param circleSize the map's circleSize value
*/
public static void init(GameContainer container, float circleSize) {
diameter = (104 - (circleSize * 8));
diameter = (diameter * HitObject.getXMultiplier()); // convert from Osupixels (640x480)
int diameterInt = (int)diameter;
diameter = (104 - (circleSize * 8));
diameter = (diameter * HitObject.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));
@@ -123,7 +123,6 @@ public class Circle implements GameObject {
private int hitResult(int time) {
int timeDiff = Math.abs(time);
int[] hitResultOffset = game.getHitResultOffsets();
int result = -1;
if (timeDiff <= hitResultOffset[GameData.HIT_300])
@@ -142,13 +141,13 @@ public class Circle implements GameObject {
@Override
public boolean mousePressed(int x, int y, int trackPosition) {
double distance = Math.hypot(this.x - x, this.y - y);
if (distance < diameter/2) {
if (distance < diameter / 2) {
int timeDiff = trackPosition - hitObject.getTime();
int result = hitResult(timeDiff);
if (result > -1) {
data.addHitError(hitObject.getTime(), x, y, timeDiff);
data.hitResult(trackPosition, result, this.x, this.y, color, comboEnd, hitObject, 0, HitObjectType.CIRCLE, null, true);
data.hitResult(trackPosition, result, this.x, this.y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
return true;
}
}
@@ -164,17 +163,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, 0, HitObjectType.CIRCLE, null, true);
data.hitResult(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, 0, HitObjectType.CIRCLE, null, true);
data.hitResult(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, 0, HitObjectType.CIRCLE, null, true);
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, HitObjectType.CIRCLE, true, 0, null, false);
return true;
}
}
@@ -199,7 +198,5 @@ public class Circle implements GameObject {
}
@Override
public void reset() {
}
public void reset() {}
}

View File

@@ -65,7 +65,5 @@ public class DummyObject implements GameObject {
}
@Override
public void reset() {
}
public void reset() {}
}

View File

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

View File

@@ -49,9 +49,11 @@ public class Slider implements GameObject {
/** Rate at which slider ticks are placed. */
private static float sliderTickRate = 1.0f;
/** Follow circle radius. */
private static float followRadius;
/** The diameter of hit circles. */
private static float diameter;
/** The amount of time, in milliseconds, to fade in the slider. */
@@ -81,8 +83,11 @@ public class Slider implements GameObject {
/** The time duration of the slider including repeats, in milliseconds. */
private float sliderTimeTotal = 0f;
/** Whether or not the result of the initial/final hit circles have been processed. */
private boolean sliderClickedInitial = false, sliderClickedFinal = false;
/** Whether or not the result of the initial hit circle has been processed. */
private boolean sliderClickedInitial = false;
/** Whether or not the slider was held to the end. */
private boolean sliderHeldToEnd = false;
/** Whether or not to show the follow circle. */
private boolean followCircleActive = false;
@@ -104,8 +109,6 @@ public class Slider implements GameObject {
/** Container dimensions. */
private static int containerWidth, containerHeight;
/**
* Initializes the Slider data type with images and dimensions.
@@ -116,12 +119,13 @@ public class Slider implements GameObject {
public static void init(GameContainer container, float circleSize, Beatmap beatmap) {
containerWidth = container.getWidth();
containerHeight = container.getHeight();
diameter = (104 - (circleSize * 8));
diameter = (diameter * HitObject.getXMultiplier()); // convert from Osupixels (640x480)
int diameterInt = (int)diameter;
diameter = (104 - (circleSize * 8));
diameter = (diameter * HitObject.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))
@@ -283,51 +287,49 @@ public class Slider implements GameObject {
/*
time scoredelta score-hit-initial-tick= unaccounted
(1/4 - 1) 396 - 300 - 30 46
(1+1/4 - 2) 442 - 300 - 30 - 10
(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
(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 =
score =
(t)ticks(10) * nticks +
(h)hitValue
(c)combo (hitValue/25 * difficultyMultiplier*(combo-1))
(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;
@@ -354,7 +356,8 @@ public class Slider implements GameObject {
type = HitObjectType.SLIDER_FIRST;
}
data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
cx, cy, color, comboEnd, hitObject, currentRepeats + 1, type, curve, sliderClickedFinal);
cx, cy, color, comboEnd, hitObject, type, sliderHeldToEnd,
currentRepeats + 1, curve, sliderHeldToEnd);
return result;
}
@@ -429,21 +432,17 @@ public class Slider implements GameObject {
float[] c = curve.pointAt(getT(trackPosition, false));
double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY);
if (distance < followRadius)
sliderClickedFinal = true;
sliderHeldToEnd = true;
}
// final circle hit
if (sliderClickedFinal){
if (sliderHeldToEnd)
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;
@@ -501,8 +500,8 @@ public class Slider implements GameObject {
}
// held near end of slider
if (!sliderClickedFinal && trackPosition > hitObject.getTime() + sliderTimeTotal - hitResultOffset[GameData.HIT_300])
sliderClickedFinal = true;
if (!sliderHeldToEnd && trackPosition > hitObject.getTime() + sliderTimeTotal - hitResultOffset[GameData.HIT_300])
sliderHeldToEnd = true;
} else {
followCircleActive = false;
@@ -563,12 +562,11 @@ public class Slider implements GameObject {
@Override
public void reset() {
sliderClickedInitial = false;
sliderClickedFinal = false;
sliderHeldToEnd = false;
followCircleActive = false;
currentRepeats = 0;
tickIndex = 0;
ticksHit = 0;
tickIntervals = 1;
}
}

View File

@@ -58,13 +58,14 @@ public class Spinner implements GameObject {
AUTO_MULTIPLIER = 1 / 20f, // angle = 477/60f * delta/1000f * TWO_PI;
SPUN_OUT_MULTIPLIER = 1 / 33.25f; // angle = 287/60f * delta/1000f * TWO_PI;
/** Maximum angle difference. */
private static final float MAX_ANG_DIFF = DELTA_UPDATE_TIME * AUTO_MULTIPLIER; // ~95.3
/** PI constants. */
private static final float
TWO_PI = (float) (Math.PI * 2),
HALF_PI = (float) (Math.PI / 2);
private static final float MAX_ANG_DIFF = DELTA_UPDATE_TIME * AUTO_MULTIPLIER; // ~95.3
/** The associated HitObject. */
private HitObject hitObject;
@@ -97,10 +98,10 @@ public class Spinner implements GameObject {
/** Current index of the stored velocities in rotations/second. */
private int deltaAngleIndex = 0;
/** The remaining amount of the angle that was not used. */
private float deltaAngleOverflow = 0;
/** The RPM that is drawn to the screen. */
private int drawnRPM = 0;
@@ -124,46 +125,47 @@ public class Spinner implements GameObject {
public Spinner(HitObject hitObject, Game game, GameData data) {
this.hitObject = hitObject;
this.data = data;
/*
1 beat = 731.707317073171ms
RPM at frame X with spinner Y beats long
10 20 30 40 50 60 <frame#
1.00 306 418 457 470
1.25 323 424 459 471 475
1.00 306 418 457 470
1.25 323 424 459 471 475
1.5 305 417 456 470 475 477
1.75 322 417 456 471 475
1.75 322 417 456 471 475
2.00 304 410 454 469 474 476
2.25 303 410 451 467 474 476
2.50 303 417 456 470 475 476
2.75 302 416 456 470 475 476
3.00 301 416 456 470 475 <-- ~2sec
4.00 274 414 453 470 475
5.00 281 409 454 469 475
4.00 274 414 453 470 475
5.00 281 409 454 469 475
6.00 232 392 451 467 472 476
6.25 193 378 443 465
6.50 133 344 431 461
6.25 193 378 443 465
6.50 133 344 431 461
6.75 85 228 378 435 463 472 <-- ~5sec
7.00 53 154 272 391 447
8.00 53 154 272 391 447
9.00 53 154 272 400 450
10.00 53 154 272 400 450
7.00 53 154 272 391 447
8.00 53 154 272 391 447
9.00 53 154 272 400 450
10.00 53 154 272 400 450
15.00 53 154 272 391 444 466
20.00 61 154 272 400 447
20.00 61 154 272 400 447
25.00 53 154 272 391 447 466
^beats
*/
//TODO not correct at all, but close enough?
//<2sec ~ 12 ~ 200ms
//>5sec ~ 48 ~ 800ms
// TODO not correct at all, but close enough?
// <2sec ~ 12 ~ 200ms
// >5sec ~ 48 ~ 800ms
final int minVel = 12;
final int maxVel = 48;
final int minTime = 2000;
final int maxTime = 5000;
maxStoredDeltaAngles = (int) Utils.clamp(
(hitObject.getEndTime() - hitObject.getTime() - minTime) * (maxVel-minVel)/(maxTime-minTime) + minVel
, minVel, maxVel);
maxStoredDeltaAngles = (int) Utils.clamp((hitObject.getEndTime() - hitObject.getTime() - minTime)
* (maxVel - minVel) / (maxTime - minTime) + minVel, minVel, maxVel);
storedDeltaAngle = new float[maxStoredDeltaAngles];
// calculate rotations needed
float spinsPerMinute = 100 + (overallDifficulty * 15);
rotationsNeeded = spinsPerMinute * (hitObject.getEndTime() - hitObject.getTime()) / 60000f;
@@ -245,19 +247,18 @@ public class Spinner implements GameObject {
result = GameData.HIT_MISS;
data.hitResult(hitObject.getEndTime(), result, width / 2, height / 2,
Color.transparent, true, hitObject, 0, HitObjectType.SPINNER, null, true);
Color.transparent, true, hitObject, HitObjectType.SPINNER, true, 0, null, false);
return result;
}
@Override
public boolean mousePressed(int x, int y, int trackPosition) {
public boolean mousePressed(int x, int y, int trackPosition) {
lastAngle = (float) Math.atan2(x - (height / 2), y - (width / 2));
return false;
}
@Override
public boolean update(boolean overlap, int delta, int mouseX, int mouseY, boolean keyPressed, int trackPosition) {
// end of spinner
if (overlap || trackPosition > hitObject.getEndTime()) {
hitResult();
@@ -270,9 +271,9 @@ public class Spinner implements GameObject {
// spin automatically
// http://osu.ppy.sh/wiki/FAQ#Spinners
deltaOverflow += delta;
float angleDiff = 0;
if (GameMod.AUTO.isActive()) {
angleDiff = delta * AUTO_MULTIPLIER;
@@ -289,13 +290,12 @@ public class Spinner implements GameObject {
isSpinning = true;
return false;
}
angleDiff = angle - lastAngle;
if(Math.abs(angleDiff) > 0.01f){
if (Math.abs(angleDiff) > 0.01f)
lastAngle = angle;
}else{
else
angleDiff = 0;
}
}
// make angleDiff the smallest angle change possible
@@ -304,36 +304,36 @@ public class Spinner implements GameObject {
angleDiff += TWO_PI;
else if (angleDiff > Math.PI)
angleDiff -= TWO_PI;
//may be a problem at higher frame rate due to float point round off
// may be a problem at higher frame rate due to floating point round off
if (isSpinning)
deltaAngleOverflow += angleDiff;
while (deltaOverflow >= DELTA_UPDATE_TIME) {
// spin caused by the cursor
float deltaAngle = 0;
if (isSpinning){
float deltaAngle = 0;
if (isSpinning) {
deltaAngle = deltaAngleOverflow * DELTA_UPDATE_TIME / deltaOverflow;
deltaAngleOverflow -= deltaAngle;
deltaAngle = Utils.clamp(deltaAngle, -MAX_ANG_DIFF, MAX_ANG_DIFF);
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 / maxStoredDeltaAngles;
rotationAngle = Utils.clamp(rotationAngle, -MAX_ANG_DIFF, MAX_ANG_DIFF);
float rotationPerSec = rotationAngle * (1000/DELTA_UPDATE_TIME) / TWO_PI;
drawnRPM = (int)(Math.abs(rotationPerSec * 60));
float rotationAngle = sumDeltaAngle / maxStoredDeltaAngles;
rotationAngle = Utils.clamp(rotationAngle, -MAX_ANG_DIFF, MAX_ANG_DIFF);
float rotationPerSec = rotationAngle * (1000 / DELTA_UPDATE_TIME) / TWO_PI;
drawnRPM = (int) (Math.abs(rotationPerSec * 60));
rotate(rotationAngle);
if (Math.abs(rotationAngle) > 0.00001f)
data.changeHealth(DELTA_UPDATE_TIME * GameData.HP_DRAIN_MULTIPLIER);
}
//TODO may need to update 1 more time when the spinner ends?
return false;
}
@@ -380,21 +380,17 @@ public class Spinner implements GameObject {
//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);
}
data.changeScore(100);
SoundController.playSound(SoundEffect.SPINNERSPIN);
}
/*
//The extra 100 for some spinners (mostly wrong)
if (Math.floor(newRotations + 0.5f) > rotations + 0.5f) {
if (newRotations + 0.5f > rotationsNeeded) { // extra rotations
data.changeScore(100);
}
}
//*/
// extra 100 for some spinners (mostly wrong)
// if (Math.floor(newRotations + 0.5f) > rotations + 0.5f) {
// if (newRotations + 0.5f > rotationsNeeded) // extra rotations
// data.changeScore(100);
// }
rotations = newRotations;
}
@@ -403,9 +399,8 @@ public class Spinner implements GameObject {
public void reset() {
deltaAngleIndex = 0;
sumDeltaAngle = 0;
for(int i=0; i<storedDeltaAngle.length; i++){
for (int i = 0; i < storedDeltaAngle.length; i++)
storedDeltaAngle[i] = 0;
}
drawRotation = 0;
rotations = 0;
deltaOverflow = 0;