opsu-dance/src/itdelatrisu/opsu/states/Game.java

1015 lines
29 KiB
Java
Raw Normal View History

2014-06-30 04:17:04 +02:00
/*
* 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.states;
import itdelatrisu.opsu.GUIMenuButton;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.GameMod;
2014-06-30 04:17:04 +02:00
import itdelatrisu.opsu.GameScore;
import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.OsuFile;
import itdelatrisu.opsu.OsuHitObject;
import itdelatrisu.opsu.OsuTimingPoint;
import itdelatrisu.opsu.SoundController;
import itdelatrisu.opsu.Utils;
2014-06-30 04:17:04 +02:00
import itdelatrisu.opsu.objects.Circle;
import itdelatrisu.opsu.objects.Slider;
import itdelatrisu.opsu.objects.Spinner;
import java.io.File;
2014-06-30 04:17:04 +02:00
import java.util.HashMap;
import java.util.Stack;
import java.util.concurrent.TimeUnit;
2014-06-30 04:17:04 +02:00
import org.lwjgl.input.Keyboard;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.state.transition.EmptyTransition;
import org.newdawn.slick.state.transition.FadeInTransition;
import org.newdawn.slick.state.transition.FadeOutTransition;
import org.newdawn.slick.util.Log;
/**
* "Game" state.
*/
public class Game extends BasicGameState {
/**
* Game restart states.
*/
public static final byte
RESTART_FALSE = 0,
RESTART_NEW = 1, // first time loading song
RESTART_MANUAL = 2, // retry
RESTART_LOSE = 3; // health is zero: no-continue/force restart
/**
* Current restart state.
*/
private static byte restart;
/**
* The associated OsuFile object.
*/
private OsuFile osu;
2014-06-30 04:17:04 +02:00
/**
* The associated GameScore object (holds all score data).
*/
private static GameScore score;
/**
* Current hit object index in OsuHitObject[] array.
*/
private int objectIndex = 0;
/**
* This map's hit circles objects, keyed by objectIndex.
*/
private HashMap<Integer, Circle> circles;
/**
* This map's slider objects, keyed by objectIndex.
*/
private HashMap<Integer, Slider> sliders;
/**
* This map's spinner objects, keyed by objectIndex.
*/
private HashMap<Integer, Spinner> spinners;
/**
* Delay time, in milliseconds, before song starts.
*/
private int leadInTime;
2014-06-30 04:17:04 +02:00
/**
* Hit object approach time, in milliseconds.
*/
private int approachTime;
/**
* Time offsets for obtaining each hit result (indexed by HIT_* constants).
*/
private int[] hitResultOffset;
/**
* Current break index in breaks ArrayList.
*/
private int breakIndex;
/**
* Break start time (0 if not in break).
*/
private int breakTime = 0;
/**
* Whether the break sound has been played.
*/
private boolean breakSound;
2014-06-30 04:17:04 +02:00
/**
* Skip button (displayed at song start, when necessary).
*/
private GUIMenuButton skipButton;
/**
* Minimum time before start of song, in milliseconds, to process skip-related actions.
*/
private static final int SKIP_OFFSET = 2000;
2014-06-30 04:17:04 +02:00
/**
* Current timing point index in timingPoints ArrayList.
*/
private int timingPointIndex;
/**
* Current beat lengths (base value and inherited value).
*/
private float beatLengthBase, beatLength;
/**
* Whether the countdown sound has been played.
*/
private boolean
countdownReadySound, countdown3Sound, countdown1Sound,
countdown2Sound, countdownGoSound;
2014-06-30 04:17:04 +02:00
/**
* Mouse coordinates before game paused.
*/
private int pausedMouseX = -1, pausedMouseY = -1;
/**
* Track position when game paused.
*/
private int pauseTime = -1;
/**
* Value for handling hitCircleSelect pulse effect (expanding, alpha level).
*/
private float pausePulse;
/**
* Default playfield background (optional).
* Overridden by song background unless "ForceDefaultPlayfield" option enabled.
*/
private Image playfield;
/**
* Whether a checkpoint has been loaded during this game.
*/
private boolean checkpointLoaded = false;
/**
* Number of deaths, used if "Easy" mod is enabled.
*/
private byte deaths = 0;
/**
* Track position at death, used if "Easy" mod is enabled.
*/
private int deathTime = -1;
/**
* Number of retries.
*/
private int retries = 0;
2014-06-30 04:17:04 +02:00
// game-related variables
private GameContainer container;
private StateBasedGame game;
private Input input;
private int state;
public Game(int state) {
this.state = state;
}
@Override
public void init(GameContainer container, StateBasedGame game)
throws SlickException {
this.container = container;
this.game = game;
input = container.getInput();
int width = container.getWidth();
int height = container.getHeight();
// create the associated GameScore object
score = new GameScore(width, height);
// playfield background
try {
playfield = new Image("playfield.png").getScaledCopy(width, height);
} catch (Exception e) {
// optional
}
2014-06-30 04:17:04 +02:00
}
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g)
throws SlickException {
int width = container.getWidth();
int height = container.getHeight();
// background
g.setBackground(Color.black);
float dimLevel = Options.getBackgroundDim();
if (Options.isDefaultPlayfieldForced() && playfield != null) {
playfield.setAlpha(dimLevel);
playfield.draw();
} else if (!osu.drawBG(width, height, dimLevel) && playfield != null) {
playfield.setAlpha(dimLevel);
playfield.draw();
}
2014-06-30 04:17:04 +02:00
int trackPosition = MusicController.getPosition();
if (pauseTime > -1) // returning from pause screen
trackPosition = pauseTime;
else if (deathTime > -1) // "Easy" mod: health bar increasing
trackPosition = deathTime;
int firstObjectTime = osu.objects[0].getTime();
int timeDiff = firstObjectTime - trackPosition;
2014-06-30 04:17:04 +02:00
// checkpoint
if (checkpointLoaded) {
int checkpoint = Options.getCheckpoint();
String checkpointText = String.format(
"Playing from checkpoint at %02d:%02d.",
TimeUnit.MILLISECONDS.toMinutes(checkpoint),
TimeUnit.MILLISECONDS.toSeconds(checkpoint) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(checkpoint))
);
Utils.FONT_MEDIUM.drawString(
(width - Utils.FONT_MEDIUM.getWidth(checkpointText)) / 2,
height - 15 - Utils.FONT_MEDIUM.getLineHeight(),
checkpointText, Color.white
);
}
2014-06-30 04:17:04 +02:00
// break periods
if (osu.breaks != null && breakIndex < osu.breaks.size()) {
if (breakTime > 0) {
int endTime = osu.breaks.get(breakIndex);
int breakLength = endTime - breakTime;
// letterbox effect (black bars on top/bottom)
if (osu.letterboxInBreaks && breakLength >= 4000) {
g.setColor(Color.black);
g.fillRect(0, 0, width, height * 0.125f);
g.fillRect(0, height * 0.875f, width, height * 0.125f);
}
score.drawGameElements(g, true, objectIndex == 0);
2014-06-30 04:17:04 +02:00
if (breakLength >= 8000 &&
trackPosition - breakTime > 2000 &&
trackPosition - breakTime < 5000) {
// show break start
if (score.getHealth() >= 50) {
GameImage.SECTION_PASS.getImage().drawCentered(width / 2f, height / 2f);
if (!breakSound) {
SoundController.playSound(SoundController.SOUND_SECTIONPASS);
breakSound = true;
}
} else {
GameImage.SECTION_FAIL.getImage().drawCentered(width / 2f, height / 2f);
if (!breakSound) {
SoundController.playSound(SoundController.SOUND_SECTIONFAIL);
breakSound = true;
}
}
2014-06-30 04:17:04 +02:00
} else if (breakLength >= 4000) {
// show break end (flash twice for 500ms)
int endTimeDiff = endTime - trackPosition;
if ((endTimeDiff > 1500 && endTimeDiff < 2000) ||
(endTimeDiff > 500 && endTimeDiff < 1000)) {
Image arrow = GameImage.WARNINGARROW.getImage();
arrow.setRotation(0);
arrow.draw(width * 0.15f, height * 0.15f);
arrow.draw(width * 0.15f, height * 0.75f);
arrow.setRotation(180);
arrow.draw(width * 0.75f, height * 0.15f);
arrow.draw(width * 0.75f, height * 0.75f);
2014-06-30 04:17:04 +02:00
}
}
if (GameMod.AUTO.isActive())
GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f);
Utils.drawFPS();
Utils.drawCursor();
2014-06-30 04:17:04 +02:00
return;
}
}
// game elements
score.drawGameElements(g, false, objectIndex == 0);
2014-06-30 04:17:04 +02:00
// skip beginning
if (objectIndex == 0 &&
firstObjectTime - SKIP_OFFSET > 5000 &&
trackPosition < osu.objects[0].getTime() - SKIP_OFFSET)
skipButton.draw();
// show retries
if (retries >= 2 && timeDiff >= -1000) {
int retryHeight = Math.max(
GameImage.SCOREBAR_BG.getImage().getHeight(),
GameImage.SCOREBAR_KI.getImage().getHeight()
);
if (timeDiff < -500)
Utils.COLOR_WHITE_FADE.a = (1000 + timeDiff) / 500f;
Utils.FONT_MEDIUM.drawString(
2 + (width / 100), retryHeight,
String.format("%d retries and counting...", retries),
Utils.COLOR_WHITE_FADE
);
Utils.COLOR_WHITE_FADE.a = 1f;
}
2014-06-30 04:17:04 +02:00
if (isLeadIn())
trackPosition = leadInTime * -1; // render approach circles during song lead-in
// countdown
if (osu.countdown > 0) { // TODO: implement half/double rate settings
if (timeDiff >= 500 && timeDiff < 3000) {
if (timeDiff >= 1500) {
GameImage.COUNTDOWN_READY.getImage().drawCentered(width / 2, height / 2);
if (!countdownReadySound) {
SoundController.playSound(SoundController.SOUND_READY);
countdownReadySound = true;
}
}
if (timeDiff < 2000) {
GameImage.COUNTDOWN_3.getImage().draw(0, 0);
if (!countdown3Sound) {
SoundController.playSound(SoundController.SOUND_COUNT3);
countdown3Sound = true;
}
}
if (timeDiff < 1500) {
GameImage.COUNTDOWN_2.getImage().draw(width - GameImage.COUNTDOWN_2.getImage().getWidth(), 0);
if (!countdown2Sound) {
SoundController.playSound(SoundController.SOUND_COUNT2);
countdown2Sound = true;
}
}
if (timeDiff < 1000) {
GameImage.COUNTDOWN_1.getImage().drawCentered(width / 2, height / 2);
if (!countdown1Sound) {
SoundController.playSound(SoundController.SOUND_COUNT1);
countdown1Sound = true;
}
}
2014-06-30 04:17:04 +02:00
} else if (timeDiff >= -500 && timeDiff < 500) {
Image go = GameImage.COUNTDOWN_GO.getImage();
go.setAlpha((timeDiff < 0) ? 1 - (timeDiff / -1000f) : 1);
go.drawCentered(width / 2, height / 2);
if (!countdownGoSound) {
SoundController.playSound(SoundController.SOUND_GO);
countdownGoSound = true;
}
2014-06-30 04:17:04 +02:00
}
}
// draw hit objects in reverse order, or else overlapping objects are unreadable
Stack<Integer> stack = new Stack<Integer>();
for (int i = objectIndex; i < osu.objects.length && osu.objects[i].getTime() < trackPosition + approachTime; i++)
2014-06-30 04:17:04 +02:00
stack.add(i);
while (!stack.isEmpty()) {
int i = stack.pop();
OsuHitObject hitObject = osu.objects[i];
if (hitObject.isCircle())
2014-06-30 04:17:04 +02:00
circles.get(i).draw(trackPosition);
else if (hitObject.isSlider())
2014-06-30 04:17:04 +02:00
sliders.get(i).draw(trackPosition, stack.isEmpty());
else if (hitObject.isSpinner()) {
2014-06-30 04:17:04 +02:00
if (stack.isEmpty()) // only draw spinner at objectIndex
spinners.get(i).draw(trackPosition, g);
else
continue;
}
}
// draw OsuHitObjectResult objects
score.drawHitResults(trackPosition);
if (GameMod.AUTO.isActive())
GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f);
2014-06-30 04:17:04 +02:00
// returning from pause screen
if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) {
// darken the screen
g.setColor(Utils.COLOR_BLACK_ALPHA);
2014-06-30 04:17:04 +02:00
g.fillRect(0, 0, width, height);
// draw glowing hit select circle and pulse effect
int circleRadius = GameImage.HITCIRCLE.getImage().getWidth();
Image cursorCircle = GameImage.HITCIRCLE_SELECT.getImage().getScaledCopy(circleRadius, circleRadius);
2014-06-30 04:17:04 +02:00
cursorCircle.setAlpha(1.0f);
cursorCircle.drawCentered(pausedMouseX, pausedMouseY);
Image cursorCirclePulse = cursorCircle.getScaledCopy(1f + pausePulse);
cursorCirclePulse.setAlpha(1f - pausePulse);
cursorCirclePulse.drawCentered(pausedMouseX, pausedMouseY);
}
Utils.drawFPS();
Utils.drawCursor();
2014-06-30 04:17:04 +02:00
}
@Override
public void update(GameContainer container, StateBasedGame game, int delta)
throws SlickException {
Utils.updateCursor(delta);
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
skipButton.hoverUpdate(delta, mouseX, mouseY);
2014-06-30 04:17:04 +02:00
if (isLeadIn()) { // stop updating during song lead-in
leadInTime -= delta;
if (!isLeadIn())
MusicController.playAt(0, false);
return;
}
// returning from pause screen: must click previous mouse position
if (pauseTime > -1) {
// paused during lead-in or break: continue immediately
if (pausedMouseX < 0 && pausedMouseY < 0) {
pauseTime = -1;
if (!isLeadIn())
MusicController.resume();
}
// focus lost: go back to pause screen
else if (!container.hasFocus()) {
game.enterState(Opsu.STATE_GAMEPAUSEMENU);
pausePulse = 0f;
}
// advance pulse animation
else {
pausePulse += delta / 750f;
if (pausePulse > 1f)
pausePulse = 0f;
}
return;
}
// "Easy" mod: multiple "lives"
if (GameMod.EASY.isActive() && deathTime > -1) {
if (score.getHealth() < 99f)
score.changeHealth(delta / 10f);
else {
MusicController.resume();
deathTime = -1;
}
}
score.updateScoreDisplay(delta);
2014-06-30 04:17:04 +02:00
// map complete!
if (objectIndex >= osu.objects.length) {
// if checkpoint used, don't show the ranking screen
int state = (checkpointLoaded) ? Opsu.STATE_SONGMENU : Opsu.STATE_GAMERANKING;
game.enterState(state, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
2014-06-30 04:17:04 +02:00
return;
}
int trackPosition = MusicController.getPosition();
// timing points
if (timingPointIndex < osu.timingPoints.size()) {
OsuTimingPoint timingPoint = osu.timingPoints.get(timingPointIndex);
if (trackPosition >= timingPoint.getTime()) {
if (!timingPoint.isInherited())
beatLengthBase = beatLength = timingPoint.getBeatLength();
2014-06-30 04:17:04 +02:00
else
beatLength = beatLengthBase * timingPoint.getSliderMultiplier();
SoundController.setSampleSet(timingPoint.getSampleType());
SoundController.setSampleVolume(timingPoint.getSampleVolume());
2014-06-30 04:17:04 +02:00
timingPointIndex++;
}
}
// song beginning
if (objectIndex == 0) {
if (trackPosition < osu.objects[0].getTime())
2014-06-30 04:17:04 +02:00
return; // nothing to do here
}
// break periods
if (osu.breaks != null && breakIndex < osu.breaks.size()) {
int breakValue = osu.breaks.get(breakIndex);
if (breakTime > 0) { // in a break period
if (trackPosition < breakValue)
return;
else {
// break is over
breakTime = 0;
breakIndex++;
}
} else if (trackPosition >= breakValue) {
// start a break
breakTime = breakValue;
breakSound = false;
2014-06-30 04:17:04 +02:00
breakIndex++;
return;
}
}
// pause game if focus lost
if (!container.hasFocus() && !GameMod.AUTO.isActive()) {
2014-06-30 04:17:04 +02:00
if (pauseTime < 0) {
pausedMouseX = mouseX;
pausedMouseY = mouseY;
2014-06-30 04:17:04 +02:00
pausePulse = 0f;
}
if (MusicController.isPlaying() || isLeadIn())
pauseTime = trackPosition;
game.enterState(Opsu.STATE_GAMEPAUSEMENU);
}
score.updateComboBurst(delta);
2014-06-30 04:17:04 +02:00
// drain health
score.changeHealth(delta * -1 * GameScore.HP_DRAIN_MULTIPLIER);
2014-06-30 04:17:04 +02:00
if (!score.isAlive()) {
// "Easy" mod
if (GameMod.EASY.isActive()) {
deaths++;
if (deaths < 3) {
deathTime = trackPosition;
MusicController.pause();
return;
}
}
2014-06-30 04:17:04 +02:00
// game over, force a restart
restart = RESTART_LOSE;
game.enterState(Opsu.STATE_GAMEPAUSEMENU);
}
// update objects (loop in unlikely event of any skipped indexes)
while (objectIndex < osu.objects.length && trackPosition > osu.objects[objectIndex].getTime()) {
2014-06-30 04:17:04 +02:00
OsuHitObject hitObject = osu.objects[objectIndex];
// check if we've already passed the next object's start time
boolean overlap = (objectIndex + 1 < osu.objects.length &&
trackPosition > osu.objects[objectIndex + 1].getTime() - hitResultOffset[GameScore.HIT_300]);
2014-06-30 04:17:04 +02:00
// check completion status of the hit object
boolean done = false;
if (hitObject.isCircle())
2014-06-30 04:17:04 +02:00
done = circles.get(objectIndex).update(overlap);
else if (hitObject.isSlider())
done = sliders.get(objectIndex).update(overlap, delta, mouseX, mouseY);
else if (hitObject.isSpinner())
done = spinners.get(objectIndex).update(overlap, delta, mouseX, mouseY);
2014-06-30 04:17:04 +02:00
// increment object index?
if (done)
objectIndex++;
else
break;
}
}
@Override
public int getID() { return state; }
@Override
public void keyPressed(int key, char c) {
int trackPosition = MusicController.getPosition();
// game keys
if (!Keyboard.isRepeatEvent()) {
if (key == Options.getGameKeyLeft())
mousePressed(Input.MOUSE_LEFT_BUTTON, input.getMouseX(), input.getMouseY());
else if (key == Options.getGameKeyRight())
mousePressed(Input.MOUSE_RIGHT_BUTTON, input.getMouseX(), input.getMouseY());
}
2014-06-30 04:17:04 +02:00
switch (key) {
case Input.KEY_ESCAPE:
// pause game
if (pauseTime < 0 && breakTime <= 0 &&
trackPosition >= osu.objects[0].getTime() &&
!GameMod.AUTO.isActive()) {
2014-06-30 04:17:04 +02:00
pausedMouseX = input.getMouseX();
pausedMouseY = input.getMouseY();
pausePulse = 0f;
}
if (MusicController.isPlaying() || isLeadIn())
pauseTime = trackPosition;
game.enterState(Opsu.STATE_GAMEPAUSEMENU, new EmptyTransition(), new FadeInTransition(Color.black));
break;
case Input.KEY_SPACE:
// skip intro
2014-06-30 04:17:04 +02:00
skipIntro();
break;
case Input.KEY_R:
// restart
if (input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) {
try {
if (trackPosition < osu.objects[0].getTime())
retries--; // don't count this retry (cancel out later increment)
restart = RESTART_MANUAL;
enter(container, game);
skipIntro();
} catch (SlickException e) {
Log.error("Failed to restart game.", e);
}
}
break;
case Input.KEY_S:
// save checkpoint
if (input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) {
if (isLeadIn())
break;
int position = (pauseTime > -1) ? pauseTime : trackPosition;
if (Options.setCheckpoint(position / 1000))
SoundController.playSound(SoundController.SOUND_MENUCLICK);
}
break;
case Input.KEY_L:
// load checkpoint
if (input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) {
int checkpoint = Options.getCheckpoint();
if (checkpoint == 0 || checkpoint > osu.endTime)
break; // invalid checkpoint
try {
restart = RESTART_MANUAL;
enter(container, game);
checkpointLoaded = true;
if (isLeadIn()) {
leadInTime = 0;
MusicController.resume();
}
SoundController.playSound(SoundController.SOUND_MENUHIT);
// skip to checkpoint
MusicController.setPosition(checkpoint);
while (objectIndex < osu.objects.length &&
osu.objects[objectIndex++].getTime() <= trackPosition)
;
objectIndex--;
} catch (SlickException e) {
Log.error("Failed to load checkpoint.", e);
}
}
break;
2014-06-30 04:17:04 +02:00
case Input.KEY_F12:
Utils.takeScreenShot();
2014-06-30 04:17:04 +02:00
break;
}
}
@Override
public void mousePressed(int button, int x, int y) {
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
// returning from pause screen
if (pauseTime > -1) {
double distance = Math.hypot(pausedMouseX - x, pausedMouseY - y);
int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2;
2014-06-30 04:17:04 +02:00
if (distance < circleRadius) {
// unpause the game
pauseTime = -1;
pausedMouseX = -1;
pausedMouseY = -1;
if (!isLeadIn())
2014-06-30 04:17:04 +02:00
MusicController.resume();
}
return;
}
if (objectIndex >= osu.objects.length) // nothing left to do here
return;
OsuHitObject hitObject = osu.objects[objectIndex];
// skip beginning
if (skipButton.contains(x, y)) {
if (skipIntro())
return; // successfully skipped
}
// "auto" mod: ignore user actions
if (GameMod.AUTO.isActive())
2014-06-30 04:17:04 +02:00
return;
// circles
if (hitObject.isCircle()) {
2014-06-30 04:17:04 +02:00
boolean hit = circles.get(objectIndex).mousePressed(x, y);
if (hit)
objectIndex++;
}
// sliders
else if (hitObject.isSlider())
2014-06-30 04:17:04 +02:00
sliders.get(objectIndex).mousePressed(x, y);
}
@Override
public void enter(GameContainer container, StateBasedGame game)
throws SlickException {
if (restart == RESTART_NEW)
osu = MusicController.getOsuFile();
2014-06-30 04:17:04 +02:00
if (osu == null || osu.objects == null)
throw new RuntimeException("Running game with no OsuFile loaded.");
// grab the mouse (not working for touchscreen)
// container.setMouseGrabbed(true);
2014-06-30 04:17:04 +02:00
// restart the game
if (restart != RESTART_FALSE) {
// new game
if (restart == RESTART_NEW) {
loadImages();
2014-06-30 04:17:04 +02:00
setMapModifiers();
retries = 0;
} else
retries++;
2014-06-30 04:17:04 +02:00
// initialize object maps
circles = new HashMap<Integer, Circle>();
sliders = new HashMap<Integer, Slider>();
spinners = new HashMap<Integer, Spinner>();
for (int i = 0; i < osu.objects.length; i++) {
OsuHitObject hitObject = osu.objects[i];
// is this the last note in the combo?
boolean comboEnd = false;
if (i + 1 < osu.objects.length && osu.objects[i + 1].isNewCombo())
2014-06-30 04:17:04 +02:00
comboEnd = true;
Color color = osu.combo[hitObject.getComboIndex()];
if (hitObject.isCircle()) {
circles.put(i, new Circle(hitObject, this, score, color, comboEnd));
} else if (hitObject.isSlider()) {
sliders.put(i, new Slider(hitObject, this, score, color, comboEnd));
} else if (hitObject.isSpinner()) {
2014-06-30 04:17:04 +02:00
spinners.put(i, new Spinner(hitObject, this, score));
}
}
// reset data
2014-06-30 04:17:04 +02:00
MusicController.setPosition(0);
MusicController.pause();
score.clear();
objectIndex = 0;
breakIndex = 0;
breakTime = 0;
breakSound = false;
2014-06-30 04:17:04 +02:00
timingPointIndex = 0;
beatLengthBase = beatLength = 1;
2014-06-30 04:17:04 +02:00
pauseTime = -1;
pausedMouseX = -1;
pausedMouseY = -1;
countdownReadySound = false;
countdown3Sound = false;
countdown1Sound = false;
countdown2Sound = false;
countdownGoSound = false;
checkpointLoaded = false;
deaths = 0;
deathTime = -1;
2014-06-30 04:17:04 +02:00
// load the first timingPoint
if (!osu.timingPoints.isEmpty()) {
OsuTimingPoint timingPoint = osu.timingPoints.get(0);
if (!timingPoint.isInherited()) {
beatLengthBase = beatLength = timingPoint.getBeatLength();
SoundController.setSampleSet(timingPoint.getSampleType());
SoundController.setSampleVolume(timingPoint.getSampleVolume());
timingPointIndex++;
}
2014-06-30 04:17:04 +02:00
}
leadInTime = osu.audioLeadIn + approachTime;
restart = RESTART_FALSE;
}
skipButton.setScale(1f);
2014-06-30 04:17:04 +02:00
}
// @Override
// public void leave(GameContainer container, StateBasedGame game)
// throws SlickException {
// container.setMouseGrabbed(false);
// }
2014-06-30 04:17:04 +02:00
/**
* Skips the beginning of a track.
* @return true if skipped, false otherwise
*/
private boolean skipIntro() {
int firstObjectTime = osu.objects[0].getTime();
2014-06-30 04:17:04 +02:00
int trackPosition = MusicController.getPosition();
if (objectIndex == 0 &&
firstObjectTime - SKIP_OFFSET > 4000 &&
trackPosition < firstObjectTime - SKIP_OFFSET) {
2014-06-30 04:17:04 +02:00
if (isLeadIn()) {
leadInTime = 0;
MusicController.resume();
}
MusicController.setPosition(firstObjectTime - SKIP_OFFSET);
SoundController.playSound(SoundController.SOUND_MENUHIT);
2014-06-30 04:17:04 +02:00
return true;
}
return false;
}
/**
* Loads all game images.
* @throws SlickException
*/
private void loadImages() throws SlickException {
int width = container.getWidth();
int height = container.getHeight();
// set images
File parent = osu.getFile().getParentFile();
for (GameImage o : GameImage.values())
o.setSkinImage(parent);
// skip button
Image skip = GameImage.SKIP.getImage();
if (!GameImage.SKIP.isScaled()) {
float skipScale = (height * 0.1f) / skip.getHeight();
skip = skip.getScaledCopy(skipScale);
GameImage.SKIP.setImage(skip);
GameImage.SKIP.setScaled();
}
skipButton = new GUIMenuButton(skip,
width - (skip.getWidth() / 2f),
height - (skip.getHeight() / 2f));
skipButton.setHoverDir(GUIMenuButton.Expand.UP_LEFT);
// countdown
float countdownHeight = height / 3f;
if (!GameImage.COUNTDOWN_READY.isScaled()) {
Image countdownReady = GameImage.COUNTDOWN_READY.getImage();
GameImage.COUNTDOWN_READY.setImage(
countdownReady.getScaledCopy(countdownHeight / countdownReady.getHeight()));
GameImage.COUNTDOWN_READY.setScaled();
}
if (!GameImage.COUNTDOWN_3.isScaled()) {
Image countdown3 = GameImage.COUNTDOWN_3.getImage();
GameImage.COUNTDOWN_3.setImage(
countdown3.getScaledCopy(countdownHeight / countdown3.getHeight()));
GameImage.COUNTDOWN_3.setScaled();
}
if (!GameImage.COUNTDOWN_2.isScaled()) {
Image countdown2 = GameImage.COUNTDOWN_2.getImage();
GameImage.COUNTDOWN_2.setImage(
countdown2.getScaledCopy(countdownHeight / countdown2.getHeight()));
GameImage.COUNTDOWN_2.setScaled();
}
if (!GameImage.COUNTDOWN_1.isScaled()) {
Image countdown1 = GameImage.COUNTDOWN_1.getImage();
GameImage.COUNTDOWN_1.setImage(
countdown1.getScaledCopy(countdownHeight / countdown1.getHeight()));
GameImage.COUNTDOWN_1.setScaled();
}
if (!GameImage.COUNTDOWN_GO.isScaled()) {
Image countdownGo = GameImage.COUNTDOWN_GO.getImage();
GameImage.COUNTDOWN_GO.setImage(
countdownGo.getScaledCopy(countdownHeight / countdownGo.getHeight()));
GameImage.COUNTDOWN_GO.setScaled();
}
// load other images...
((GamePauseMenu) game.getState(Opsu.STATE_GAMEPAUSEMENU)).loadImages();
score.loadImages();
}
2014-06-30 04:17:04 +02:00
/**
* Set map modifiers.
*/
private void setMapModifiers() {
try {
// map-based properties, re-initialized each game
float circleSize = osu.circleSize;
float approachRate = osu.approachRate;
float overallDifficulty = osu.overallDifficulty;
float HPDrainRate = osu.HPDrainRate;
// "Hard Rock" modifiers
if (GameMod.HARD_ROCK.isActive()) {
circleSize = Math.min(circleSize * 1.4f, 10);
approachRate = Math.min(approachRate * 1.4f, 10);
overallDifficulty = Math.min(overallDifficulty * 1.4f, 10);
HPDrainRate = Math.min(HPDrainRate * 1.4f, 10);
2014-06-30 04:17:04 +02:00
}
// "Easy" modifiers
else if (GameMod.EASY.isActive()) {
circleSize /= 2f;
approachRate /= 2f;
overallDifficulty /= 2f;
HPDrainRate /= 2f;
}
// fixed difficulty overrides
if (Options.getFixedCS() > 0f)
circleSize = Options.getFixedCS();
if (Options.getFixedAR() > 0f)
approachRate = Options.getFixedAR();
if (Options.getFixedOD() > 0f)
overallDifficulty = Options.getFixedOD();
if (Options.getFixedHP() > 0f)
HPDrainRate = Options.getFixedHP();
// initialize objects
2014-06-30 04:17:04 +02:00
Circle.init(container, circleSize);
Slider.init(container, circleSize, osu);
Spinner.init(container);
2014-06-30 04:17:04 +02:00
// approachRate (hit object approach time)
if (approachRate < 5)
approachTime = (int) (1800 - (approachRate * 120));
2014-06-30 04:17:04 +02:00
else
approachTime = (int) (1200 - ((approachRate - 5) * 150));
2014-06-30 04:17:04 +02:00
// overallDifficulty (hit result time offsets)
hitResultOffset = new int[GameScore.HIT_MAX];
hitResultOffset[GameScore.HIT_300] = (int) (78 - (overallDifficulty * 6));
hitResultOffset[GameScore.HIT_100] = (int) (138 - (overallDifficulty * 8));
hitResultOffset[GameScore.HIT_50] = (int) (198 - (overallDifficulty * 10));
hitResultOffset[GameScore.HIT_MISS] = (int) (500 - (overallDifficulty * 10));
2014-06-30 04:17:04 +02:00
// HPDrainRate (health change), overallDifficulty (scoring)
score.setDrainRate(HPDrainRate);
score.setDifficulty(overallDifficulty);
} catch (SlickException e) {
Log.error("Error while setting map modifiers.", e);
}
}
/**
* Sets/returns whether entering the state will restart it.
*/
public static void setRestart(byte restart) { Game.restart = restart; }
public static byte getRestart() { return Game.restart; }
/**
* Returns the associated GameScore object.
*/
public static GameScore getGameScore() { return score; }
/**
* Returns whether or not the track is in the lead-in time state.
*/
public boolean isLeadIn() { return leadInTime > 0; }
2014-06-30 04:17:04 +02:00
/**
* Returns the object approach time, in milliseconds.
*/
public int getApproachTime() { return approachTime; }
/**
* Returns an array of hit result offset times, in milliseconds (indexed by GameScore.HIT_* constants).
*/
public int[] getHitResultOffsets() { return hitResultOffset; }
/**
* Returns the beat length.
*/
public float getBeatLength() { return beatLength; }
/**
* Returns the slider multiplier given by the current timing point.
*/
public float getTimingPointMultiplier() { return beatLength / beatLengthBase; }
}