/** * Plays the current track at the given position. * * @param position the track position (in ms) * @param loop whether or not to loop the track */ public static void playAt(final int position, final boolean loop) { if (trackExists()) { setVolume(Options.getMusicVolume() * Options.getMasterVolume()); trackEnded = false; pauseTime = 0f; if (loop) player.loop(); else player.play(); if (position >= 0) player.setPosition(position / 1000f); } }
/** Plays the theme song. */ public static void playThemeSong() { Beatmap beatmap = Options.getThemeBeatmap(); if (beatmap != null) { play(beatmap, true, false); themePlaying = true; } }
@Override public void keyPressed(int key, char c) { switch (key) { case Input.KEY_ESCAPE: case Input.KEY_Q: ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(MenuState.EXIT); game.enterState(Opsu.STATE_BUTTONMENU); break; case Input.KEY_P: SoundController.playSound(SoundEffect.MENUHIT); if (!logoClicked) { logoClicked = true; logoTimer = 0; playButton.getImage().setAlpha(0f); exitButton.getImage().setAlpha(0f); } else enterSongMenu(); break; case Input.KEY_D: SoundController.playSound(SoundEffect.MENUHIT); game.enterState( Opsu.STATE_DOWNLOADSMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black)); break; case Input.KEY_R: nextTrack(); break; case Input.KEY_UP: UI.changeVolume(1); break; case Input.KEY_DOWN: UI.changeVolume(-1); break; case Input.KEY_F7: Options.setNextFPS(container); break; case Input.KEY_F10: Options.toggleMouseDisabled(); break; case Input.KEY_F12: Utils.takeScreenShot(); break; } }
@Override public void keyPressed(int key, char c) { switch (key) { case Input.KEY_ESCAPE: if (menuState != null) menuState.leave(container, game); break; case Input.KEY_F7: Options.setNextFPS(container); break; case Input.KEY_F10: Options.toggleMouseDisabled(); break; case Input.KEY_F12: Utils.takeScreenShot(); break; default: if (menuState != null) menuState.keyPress(container, game, key, c); break; } }
/** Plays the next track, and adds the previous one to the stack. */ private void nextTrack() { boolean isTheme = MusicController.isThemePlaying(); SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU); BeatmapSetNode node = menu.setFocus(BeatmapSetList.get().getRandomNode(), -1, true, false); boolean sameAudio = false; if (node != null) { sameAudio = MusicController.getBeatmap() .audioFilename .equals(node.getBeatmapSet().get(0).audioFilename); if (!isTheme && !sameAudio) previous.add(node.index); } if (Options.isDynamicBackgroundEnabled() && !sameAudio && !MusicController.isThemePlaying()) bgAlpha = 0f; }
@Override public void draw(Graphics g, int trackPosition) { int timeDiff = hitObject.getTime() - trackPosition; float scale = timeDiff / (float) game.getApproachTime(); float fadeinScale = (timeDiff - game.getApproachTime() + FADE_IN_TIME) / (float) FADE_IN_TIME; float approachScale = 1 + scale * 3; float alpha = Utils.clamp(1 - fadeinScale, 0, 1); boolean overlayAboveNumber = Options.getSkin().isHitCircleOverlayAboveNumber(); float oldAlpha = Utils.COLOR_WHITE_FADE.a; Utils.COLOR_WHITE_FADE.a = color.a = alpha; Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage(); Image hitCircle = GameImage.HITCIRCLE.getImage(); float[] endPos = curve.pointAt(1); curve.draw(color); color.a = alpha; // end circle hitCircle.drawCentered(endPos[0], endPos[1], color); hitCircleOverlay.drawCentered(endPos[0], endPos[1], Utils.COLOR_WHITE_FADE); // start circle hitCircle.drawCentered(x, y, color); if (!overlayAboveNumber) hitCircleOverlay.drawCentered(x, y, Utils.COLOR_WHITE_FADE); // ticks if (ticksT != null) { Image tick = GameImage.SLIDER_TICK.getImage(); for (int i = 0; i < ticksT.length; i++) { float[] c = curve.pointAt(ticksT[i]); tick.drawCentered(c[0], c[1], Utils.COLOR_WHITE_FADE); } } if (sliderClickedInitial) ; // don't draw current combo number if already clicked else data.drawSymbolNumber( hitObject.getComboNumber(), x, y, hitCircle.getWidth() * 0.40f / data.getDefaultSymbolImage(0).getHeight(), alpha); if (overlayAboveNumber) hitCircleOverlay.drawCentered(x, y, Utils.COLOR_WHITE_FADE); // repeats for (int tcurRepeat = currentRepeats; tcurRepeat <= currentRepeats + 1; tcurRepeat++) { if (hitObject.getRepeatCount() - 1 > tcurRepeat) { Image arrow = GameImage.REVERSEARROW.getImage(); if (tcurRepeat != currentRepeats) { if (sliderTime == 0) continue; float t = Math.max(getT(trackPosition, true), 0); arrow.setAlpha((float) (t - Math.floor(t))); } else arrow.setAlpha(1f); if (tcurRepeat % 2 == 0) { // last circle arrow.setRotation(curve.getEndAngle()); arrow.drawCentered(endPos[0], endPos[1]); } else { // first circle arrow.setRotation(curve.getStartAngle()); arrow.drawCentered(x, y); } } } if (timeDiff >= 0) { // approach circle GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale).drawCentered(x, y, color); } else { // Since update() might not have run before drawing during a replay, the // slider time may not have been calculated, which causes NAN numbers and flicker. if (sliderTime == 0) return; float[] c = curve.pointAt(getT(trackPosition, false)); float[] c2 = curve.pointAt(getT(trackPosition, false) + 0.01f); float t = getT(trackPosition, false); // float dis = hitObject.getPixelLength() * HitObject.getXMultiplier() * (t - (int) t); // Image sliderBallFrame = sliderBallImages[(int) (dis / (diameter * Math.PI) * 30) % // sliderBallImages.length]; Image sliderBallFrame = sliderBallImages[(int) (t * sliderTime * 60 / 1000) % sliderBallImages.length]; float angle = (float) (Math.atan2(c2[1] - c[1], c2[0] - c[0]) * 180 / Math.PI); sliderBallFrame.setRotation(angle); sliderBallFrame.drawCentered(c[0], c[1]); // follow circle if (followCircleActive) { GameImage.SLIDER_FOLLOWCIRCLE.getImage().drawCentered(c[0], c[1]); // "flashlight" mod: dim the screen if (GameMod.FLASHLIGHT.isActive()) { float oldAlphaBlack = Utils.COLOR_BLACK_ALPHA.a; Utils.COLOR_BLACK_ALPHA.a = 0.75f; g.setColor(Utils.COLOR_BLACK_ALPHA); g.fillRect(0, 0, containerWidth, containerHeight); Utils.COLOR_BLACK_ALPHA.a = oldAlphaBlack; } } } Utils.COLOR_WHITE_FADE.a = oldAlpha; }
/** * Toggles the volume dim state of the current track. * * @param multiplier the volume multiplier when the track is dimmed */ public static void toggleTrackDimmed(float multiplier) { float volume = Options.getMusicVolume() * Options.getMasterVolume(); dimLevel = (isTrackDimmed()) ? 1f : multiplier; trackDimmed = !trackDimmed; setVolume(volume); }
/** * Returns the position in the current track, in milliseconds. If no track is loaded, 0 will be * returned. */ public static int getPosition() { if (isPlaying()) return (int) (player.getPosition() * 1000 + Options.getMusicOffset()); else if (isPaused()) return Math.max((int) (pauseTime * 1000 + Options.getMusicOffset()), 0); else return 0; }
@Override public void mousePressed(int button, int x, int y) { // check mouse button if (button == Input.MOUSE_MIDDLE_BUTTON) return; // music position bar if (MusicController.isPlaying()) { if (musicPositionBarContains(x, y)) { float pos = (x - musicBarX) / musicBarWidth; MusicController.setPosition((int) (pos * MusicController.getDuration())); return; } } // music button actions if (musicPlay.contains(x, y)) { if (MusicController.isPlaying()) { MusicController.pause(); UI.sendBarNotification("Pause"); } else if (!MusicController.isTrackLoading()) { MusicController.resume(); UI.sendBarNotification("Play"); } } else if (musicNext.contains(x, y)) { nextTrack(); UI.sendBarNotification(">> Next"); } else if (musicPrevious.contains(x, y)) { if (!previous.isEmpty()) { SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU); menu.setFocus(BeatmapSetList.get().getBaseNode(previous.pop()), -1, true, false); if (Options.isDynamicBackgroundEnabled()) bgAlpha = 0f; } else MusicController.setPosition(0); UI.sendBarNotification("<< Previous"); } // downloads button actions else if (downloadsButton.contains(x, y)) { SoundController.playSound(SoundEffect.MENUHIT); game.enterState( Opsu.STATE_DOWNLOADSMENU, new FadeOutTransition(Color.black), new FadeInTransition(Color.black)); } // repository button actions else if (repoButton != null && repoButton.contains(x, y)) { try { Desktop.getDesktop().browse(Options.REPOSITORY_URI); } catch (IOException e) { ErrorHandler.error("Could not browse to repository URI.", e, false); } } // update button actions else if (Updater.get().showButton() && updateButton.contains(x, y)) { switch (Updater.get().getStatus()) { case UPDATE_AVAILABLE: SoundController.playSound(SoundEffect.MENUHIT); Updater.get().startDownload(); break; case UPDATE_DOWNLOADED: SoundController.playSound(SoundEffect.MENUHIT); Updater.get().prepareUpdate(); container.setForceExit(false); container.exit(); break; default: break; } } // start moving logo (if clicked) else if (!logoClicked) { if (logo.contains(x, y, 0.25f)) { logoClicked = true; logoTimer = 0; playButton.getImage().setAlpha(0f); exitButton.getImage().setAlpha(0f); SoundController.playSound(SoundEffect.MENUHIT); } } // other button actions (if visible) else if (logoClicked) { if (logo.contains(x, y, 0.25f) || playButton.contains(x, y, 0.25f)) { SoundController.playSound(SoundEffect.MENUHIT); enterSongMenu(); } else if (exitButton.contains(x, y, 0.25f)) { container.exit(); } else if (optionsButton.contains(x, y, 0.25f)) { enterOptionsMenu(); } } }
@Override public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException { int width = container.getWidth(); int height = container.getHeight(); // draw background Beatmap beatmap = MusicController.getBeatmap(); if (Options.isDynamicBackgroundEnabled() && beatmap != null && beatmap.drawBG(width, height, bgAlpha, true)) ; else { Image bg = GameImage.MENU_BG.getImage(); bg.setAlpha(bgAlpha); bg.draw(); } // top/bottom horizontal bars float oldAlpha = Utils.COLOR_BLACK_ALPHA.a; Utils.COLOR_BLACK_ALPHA.a = 0.2f; g.setColor(Utils.COLOR_BLACK_ALPHA); g.fillRect(0, 0, width, height / 9f); g.fillRect(0, height * 8 / 9f, width, height / 9f); Utils.COLOR_BLACK_ALPHA.a = oldAlpha; // draw downloads button downloadsButton.draw(); // draw buttons if (logoTimer > 0) { playButton.draw(); exitButton.draw(); optionsButton.draw(); } logo.draw(); // draw music buttons if (MusicController.isPlaying()) musicPause.draw(); else musicPlay.draw(); musicNext.draw(); musicPrevious.draw(); // draw music position bar int mouseX = input.getMouseX(), mouseY = input.getMouseY(); g.setColor((musicPositionBarContains(mouseX, mouseY)) ? BG_HOVER : BG_NORMAL); g.fillRoundRect(musicBarX, musicBarY, musicBarWidth, musicBarHeight, 4); g.setColor(Color.white); if (!MusicController.isTrackLoading() && beatmap != null) { float musicBarPosition = Math.min((float) MusicController.getPosition() / MusicController.getDuration(), 1f); g.fillRoundRect(musicBarX, musicBarY, musicBarWidth * musicBarPosition, musicBarHeight, 4); } // draw repository button if (repoButton != null) repoButton.draw(); // draw update button if (Updater.get().showButton()) { Color updateColor = null; switch (Updater.get().getStatus()) { case UPDATE_AVAILABLE: updateColor = Color.red; break; case UPDATE_DOWNLOADED: updateColor = Color.green; break; case UPDATE_DOWNLOADING: updateColor = Color.yellow; break; default: updateColor = Color.white; break; } updateButton.draw(updateColor); } // draw text float marginX = width * 0.015f, topMarginY = height * 0.01f, bottomMarginY = height * 0.015f; g.setFont(Utils.FONT_MEDIUM); float lineHeight = Utils.FONT_MEDIUM.getLineHeight() * 0.925f; g.drawString( String.format( "Loaded %d songs and %d beatmaps.", BeatmapSetList.get().getMapSetCount(), BeatmapSetList.get().getMapCount()), marginX, topMarginY); if (MusicController.isTrackLoading()) g.drawString("Track loading...", marginX, topMarginY + lineHeight); else if (MusicController.trackExists()) { if (Options.useUnicodeMetadata()) // load glyphs Utils.loadGlyphs(Utils.FONT_MEDIUM, beatmap.titleUnicode, beatmap.artistUnicode); g.drawString( (MusicController.isPlaying()) ? "Now Playing:" : "Paused:", marginX, topMarginY + lineHeight); g.drawString( String.format("%s: %s", beatmap.getArtist(), beatmap.getTitle()), marginX + 25, topMarginY + (lineHeight * 2)); } g.drawString( String.format( "opsu! has been running for %s.", Utils.getTimeString((int) (System.currentTimeMillis() - programStartTime) / 1000)), marginX, height - bottomMarginY - (lineHeight * 2)); g.drawString( String.format("It is currently %s.", new SimpleDateFormat("h:mm a").format(new Date())), marginX, height - bottomMarginY - lineHeight); UI.draw(g); }