Added initial sound effect support.

- Added SoundController module, which uses Java Sound since OpenAL has a slight delay on playing sounds.
- Uploaded all effect WAVs. (credits: WWWskin, AL's IA, Fantasy's Skin, Minimalist Miku)
- Added a missing entry in credits.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han
2014-07-01 01:14:03 -04:00
parent 60eaa42997
commit 0604a25822
54 changed files with 500 additions and 61 deletions
+1
View File
@@ -23,4 +23,5 @@ Projects
The following projects were referenced in creating opsu!:
* "Wojtkosu" - Wojtek Kowaluk (https://osu.ppy.sh/forum/t/97260)
* "osu-parser" - nojhamster (https://github.com/nojhamster/osu-parser)
* "osu! web" - pictuga (https://github.com/pictuga/osu-web)
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+22 -8
View File
@@ -717,29 +717,41 @@ public class GameScore {
}
}
/**
* Resets the combo streak to zero.
*/
private void resetComboStreak() {
if (combo >= 20)
SoundController.playSound(SoundController.SOUND_COMBOBREAK);
combo = 0;
if (Options.isModActive(Options.MOD_SUDDEN_DEATH))
health = 0f;
}
/**
* Handles a slider tick result.
* @param time the tick start time
* @param result the hit result (HIT_* constants)
* @param x the x coordinate
* @param y the y coordinate
* @param hitSound the object's hit sound
*/
public void sliderTickResult(int time, int result, float x, float y) {
public void sliderTickResult(int time, int result, float x, float y, byte hitSound) {
int hitValue = 0;
switch (result) {
case HIT_SLIDER30:
hitValue = 30;
incrementComboStreak();
changeHealth(1f);
SoundController.playHitSound(hitSound);
break;
case HIT_SLIDER10:
hitValue = 10;
incrementComboStreak();
SoundController.playHitSound(SoundController.HIT_SLIDERTICK);
break;
case HIT_MISS:
combo = 0;
if (Options.isModActive(Options.MOD_SUDDEN_DEATH))
health = 0f;
resetComboStreak();
break;
default:
return;
@@ -760,8 +772,10 @@ public class GameScore {
* @param y the y coordinate
* @param color the combo color
* @param end true if this is the last hit object in the combo
* @param hitSound the object's hit sound
*/
public void hitResult(int time, int result, float x, float y, Color color, boolean end) {
public void hitResult(int time, int result, float x, float y, Color color,
boolean end, byte hitSound) {
int hitValue = 0;
switch (result) {
case HIT_300:
@@ -784,15 +798,15 @@ public class GameScore {
hitValue = 0;
changeHealth(-10f);
comboEnd |= 2;
combo = 0;
if (Options.isModActive(Options.MOD_SUDDEN_DEATH))
health = 0f;
resetComboStreak();
objectCount++;
break;
default:
return;
}
if (hitValue > 0) {
SoundController.playHitSound(hitSound);
// game mod score multipliers
float modMultiplier = 1f;
if (Options.isModActive(Options.MOD_NO_FAIL))
+3
View File
@@ -143,6 +143,9 @@ public class Opsu extends StateBasedGame {
}
Options.TMP_DIR.deleteOnExit();
// load sounds
SoundController.init();
app.start();
} catch (SlickException e) {
Log.error("Error while creating game container.", e);
+1 -1
View File
@@ -40,7 +40,7 @@ public class OsuFile implements Comparable<OsuFile> {
// public String audioHash = ""; // audio hash (deprecated)
public int previewTime = -1; // start position of music preview (in ms)
public byte countdown = 0; // countdown type (0:disabled, 1:normal, 2:half, 3:double)
public String sampleSet = ""; // ? ("Normal", "Soft")
public String sampleSet = ""; // sound samples ("None", "Normal", "Soft")
public float stackLeniency = 0.7f; // how often closely placed hit objects will be stacked together
public byte mode = 0; // game mode (0:osu!, 1:taiko, 2:catch the beat, 3:osu!mania)
public boolean letterboxInBreaks = false; // whether the letterbox (top/bottom black bars) appears during breaks
-1
View File
@@ -38,7 +38,6 @@ public class OsuHitObject {
SOUND_NORMAL = 0,
SOUND_WHISTLE = 2,
SOUND_FINISH = 4,
SOUND_WHISTLEFINISH = 6,
SOUND_CLAP = 8;
/**
+2 -2
View File
@@ -26,8 +26,8 @@ public class OsuTimingPoint {
public float beatLength; // (non-inherited) ms per beat
public int velocity = 0; // (inherited) slider multiplier = -100 / value
public int meter; // beats per measure
public byte sampleType; // sound samples (0:none, 1:normal, 2:soft)
public byte sampleTypeCustom; // custom samples (0:default, 1:custom1, 2:custom2
public byte sampleType; // sound samples (0:none, 1:normal, 2:soft, 3:drum)
public byte sampleTypeCustom; // custom samples (0:default, 1:custom1, 2:custom2)
public int sampleVolume; // volume of samples (0~100)
public boolean inherited; // is this timing point inherited?
public boolean kiai; // is Kiai Mode active?
+285
View File
@@ -0,0 +1,285 @@
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014 Jeffrey Han
*
* opsu! is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu! is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu;
import itdelatrisu.opsu.states.Options;
import java.io.IOException;
import java.net.URL;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
/**
* Controller for all sound effects.
* Note: Uses Java Sound because OpenAL lags too much for accurate hit sounds.
*/
public class SoundController {
/**
* Sound effect constants.
*/
public static final int
SOUND_APPLAUSE = 0,
SOUND_COMBOBREAK = 1,
// SOUND_COUNT = , // ?
SOUND_COUNT1 = 2,
SOUND_COUNT2 = 3,
SOUND_COUNT3 = 4,
SOUND_FAIL = 5,
SOUND_GO = 6,
SOUND_MENUBACK = 7,
SOUND_MENUCLICK = 8,
SOUND_MENUHIT = 9,
SOUND_READY = 10,
SOUND_SECTIONFAIL = 11,
SOUND_SECTIONPASS = 12,
SOUND_SHUTTER = 13,
SOUND_SPINNERBONUS = 14,
SOUND_SPINNEROSU = 15,
SOUND_SPINNERSPIN = 16,
SOUND_MAX = 17; // not a sound
/**
* Sound effect names (indexed by SOUND_* constants).
*/
private static final String[] soundNames = {
"applause",
"combobreak",
// "count", // ?
"count1s",
"count2s",
"count3s",
"failsound",
"gos",
"menuback",
"menuclick",
"menuhit",
"readys",
"sectionfail",
"sectionpass",
"shutter",
"spinnerbonus",
"spinner-osu",
"spinnerspin",
};
/**
* Sound effects (indexed by SOUND_* constants).
*/
private static Clip[] sounds = new Clip[SOUND_MAX];
/**
* Sound sample sets.
*/
private static final String[] sampleSets = {
"normal",
"soft",
"drum",
// "taiko"
};
/**
* Current sample set (index in sampleSet[] array).
*/
private static int sampleSetIndex = -1;
/**
* Hit sound effects.
*/
public static final int
HIT_CLAP = 0,
HIT_FINISH = 1,
HIT_NORMAL = 2,
HIT_WHISTLE = 3,
HIT_SLIDERSLIDE = 4,
HIT_SLIDERTICK = 5,
HIT_SLIDERWHISTLE = 6,
HIT_MAX = 7; // not a sound
/**
* Hit sound effect names (indexed by HIT_* constants).
*/
private static final String[] hitSoundNames = {
"hitclap",
"hitfinish",
"hitnormal",
"hitwhistle",
"sliderslide",
"slidertick",
"sliderwhistle"
};
/**
* Hit sound effects (indexed by sampleSets[], HIT_* constants).
*/
private static Clip[][] hitSounds = new Clip[sampleSets.length][HIT_MAX];
/**
* Sample volume multiplier, from timing points [0, 1].
*/
private static float sampleVolumeMultiplier = 1f;
// This class should not be instantiated.
private SoundController() {}
/**
* Loads and returns a Clip from a resource.
* @param ref the resource name
* @return the loaded and opened clip
*/
private static Clip loadClip(String ref) {
try {
URL url = ResourceLoader.getResource(ref);
AudioInputStream audioIn = AudioSystem.getAudioInputStream(url);
Clip clip = AudioSystem.getClip();
clip.open(audioIn);
return clip;
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) {
Log.error(String.format("Failed to load file '%s'.", ref), e);
}
return null;
}
/**
* Loads all sound files.
*/
public static void init() {
// TODO: support MP3 sounds?
// menu and game sounds
for (int i = 0; i < SOUND_MAX; i++)
sounds[i] = loadClip(String.format("%s.wav", soundNames[i]));
// hit sounds
for (int i = 0; i < sampleSets.length; i++) {
for (int j = 0; j < HIT_MAX; j++)
hitSounds[i][j] = loadClip(String.format("%s-%s.wav", sampleSets[i], hitSoundNames[j]));
}
}
/**
* Sets the sample set to use when playing hit sounds.
* @param sampleSet the sample set ("None", "Normal", "Soft", "Drum")
*/
public static void setSampleSet(String sampleSet) {
sampleSetIndex = -1;
for (int i = 0; i < sampleSets.length; i++) {
if (sampleSet.equalsIgnoreCase(sampleSets[i])) {
sampleSetIndex = i;
return;
}
}
}
/**
* Sets the sample set to use when playing hit sounds.
* @param sampleType the sample set (0:none, 1:normal, 2:soft, 3:drum)
*/
public static void setSampleSet(byte sampleType) {
if (sampleType >= 0 && sampleType <= 3)
sampleSetIndex = sampleType - 1;
}
/**
* Sets the sample volume (modifies the global sample volume).
* @param volume the sample volume [0, 100]
*/
public static void setSampleVolume(int volume) {
if (volume >= 0 && volume <= 100)
sampleVolumeMultiplier = volume / 100f;
}
/**
* Plays a sound clip.
* @param clip the Clip to play
* @param volume the volume [0, 1]
*/
private static void playClip(Clip clip, float volume) {
if (volume > 0f) {
// stop clip if running
if (clip.isRunning()) {
clip.stop();
clip.flush();
}
// set volume
FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
float dB = (float) (Math.log(volume) / Math.log(10.0) * 20.0);
gainControl.setValue(dB * sampleVolumeMultiplier);
// play clip
clip.setFramePosition(0);
clip.start();
}
}
/**
* Plays a sound.
* @param sound the sound (SOUND_* constant)
*/
public static void playSound(int sound) {
if (sound < 0 || sound >= SOUND_MAX)
return;
playClip(sounds[sound], Options.getEffectVolume());
}
/**
* Plays hit sound(s) using an OsuHitObject bitmask.
* @param hitSound the sound (bitmask)
*/
public static void playHitSound(byte hitSound) {
if (sampleSetIndex < 0 || hitSound < 0)
return;
float volume = Options.getEffectVolume();
if (volume == 0f)
return;
// play all sounds
if (hitSound == OsuHitObject.SOUND_NORMAL)
playClip(hitSounds[sampleSetIndex][HIT_NORMAL], volume);
else {
if ((hitSound & OsuHitObject.SOUND_WHISTLE) > 0)
playClip(hitSounds[sampleSetIndex][HIT_WHISTLE], volume);
if ((hitSound & OsuHitObject.SOUND_FINISH) > 0)
playClip(hitSounds[sampleSetIndex][HIT_FINISH], volume);
if ((hitSound & OsuHitObject.SOUND_CLAP) > 0)
playClip(hitSounds[sampleSetIndex][HIT_CLAP], volume);
}
}
/**
* Plays a hit sound.
* @param sound (HIT_* constant)
*/
public static void playHitSound(int sound) {
if (sampleSetIndex < 0 || sound < 0 || sound > HIT_MAX)
return;
playClip(hitSounds[sampleSetIndex][sound], Options.getEffectVolume());
}
}
+6 -4
View File
@@ -172,7 +172,9 @@ public class Circle {
if (distance < circleRadius) {
int result = hitResult(hitObject.time);
if (result > -1) {
score.hitResult(hitObject.time, result, hitObject.x, hitObject.y, color, comboEnd);
score.hitResult(hitObject.time, result, hitObject.x, hitObject.y,
color, comboEnd, hitObject.hitSound
);
return true;
}
}
@@ -192,11 +194,11 @@ public class Circle {
if (overlap || trackPosition > hitObject.time + hitResultOffset[GameScore.HIT_50]) {
if (isAutoMod) // "auto" mod: catch any missed notes due to lag
score.hitResult(hitObject.time, GameScore.HIT_300,
hitObject.x, hitObject.y, color, comboEnd);
hitObject.x, hitObject.y, color, comboEnd, hitObject.hitSound);
else // no more points can be scored, so send a miss
score.hitResult(hitObject.time, GameScore.HIT_MISS,
hitObject.x, hitObject.y, null, comboEnd);
hitObject.x, hitObject.y, null, comboEnd, hitObject.hitSound);
return true;
}
@@ -204,7 +206,7 @@ public class Circle {
else if (isAutoMod) {
if (Math.abs(trackPosition - hitObject.time) < hitResultOffset[GameScore.HIT_300]) {
score.hitResult(hitObject.time, GameScore.HIT_300,
hitObject.x, hitObject.y, color, comboEnd);
hitObject.x, hitObject.y, color, comboEnd, hitObject.hitSound);
return true;
}
}
+18 -11
View File
@@ -384,10 +384,11 @@ public class Slider {
if (currentRepeats % 2 == 0) // last circle
score.hitResult(hitObject.time + (int) sliderTimeTotal, result,
hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex], color, comboEnd);
hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex],
color, comboEnd, hitObject.hitSound);
else // first circle
score.hitResult(hitObject.time + (int) sliderTimeTotal, result,
hitObject.x, hitObject.y, color, comboEnd);
hitObject.x, hitObject.y, color, comboEnd, hitObject.hitSound);
return result;
}
@@ -420,7 +421,8 @@ public class Slider {
if (result > -1) {
sliderClicked = true;
score.sliderTickResult(hitObject.time, result, hitObject.x, hitObject.y);
score.sliderTickResult(hitObject.time, result,
hitObject.x, hitObject.y, hitObject.hitSound);
return true;
}
}
@@ -465,9 +467,11 @@ public class Slider {
sliderClicked = true;
if (isAutoMod) { // "auto" mod: catch any missed notes due to lag
ticksHit++;
score.sliderTickResult(hitObject.time, GameScore.HIT_SLIDER30, hitObject.x, hitObject.y);
score.sliderTickResult(hitObject.time, GameScore.HIT_SLIDER30,
hitObject.x, hitObject.y, hitObject.hitSound);
} else
score.sliderTickResult(hitObject.time, GameScore.HIT_MISS, hitObject.x, hitObject.y);
score.sliderTickResult(hitObject.time, GameScore.HIT_MISS,
hitObject.x, hitObject.y, hitObject.hitSound);
}
// "auto" mod: send a perfect hit result
@@ -475,7 +479,8 @@ public class Slider {
if (Math.abs(trackPosition - hitObject.time) < hitResultOffset[GameScore.HIT_300]) {
ticksHit++;
sliderClicked = true;
score.sliderTickResult(hitObject.time, GameScore.HIT_SLIDER30, hitObject.x, hitObject.y);
score.sliderTickResult(hitObject.time, GameScore.HIT_SLIDER30,
hitObject.x, hitObject.y, hitObject.hitSound);
}
}
}
@@ -540,24 +545,26 @@ public class Slider {
ticksHit++;
if (currentRepeats % 2 > 0) // last circle
score.sliderTickResult(trackPosition, GameScore.HIT_SLIDER30,
hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex]);
hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex],
hitObject.hitSound);
else // first circle
score.sliderTickResult(trackPosition, GameScore.HIT_SLIDER30,
c[0], c[1]);
c[0], c[1], hitObject.hitSound);
}
// held during new tick
if (isNewTick) {
ticksHit++;
score.sliderTickResult(trackPosition, GameScore.HIT_SLIDER10, c[0], c[1]);
score.sliderTickResult(trackPosition, GameScore.HIT_SLIDER10,
c[0], c[1], (byte) -1);
}
} else {
followCircleActive = false;
if (isNewRepeat)
score.sliderTickResult(trackPosition, GameScore.HIT_MISS, 0, 0);
score.sliderTickResult(trackPosition, GameScore.HIT_MISS, 0, 0, hitObject.hitSound);
if (isNewTick)
score.sliderTickResult(trackPosition, GameScore.HIT_MISS, 0, 0);
score.sliderTickResult(trackPosition, GameScore.HIT_MISS, 0, 0, (byte) -1);
}
return false;
+12 -5
View File
@@ -21,6 +21,7 @@ package itdelatrisu.opsu.objects;
import itdelatrisu.opsu.GameScore;
import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.OsuHitObject;
import itdelatrisu.opsu.SoundController;
import itdelatrisu.opsu.states.Game;
import itdelatrisu.opsu.states.Options;
@@ -163,16 +164,19 @@ public class Spinner {
int result;
float ratio = rotations / rotationsNeeded;
if (ratio >= 1.0f ||
Options.isModActive(Options.MOD_AUTO) || Options.isModActive(Options.MOD_SPUN_OUT))
Options.isModActive(Options.MOD_AUTO) ||
Options.isModActive(Options.MOD_SPUN_OUT)) {
result = GameScore.HIT_300;
else if (ratio >= 0.8f)
SoundController.playSound(SoundController.SOUND_SPINNEROSU);
} else if (ratio >= 0.8f)
result = GameScore.HIT_100;
else if (ratio >= 0.5f)
result = GameScore.HIT_50;
else
result = GameScore.HIT_MISS;
score.hitResult(hitObject.endTime, result, width / 2, height / 2, Color.transparent, true);
score.hitResult(hitObject.endTime, result, width / 2, height / 2,
Color.transparent, true, (byte) -1);
return result;
}
@@ -240,10 +244,13 @@ public class Spinner {
// added one whole rotation...
if (Math.floor(newRotations) > rotations) {
if (newRotations > rotationsNeeded) // extra rotations
if (newRotations > rotationsNeeded) { // extra rotations
score.changeScore(1000);
else
SoundController.playSound(SoundController.SOUND_SPINNERBONUS);
} else {
score.changeScore(100);
SoundController.playSound(SoundController.SOUND_SPINNERSPIN);
}
}
rotations = newRotations;
+71 -11
View File
@@ -25,6 +25,7 @@ import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.OsuFile;
import itdelatrisu.opsu.OsuHitObject;
import itdelatrisu.opsu.OsuTimingPoint;
import itdelatrisu.opsu.SoundController;
import itdelatrisu.opsu.objects.Circle;
import itdelatrisu.opsu.objects.Slider;
import itdelatrisu.opsu.objects.Spinner;
@@ -134,6 +135,11 @@ public class Game extends BasicGameState {
*/
private int breakTime = 0;
/**
* Whether the break sound has been played.
*/
private boolean breakSound;
/**
* Skip button (displayed at song start, when necessary).
*/
@@ -164,6 +170,13 @@ public class Game extends BasicGameState {
countdown2, // "1" text
countdownGo; // "GO!" text
/**
* Whether the countdown sound has been played.
*/
private boolean
countdownReadySound, countdown3Sound, countdown1Sound,
countdown2Sound, countdownGoSound;
/**
* Glowing hit circle outline which must be clicked when returning from pause menu.
*/
@@ -276,10 +289,19 @@ public class Game extends BasicGameState {
trackPosition - breakTime > 2000 &&
trackPosition - breakTime < 5000) {
// show break start
if (score.getHealth() >= 50)
if (score.getHealth() >= 50) {
breakStartPass.drawCentered(width / 2f, height / 2f);
else
if (!breakSound) {
SoundController.playSound(SoundController.SOUND_SECTIONPASS);
breakSound = true;
}
} else {
breakStartFail.drawCentered(width / 2f, height / 2f);
if (!breakSound) {
SoundController.playSound(SoundController.SOUND_SECTIONFAIL);
breakSound = true;
}
}
} else if (breakLength >= 4000) {
// show break end (flash twice for 500ms)
int endTimeDiff = endTime - trackPosition;
@@ -326,18 +348,41 @@ public class Game extends BasicGameState {
if (osu.countdown > 0) { // TODO: implement half/double rate settings
int timeDiff = osu.objects[0].time - trackPosition;
if (timeDiff >= 500 && timeDiff < 3000) {
if (timeDiff >= 1500)
if (timeDiff >= 1500) {
countdownReady.drawCentered(width / 2, height / 2);
if (timeDiff < 2000)
if (!countdownReadySound) {
SoundController.playSound(SoundController.SOUND_READY);
countdownReadySound = true;
}
}
if (timeDiff < 2000) {
countdown3.draw(0, 0);
if (timeDiff < 1500)
if (!countdown3Sound) {
SoundController.playSound(SoundController.SOUND_COUNT3);
countdown3Sound = true;
}
}
if (timeDiff < 1500) {
countdown2.draw(width - countdown2.getWidth(), 0);
if (timeDiff < 1000)
if (!countdown2Sound) {
SoundController.playSound(SoundController.SOUND_COUNT2);
countdown2Sound = true;
}
}
if (timeDiff < 1000) {
countdown1.drawCentered(width / 2, height / 2);
if (!countdown1Sound) {
SoundController.playSound(SoundController.SOUND_COUNT1);
countdown1Sound = true;
}
}
} else if (timeDiff >= -500 && timeDiff < 500) {
countdownGo.setAlpha((timeDiff < 0) ? 1 - (timeDiff / -1000f) : 1);
countdownGo.drawCentered(width / 2, height / 2);
if (!countdownGoSound) {
SoundController.playSound(SoundController.SOUND_GO);
countdownGoSound = true;
}
}
}
@@ -432,6 +477,8 @@ public class Game extends BasicGameState {
beatLengthBase = beatLength = timingPoint.beatLength;
else
beatLength = beatLengthBase * (timingPoint.velocity / -100f);
SoundController.setSampleSet(timingPoint.sampleType);
SoundController.setSampleVolume(timingPoint.sampleVolume);
timingPointIndex++;
}
}
@@ -456,6 +503,7 @@ public class Game extends BasicGameState {
} else if (trackPosition >= breakValue) {
// start a break
breakTime = breakValue;
breakSound = false;
breakIndex++;
return;
}
@@ -638,22 +686,33 @@ public class Game extends BasicGameState {
}
}
// reset indexes
// reset data
MusicController.setPosition(0);
MusicController.pause();
score.clear();
objectIndex = 0;
breakIndex = 0;
breakTime = 0;
breakSound = false;
timingPointIndex = 0;
pauseTime = -1;
pausedMouseX = -1;
pausedMouseY = -1;
countdownReadySound = false;
countdown3Sound = false;
countdown1Sound = false;
countdown2Sound = false;
countdownGoSound = false;
// load the first timingPoint
if (!osu.timingPoints.isEmpty() && osu.timingPoints.get(0).velocity >= 0) {
beatLengthBase = beatLength = osu.timingPoints.get(0).beatLength;
timingPointIndex++;
if (!osu.timingPoints.isEmpty()) {
OsuTimingPoint timingPoint = osu.timingPoints.get(0);
if (timingPoint.velocity >= 0) {
beatLengthBase = beatLength = timingPoint.beatLength;
SoundController.setSampleSet(timingPoint.sampleType);
SoundController.setSampleVolume(timingPoint.sampleVolume);
timingPointIndex++;
}
}
leadInTime = osu.audioLeadIn + approachTime;
@@ -675,6 +734,7 @@ public class Game extends BasicGameState {
MusicController.resume();
}
MusicController.setPosition(osu.objects[0].time - skipOffsetTime);
SoundController.playSound(SoundController.SOUND_MENUHIT);
return true;
}
return false;
+10 -2
View File
@@ -21,6 +21,7 @@ package itdelatrisu.opsu.states;
import itdelatrisu.opsu.GUIMenuButton;
import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.SoundController;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
@@ -142,6 +143,7 @@ public class GamePauseMenu extends BasicGameState {
if (Game.getRestart() == Game.RESTART_LOSE) {
MusicController.stop();
MusicController.playAt(Game.getOsuFile().previewTime, true);
SoundController.playSound(SoundController.SOUND_MENUBACK);
game.enterState(Opsu.STATE_SONGMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
} else
unPause(Game.RESTART_FALSE);
@@ -171,6 +173,7 @@ public class GamePauseMenu extends BasicGameState {
} else if (backButton.contains(x, y)) {
MusicController.pause(); // lose state
MusicController.playAt(Game.getOsuFile().previewTime, true);
SoundController.playSound(SoundController.SOUND_MENUBACK);
game.enterState(Opsu.STATE_SONGMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
}
}
@@ -179,9 +182,10 @@ public class GamePauseMenu extends BasicGameState {
public void enter(GameContainer container, StateBasedGame game)
throws SlickException {
pauseStartTime = System.currentTimeMillis();
if (Game.getRestart() == Game.RESTART_LOSE)
if (Game.getRestart() == Game.RESTART_LOSE) {
MusicController.fadeOut(FADEOUT_TIME);
else
SoundController.playSound(SoundController.SOUND_FAIL);
} else
MusicController.pause();
}
@@ -189,6 +193,10 @@ public class GamePauseMenu extends BasicGameState {
* Unpause and return to the Game state.
*/
private void unPause(byte restart) {
if (restart == Game.RESTART_MANUAL)
SoundController.playSound(SoundController.SOUND_MENUHIT);
else
SoundController.playSound(SoundController.SOUND_MENUBACK);
Game.setRestart(restart);
game.enterState(Opsu.STATE_GAME);
}
+10 -3
View File
@@ -23,6 +23,7 @@ import itdelatrisu.opsu.GameScore;
import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.OsuFile;
import itdelatrisu.opsu.SoundController;
import org.lwjgl.opengl.Display;
import org.newdawn.slick.Color;
@@ -143,7 +144,9 @@ public class GameRanking extends BasicGameState {
public void keyPressed(int key, char c) {
switch (key) {
case Input.KEY_ESCAPE:
MusicController.pause();
MusicController.playAt(Game.getOsuFile().previewTime, true);
SoundController.playSound(SoundController.SOUND_MENUBACK);
game.enterState(Opsu.STATE_SONGMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
break;
case Input.KEY_F12:
@@ -162,12 +165,15 @@ public class GameRanking extends BasicGameState {
OsuFile osu = Game.getOsuFile();
Display.setTitle(String.format("%s - %s", game.getTitle(), osu.toString()));
Game.setRestart(Game.RESTART_MANUAL);
SoundController.playSound(SoundController.SOUND_MENUHIT);
game.enterState(Opsu.STATE_GAME, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
} else if (exitButton.contains(x, y))
} else if (exitButton.contains(x, y)) {
SoundController.playSound(SoundController.SOUND_MENUBACK);
game.enterState(Opsu.STATE_MAINMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
else if (Options.getBackButton().contains(x, y)) {
MusicController.stop();
} else if (Options.getBackButton().contains(x, y)) {
MusicController.pause();
MusicController.playAt(Game.getOsuFile().previewTime, true);
SoundController.playSound(SoundController.SOUND_MENUBACK);
game.enterState(Opsu.STATE_SONGMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
}
}
@@ -176,5 +182,6 @@ public class GameRanking extends BasicGameState {
public void enter(GameContainer container, StateBasedGame game)
throws SlickException {
Display.setTitle(game.getTitle());
SoundController.playSound(SoundController.SOUND_APPLAUSE);
}
}
+9 -3
View File
@@ -22,6 +22,7 @@ import itdelatrisu.opsu.GUIMenuButton;
import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.OsuGroupNode;
import itdelatrisu.opsu.SoundController;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -287,17 +288,22 @@ public class MainMenu extends BasicGameState {
logoTimer = 0;
playButton.getImage().setAlpha(0f);
exitButton.getImage().setAlpha(0f);
SoundController.playSound(SoundController.SOUND_MENUHIT);
}
}
// other button actions (if visible)
else if (logoClicked) {
if (logo.contains(x, y))
if (logo.contains(x, y)) {
SoundController.playSound(SoundController.SOUND_MENUHIT);
logoTimer = MOVE_DELAY;
else if (playButton.contains(x, y))
} else if (playButton.contains(x, y)) {
SoundController.playSound(SoundController.SOUND_MENUHIT);
game.enterState(Opsu.STATE_SONGMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
else if (exitButton.contains(x, y))
} else if (exitButton.contains(x, y)) {
SoundController.playSound(SoundController.SOUND_MENUHIT);
game.enterState(Opsu.STATE_MAINMENUEXIT);
}
}
}
+43 -9
View File
@@ -20,6 +20,7 @@ package itdelatrisu.opsu.states;
import itdelatrisu.opsu.GUIMenuButton;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.SoundController;
import java.awt.Font;
import java.io.BufferedReader;
@@ -148,12 +149,13 @@ public class Options extends BasicGameState {
// OPTIONS_FULLSCREEN = ,
OPTIONS_TARGET_FPS = 1,
OPTIONS_MUSIC_VOLUME = 2,
OPTIONS_MUSIC_OFFSET = 3,
OPTIONS_SCREENSHOT_FORMAT = 4,
OPTIONS_DISPLAY_FPS = 5,
OPTIONS_HIT_LIGHTING = 6,
OPTIONS_COMBO_BURSTS = 7,
OPTIONS_MAX = 8; // not an option
OPTIONS_EFFECT_VOLUME = 3,
OPTIONS_MUSIC_OFFSET = 4,
OPTIONS_SCREENSHOT_FORMAT = 5,
OPTIONS_DISPLAY_FPS = 6,
OPTIONS_HIT_LIGHTING = 7,
OPTIONS_COMBO_BURSTS = 8,
OPTIONS_MAX = 9; // not an option
/**
* Screen resolutions.
@@ -208,7 +210,12 @@ public class Options extends BasicGameState {
/**
* Default music volume.
*/
private static int musicVolume = 20;
private static int musicVolume = 30;
/**
* Default sound effect volume.
*/
private static int effectVolume = 20;
/**
* Offset time, in milliseconds, for music position-related elements.
@@ -258,8 +265,8 @@ public class Options extends BasicGameState {
Options.game = game;
this.input = container.getInput();
// game settings;
container.setTargetFrameRate(60);
// game settings
container.setTargetFrameRate(targetFPS[targetFPSindex]);
container.setMouseCursor("cursor.png", 16, 16);
container.setMusicVolume(getMusicVolume());
container.setShowFPS(false);
@@ -375,6 +382,10 @@ public class Options extends BasicGameState {
String.format("%d%%", musicVolume),
"Global music volume."
);
this.drawOption(g, OPTIONS_EFFECT_VOLUME, "Effect Volume",
String.format("%d%%", effectVolume),
"Sound effect volume."
);
this.drawOption(g, OPTIONS_MUSIC_OFFSET, "Music Offset",
String.format("%dms", musicOffset),
"Adjust this value if hit objects are out of sync."
@@ -509,6 +520,14 @@ public class Options extends BasicGameState {
container.setMusicVolume(getMusicVolume());
return;
}
if (isOptionClicked(OPTIONS_EFFECT_VOLUME, oldy)) {
effectVolume += diff;
if (effectVolume < 0)
effectVolume = 0;
else if (effectVolume > 100)
effectVolume = 100;
return;
}
if (isOptionClicked(OPTIONS_MUSIC_OFFSET, oldy)) {
musicOffset += diff;
if (musicOffset < -500)
@@ -601,6 +620,12 @@ public class Options extends BasicGameState {
*/
public static float getMusicVolume() { return musicVolume / 100f; }
/**
* Returns the default sound effect volume.
* @return the sound volume [0, 1]
*/
public static float getEffectVolume() { return effectVolume / 100f; }
/**
* Returns the music offset time.
* @return the offset (in milliseconds)
@@ -639,6 +664,8 @@ public class Options extends BasicGameState {
SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd_HHmmss");
String file = date.format(new Date());
SoundController.playSound(SoundController.SOUND_SHUTTER);
// copy the screen
Image screen = new Image(container.getWidth(), container.getHeight());
container.getGraphics().copyArea(screen, 0, 0);
@@ -760,6 +787,11 @@ public class Options extends BasicGameState {
if (i >= 0 && i <= 100)
musicVolume = i;
break;
case "VolumeEffect":
i = Integer.parseInt(value);
if (i >= 0 && i <= 100)
effectVolume = i;
break;
case "Offset":
i = Integer.parseInt(value);
if (i >= -500 && i <= 500)
@@ -827,6 +859,8 @@ public class Options extends BasicGameState {
writer.newLine();
writer.write(String.format("VolumeMusic = %d", musicVolume));
writer.newLine();
writer.write(String.format("VolumeEffect = %d", effectVolume));
writer.newLine();
writer.write(String.format("Offset = %d", musicOffset));
writer.newLine();
writer.write(String.format("ScreenshotFormat = %d", screenshotFormatIndex));
+7 -1
View File
@@ -25,6 +25,7 @@ import itdelatrisu.opsu.OsuFile;
import itdelatrisu.opsu.OsuGroupList;
import itdelatrisu.opsu.OsuGroupNode;
import itdelatrisu.opsu.OsuParser;
import itdelatrisu.opsu.SoundController;
import org.lwjgl.opengl.Display;
import org.newdawn.slick.Color;
@@ -343,6 +344,7 @@ public class SongMenu extends BasicGameState {
// back
if (Options.getBackButton().contains(x, y)) {
SoundController.playSound(SoundController.SOUND_MENUBACK);
game.enterState(Opsu.STATE_MAINMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
return;
}
@@ -422,8 +424,10 @@ public class SongMenu extends BasicGameState {
if (!search.getText().isEmpty()) {
search.setText("");
searchTimer = SEARCH_DELAY;
} else
} else {
SoundController.playSound(SoundController.SOUND_MENUBACK);
game.enterState(Opsu.STATE_MAINMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
}
break;
case Input.KEY_F1:
game.enterState(Opsu.STATE_OPTIONS, new EmptyTransition(), new FadeInTransition(Color.black));
@@ -592,8 +596,10 @@ public class SongMenu extends BasicGameState {
if (MusicController.isConverting())
return;
SoundController.playSound(SoundController.SOUND_MENUHIT);
Display.setTitle(String.format("%s - %s", game.getTitle(), osu.toString()));
OsuParser.parseHitObjects(osu);
SoundController.setSampleSet(osu.sampleSet);
Game.setOsuFile(osu);
Game.setRestart(Game.RESTART_NEW);
game.enterState(Opsu.STATE_GAME, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));