diff --git a/src/itdelatrisu/opsu/OsuHitObject.java b/src/itdelatrisu/opsu/OsuHitObject.java index 35706691..6d0a0072 100644 --- a/src/itdelatrisu/opsu/OsuHitObject.java +++ b/src/itdelatrisu/opsu/OsuHitObject.java @@ -201,7 +201,12 @@ public class OsuHitObject { if (index != -1) tokens[5] = tokens[5].substring(0, index); this.endTime = Integer.parseInt(tokens[5]); - /* TODO: 'addition' not implemented. */ + if (tokens.length > 6) { + String[] additionTokens = tokens[6].split(":"); + this.addition = new byte[additionTokens.length]; + for (int j = 0; j < additionTokens.length; j++) + this.addition[j] = Byte.parseByte(additionTokens[j]); + } } } diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index ac70bf52..75fada10 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -256,7 +256,7 @@ public class Utils { * @return the clamped value * @author fluddokt */ - public static float clamp(float val, int low, int high) { + public static float clamp(float val, float low, float high) { if (val < low) return low; if (val > high) diff --git a/src/itdelatrisu/opsu/objects/Spinner.java b/src/itdelatrisu/opsu/objects/Spinner.java index 0c8fbab6..ac461fe4 100644 --- a/src/itdelatrisu/opsu/objects/Spinner.java +++ b/src/itdelatrisu/opsu/objects/Spinner.java @@ -40,9 +40,20 @@ public class Spinner implements HitObject { /** Container dimensions. */ private static int width, height; + + // Currently it takes about 200ms to spin up (4 * 50) /** The number of rotation velocities to store. */ private static final int MAX_ROTATION_VELOCITIES = 50; + /** The amount of time in ms before another velocity is stored */ + private static final int DELTA_UPDATE_TIME = 4; + + /** The amount of time in ms to fade in the spinner*/ + private static final float FADE_IN_TIME = 500; + + /** The remaining amount of time that was not used */ + private int deltaOverflow; + /** PI constants. */ private static final float TWO_PI = (float) (Math.PI * 2); @@ -57,6 +68,9 @@ public class Spinner implements HitObject { /** The current number of rotations. */ private float rotations = 0f; + + /** The current rotation to draw */ + private float drawRotation = 0f; /** The total number of rotations needed to clear the spinner. */ private float rotationsNeeded; @@ -99,24 +113,29 @@ public class Spinner implements HitObject { @Override public void draw(int trackPosition, boolean currentObject, Graphics g) { - // only draw spinners if current object - if (!currentObject) - return; - int timeDiff = hitObject.getTime() - trackPosition; boolean spinnerComplete = (rotations >= rotationsNeeded); + // only draw spinners if + if (timeDiff - FADE_IN_TIME > 0 ) + return; + + // darken screen - g.setColor(Utils.COLOR_BLACK_ALPHA); + float alpha = Utils.clamp(1 - timeDiff / FADE_IN_TIME, 0f, 1f); + if (timeDiff > 0) { + Color c = new Color(Utils.COLOR_BLACK_ALPHA); + c.a *= alpha; + g.setColor(c); + } else + g.setColor(Utils.COLOR_BLACK_ALPHA); g.fillRect(0, 0, width, height); - if (timeDiff > 0) - return; - - // rpm (TODO: make this work for Auto/Spun-Out mods) 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), (int) ((width + rpmImg.getWidth() * 0.95f) / 2f), (int) (height - data.getScoreSymbolImage('0').getHeight() * 1.025f), 1f, 1f, true); @@ -127,13 +146,19 @@ public class Spinner implements HitObject { 0, spinnerMetreY, spinnerMetre.getWidth(), spinnerMetre.getHeight() - spinnerMetreY ); + spinnerMetreSub.setAlpha(alpha); spinnerMetreSub.draw(0, height - spinnerMetreSub.getHeight()); // main spinner elements - float approachScale = 1 - ((float) timeDiff / (hitObject.getTime() - hitObject.getEndTime())); - GameImage.SPINNER_CIRCLE.getImage().setRotation(rotations * 360f); + float approachScale = 1 - Utils.clamp( ((float) timeDiff / (hitObject.getTime() - hitObject.getEndTime())), 0f, 1f); + + GameImage.SPINNER_CIRCLE.getImage().setAlpha(alpha); + GameImage.SPINNER_CIRCLE.getImage().setRotation(drawRotation * 360f); GameImage.SPINNER_CIRCLE.getImage().drawCentered(width / 2, height / 2); - GameImage.SPINNER_APPROACHCIRCLE.getImage().getScaledCopy(approachScale).drawCentered(width / 2, height / 2); + Image approachCircleScaled = GameImage.SPINNER_APPROACHCIRCLE.getImage().getScaledCopy(approachScale); + approachCircleScaled.setAlpha(alpha); + approachCircleScaled.drawCentered(width / 2, height / 2); + GameImage.SPINNER_SPIN.getImage().setAlpha(alpha); GameImage.SPINNER_SPIN.getImage().drawCentered(width / 2, height * 3 / 4); if (spinnerComplete) { @@ -181,31 +206,36 @@ public class Spinner implements HitObject { return true; } - // spin automatically (TODO: correct rotation angles) - if (GameMod.AUTO.isActive()) { - // "auto" mod (fast) - data.changeHealth(delta * GameData.HP_DRAIN_MULTIPLIER); - rotate(delta / 20f); - return false; - } else if (GameMod.SPUN_OUT.isActive()) { - // "spun out" mod (slow) - data.changeHealth(delta * GameData.HP_DRAIN_MULTIPLIER); - rotate(delta / 32f); - return false; - } - // game button is released if (isSpinning && !(Utils.isGameKeyPressed() || GameMod.RELAX.isActive())) isSpinning = false; - float angle = (float) Math.atan2(mouseY - (height / 2), mouseX - (width / 2)); - - // set initial angle to current mouse position to skip first click - if (!isSpinning && (Utils.isGameKeyPressed() || GameMod.RELAX.isActive())) { - lastAngle = angle; + + float angle; + // http://osu.ppy.sh/wiki/FAQ#Spinners + // spin automatically (TODO: correct rotation angles) + if (GameMod.AUTO.isActive()) { + // "auto" mod (fast) + lastAngle = 0; + //angle = 477/60f * delta/1000f * TWO_PI; ~delta/20 + angle = delta / 20f; isSpinning = true; - return false; + } else if (GameMod.SPUN_OUT.isActive()) { + // "spun out" mod (slow) + lastAngle = 0; + //angle = 287/60f * delta/1000f * TWO_PI; + angle = delta / 32f; + 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 && (Utils.isGameKeyPressed() || GameMod.RELAX.isActive())) { + lastAngle = angle; + isSpinning = true; + return false; + } } + float angleDiff = angle - lastAngle; @@ -219,16 +249,19 @@ public class Spinner implements HitObject { // spin caused by the cursor float cursorVelocity = 0; if (isSpinning) - cursorVelocity = Math.min(angleDiff / TWO_PI / delta * 1000, 8f); - - sumVelocity -= storedVelocities[velocityIndex]; - sumVelocity += cursorVelocity; - storedVelocities[velocityIndex++] = cursorVelocity; - velocityIndex %= storedVelocities.length; + cursorVelocity = Utils.clamp(angleDiff / TWO_PI / delta * 1000, -8f, 8f); + deltaOverflow += delta; + while(deltaOverflow >= DELTA_UPDATE_TIME){ + sumVelocity -= storedVelocities[velocityIndex]; + sumVelocity += cursorVelocity; + storedVelocities[velocityIndex++] = cursorVelocity; + velocityIndex %= storedVelocities.length; + deltaOverflow -= DELTA_UPDATE_TIME; + } float rotationAngle = sumVelocity / storedVelocities.length * TWO_PI * delta / 1000; rotate(rotationAngle); - if (rotationAngle > 0.00001f) + if (Math.abs(rotationAngle) > 0.00001f) data.changeHealth(delta * GameData.HP_DRAIN_MULTIPLIER); lastAngle = angle; @@ -240,6 +273,7 @@ public class Spinner implements HitObject { * @param angle the angle to rotate (in radians) */ private void rotate(float angle) { + drawRotation += (angle / TWO_PI); angle = Math.abs(angle); float newRotations = rotations + (angle / TWO_PI);