2014-06-30 04:17:04 +02:00
|
|
|
/*
|
|
|
|
* opsu! - an open-source osu! client
|
2015-01-16 18:05:44 +01:00
|
|
|
* Copyright (C) 2014, 2015 Jeffrey Han
|
2014-06-30 04:17:04 +02:00
|
|
|
*
|
|
|
|
* 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;
|
|
|
|
|
2015-01-16 03:55:26 +01:00
|
|
|
import itdelatrisu.opsu.ErrorHandler;
|
2015-01-30 02:36:23 +01:00
|
|
|
import itdelatrisu.opsu.GameData;
|
2014-07-04 22:41:52 +02:00
|
|
|
import itdelatrisu.opsu.GameImage;
|
2014-07-16 22:01:36 +02:00
|
|
|
import itdelatrisu.opsu.GameMod;
|
2014-06-30 04:17:04 +02:00
|
|
|
import itdelatrisu.opsu.Opsu;
|
2015-01-21 05:56:10 +01:00
|
|
|
import itdelatrisu.opsu.Options;
|
2015-01-30 02:36:23 +01:00
|
|
|
import itdelatrisu.opsu.ScoreData;
|
2014-07-02 01:32:03 +02:00
|
|
|
import itdelatrisu.opsu.Utils;
|
2015-01-08 01:29:51 +01:00
|
|
|
import itdelatrisu.opsu.audio.HitSound;
|
|
|
|
import itdelatrisu.opsu.audio.MusicController;
|
|
|
|
import itdelatrisu.opsu.audio.SoundController;
|
|
|
|
import itdelatrisu.opsu.audio.SoundEffect;
|
2015-05-17 03:25:19 +02:00
|
|
|
import itdelatrisu.opsu.beatmap.Beatmap;
|
2015-05-29 09:07:58 +02:00
|
|
|
import itdelatrisu.opsu.beatmap.BeatmapParser;
|
2015-06-08 21:02:28 +02:00
|
|
|
import itdelatrisu.opsu.beatmap.HitObject;
|
2015-05-17 03:25:19 +02:00
|
|
|
import itdelatrisu.opsu.beatmap.TimingPoint;
|
|
|
|
import itdelatrisu.opsu.db.BeatmapDB;
|
2015-03-05 03:03:06 +01:00
|
|
|
import itdelatrisu.opsu.db.ScoreDB;
|
2014-06-30 04:17:04 +02:00
|
|
|
import itdelatrisu.opsu.objects.Circle;
|
2015-03-20 00:03:07 +01:00
|
|
|
import itdelatrisu.opsu.objects.DummyObject;
|
2015-05-17 03:42:03 +02:00
|
|
|
import itdelatrisu.opsu.objects.GameObject;
|
2014-06-30 04:17:04 +02:00
|
|
|
import itdelatrisu.opsu.objects.Slider;
|
|
|
|
import itdelatrisu.opsu.objects.Spinner;
|
2015-06-08 15:38:46 +02:00
|
|
|
import itdelatrisu.opsu.objects.curves.Curve;
|
2015-09-05 19:53:42 +02:00
|
|
|
import itdelatrisu.opsu.objects.curves.Vec2f;
|
2015-03-30 14:19:39 +02:00
|
|
|
import itdelatrisu.opsu.render.FrameBufferCache;
|
2015-04-10 18:34:18 +02:00
|
|
|
import itdelatrisu.opsu.replay.PlaybackSpeed;
|
2015-03-09 23:32:43 +01:00
|
|
|
import itdelatrisu.opsu.replay.Replay;
|
|
|
|
import itdelatrisu.opsu.replay.ReplayFrame;
|
2015-08-21 03:02:23 +02:00
|
|
|
import itdelatrisu.opsu.ui.Colors;
|
2015-08-21 03:40:07 +02:00
|
|
|
import itdelatrisu.opsu.ui.Fonts;
|
2015-05-29 07:55:57 +02:00
|
|
|
import itdelatrisu.opsu.ui.MenuButton;
|
|
|
|
import itdelatrisu.opsu.ui.UI;
|
2015-08-06 05:28:14 +02:00
|
|
|
import itdelatrisu.opsu.ui.animations.AnimationEquation;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-08-29 04:29:21 +02:00
|
|
|
import java.io.File;
|
2015-11-19 22:56:28 +01:00
|
|
|
import java.util.IdentityHashMap;
|
2015-08-29 04:29:21 +02:00
|
|
|
import java.util.LinkedList;
|
|
|
|
import java.util.Stack;
|
|
|
|
|
|
|
|
import org.lwjgl.input.Keyboard;
|
|
|
|
import org.lwjgl.opengl.Display;
|
|
|
|
import org.newdawn.slick.Animation;
|
|
|
|
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;
|
2015-11-19 22:56:28 +01:00
|
|
|
import org.newdawn.slick.state.transition.DelayedFadeOutTransition;
|
2015-11-19 23:47:46 +01:00
|
|
|
import org.newdawn.slick.state.transition.EasedFadeOutTransition;
|
2015-08-29 04:29:21 +02:00
|
|
|
import org.newdawn.slick.state.transition.EmptyTransition;
|
|
|
|
import org.newdawn.slick.state.transition.FadeInTransition;
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* "Game" state.
|
|
|
|
*/
|
|
|
|
public class Game extends BasicGameState {
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Game restart states. */
|
2015-01-15 06:56:30 +01:00
|
|
|
public enum Restart {
|
2015-02-13 09:45:38 +01:00
|
|
|
/** No restart. */
|
|
|
|
FALSE,
|
|
|
|
/** First time loading the song. */
|
|
|
|
NEW,
|
|
|
|
/** Manual retry. */
|
|
|
|
MANUAL,
|
2015-03-11 20:53:19 +01:00
|
|
|
/** Replay. */
|
|
|
|
REPLAY,
|
2015-02-13 09:45:38 +01:00
|
|
|
/** Health is zero: no-continue/force restart. */
|
2015-03-28 13:11:43 +01:00
|
|
|
LOSE
|
2015-01-15 06:56:30 +01:00
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-11-18 22:26:03 +01:00
|
|
|
/** Music fade-out time, in milliseconds. */
|
2015-11-19 22:56:28 +01:00
|
|
|
private static final int MUSIC_FADEOUT_TIME = 2000;
|
|
|
|
|
|
|
|
/** Screen fade-out time, in milliseconds, when health hits zero. */
|
|
|
|
private static final int LOSE_FADEOUT_TIME = 500;
|
2015-11-18 22:26:03 +01:00
|
|
|
|
2015-11-19 22:56:28 +01:00
|
|
|
/** Maximum rotation, in degrees, over fade out upon death. */
|
|
|
|
private static final float MAX_ROTATION = 90f;
|
2015-11-18 22:26:03 +01:00
|
|
|
|
2016-10-13 20:41:58 +02:00
|
|
|
/** The duration of the score changing animation. */
|
2015-11-29 09:56:10 +01:00
|
|
|
private static final float SCOREBOARD_ANIMATION_TIME = 500f;
|
|
|
|
|
2016-10-13 20:41:58 +02:00
|
|
|
/** The time the scoreboard takes to fade in. */
|
|
|
|
private static final float SCOREBOARD_FADE_IN_TIME = 300f;
|
2015-11-29 09:56:10 +01:00
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Minimum time before start of song, in milliseconds, to process skip-related actions. */
|
2015-01-15 05:57:50 +01:00
|
|
|
private static final int SKIP_OFFSET = 2000;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-03-31 05:06:52 +02:00
|
|
|
/** Tolerance in case if hit object is not snapped to the grid. */
|
2015-03-28 13:11:43 +01:00
|
|
|
private static final float STACK_LENIENCE = 3f;
|
|
|
|
|
2015-03-31 05:06:52 +02:00
|
|
|
/** Stack time window of the previous object, in ms. */
|
2015-03-28 13:11:43 +01:00
|
|
|
private static final int STACK_TIMEOUT = 1000;
|
|
|
|
|
2015-03-30 12:02:38 +02:00
|
|
|
/** Stack position offset modifier. */
|
|
|
|
private static final float STACK_OFFSET_MODIFIER = 0.05f;
|
|
|
|
|
2015-05-17 03:25:19 +02:00
|
|
|
/** The associated beatmap. */
|
|
|
|
private Beatmap beatmap;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-01-27 09:19:39 +01:00
|
|
|
/** The associated GameData object. */
|
|
|
|
private GameData data;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-05-17 03:42:03 +02:00
|
|
|
/** Current hit object index (in both hit object arrays). */
|
2014-06-30 04:17:04 +02:00
|
|
|
private int objectIndex = 0;
|
|
|
|
|
2015-05-17 03:42:03 +02:00
|
|
|
/** The map's game objects, indexed by objectIndex. */
|
|
|
|
private GameObject[] gameObjects;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Delay time, in milliseconds, before song starts. */
|
2014-07-09 19:36:42 +02:00
|
|
|
private int leadInTime;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Hit object approach time, in milliseconds. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private int approachTime;
|
|
|
|
|
2015-08-29 04:12:47 +02:00
|
|
|
/** The amount of time for hit objects to fade in, in milliseconds. */
|
|
|
|
private int fadeInTime;
|
|
|
|
|
|
|
|
/** Decay time for hit objects in the "Hidden" mod, in milliseconds. */
|
|
|
|
private int hiddenDecayTime;
|
|
|
|
|
|
|
|
/** Time before the hit object time by which the objects have completely faded in the "Hidden" mod, in milliseconds. */
|
|
|
|
private int hiddenTimeDiff;
|
2015-08-09 02:15:49 +02:00
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Time offsets for obtaining each hit result (indexed by HIT_* constants). */
|
2014-06-30 04:17:04 +02:00
|
|
|
private int[] hitResultOffset;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Current restart state. */
|
2015-01-15 06:56:30 +01:00
|
|
|
private Restart restart;
|
2015-01-15 05:57:50 +01:00
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Current break index in breaks ArrayList. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private int breakIndex;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Break start time (0 if not in break). */
|
2014-06-30 04:17:04 +02:00
|
|
|
private int breakTime = 0;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Whether the break sound has been played. */
|
2014-07-01 07:14:03 +02:00
|
|
|
private boolean breakSound;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Skip button (displayed at song start, when necessary). */
|
2014-12-30 06:00:58 +01:00
|
|
|
private MenuButton skipButton;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Current timing point index in timingPoints ArrayList. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private int timingPointIndex;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Current beat lengths (base value and inherited value). */
|
2014-06-30 04:17:04 +02:00
|
|
|
private float beatLengthBase, beatLength;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Whether the countdown sound has been played. */
|
2014-07-01 07:14:03 +02:00
|
|
|
private boolean
|
|
|
|
countdownReadySound, countdown3Sound, countdown1Sound,
|
|
|
|
countdown2Sound, countdownGoSound;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Mouse coordinates before game paused. */
|
2015-09-05 19:53:42 +02:00
|
|
|
private Vec2f pausedMousePosition;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Track position when game paused. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private int pauseTime = -1;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Value for handling hitCircleSelect pulse effect (expanding, alpha level). */
|
2014-06-30 04:17:04 +02:00
|
|
|
private float pausePulse;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Whether a checkpoint has been loaded during this game. */
|
2014-07-09 04:17:48 +02:00
|
|
|
private boolean checkpointLoaded = false;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Number of deaths, used if "Easy" mod is enabled. */
|
2014-07-16 22:56:23 +02:00
|
|
|
private byte deaths = 0;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Track position at death, used if "Easy" mod is enabled. */
|
2014-07-16 22:56:23 +02:00
|
|
|
private int deathTime = -1;
|
|
|
|
|
2015-11-18 22:26:03 +01:00
|
|
|
/** System time position at death. */
|
|
|
|
private long failTime;
|
2015-11-19 22:56:28 +01:00
|
|
|
|
|
|
|
/** Track time position at death. */
|
2015-11-18 22:26:03 +01:00
|
|
|
private int failTrackTime;
|
|
|
|
|
2015-11-19 22:56:28 +01:00
|
|
|
/** Rotations for game objects at death. */
|
2015-11-18 22:26:03 +01:00
|
|
|
private IdentityHashMap<GameObject, Float> rotations;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Number of retries. */
|
2014-12-30 05:35:31 +01:00
|
|
|
private int retries = 0;
|
|
|
|
|
2015-03-10 05:48:04 +01:00
|
|
|
/** Whether or not this game is a replay. */
|
|
|
|
private boolean isReplay = false;
|
|
|
|
|
2015-03-09 23:32:43 +01:00
|
|
|
/** The replay, if any. */
|
|
|
|
private Replay replay;
|
|
|
|
|
|
|
|
/** The current replay frame index. */
|
|
|
|
private int replayIndex = 0;
|
|
|
|
|
|
|
|
/** The replay cursor coordinates. */
|
|
|
|
private int replayX, replayY;
|
|
|
|
|
|
|
|
/** Whether a replay key is currently pressed. */
|
|
|
|
private boolean replayKeyPressed;
|
|
|
|
|
|
|
|
/** The replay skip time, or -1 if none. */
|
|
|
|
private int replaySkipTime = -1;
|
|
|
|
|
2015-03-11 03:37:23 +01:00
|
|
|
/** The last replay frame time. */
|
|
|
|
private int lastReplayTime = 0;
|
2015-03-19 08:04:35 +01:00
|
|
|
|
|
|
|
/** The keys from the previous replay frame. */
|
2015-03-19 00:40:47 +01:00
|
|
|
private int lastReplayKeys = 0;
|
2015-03-19 08:04:35 +01:00
|
|
|
|
2015-03-11 03:37:23 +01:00
|
|
|
/** The last game keys pressed. */
|
|
|
|
private int lastKeysPressed = ReplayFrame.KEY_NONE;
|
|
|
|
|
2015-03-09 23:32:43 +01:00
|
|
|
/** The previous game mod state (before the replay). */
|
|
|
|
private int previousMods = 0;
|
|
|
|
|
2015-03-10 23:10:51 +01:00
|
|
|
/** The list of current replay frames (for recording replays). */
|
2015-03-11 03:37:23 +01:00
|
|
|
private LinkedList<ReplayFrame> replayFrames;
|
2015-03-10 23:10:51 +01:00
|
|
|
|
2015-03-15 20:38:04 +01:00
|
|
|
/** The offscreen image rendered to. */
|
|
|
|
private Image offscreen;
|
|
|
|
|
|
|
|
/** The offscreen graphics. */
|
|
|
|
private Graphics gOffscreen;
|
|
|
|
|
|
|
|
/** The current flashlight area radius. */
|
|
|
|
private int flashlightRadius;
|
|
|
|
|
2015-03-18 00:03:50 +01:00
|
|
|
/** The cursor coordinates using the "auto" or "relax" mods. */
|
2015-09-05 19:53:42 +02:00
|
|
|
private Vec2f autoMousePosition;
|
2015-03-18 00:03:50 +01:00
|
|
|
|
|
|
|
/** Whether or not the cursor should be pressed using the "auto" mod. */
|
|
|
|
private boolean autoMousePressed;
|
|
|
|
|
2015-04-04 00:08:35 +02:00
|
|
|
/** Playback speed (used in replays and "auto" mod). */
|
|
|
|
private PlaybackSpeed playbackSpeed;
|
|
|
|
|
2015-06-30 02:22:38 +02:00
|
|
|
/** Whether the game is currently seeking to a replay position. */
|
|
|
|
private boolean isSeeking;
|
|
|
|
|
2015-07-03 05:16:14 +02:00
|
|
|
/** Music position bar coordinates and dimensions (for replay seeking). */
|
|
|
|
private float musicBarX, musicBarY, musicBarWidth, musicBarHeight;
|
|
|
|
|
2015-11-29 09:56:10 +01:00
|
|
|
/** The previous scores. */
|
|
|
|
private ScoreData[] previousScores;
|
|
|
|
|
|
|
|
/** The current rank in the scores. */
|
|
|
|
private int currentRank;
|
|
|
|
|
|
|
|
/** The time the rank was last updated. */
|
|
|
|
private int lastRankUpdateTime;
|
|
|
|
|
2016-10-13 20:41:58 +02:00
|
|
|
/** Whether the scoreboard is visible. */
|
2015-11-29 09:56:10 +01:00
|
|
|
private boolean scoreboardVisible;
|
|
|
|
|
2016-10-13 20:41:58 +02:00
|
|
|
/** The current alpha of the scoreboard. */
|
2015-11-30 16:19:25 +01:00
|
|
|
private float currentScoreboardAlpha;
|
|
|
|
|
2015-07-03 05:16:14 +02:00
|
|
|
/** Music position bar background colors. */
|
|
|
|
private static final Color
|
|
|
|
MUSICBAR_NORMAL = new Color(12, 9, 10, 0.25f),
|
|
|
|
MUSICBAR_HOVER = new Color(12, 9, 10, 0.35f),
|
|
|
|
MUSICBAR_FILL = new Color(255, 255, 255, 0.75f);
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
// game-related variables
|
|
|
|
private GameContainer container;
|
|
|
|
private StateBasedGame game;
|
|
|
|
private Input input;
|
2015-08-21 04:11:55 +02:00
|
|
|
private final int state;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
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();
|
|
|
|
|
2015-06-30 02:22:38 +02:00
|
|
|
int width = container.getWidth();
|
|
|
|
int height = container.getHeight();
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-03-15 20:38:04 +01:00
|
|
|
// create offscreen graphics
|
|
|
|
offscreen = new Image(width, height);
|
|
|
|
gOffscreen = offscreen.getGraphics();
|
|
|
|
gOffscreen.setBackground(Color.black);
|
|
|
|
|
2015-07-03 05:16:14 +02:00
|
|
|
// initialize music position bar location
|
|
|
|
musicBarX = width * 0.01f;
|
|
|
|
musicBarY = height * 0.05f;
|
|
|
|
musicBarWidth = Math.max(width * 0.005f, 7);
|
|
|
|
musicBarHeight = height * 0.9f;
|
|
|
|
|
2015-01-27 09:19:39 +01:00
|
|
|
// create the associated GameData object
|
|
|
|
data = new GameData(width, height);
|
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();
|
2015-03-24 06:03:58 +01: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;
|
2015-05-17 03:25:19 +02:00
|
|
|
int firstObjectTime = beatmap.objects[0].getTime();
|
2015-03-24 06:03:58 +01:00
|
|
|
int timeDiff = firstObjectTime - trackPosition;
|
|
|
|
|
2015-03-15 20:38:04 +01:00
|
|
|
g.setBackground(Color.black);
|
|
|
|
|
|
|
|
// "flashlight" mod: initialize offscreen graphics
|
|
|
|
if (GameMod.FLASHLIGHT.isActive()) {
|
|
|
|
gOffscreen.clear();
|
|
|
|
Graphics.setCurrent(gOffscreen);
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
// background
|
2014-07-03 07:05:23 +02:00
|
|
|
float dimLevel = Options.getBackgroundDim();
|
2015-03-24 06:03:58 +01:00
|
|
|
if (trackPosition < firstObjectTime) {
|
|
|
|
if (timeDiff < approachTime)
|
2015-06-09 00:13:49 +02:00
|
|
|
dimLevel += (1f - dimLevel) * ((float) timeDiff / approachTime);
|
2015-03-24 06:03:58 +01:00
|
|
|
else
|
|
|
|
dimLevel = 1f;
|
|
|
|
}
|
2015-09-04 02:24:07 +02:00
|
|
|
if (Options.isDefaultPlayfieldForced() || !beatmap.drawBackground(width, height, dimLevel, false)) {
|
2015-01-21 01:01:18 +01:00
|
|
|
Image playfield = GameImage.PLAYFIELD.getImage();
|
2014-07-03 07:05:23 +02:00
|
|
|
playfield.setAlpha(dimLevel);
|
|
|
|
playfield.draw();
|
2015-02-11 08:56:02 +01:00
|
|
|
playfield.setAlpha(1f);
|
2014-07-03 07:05:23 +02:00
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-03-15 20:38:04 +01:00
|
|
|
if (GameMod.FLASHLIGHT.isActive())
|
|
|
|
Graphics.setCurrent(g);
|
|
|
|
|
2015-03-18 00:03:50 +01:00
|
|
|
// "auto" and "autopilot" mods: move cursor automatically
|
|
|
|
// TODO: this should really be in update(), not render()
|
2015-09-05 19:53:42 +02:00
|
|
|
autoMousePosition.set(width / 2, height / 2);
|
2015-03-18 00:03:50 +01:00
|
|
|
autoMousePressed = false;
|
|
|
|
if (GameMod.AUTO.isActive() || GameMod.AUTOPILOT.isActive()) {
|
2015-09-05 19:53:42 +02:00
|
|
|
Vec2f autoPoint = null;
|
2015-03-17 19:48:13 +01:00
|
|
|
if (isLeadIn()) {
|
|
|
|
// lead-in
|
2015-05-17 03:25:19 +02:00
|
|
|
float progress = Math.max((float) (leadInTime - beatmap.audioLeadIn) / approachTime, 0f);
|
2015-09-05 19:53:42 +02:00
|
|
|
autoMousePosition.y = height / (2f - progress);
|
2015-03-17 19:48:13 +01:00
|
|
|
} else if (objectIndex == 0 && trackPosition < firstObjectTime) {
|
|
|
|
// before first object
|
|
|
|
timeDiff = firstObjectTime - trackPosition;
|
|
|
|
if (timeDiff < approachTime) {
|
2015-09-05 19:53:42 +02:00
|
|
|
Vec2f point = gameObjects[0].getPointAt(trackPosition);
|
|
|
|
autoPoint = getPointAt(autoMousePosition.x, autoMousePosition.y, point.x, point.y, 1f - ((float) timeDiff / approachTime));
|
2015-03-17 19:48:13 +01:00
|
|
|
}
|
2015-05-17 03:25:19 +02:00
|
|
|
} else if (objectIndex < beatmap.objects.length) {
|
2015-03-17 19:48:13 +01:00
|
|
|
// normal object
|
2015-05-17 03:25:19 +02:00
|
|
|
int objectTime = beatmap.objects[objectIndex].getTime();
|
2015-03-17 19:48:13 +01:00
|
|
|
if (trackPosition < objectTime) {
|
2015-09-05 19:53:42 +02:00
|
|
|
Vec2f startPoint = gameObjects[objectIndex - 1].getPointAt(trackPosition);
|
2015-05-17 03:42:03 +02:00
|
|
|
int startTime = gameObjects[objectIndex - 1].getEndTime();
|
2015-05-17 03:25:19 +02:00
|
|
|
if (beatmap.breaks != null && breakIndex < beatmap.breaks.size()) {
|
2015-03-17 19:48:13 +01:00
|
|
|
// starting a break: keep cursor at previous hit object position
|
2015-05-17 03:25:19 +02:00
|
|
|
if (breakTime > 0 || objectTime > beatmap.breaks.get(breakIndex))
|
2015-09-05 19:53:42 +02:00
|
|
|
autoPoint = startPoint;
|
2015-03-17 19:48:13 +01:00
|
|
|
|
|
|
|
// after a break ends: move startTime to break end time
|
|
|
|
else if (breakIndex > 1) {
|
2015-05-17 03:25:19 +02:00
|
|
|
int lastBreakEndTime = beatmap.breaks.get(breakIndex - 1);
|
2015-03-17 19:48:13 +01:00
|
|
|
if (objectTime > lastBreakEndTime && startTime < lastBreakEndTime)
|
|
|
|
startTime = lastBreakEndTime;
|
|
|
|
}
|
|
|
|
}
|
2015-09-05 19:53:42 +02:00
|
|
|
if (autoPoint == null) {
|
|
|
|
Vec2f endPoint = gameObjects[objectIndex].getPointAt(trackPosition);
|
2015-03-17 19:48:13 +01:00
|
|
|
int totalTime = objectTime - startTime;
|
2015-09-05 19:53:42 +02:00
|
|
|
autoPoint = getPointAt(startPoint.x, startPoint.y, endPoint.x, endPoint.y, (float) (trackPosition - startTime) / totalTime);
|
2015-03-17 19:48:13 +01:00
|
|
|
|
|
|
|
// hit circles: show a mouse press
|
|
|
|
int offset300 = hitResultOffset[GameData.HIT_300];
|
2015-05-17 03:25:19 +02:00
|
|
|
if ((beatmap.objects[objectIndex].isCircle() && objectTime - trackPosition < offset300) ||
|
|
|
|
(beatmap.objects[objectIndex - 1].isCircle() && trackPosition - beatmap.objects[objectIndex - 1].getTime() < offset300))
|
2015-03-18 00:03:50 +01:00
|
|
|
autoMousePressed = true;
|
2015-03-17 19:48:13 +01:00
|
|
|
}
|
|
|
|
} else {
|
2015-09-05 19:53:42 +02:00
|
|
|
autoPoint = gameObjects[objectIndex].getPointAt(trackPosition);
|
2015-03-18 00:03:50 +01:00
|
|
|
autoMousePressed = true;
|
2015-03-17 19:48:13 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// last object
|
2015-09-05 19:53:42 +02:00
|
|
|
autoPoint = gameObjects[objectIndex - 1].getPointAt(trackPosition);
|
2015-03-17 19:48:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// set mouse coordinates
|
2015-09-05 19:53:42 +02:00
|
|
|
if (autoPoint != null)
|
|
|
|
autoMousePosition.set(autoPoint.x, autoPoint.y);
|
2015-03-17 19:48:13 +01:00
|
|
|
}
|
|
|
|
|
2015-03-15 20:38:04 +01:00
|
|
|
// "flashlight" mod: restricted view of hit objects around cursor
|
|
|
|
if (GameMod.FLASHLIGHT.isActive()) {
|
|
|
|
// render hit objects offscreen
|
|
|
|
Graphics.setCurrent(gOffscreen);
|
|
|
|
int trackPos = (isLeadIn()) ? (leadInTime - Options.getMusicOffset()) * -1 : trackPosition;
|
|
|
|
drawHitObjects(gOffscreen, trackPos);
|
|
|
|
|
|
|
|
// restore original graphics context
|
|
|
|
gOffscreen.flush();
|
|
|
|
Graphics.setCurrent(g);
|
|
|
|
|
|
|
|
// draw alpha map around cursor
|
|
|
|
g.setDrawMode(Graphics.MODE_ALPHA_MAP);
|
|
|
|
g.clearAlphaMap();
|
|
|
|
int mouseX, mouseY;
|
2015-09-05 19:53:42 +02:00
|
|
|
if (pauseTime > -1 && pausedMousePosition != null) {
|
|
|
|
mouseX = (int) pausedMousePosition.x;
|
|
|
|
mouseY = (int) pausedMousePosition.y;
|
2015-03-18 00:03:50 +01:00
|
|
|
} else if (GameMod.AUTO.isActive() || GameMod.AUTOPILOT.isActive()) {
|
2015-09-05 19:53:42 +02:00
|
|
|
mouseX = (int) autoMousePosition.x;
|
|
|
|
mouseY = (int) autoMousePosition.y;
|
2015-03-15 20:38:04 +01:00
|
|
|
} else if (isReplay) {
|
|
|
|
mouseX = replayX;
|
|
|
|
mouseY = replayY;
|
|
|
|
} else {
|
|
|
|
mouseX = input.getMouseX();
|
|
|
|
mouseY = input.getMouseY();
|
|
|
|
}
|
2015-03-15 21:39:28 +01:00
|
|
|
int alphaRadius = flashlightRadius * 256 / 215;
|
|
|
|
int alphaX = mouseX - alphaRadius / 2;
|
|
|
|
int alphaY = mouseY - alphaRadius / 2;
|
|
|
|
GameImage.ALPHA_MAP.getImage().draw(alphaX, alphaY, alphaRadius, alphaRadius);
|
2015-03-15 20:38:04 +01:00
|
|
|
|
|
|
|
// blend offscreen image
|
|
|
|
g.setDrawMode(Graphics.MODE_ALPHA_BLEND);
|
2015-03-15 21:39:28 +01:00
|
|
|
g.setClip(alphaX, alphaY, alphaRadius, alphaRadius);
|
2015-03-15 20:38:04 +01:00
|
|
|
g.drawImage(offscreen, 0, 0);
|
|
|
|
g.clearClip();
|
|
|
|
g.setDrawMode(Graphics.MODE_NORMAL);
|
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
// break periods
|
2015-05-17 03:25:19 +02:00
|
|
|
if (beatmap.breaks != null && breakIndex < beatmap.breaks.size() && breakTime > 0) {
|
|
|
|
int endTime = beatmap.breaks.get(breakIndex);
|
2015-03-15 20:38:04 +01:00
|
|
|
int breakLength = endTime - breakTime;
|
|
|
|
|
|
|
|
// letterbox effect (black bars on top/bottom)
|
2015-05-17 03:25:19 +02:00
|
|
|
if (beatmap.letterboxInBreaks && breakLength >= 4000) {
|
2015-03-15 20:38:04 +01:00
|
|
|
g.setColor(Color.black);
|
|
|
|
g.fillRect(0, 0, width, height * 0.125f);
|
|
|
|
g.fillRect(0, height * 0.875f, width, height * 0.125f);
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-03-15 20:38:04 +01:00
|
|
|
data.drawGameElements(g, true, objectIndex == 0);
|
|
|
|
|
|
|
|
if (breakLength >= 8000 &&
|
|
|
|
trackPosition - breakTime > 2000 &&
|
|
|
|
trackPosition - breakTime < 5000) {
|
|
|
|
// show break start
|
|
|
|
if (data.getHealth() >= 50) {
|
|
|
|
GameImage.SECTION_PASS.getImage().drawCentered(width / 2f, height / 2f);
|
|
|
|
if (!breakSound) {
|
|
|
|
SoundController.playSound(SoundEffect.SECTIONPASS);
|
|
|
|
breakSound = true;
|
2014-07-01 07:14:03 +02:00
|
|
|
}
|
2015-03-15 20:38:04 +01:00
|
|
|
} else {
|
|
|
|
GameImage.SECTION_FAIL.getImage().drawCentered(width / 2f, height / 2f);
|
|
|
|
if (!breakSound) {
|
|
|
|
SoundController.playSound(SoundEffect.SECTIONFAIL);
|
|
|
|
breakSound = true;
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
}
|
2015-03-15 20:38:04 +01: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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-15 20:38:04 +01:00
|
|
|
// non-break
|
|
|
|
else {
|
|
|
|
// game elements
|
|
|
|
data.drawGameElements(g, false, objectIndex == 0);
|
2015-03-19 08:04:35 +01:00
|
|
|
|
2015-03-15 20:38:04 +01:00
|
|
|
// skip beginning
|
|
|
|
if (objectIndex == 0 &&
|
2015-05-17 03:25:19 +02:00
|
|
|
trackPosition < beatmap.objects[0].getTime() - SKIP_OFFSET)
|
2015-03-15 20:38:04 +01:00
|
|
|
skipButton.draw();
|
|
|
|
|
|
|
|
// show retries
|
|
|
|
if (retries >= 2 && timeDiff >= -1000) {
|
|
|
|
int retryHeight = Math.max(
|
|
|
|
GameImage.SCOREBAR_BG.getImage().getHeight(),
|
|
|
|
GameImage.SCOREBAR_KI.getImage().getHeight()
|
|
|
|
);
|
2015-08-21 03:02:23 +02:00
|
|
|
float oldAlpha = Colors.WHITE_FADE.a;
|
2015-03-15 20:38:04 +01:00
|
|
|
if (timeDiff < -500)
|
2015-08-21 03:02:23 +02:00
|
|
|
Colors.WHITE_FADE.a = (1000 + timeDiff) / 500f;
|
2015-08-21 03:40:07 +02:00
|
|
|
Fonts.MEDIUM.drawString(
|
2015-03-15 20:38:04 +01:00
|
|
|
2 + (width / 100), retryHeight,
|
|
|
|
String.format("%d retries and counting...", retries),
|
2015-08-21 03:02:23 +02:00
|
|
|
Colors.WHITE_FADE
|
2015-03-15 20:38:04 +01:00
|
|
|
);
|
2015-08-21 03:02:23 +02:00
|
|
|
Colors.WHITE_FADE.a = oldAlpha;
|
2015-03-15 20:38:04 +01:00
|
|
|
}
|
2014-12-30 05:35:31 +01:00
|
|
|
|
2015-03-15 20:38:04 +01:00
|
|
|
if (isLeadIn())
|
|
|
|
trackPosition = (leadInTime - Options.getMusicOffset()) * -1; // render approach circles during song lead-in
|
|
|
|
|
|
|
|
// countdown
|
2015-05-17 03:25:19 +02:00
|
|
|
if (beatmap.countdown > 0) {
|
2015-04-04 00:08:35 +02:00
|
|
|
float speedModifier = GameMod.getSpeedMultiplier() * playbackSpeed.getModifier();
|
2015-03-15 20:38:04 +01:00
|
|
|
timeDiff = firstObjectTime - trackPosition;
|
2015-04-04 00:08:35 +02:00
|
|
|
if (timeDiff >= 500 * speedModifier && timeDiff < 3000 * speedModifier) {
|
|
|
|
if (timeDiff >= 1500 * speedModifier) {
|
2015-03-15 20:38:04 +01:00
|
|
|
GameImage.COUNTDOWN_READY.getImage().drawCentered(width / 2, height / 2);
|
|
|
|
if (!countdownReadySound) {
|
|
|
|
SoundController.playSound(SoundEffect.READY);
|
|
|
|
countdownReadySound = true;
|
|
|
|
}
|
2014-07-01 07:14:03 +02:00
|
|
|
}
|
2015-04-04 00:08:35 +02:00
|
|
|
if (timeDiff < 2000 * speedModifier) {
|
2015-03-15 20:38:04 +01:00
|
|
|
GameImage.COUNTDOWN_3.getImage().draw(0, 0);
|
|
|
|
if (!countdown3Sound) {
|
|
|
|
SoundController.playSound(SoundEffect.COUNT3);
|
|
|
|
countdown3Sound = true;
|
|
|
|
}
|
2014-07-01 07:14:03 +02:00
|
|
|
}
|
2015-04-04 00:08:35 +02:00
|
|
|
if (timeDiff < 1500 * speedModifier) {
|
2015-03-15 20:38:04 +01:00
|
|
|
GameImage.COUNTDOWN_2.getImage().draw(width - GameImage.COUNTDOWN_2.getImage().getWidth(), 0);
|
|
|
|
if (!countdown2Sound) {
|
|
|
|
SoundController.playSound(SoundEffect.COUNT2);
|
|
|
|
countdown2Sound = true;
|
|
|
|
}
|
2014-07-01 07:14:03 +02:00
|
|
|
}
|
2015-04-04 00:08:35 +02:00
|
|
|
if (timeDiff < 1000 * speedModifier) {
|
2015-03-15 20:38:04 +01:00
|
|
|
GameImage.COUNTDOWN_1.getImage().drawCentered(width / 2, height / 2);
|
|
|
|
if (!countdown1Sound) {
|
|
|
|
SoundController.playSound(SoundEffect.COUNT1);
|
|
|
|
countdown1Sound = true;
|
|
|
|
}
|
|
|
|
}
|
2015-04-04 00:08:35 +02:00
|
|
|
} else if (timeDiff >= -500 * speedModifier && timeDiff < 500 * speedModifier) {
|
2015-03-15 20:38:04 +01:00
|
|
|
Image go = GameImage.COUNTDOWN_GO.getImage();
|
2015-04-04 00:08:35 +02:00
|
|
|
go.setAlpha((timeDiff < 0) ? 1 - (timeDiff / speedModifier / -500f) : 1);
|
2015-03-15 20:38:04 +01:00
|
|
|
go.drawCentered(width / 2, height / 2);
|
|
|
|
if (!countdownGoSound) {
|
|
|
|
SoundController.playSound(SoundEffect.GO);
|
|
|
|
countdownGoSound = true;
|
2014-07-01 07:14:03 +02:00
|
|
|
}
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
2015-03-15 20:38:04 +01:00
|
|
|
// draw hit objects
|
|
|
|
if (!GameMod.FLASHLIGHT.isActive())
|
|
|
|
drawHitObjects(g, trackPosition);
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2016-10-13 20:41:58 +02:00
|
|
|
// in-game scoreboard
|
2015-11-30 16:19:25 +01:00
|
|
|
if (previousScores != null && trackPosition >= firstObjectTime && !GameMod.RELAX.isActive() && !GameMod.AUTOPILOT.isActive()) {
|
2016-10-13 20:41:58 +02:00
|
|
|
ScoreData currentScore = data.getCurrentScoreData(beatmap, true);
|
|
|
|
while (currentRank > 0 && previousScores[currentRank - 1].score < currentScore.score) {
|
2015-11-29 09:56:10 +01:00
|
|
|
currentRank--;
|
|
|
|
lastRankUpdateTime = trackPosition;
|
|
|
|
}
|
|
|
|
|
2016-10-13 20:41:58 +02:00
|
|
|
float animation = AnimationEquation.IN_OUT_QUAD.calc(
|
|
|
|
Utils.clamp((trackPosition - lastRankUpdateTime) / SCOREBOARD_ANIMATION_TIME, 0f, 1f)
|
|
|
|
);
|
2015-11-29 09:56:10 +01:00
|
|
|
int scoreboardPosition = 2 * container.getHeight() / 3;
|
|
|
|
|
|
|
|
if (currentRank < 4) {
|
2016-10-13 20:41:58 +02:00
|
|
|
// draw the (new) top 5 ranks
|
2015-11-29 09:56:10 +01:00
|
|
|
for (int i = 0; i < 4; i++) {
|
2016-10-13 20:41:58 +02:00
|
|
|
int index = i + (i >= currentRank ? 1 : 0);
|
|
|
|
if (i < previousScores.length) {
|
|
|
|
float position = index + (i == currentRank ? animation - 3f : -2f);
|
|
|
|
previousScores[i].drawSmall(g, scoreboardPosition, index + 1, position, data, currentScoreboardAlpha, false);
|
|
|
|
}
|
2015-11-29 09:56:10 +01:00
|
|
|
}
|
2015-11-30 16:19:25 +01:00
|
|
|
currentScore.drawSmall(g, scoreboardPosition, currentRank + 1, currentRank - 1f - animation, data, currentScoreboardAlpha, true);
|
2015-11-29 09:56:10 +01:00
|
|
|
} else {
|
2016-10-13 20:41:58 +02:00
|
|
|
// draw the top 2 and next 2 ranks
|
2015-11-30 16:19:25 +01:00
|
|
|
previousScores[0].drawSmall(g, scoreboardPosition, 1, -2f, data, currentScoreboardAlpha, false);
|
|
|
|
previousScores[1].drawSmall(g, scoreboardPosition, 2, -1f, data, currentScoreboardAlpha, false);
|
2016-10-13 20:41:58 +02:00
|
|
|
previousScores[currentRank - 2].drawSmall(
|
|
|
|
g, scoreboardPosition, currentRank - 1, animation - 1f, data, currentScoreboardAlpha * animation, false
|
|
|
|
);
|
|
|
|
previousScores[currentRank - 1].drawSmall(g, scoreboardPosition, currentRank, animation, data, currentScoreboardAlpha, false);
|
2015-11-30 16:19:25 +01:00
|
|
|
currentScore.drawSmall(g, scoreboardPosition, currentRank + 1, 2f, data, currentScoreboardAlpha, true);
|
2015-11-29 09:56:10 +01:00
|
|
|
if (animation < 1.0f && currentRank < previousScores.length) {
|
2016-10-13 20:41:58 +02:00
|
|
|
previousScores[currentRank].drawSmall(
|
|
|
|
g, scoreboardPosition, currentRank + 2, 1f + 5 * animation, data, currentScoreboardAlpha * (1f - animation), false
|
|
|
|
);
|
2015-11-29 09:56:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-16 22:01:36 +02:00
|
|
|
if (GameMod.AUTO.isActive())
|
2014-07-04 22:41:52 +02:00
|
|
|
GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f);
|
2014-07-03 07:05:23 +02:00
|
|
|
|
2015-04-04 00:08:35 +02:00
|
|
|
// draw replay speed button
|
|
|
|
if (isReplay || GameMod.AUTO.isActive())
|
|
|
|
playbackSpeed.getButton().draw();
|
|
|
|
|
2015-07-03 05:16:14 +02:00
|
|
|
// draw music position bar (for replay seeking)
|
|
|
|
if (isReplay && Options.isReplaySeekingEnabled()) {
|
|
|
|
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
|
|
|
g.setColor((musicPositionBarContains(mouseX, mouseY)) ? MUSICBAR_HOVER : MUSICBAR_NORMAL);
|
|
|
|
g.fillRoundRect(musicBarX, musicBarY, musicBarWidth, musicBarHeight, 4);
|
|
|
|
if (!isLeadIn()) {
|
|
|
|
g.setColor(MUSICBAR_FILL);
|
|
|
|
float musicBarPosition = Math.min((float) trackPosition / beatmap.endTime, 1f);
|
|
|
|
g.fillRoundRect(musicBarX, musicBarY, musicBarWidth, musicBarHeight * musicBarPosition, 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
// returning from pause screen
|
2015-09-05 19:53:42 +02:00
|
|
|
if (pauseTime > -1 && pausedMousePosition != null) {
|
2014-06-30 04:17:04 +02:00
|
|
|
// darken the screen
|
2015-08-21 03:02:23 +02:00
|
|
|
g.setColor(Colors.BLACK_ALPHA);
|
2014-06-30 04:17:04 +02:00
|
|
|
g.fillRect(0, 0, width, height);
|
|
|
|
|
|
|
|
// draw glowing hit select circle and pulse effect
|
2015-09-05 19:53:42 +02:00
|
|
|
int circleDiameter = GameImage.HITCIRCLE.getImage().getWidth();
|
|
|
|
Image cursorCircle = GameImage.HITCIRCLE_SELECT.getImage().getScaledCopy(circleDiameter, circleDiameter);
|
2014-06-30 04:17:04 +02:00
|
|
|
cursorCircle.setAlpha(1.0f);
|
2015-09-05 19:53:42 +02:00
|
|
|
cursorCircle.drawCentered(pausedMousePosition.x, pausedMousePosition.y);
|
2014-06-30 04:17:04 +02:00
|
|
|
Image cursorCirclePulse = cursorCircle.getScaledCopy(1f + pausePulse);
|
|
|
|
cursorCirclePulse.setAlpha(1f - pausePulse);
|
2015-09-05 19:53:42 +02:00
|
|
|
cursorCirclePulse.drawCentered(pausedMousePosition.x, pausedMousePosition.y);
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
2014-07-02 01:32:03 +02:00
|
|
|
|
2015-03-19 00:40:47 +01:00
|
|
|
if (isReplay)
|
|
|
|
UI.draw(g, replayX, replayY, replayKeyPressed);
|
|
|
|
else if (GameMod.AUTO.isActive())
|
2015-09-05 19:53:42 +02:00
|
|
|
UI.draw(g, (int) autoMousePosition.x, (int) autoMousePosition.y, autoMousePressed);
|
2015-03-18 00:03:50 +01:00
|
|
|
else if (GameMod.AUTOPILOT.isActive())
|
2015-09-05 19:53:42 +02:00
|
|
|
UI.draw(g, (int) autoMousePosition.x, (int) autoMousePosition.y, Utils.isGameKeyPressed());
|
2015-03-09 23:32:43 +01:00
|
|
|
else
|
2015-03-19 00:40:47 +01:00
|
|
|
UI.draw(g);
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void update(GameContainer container, StateBasedGame game, int delta)
|
|
|
|
throws SlickException {
|
2015-03-05 19:27:45 +01:00
|
|
|
UI.update(delta);
|
2015-03-19 08:04:35 +01:00
|
|
|
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
|
|
|
skipButton.hoverUpdate(delta, mouseX, mouseY);
|
2015-04-02 18:38:45 +02:00
|
|
|
if (isReplay || GameMod.AUTO.isActive())
|
2015-04-04 00:08:35 +02:00
|
|
|
playbackSpeed.getButton().hoverUpdate(delta, mouseX, mouseY);
|
2015-03-19 08:04:35 +01:00
|
|
|
int trackPosition = MusicController.getPosition();
|
2015-11-30 16:19:25 +01:00
|
|
|
int firstObjectTime = beatmap.objects[0].getTime();
|
|
|
|
|
2015-03-20 00:03:07 +01:00
|
|
|
// returning from pause screen: must click previous mouse position
|
|
|
|
if (pauseTime > -1) {
|
|
|
|
// paused during lead-in or break, or "relax" or "autopilot": continue immediately
|
2015-09-05 19:53:42 +02:00
|
|
|
if (pausedMousePosition == null || (GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive())) {
|
2015-03-20 00:03:07 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// replays: skip intro
|
|
|
|
if (isReplay && replaySkipTime > -1 && trackPosition >= replaySkipTime) {
|
|
|
|
if (skipIntro())
|
|
|
|
trackPosition = MusicController.getPosition();
|
|
|
|
}
|
|
|
|
|
|
|
|
// "flashlight" mod: calculate visible area radius
|
|
|
|
updateFlashlightRadius(delta, trackPosition);
|
|
|
|
|
2015-03-19 08:04:35 +01:00
|
|
|
// stop updating during song lead-in
|
|
|
|
if (isLeadIn()) {
|
2015-03-15 19:15:34 +01:00
|
|
|
leadInTime -= delta;
|
|
|
|
if (!isLeadIn())
|
|
|
|
MusicController.resume();
|
|
|
|
return;
|
|
|
|
}
|
2015-03-19 00:40:47 +01:00
|
|
|
|
2015-09-03 07:14:25 +02:00
|
|
|
// "Easy" mod: multiple "lives"
|
|
|
|
if (GameMod.EASY.isActive() && deathTime > -1) {
|
|
|
|
if (data.getHealth() < 99f) {
|
|
|
|
data.changeHealth(delta / 10f);
|
|
|
|
data.updateDisplays(delta);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
MusicController.resume();
|
|
|
|
deathTime = -1;
|
|
|
|
}
|
|
|
|
|
2015-03-19 08:04:35 +01:00
|
|
|
// normal game update
|
|
|
|
if (!isReplay)
|
2015-03-19 00:40:47 +01:00
|
|
|
addReplayFrameAndRun(mouseX, mouseY, lastKeysPressed, trackPosition);
|
2015-03-19 08:04:35 +01:00
|
|
|
|
|
|
|
// watching replay
|
|
|
|
else {
|
|
|
|
// out of frames, use previous data
|
|
|
|
if (replayIndex >= replay.frames.length)
|
2015-03-19 00:40:47 +01:00
|
|
|
updateGame(replayX, replayY, delta, MusicController.getPosition(), lastKeysPressed);
|
2015-06-30 02:22:38 +02:00
|
|
|
|
2015-07-03 05:16:14 +02:00
|
|
|
// seeking to a position earlier than original track position
|
2015-06-30 02:22:38 +02:00
|
|
|
if (isSeeking && replayIndex - 1 >= 1 && replayIndex < replay.frames.length &&
|
|
|
|
trackPosition < replay.frames[replayIndex - 1].getTime()) {
|
2015-04-05 17:24:05 +02:00
|
|
|
replayIndex = 0;
|
2015-06-30 02:22:38 +02:00
|
|
|
while (objectIndex >= 0) {
|
2015-06-14 03:16:27 +02:00
|
|
|
gameObjects[objectIndex].reset();
|
2015-04-05 17:24:05 +02:00
|
|
|
objectIndex--;
|
|
|
|
}
|
2015-06-30 02:22:38 +02:00
|
|
|
|
2015-06-14 03:16:27 +02:00
|
|
|
// reset game data
|
|
|
|
resetGameData();
|
|
|
|
|
2015-06-22 01:45:38 +02:00
|
|
|
// load the first timingPoint
|
2015-06-14 03:16:27 +02:00
|
|
|
if (!beatmap.timingPoints.isEmpty()) {
|
|
|
|
TimingPoint timingPoint = beatmap.timingPoints.get(0);
|
2015-04-05 17:24:05 +02:00
|
|
|
if (!timingPoint.isInherited()) {
|
2015-06-14 03:16:27 +02:00
|
|
|
setBeatLength(timingPoint, true);
|
2015-04-05 17:24:05 +02:00
|
|
|
timingPointIndex++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-03-19 08:04:35 +01:00
|
|
|
|
|
|
|
// update and run replay frames
|
2015-03-15 19:15:34 +01:00
|
|
|
while (replayIndex < replay.frames.length && trackPosition >= replay.frames[replayIndex].getTime()) {
|
|
|
|
ReplayFrame frame = replay.frames[replayIndex];
|
|
|
|
replayX = frame.getScaledX();
|
|
|
|
replayY = frame.getScaledY();
|
2015-03-16 04:05:27 +01:00
|
|
|
replayKeyPressed = frame.isKeyPressed();
|
|
|
|
lastKeysPressed = frame.getKeys();
|
2015-03-19 00:40:47 +01:00
|
|
|
runReplayFrame(frame);
|
2015-03-15 19:15:34 +01:00
|
|
|
replayIndex++;
|
|
|
|
}
|
2015-03-09 23:32:43 +01:00
|
|
|
mouseX = replayX;
|
|
|
|
mouseY = replayY;
|
2015-07-03 05:16:14 +02:00
|
|
|
|
|
|
|
// unmute sounds
|
|
|
|
if (isSeeking) {
|
|
|
|
isSeeking = false;
|
|
|
|
SoundController.mute(false);
|
|
|
|
}
|
2015-03-09 23:32:43 +01:00
|
|
|
}
|
2014-07-07 05:13:33 +02:00
|
|
|
|
2016-10-13 20:41:58 +02:00
|
|
|
// update in-game scoreboard
|
|
|
|
if (previousScores != null && trackPosition > firstObjectTime) {
|
|
|
|
// show scoreboard if selected, and always in break
|
|
|
|
if (scoreboardVisible || breakTime > 0) {
|
|
|
|
currentScoreboardAlpha += 1f / SCOREBOARD_FADE_IN_TIME * delta;
|
|
|
|
if (currentScoreboardAlpha > 1f)
|
|
|
|
currentScoreboardAlpha = 1f;
|
|
|
|
} else {
|
|
|
|
currentScoreboardAlpha -= 1f / SCOREBOARD_FADE_IN_TIME * delta;
|
|
|
|
if (currentScoreboardAlpha < 0f)
|
|
|
|
currentScoreboardAlpha = 0f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-19 00:40:47 +01:00
|
|
|
data.updateDisplays(delta);
|
|
|
|
}
|
2015-03-19 05:50:39 +01:00
|
|
|
|
|
|
|
/**
|
2015-03-19 08:04:35 +01:00
|
|
|
* Updates the game.
|
|
|
|
* @param mouseX the mouse x coordinate
|
|
|
|
* @param mouseY the mouse y coordinate
|
|
|
|
* @param delta the delta interval
|
2015-03-19 05:50:39 +01:00
|
|
|
* @param trackPosition the track position
|
2015-03-19 08:04:35 +01:00
|
|
|
* @param keys the keys that are pressed
|
2015-03-19 05:50:39 +01:00
|
|
|
*/
|
2015-03-19 08:04:35 +01:00
|
|
|
private void updateGame(int mouseX, int mouseY, int delta, int trackPosition, int keys) {
|
2014-06-30 04:17:04 +02:00
|
|
|
// map complete!
|
2015-05-17 03:42:03 +02:00
|
|
|
if (objectIndex >= gameObjects.length || (MusicController.trackEnded() && objectIndex > 0)) {
|
2015-01-30 03:24:21 +01:00
|
|
|
// track ended before last object was processed: force a hit result
|
2015-05-17 03:42:03 +02:00
|
|
|
if (MusicController.trackEnded() && objectIndex < gameObjects.length)
|
|
|
|
gameObjects[objectIndex].update(true, delta, mouseX, mouseY, false, trackPosition);
|
2015-01-30 03:24:21 +01:00
|
|
|
|
2015-03-11 03:37:23 +01:00
|
|
|
// if checkpoint used, skip ranking screen
|
|
|
|
if (checkpointLoaded)
|
2015-01-16 20:10:42 +01:00
|
|
|
game.closeRequested();
|
2015-03-11 03:37:23 +01:00
|
|
|
|
|
|
|
// go to ranking screen
|
|
|
|
else {
|
2015-03-17 19:48:13 +01:00
|
|
|
boolean unranked = (GameMod.AUTO.isActive() || GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive());
|
2015-01-28 09:47:24 +01:00
|
|
|
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
|
2015-03-13 23:03:44 +01:00
|
|
|
if (isReplay)
|
|
|
|
data.setReplay(replay);
|
|
|
|
else if (replayFrames != null) {
|
2015-03-11 03:37:23 +01:00
|
|
|
// finalize replay frames with start/skip frames
|
|
|
|
if (!replayFrames.isEmpty())
|
|
|
|
replayFrames.getFirst().setTimeDiff(replaySkipTime * -1);
|
|
|
|
replayFrames.addFirst(ReplayFrame.getStartFrame(replaySkipTime));
|
|
|
|
replayFrames.addFirst(ReplayFrame.getStartFrame(0));
|
2015-05-17 03:25:19 +02:00
|
|
|
Replay r = data.getReplay(replayFrames.toArray(new ReplayFrame[replayFrames.size()]), beatmap);
|
2015-03-17 19:48:13 +01:00
|
|
|
if (r != null && !unranked)
|
2015-03-12 01:52:51 +01:00
|
|
|
r.save();
|
2015-03-11 03:37:23 +01:00
|
|
|
}
|
2015-05-17 03:25:19 +02:00
|
|
|
ScoreData score = data.getScoreData(beatmap);
|
2015-08-21 05:06:27 +02:00
|
|
|
data.setGameplay(!isReplay);
|
2015-03-11 03:37:23 +01:00
|
|
|
|
|
|
|
// add score to database
|
2015-03-17 19:48:13 +01:00
|
|
|
if (!unranked && !isReplay)
|
2015-01-30 02:36:23 +01:00
|
|
|
ScoreDB.addScore(score);
|
2015-03-11 03:37:23 +01:00
|
|
|
|
2015-11-19 23:47:46 +01:00
|
|
|
game.enterState(Opsu.STATE_GAMERANKING, new EasedFadeOutTransition(), new FadeInTransition());
|
2015-01-28 09:47:24 +01:00
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// timing points
|
2015-05-17 03:25:19 +02:00
|
|
|
if (timingPointIndex < beatmap.timingPoints.size()) {
|
|
|
|
TimingPoint timingPoint = beatmap.timingPoints.get(timingPointIndex);
|
2014-07-09 19:36:42 +02:00
|
|
|
if (trackPosition >= timingPoint.getTime()) {
|
2015-04-10 18:34:18 +02:00
|
|
|
setBeatLength(timingPoint, true);
|
2014-06-30 04:17:04 +02:00
|
|
|
timingPointIndex++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// song beginning
|
2015-05-17 03:25:19 +02:00
|
|
|
if (objectIndex == 0 && trackPosition < beatmap.objects[0].getTime())
|
2014-12-30 07:17:05 +01:00
|
|
|
return; // nothing to do here
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
// break periods
|
2015-05-17 03:25:19 +02:00
|
|
|
if (beatmap.breaks != null && breakIndex < beatmap.breaks.size()) {
|
|
|
|
int breakValue = beatmap.breaks.get(breakIndex);
|
2014-06-30 04:17:04 +02:00
|
|
|
if (breakTime > 0) { // in a break period
|
2016-12-09 23:13:05 +01:00
|
|
|
if (trackPosition < breakValue && trackPosition < beatmap.objects[objectIndex].getTime() - approachTime)
|
2014-06-30 04:17:04 +02:00
|
|
|
return;
|
|
|
|
else {
|
|
|
|
// break is over
|
|
|
|
breakTime = 0;
|
|
|
|
breakIndex++;
|
|
|
|
}
|
|
|
|
} else if (trackPosition >= breakValue) {
|
|
|
|
// start a break
|
|
|
|
breakTime = breakValue;
|
2014-07-01 07:14:03 +02:00
|
|
|
breakSound = false;
|
2014-06-30 04:17:04 +02:00
|
|
|
breakIndex++;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-20 00:03:07 +01:00
|
|
|
// pause game if focus lost
|
|
|
|
if (!container.hasFocus() && !GameMod.AUTO.isActive() && !isReplay) {
|
|
|
|
if (pauseTime < 0) {
|
2015-09-05 19:53:42 +02:00
|
|
|
pausedMousePosition = new Vec2f(mouseX, mouseY);
|
2015-03-20 00:03:07 +01:00
|
|
|
pausePulse = 0f;
|
|
|
|
}
|
|
|
|
if (MusicController.isPlaying() || isLeadIn())
|
|
|
|
pauseTime = trackPosition;
|
2015-11-19 23:47:46 +01:00
|
|
|
game.enterState(Opsu.STATE_GAMEPAUSEMENU, new EmptyTransition(), new FadeInTransition());
|
2015-03-20 00:03:07 +01:00
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
// drain health
|
2015-01-27 09:19:39 +01:00
|
|
|
data.changeHealth(delta * -1 * GameData.HP_DRAIN_MULTIPLIER);
|
|
|
|
if (!data.isAlive()) {
|
2014-07-16 22:56:23 +02:00
|
|
|
// "Easy" mod
|
2015-01-29 07:50:26 +01:00
|
|
|
if (GameMod.EASY.isActive() && !GameMod.SUDDEN_DEATH.isActive()) {
|
2014-07-16 22:56:23 +02:00
|
|
|
deaths++;
|
|
|
|
if (deaths < 3) {
|
|
|
|
deathTime = trackPosition;
|
|
|
|
MusicController.pause();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
// game over, force a restart
|
2015-03-10 05:48:04 +01:00
|
|
|
if (!isReplay) {
|
2015-11-18 22:26:03 +01:00
|
|
|
if (restart != Restart.LOSE) {
|
|
|
|
restart = Restart.LOSE;
|
|
|
|
failTime = System.currentTimeMillis();
|
|
|
|
failTrackTime = MusicController.getPosition();
|
2015-11-19 22:56:28 +01:00
|
|
|
MusicController.fadeOut(MUSIC_FADEOUT_TIME);
|
|
|
|
MusicController.pitchFadeOut(MUSIC_FADEOUT_TIME);
|
2015-11-18 22:26:03 +01:00
|
|
|
rotations = new IdentityHashMap<GameObject, Float>();
|
|
|
|
SoundController.playSound(SoundEffect.FAIL);
|
2015-11-19 22:56:28 +01:00
|
|
|
|
|
|
|
// fade to pause menu
|
|
|
|
game.enterState(Opsu.STATE_GAMEPAUSEMENU,
|
|
|
|
new DelayedFadeOutTransition(Color.black, MUSIC_FADEOUT_TIME, MUSIC_FADEOUT_TIME - LOSE_FADEOUT_TIME),
|
2015-11-19 23:47:46 +01:00
|
|
|
new FadeInTransition());
|
2015-11-18 22:26:03 +01:00
|
|
|
}
|
2015-03-09 23:32:43 +01:00
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
2015-11-19 22:56:28 +01:00
|
|
|
// don't process hit results when already lost
|
2015-11-18 22:26:03 +01:00
|
|
|
if (restart != Restart.LOSE) {
|
|
|
|
// update objects (loop in unlikely event of any skipped indexes)
|
|
|
|
boolean keyPressed = keys != ReplayFrame.KEY_NONE;
|
|
|
|
while (objectIndex < gameObjects.length && trackPosition > beatmap.objects[objectIndex].getTime()) {
|
|
|
|
// check if we've already passed the next object's start time
|
|
|
|
boolean overlap = (objectIndex + 1 < gameObjects.length &&
|
|
|
|
trackPosition > beatmap.objects[objectIndex + 1].getTime() - hitResultOffset[GameData.HIT_50]);
|
|
|
|
|
|
|
|
// update hit object and check completion status
|
|
|
|
if (gameObjects[objectIndex].update(overlap, delta, mouseX, mouseY, keyPressed, trackPosition))
|
|
|
|
objectIndex++; // done, so increment object index
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getID() { return state; }
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void keyPressed(int key, char c) {
|
2014-12-30 05:35:31 +01:00
|
|
|
int trackPosition = MusicController.getPosition();
|
2015-03-15 19:15:34 +01:00
|
|
|
int mouseX = input.getMouseX();
|
|
|
|
int mouseY = input.getMouseY();
|
2015-03-19 08:04:35 +01:00
|
|
|
|
|
|
|
// game keys
|
2015-03-19 00:40:47 +01:00
|
|
|
if (!Keyboard.isRepeatEvent()) {
|
2015-03-19 08:04:35 +01:00
|
|
|
int keys = ReplayFrame.KEY_NONE;
|
2014-07-18 05:58:37 +02:00
|
|
|
if (key == Options.getGameKeyLeft())
|
2015-03-19 00:40:47 +01:00
|
|
|
keys = ReplayFrame.KEY_K1;
|
2014-07-18 05:58:37 +02:00
|
|
|
else if (key == Options.getGameKeyRight())
|
2015-03-19 00:40:47 +01:00
|
|
|
keys = ReplayFrame.KEY_K2;
|
2015-03-19 08:04:35 +01:00
|
|
|
if (keys != ReplayFrame.KEY_NONE)
|
|
|
|
gameKeyPressed(keys, mouseX, mouseY, trackPosition);
|
2014-07-18 05:58:37 +02:00
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
switch (key) {
|
|
|
|
case Input.KEY_ESCAPE:
|
2015-03-09 23:32:43 +01:00
|
|
|
// "auto" mod or watching replay: go back to song menu
|
2015-03-10 05:48:04 +01:00
|
|
|
if (GameMod.AUTO.isActive() || isReplay) {
|
2015-01-16 08:00:42 +01:00
|
|
|
game.closeRequested();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
// pause game
|
2015-05-17 03:25:19 +02:00
|
|
|
if (pauseTime < 0 && breakTime <= 0 && trackPosition >= beatmap.objects[0].getTime()) {
|
2015-09-05 19:53:42 +02:00
|
|
|
pausedMousePosition = new Vec2f(mouseX, mouseY);
|
2014-06-30 04:17:04 +02:00
|
|
|
pausePulse = 0f;
|
|
|
|
}
|
|
|
|
if (MusicController.isPlaying() || isLeadIn())
|
|
|
|
pauseTime = trackPosition;
|
2015-11-19 23:47:46 +01:00
|
|
|
game.enterState(Opsu.STATE_GAMEPAUSEMENU, new EmptyTransition(), new FadeInTransition());
|
2014-06-30 04:17:04 +02:00
|
|
|
break;
|
|
|
|
case Input.KEY_SPACE:
|
2014-07-09 04:17:48 +02:00
|
|
|
// skip intro
|
2014-06-30 04:17:04 +02:00
|
|
|
skipIntro();
|
|
|
|
break;
|
2014-07-05 20:29:48 +02:00
|
|
|
case Input.KEY_R:
|
2014-07-09 04:17:48 +02:00
|
|
|
// restart
|
2014-07-05 20:29:48 +02:00
|
|
|
if (input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) {
|
|
|
|
try {
|
2015-05-17 03:25:19 +02:00
|
|
|
if (trackPosition < beatmap.objects[0].getTime())
|
2014-12-30 05:35:31 +01:00
|
|
|
retries--; // don't count this retry (cancel out later increment)
|
2015-01-15 06:56:30 +01:00
|
|
|
restart = Restart.MANUAL;
|
2014-07-05 20:29:48 +02:00
|
|
|
enter(container, game);
|
|
|
|
skipIntro();
|
|
|
|
} catch (SlickException e) {
|
2015-01-16 03:55:26 +01:00
|
|
|
ErrorHandler.error("Failed to restart game.", e, false);
|
2014-07-05 20:29:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2014-07-09 04:17:48 +02:00
|
|
|
case Input.KEY_S:
|
|
|
|
// save checkpoint
|
|
|
|
if (input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) {
|
|
|
|
if (isLeadIn())
|
|
|
|
break;
|
|
|
|
|
2014-12-30 05:35:31 +01:00
|
|
|
int position = (pauseTime > -1) ? pauseTime : trackPosition;
|
2015-03-05 20:40:57 +01:00
|
|
|
if (Options.setCheckpoint(position / 1000)) {
|
2015-01-08 01:29:51 +01:00
|
|
|
SoundController.playSound(SoundEffect.MENUCLICK);
|
2015-03-05 20:40:57 +01:00
|
|
|
UI.sendBarNotification("Checkpoint saved.");
|
|
|
|
}
|
2014-07-09 04:17:48 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Input.KEY_L:
|
|
|
|
// load checkpoint
|
|
|
|
if (input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) {
|
|
|
|
int checkpoint = Options.getCheckpoint();
|
2015-05-17 03:25:19 +02:00
|
|
|
if (checkpoint == 0 || checkpoint > beatmap.endTime)
|
2014-07-09 04:17:48 +02:00
|
|
|
break; // invalid checkpoint
|
|
|
|
try {
|
2015-01-15 06:56:30 +01:00
|
|
|
restart = Restart.MANUAL;
|
2014-07-09 04:17:48 +02:00
|
|
|
enter(container, game);
|
|
|
|
checkpointLoaded = true;
|
|
|
|
if (isLeadIn()) {
|
|
|
|
leadInTime = 0;
|
|
|
|
MusicController.resume();
|
|
|
|
}
|
2015-01-08 01:29:51 +01:00
|
|
|
SoundController.playSound(SoundEffect.MENUHIT);
|
2015-03-05 20:40:57 +01:00
|
|
|
UI.sendBarNotification("Checkpoint loaded.");
|
2014-07-09 04:17:48 +02:00
|
|
|
|
|
|
|
// skip to checkpoint
|
|
|
|
MusicController.setPosition(checkpoint);
|
2015-04-04 00:08:35 +02:00
|
|
|
MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier());
|
2015-05-17 03:42:03 +02:00
|
|
|
while (objectIndex < gameObjects.length &&
|
2015-05-17 03:25:19 +02:00
|
|
|
beatmap.objects[objectIndex++].getTime() <= checkpoint)
|
2014-07-09 04:17:48 +02:00
|
|
|
;
|
|
|
|
objectIndex--;
|
2015-05-17 03:25:19 +02:00
|
|
|
lastReplayTime = beatmap.objects[objectIndex].getTime();
|
2014-07-09 04:17:48 +02:00
|
|
|
} catch (SlickException e) {
|
2015-01-16 03:55:26 +01:00
|
|
|
ErrorHandler.error("Failed to load checkpoint.", e, false);
|
2014-07-09 04:17:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2015-08-29 02:41:02 +02:00
|
|
|
case Input.KEY_F:
|
|
|
|
// change playback speed
|
|
|
|
if (isReplay || GameMod.AUTO.isActive()) {
|
|
|
|
playbackSpeed = playbackSpeed.next();
|
|
|
|
MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier());
|
|
|
|
}
|
|
|
|
break;
|
2015-01-21 04:19:14 +01:00
|
|
|
case Input.KEY_UP:
|
2015-03-05 19:27:45 +01:00
|
|
|
UI.changeVolume(1);
|
2015-01-21 04:19:14 +01:00
|
|
|
break;
|
|
|
|
case Input.KEY_DOWN:
|
2015-03-05 19:27:45 +01:00
|
|
|
UI.changeVolume(-1);
|
2015-01-21 04:19:14 +01:00
|
|
|
break;
|
2015-03-05 20:40:57 +01:00
|
|
|
case Input.KEY_F7:
|
|
|
|
Options.setNextFPS(container);
|
|
|
|
break;
|
|
|
|
case Input.KEY_F10:
|
|
|
|
Options.toggleMouseDisabled();
|
|
|
|
break;
|
2014-06-30 04:17:04 +02:00
|
|
|
case Input.KEY_F12:
|
2014-07-02 01:32:03 +02:00
|
|
|
Utils.takeScreenShot();
|
2014-06-30 04:17:04 +02:00
|
|
|
break;
|
2015-11-29 09:56:10 +01:00
|
|
|
case Input.KEY_TAB:
|
2016-10-13 20:41:58 +02:00
|
|
|
scoreboardVisible = !scoreboardVisible;
|
2015-11-29 09:56:10 +01:00
|
|
|
break;
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void mousePressed(int button, int x, int y) {
|
2015-03-09 23:32:43 +01:00
|
|
|
// watching replay
|
2015-04-02 18:38:45 +02:00
|
|
|
if (isReplay || GameMod.AUTO.isActive()) {
|
2015-04-04 00:08:35 +02:00
|
|
|
if (button == Input.MOUSE_MIDDLE_BUTTON)
|
2015-04-02 18:38:45 +02:00
|
|
|
return;
|
2015-04-04 00:08:35 +02:00
|
|
|
|
|
|
|
// skip button
|
|
|
|
if (skipButton.contains(x, y))
|
2015-03-10 00:54:28 +01:00
|
|
|
skipIntro();
|
2015-06-14 03:16:27 +02:00
|
|
|
|
|
|
|
// playback speed button
|
|
|
|
else if (playbackSpeed.getButton().contains(x, y)) {
|
|
|
|
playbackSpeed = playbackSpeed.next();
|
|
|
|
MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier());
|
|
|
|
}
|
|
|
|
|
2015-07-03 05:16:14 +02:00
|
|
|
// replay seeking
|
|
|
|
else if (Options.isReplaySeekingEnabled() && !GameMod.AUTO.isActive() && musicPositionBarContains(x, y)) {
|
|
|
|
SoundController.mute(true); // mute sounds while seeking
|
|
|
|
float pos = (y - musicBarY) / musicBarHeight * beatmap.endTime;
|
2015-06-30 02:22:38 +02:00
|
|
|
MusicController.setPosition((int) pos);
|
|
|
|
isSeeking = true;
|
2015-04-05 17:24:05 +02:00
|
|
|
}
|
2015-03-09 23:32:43 +01:00
|
|
|
return;
|
2015-03-10 00:54:28 +01:00
|
|
|
}
|
2015-03-09 23:32:43 +01:00
|
|
|
|
2015-04-02 18:38:45 +02:00
|
|
|
if (Options.isMouseDisabled())
|
|
|
|
return;
|
|
|
|
|
2015-03-03 04:12:57 +01:00
|
|
|
// mouse wheel: pause the game
|
|
|
|
if (button == Input.MOUSE_MIDDLE_BUTTON && !Options.isMouseWheelDisabled()) {
|
|
|
|
int trackPosition = MusicController.getPosition();
|
2015-05-17 03:25:19 +02:00
|
|
|
if (pauseTime < 0 && breakTime <= 0 && trackPosition >= beatmap.objects[0].getTime()) {
|
2015-09-05 19:53:42 +02:00
|
|
|
pausedMousePosition = new Vec2f(x, y);
|
2015-03-03 04:12:57 +01:00
|
|
|
pausePulse = 0f;
|
|
|
|
}
|
|
|
|
if (MusicController.isPlaying() || isLeadIn())
|
|
|
|
pauseTime = trackPosition;
|
2015-11-19 23:47:46 +01:00
|
|
|
game.enterState(Opsu.STATE_GAMEPAUSEMENU, new EmptyTransition(), new FadeInTransition());
|
2015-03-03 04:12:57 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-03-19 08:04:35 +01:00
|
|
|
// game keys
|
|
|
|
int keys = ReplayFrame.KEY_NONE;
|
2015-03-19 00:40:47 +01:00
|
|
|
if (button == Input.MOUSE_LEFT_BUTTON)
|
|
|
|
keys = ReplayFrame.KEY_M1;
|
|
|
|
else if (button == Input.MOUSE_RIGHT_BUTTON)
|
|
|
|
keys = ReplayFrame.KEY_M2;
|
2015-03-19 08:04:35 +01:00
|
|
|
if (keys != ReplayFrame.KEY_NONE)
|
|
|
|
gameKeyPressed(keys, x, y, MusicController.getPosition());
|
2015-03-03 04:12:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles a game key pressed event.
|
2015-03-11 03:37:23 +01:00
|
|
|
* @param keys the game keys pressed
|
2015-03-03 04:12:57 +01:00
|
|
|
* @param x the mouse x coordinate
|
|
|
|
* @param y the mouse y coordinate
|
2015-03-19 08:04:35 +01:00
|
|
|
* @param trackPosition the track position
|
2015-03-03 04:12:57 +01:00
|
|
|
*/
|
2015-03-15 19:15:34 +01:00
|
|
|
private void gameKeyPressed(int keys, int x, int y, int trackPosition) {
|
2014-06-30 04:17:04 +02:00
|
|
|
// returning from pause screen
|
|
|
|
if (pauseTime > -1) {
|
2015-09-05 19:53:42 +02:00
|
|
|
double distance = Math.hypot(pausedMousePosition.x - x, pausedMousePosition.y - y);
|
2014-07-04 22:41:52 +02:00
|
|
|
int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2;
|
2014-06-30 04:17:04 +02:00
|
|
|
if (distance < circleRadius) {
|
|
|
|
// unpause the game
|
|
|
|
pauseTime = -1;
|
2015-09-05 19:53:42 +02:00
|
|
|
pausedMousePosition = null;
|
2014-07-09 19:36:42 +02:00
|
|
|
if (!isLeadIn())
|
2014-06-30 04:17:04 +02:00
|
|
|
MusicController.resume();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2015-03-19 08:04:35 +01:00
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
// skip beginning
|
|
|
|
if (skipButton.contains(x, y)) {
|
|
|
|
if (skipIntro())
|
|
|
|
return; // successfully skipped
|
|
|
|
}
|
2015-03-19 08:04:35 +01:00
|
|
|
|
|
|
|
// "auto" and "relax" mods: ignore user actions
|
|
|
|
if (GameMod.AUTO.isActive() || GameMod.RELAX.isActive())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// send a game key press
|
|
|
|
if (!isReplay && keys != ReplayFrame.KEY_NONE) {
|
|
|
|
lastKeysPressed |= keys; // set keys bits
|
2015-03-19 00:40:47 +01:00
|
|
|
addReplayFrameAndRun(x, y, lastKeysPressed, trackPosition);
|
2015-03-18 00:03:50 +01:00
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
2015-03-19 08:04:35 +01:00
|
|
|
|
2015-03-11 03:37:23 +01:00
|
|
|
@Override
|
|
|
|
public void mouseReleased(int button, int x, int y) {
|
|
|
|
if (Options.isMouseDisabled())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (button == Input.MOUSE_MIDDLE_BUTTON)
|
|
|
|
return;
|
|
|
|
|
2015-03-19 08:04:35 +01:00
|
|
|
int keys = ReplayFrame.KEY_NONE;
|
2015-03-19 00:40:47 +01:00
|
|
|
if (button == Input.MOUSE_LEFT_BUTTON)
|
|
|
|
keys = ReplayFrame.KEY_M1;
|
|
|
|
else if (button == Input.MOUSE_RIGHT_BUTTON)
|
|
|
|
keys = ReplayFrame.KEY_M2;
|
2015-03-19 08:04:35 +01:00
|
|
|
if (keys != ReplayFrame.KEY_NONE)
|
|
|
|
gameKeyReleased(keys, x, y, MusicController.getPosition());
|
2015-03-11 03:37:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void keyReleased(int key, char c) {
|
2015-03-19 08:04:35 +01:00
|
|
|
int keys = ReplayFrame.KEY_NONE;
|
2015-03-19 00:40:47 +01:00
|
|
|
if (key == Options.getGameKeyLeft())
|
|
|
|
keys = ReplayFrame.KEY_K1;
|
|
|
|
else if (key == Options.getGameKeyRight())
|
|
|
|
keys = ReplayFrame.KEY_K2;
|
2015-03-19 08:04:35 +01:00
|
|
|
if (keys != ReplayFrame.KEY_NONE)
|
|
|
|
gameKeyReleased(keys, input.getMouseX(), input.getMouseY(), MusicController.getPosition());
|
2015-03-19 00:40:47 +01:00
|
|
|
}
|
2015-03-19 05:50:39 +01:00
|
|
|
|
2015-03-19 00:40:47 +01:00
|
|
|
/**
|
2015-03-19 08:04:35 +01:00
|
|
|
* Handles a game key released event.
|
2015-03-19 00:40:47 +01:00
|
|
|
* @param keys the game keys released
|
|
|
|
* @param x the mouse x coordinate
|
|
|
|
* @param y the mouse y coordinate
|
2015-03-19 08:04:35 +01:00
|
|
|
* @param trackPosition the track position
|
2015-03-19 00:40:47 +01:00
|
|
|
*/
|
|
|
|
private void gameKeyReleased(int keys, int x, int y, int trackPosition) {
|
2015-03-20 00:03:07 +01:00
|
|
|
if (!isReplay && keys != ReplayFrame.KEY_NONE && !isLeadIn() && pauseTime == -1) {
|
2015-03-19 08:04:35 +01:00
|
|
|
lastKeysPressed &= ~keys; // clear keys bits
|
2015-03-19 00:40:47 +01:00
|
|
|
addReplayFrameAndRun(x, y, lastKeysPressed, trackPosition);
|
2015-03-15 19:15:34 +01:00
|
|
|
}
|
2015-03-11 03:37:23 +01:00
|
|
|
}
|
|
|
|
|
2015-01-20 20:52:02 +01:00
|
|
|
@Override
|
|
|
|
public void mouseWheelMoved(int newValue) {
|
2015-03-03 04:12:57 +01:00
|
|
|
if (Options.isMouseWheelDisabled() || Options.isMouseDisabled())
|
|
|
|
return;
|
|
|
|
|
2015-03-05 19:27:45 +01:00
|
|
|
UI.changeVolume((newValue < 0) ? -1 : 1);
|
2015-01-20 20:52:02 +01:00
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
@Override
|
|
|
|
public void enter(GameContainer container, StateBasedGame game)
|
|
|
|
throws SlickException {
|
2015-03-05 19:27:45 +01:00
|
|
|
UI.enter();
|
2014-07-02 09:02:11 +02:00
|
|
|
|
2015-05-17 03:25:19 +02:00
|
|
|
if (beatmap == null || beatmap.objects == null)
|
|
|
|
throw new RuntimeException("Running game with no beatmap loaded.");
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-06-08 21:02:28 +02:00
|
|
|
// free all previously cached hitobject to framebuffer mappings if some still exist
|
2015-03-30 14:19:39 +02:00
|
|
|
FrameBufferCache.getInstance().freeMap();
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2014-07-02 07:53:42 +02:00
|
|
|
// grab the mouse (not working for touchscreen)
|
|
|
|
// container.setMouseGrabbed(true);
|
2014-07-02 01:32:03 +02:00
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
// restart the game
|
2015-01-15 06:56:30 +01:00
|
|
|
if (restart != Restart.FALSE) {
|
2015-08-11 00:14:02 +02:00
|
|
|
// load mods
|
|
|
|
if (isReplay) {
|
|
|
|
previousMods = GameMod.getModState();
|
|
|
|
GameMod.loadModState(replay.mods);
|
|
|
|
}
|
|
|
|
|
2015-08-21 05:06:27 +02:00
|
|
|
data.setGameplay(true);
|
|
|
|
|
2015-08-11 00:14:02 +02:00
|
|
|
// check restart state
|
2015-01-15 06:56:30 +01:00
|
|
|
if (restart == Restart.NEW) {
|
2015-01-15 22:47:55 +01:00
|
|
|
// new game
|
2014-07-04 22:41:52 +02:00
|
|
|
loadImages();
|
2014-06-30 04:17:04 +02:00
|
|
|
setMapModifiers();
|
2014-12-30 05:35:31 +01:00
|
|
|
retries = 0;
|
2015-03-11 20:53:19 +01:00
|
|
|
} else if (restart == Restart.MANUAL) {
|
2015-01-15 22:47:55 +01:00
|
|
|
// retry
|
2014-12-30 05:35:31 +01:00
|
|
|
retries++;
|
2015-03-11 20:53:19 +01:00
|
|
|
} else if (restart == Restart.REPLAY)
|
|
|
|
retries = 0;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-06-14 03:16:27 +02:00
|
|
|
gameObjects = new GameObject[beatmap.objects.length];
|
2015-06-22 04:57:30 +02:00
|
|
|
playbackSpeed = PlaybackSpeed.NORMAL;
|
|
|
|
|
2015-01-15 22:47:55 +01:00
|
|
|
// reset game data
|
|
|
|
resetGameData();
|
2015-02-14 07:07:17 +01:00
|
|
|
|
2015-04-12 19:19:33 +02:00
|
|
|
// load the first timingPoint for stacking
|
2015-05-17 03:25:19 +02:00
|
|
|
if (!beatmap.timingPoints.isEmpty()) {
|
|
|
|
TimingPoint timingPoint = beatmap.timingPoints.get(0);
|
2015-04-12 16:25:03 +02:00
|
|
|
if (!timingPoint.isInherited()) {
|
|
|
|
setBeatLength(timingPoint, true);
|
|
|
|
timingPointIndex++;
|
|
|
|
}
|
|
|
|
}
|
2015-06-22 01:45:38 +02:00
|
|
|
|
2015-01-15 22:47:55 +01:00
|
|
|
// initialize object maps
|
2015-06-08 23:23:45 +02:00
|
|
|
Color[] combo = beatmap.getComboColors();
|
2015-05-17 03:25:19 +02:00
|
|
|
for (int i = 0; i < beatmap.objects.length; i++) {
|
2015-05-17 03:42:03 +02:00
|
|
|
HitObject hitObject = beatmap.objects[i];
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
// is this the last note in the combo?
|
|
|
|
boolean comboEnd = false;
|
2015-06-22 01:45:38 +02:00
|
|
|
if (i + 1 >= beatmap.objects.length || beatmap.objects[i + 1].isNewCombo())
|
2014-06-30 04:17:04 +02:00
|
|
|
comboEnd = true;
|
|
|
|
|
2015-06-08 23:23:45 +02:00
|
|
|
Color color = combo[hitObject.getComboIndex()];
|
2015-03-20 00:03:07 +01:00
|
|
|
|
2015-03-28 13:11:43 +01:00
|
|
|
// pass beatLength to hit objects
|
|
|
|
int hitObjectTime = hitObject.getTime();
|
2015-05-17 03:25:19 +02:00
|
|
|
while (timingPointIndex < beatmap.timingPoints.size()) {
|
|
|
|
TimingPoint timingPoint = beatmap.timingPoints.get(timingPointIndex);
|
2015-03-31 05:06:52 +02:00
|
|
|
if (timingPoint.getTime() > hitObjectTime)
|
2015-03-28 13:11:43 +01:00
|
|
|
break;
|
2015-04-10 18:34:18 +02:00
|
|
|
setBeatLength(timingPoint, false);
|
2015-03-28 13:11:43 +01:00
|
|
|
timingPointIndex++;
|
|
|
|
}
|
|
|
|
|
2015-03-20 00:03:07 +01:00
|
|
|
try {
|
|
|
|
if (hitObject.isCircle())
|
2015-05-17 03:42:03 +02:00
|
|
|
gameObjects[i] = new Circle(hitObject, this, data, color, comboEnd);
|
2015-03-20 00:03:07 +01:00
|
|
|
else if (hitObject.isSlider())
|
2015-05-17 03:42:03 +02:00
|
|
|
gameObjects[i] = new Slider(hitObject, this, data, color, comboEnd);
|
2015-03-20 00:03:07 +01:00
|
|
|
else if (hitObject.isSpinner())
|
2015-05-17 03:42:03 +02:00
|
|
|
gameObjects[i] = new Spinner(hitObject, this, data);
|
2015-03-20 00:03:07 +01:00
|
|
|
} catch (Exception e) {
|
2015-05-17 03:42:03 +02:00
|
|
|
// try to handle the error gracefully: substitute in a dummy GameObject
|
2015-03-20 00:03:07 +01:00
|
|
|
ErrorHandler.error(String.format("Failed to create %s at index %d:\n%s",
|
|
|
|
hitObject.getTypeName(), i, hitObject.toString()), e, true);
|
2015-05-17 03:42:03 +02:00
|
|
|
gameObjects[i] = new DummyObject(hitObject);
|
2015-03-20 00:03:07 +01:00
|
|
|
continue;
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
2015-03-31 05:06:52 +02:00
|
|
|
// stack calculations
|
|
|
|
calculateStacks();
|
2015-03-28 13:11:43 +01:00
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
// load the first timingPoint
|
2015-04-10 18:34:18 +02:00
|
|
|
timingPointIndex = 0;
|
|
|
|
beatLengthBase = beatLength = 1;
|
2015-05-17 03:25:19 +02:00
|
|
|
if (!beatmap.timingPoints.isEmpty()) {
|
|
|
|
TimingPoint timingPoint = beatmap.timingPoints.get(0);
|
2014-07-09 19:36:42 +02:00
|
|
|
if (!timingPoint.isInherited()) {
|
2015-04-10 18:34:18 +02:00
|
|
|
setBeatLength(timingPoint, true);
|
2014-07-01 07:14:03 +02:00
|
|
|
timingPointIndex++;
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
2015-03-17 19:48:13 +01:00
|
|
|
// unhide cursor for "auto" mod and replays
|
|
|
|
if (GameMod.AUTO.isActive() || isReplay)
|
2015-05-29 10:48:03 +02:00
|
|
|
UI.getCursor().show();
|
2015-03-10 00:54:28 +01:00
|
|
|
|
2015-03-17 19:48:13 +01:00
|
|
|
// load replay frames
|
|
|
|
if (isReplay) {
|
2015-03-09 23:32:43 +01:00
|
|
|
// load initial data
|
|
|
|
replayX = container.getWidth() / 2;
|
|
|
|
replayY = container.getHeight() / 2;
|
|
|
|
replayKeyPressed = false;
|
|
|
|
replaySkipTime = -1;
|
|
|
|
for (replayIndex = 0; replayIndex < replay.frames.length; replayIndex++) {
|
|
|
|
ReplayFrame frame = replay.frames[replayIndex];
|
|
|
|
if (frame.getY() < 0) { // skip time (?)
|
2015-03-20 00:03:07 +01:00
|
|
|
if (frame.getTime() >= 0 && replayIndex > 0)
|
2015-03-09 23:32:43 +01:00
|
|
|
replaySkipTime = frame.getTime();
|
|
|
|
} else if (frame.getTime() == 0) {
|
2015-03-13 01:12:43 +01:00
|
|
|
replayX = frame.getScaledX();
|
|
|
|
replayY = frame.getScaledY();
|
2015-03-09 23:32:43 +01:00
|
|
|
replayKeyPressed = frame.isKeyPressed();
|
|
|
|
} else
|
|
|
|
break;
|
|
|
|
}
|
2015-03-11 03:37:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// initialize replay-recording structures
|
|
|
|
else {
|
|
|
|
lastKeysPressed = ReplayFrame.KEY_NONE;
|
|
|
|
replaySkipTime = -1;
|
|
|
|
replayFrames = new LinkedList<ReplayFrame>();
|
|
|
|
replayFrames.add(new ReplayFrame(0, 0, input.getMouseX(), input.getMouseY(), 0));
|
|
|
|
}
|
2015-03-09 23:32:43 +01:00
|
|
|
|
2015-05-17 03:25:19 +02:00
|
|
|
leadInTime = beatmap.audioLeadIn + approachTime;
|
2015-01-15 06:56:30 +01:00
|
|
|
restart = Restart.FALSE;
|
2015-03-31 17:14:52 +02:00
|
|
|
|
2016-10-13 20:41:58 +02:00
|
|
|
// fetch previous scores
|
|
|
|
previousScores = ScoreDB.getMapScoresExcluding(beatmap, replay == null ? null : replay.getReplayFilename());
|
|
|
|
lastRankUpdateTime = -1000;
|
|
|
|
if (previousScores != null)
|
|
|
|
currentRank = previousScores.length;
|
|
|
|
scoreboardVisible = true;
|
|
|
|
currentScoreboardAlpha = 0f;
|
|
|
|
|
2015-03-31 17:14:52 +02:00
|
|
|
// needs to play before setting position to resume without lag later
|
2015-04-02 18:38:45 +02:00
|
|
|
MusicController.play(false);
|
2015-03-31 17:14:52 +02:00
|
|
|
MusicController.setPosition(0);
|
2015-04-02 18:38:45 +02:00
|
|
|
MusicController.setPitch(GameMod.getSpeedMultiplier());
|
2015-03-31 17:14:52 +02:00
|
|
|
MusicController.pause();
|
2015-07-03 05:16:14 +02:00
|
|
|
|
|
|
|
SoundController.mute(false);
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
2014-12-24 08:45:43 +01:00
|
|
|
|
2015-02-02 06:15:16 +01:00
|
|
|
skipButton.resetHover();
|
2015-04-02 18:38:45 +02:00
|
|
|
if (isReplay || GameMod.AUTO.isActive())
|
2015-04-04 00:08:35 +02:00
|
|
|
playbackSpeed.getButton().resetHover();
|
|
|
|
MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier());
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
2015-03-09 23:32:43 +01:00
|
|
|
@Override
|
|
|
|
public void leave(GameContainer container, StateBasedGame game)
|
|
|
|
throws SlickException {
|
2014-07-02 07:53:42 +02:00
|
|
|
// container.setMouseGrabbed(false);
|
2015-03-09 23:32:43 +01:00
|
|
|
|
2015-03-17 19:48:13 +01:00
|
|
|
// re-hide cursor
|
|
|
|
if (GameMod.AUTO.isActive() || isReplay)
|
2015-05-29 10:48:03 +02:00
|
|
|
UI.getCursor().hide();
|
2015-03-17 19:48:13 +01:00
|
|
|
|
2015-03-10 05:48:04 +01:00
|
|
|
// replays
|
2015-03-19 20:58:50 +01:00
|
|
|
if (isReplay)
|
2015-03-09 23:32:43 +01:00
|
|
|
GameMod.loadModState(previousMods);
|
|
|
|
}
|
2014-07-02 01:32:03 +02:00
|
|
|
|
2015-03-15 20:38:04 +01:00
|
|
|
/**
|
2015-03-19 03:20:37 +01:00
|
|
|
* Draws hit objects, hit results, and follow points.
|
2015-03-15 20:38:04 +01:00
|
|
|
* @param g the graphics context
|
|
|
|
* @param trackPosition the track position
|
|
|
|
*/
|
|
|
|
private void drawHitObjects(Graphics g, int trackPosition) {
|
2015-03-19 03:20:37 +01:00
|
|
|
// include previous object in follow points
|
|
|
|
int lastObjectIndex = -1;
|
2015-05-17 03:25:19 +02:00
|
|
|
if (objectIndex > 0 && objectIndex < beatmap.objects.length &&
|
|
|
|
trackPosition < beatmap.objects[objectIndex].getTime() && !beatmap.objects[objectIndex - 1].isSpinner())
|
2015-03-19 03:20:37 +01:00
|
|
|
lastObjectIndex = objectIndex - 1;
|
|
|
|
|
2015-11-19 22:56:28 +01:00
|
|
|
boolean loseState = (restart == Restart.LOSE);
|
|
|
|
if (loseState)
|
|
|
|
trackPosition = failTrackTime + (int) (System.currentTimeMillis() - failTime);
|
2015-11-18 22:26:03 +01:00
|
|
|
|
2015-11-19 22:56:28 +01:00
|
|
|
// get hit objects in reverse order, or else overlapping objects are unreadable
|
2015-03-15 20:38:04 +01:00
|
|
|
Stack<Integer> stack = new Stack<Integer>();
|
2015-05-17 03:42:03 +02:00
|
|
|
for (int index = objectIndex; index < gameObjects.length && beatmap.objects[index].getTime() < trackPosition + approachTime; index++) {
|
2015-03-19 03:20:37 +01:00
|
|
|
stack.add(index);
|
|
|
|
|
|
|
|
// draw follow points
|
2015-11-19 22:56:28 +01:00
|
|
|
if (!Options.isFollowPointEnabled() || loseState)
|
2015-03-19 04:23:34 +01:00
|
|
|
continue;
|
2015-05-17 03:25:19 +02:00
|
|
|
if (beatmap.objects[index].isSpinner()) {
|
2015-03-19 03:20:37 +01:00
|
|
|
lastObjectIndex = -1;
|
|
|
|
continue;
|
|
|
|
}
|
2015-05-17 03:25:19 +02:00
|
|
|
if (lastObjectIndex != -1 && !beatmap.objects[index].isNewCombo()) {
|
2015-03-19 03:20:37 +01:00
|
|
|
// calculate points
|
|
|
|
final int followPointInterval = container.getHeight() / 14;
|
2015-05-17 03:42:03 +02:00
|
|
|
int lastObjectEndTime = gameObjects[lastObjectIndex].getEndTime() + 1;
|
2015-05-17 03:25:19 +02:00
|
|
|
int objectStartTime = beatmap.objects[index].getTime();
|
2015-09-05 19:53:42 +02:00
|
|
|
Vec2f startPoint = gameObjects[lastObjectIndex].getPointAt(lastObjectEndTime);
|
|
|
|
Vec2f endPoint = gameObjects[index].getPointAt(objectStartTime);
|
|
|
|
float xDiff = endPoint.x - startPoint.x;
|
|
|
|
float yDiff = endPoint.y - startPoint.y;
|
2015-03-19 03:20:37 +01:00
|
|
|
float dist = (float) Math.hypot(xDiff, yDiff);
|
|
|
|
int numPoints = (int) ((dist - GameImage.HITCIRCLE.getImage().getWidth()) / followPointInterval);
|
|
|
|
if (numPoints > 0) {
|
|
|
|
// set the image angle
|
|
|
|
Image followPoint = GameImage.FOLLOWPOINT.getImage();
|
|
|
|
float angle = (float) Math.toDegrees(Math.atan2(yDiff, xDiff));
|
|
|
|
followPoint.setRotation(angle);
|
|
|
|
|
|
|
|
// draw points
|
2015-03-19 03:57:44 +01:00
|
|
|
float progress = 0f, alpha = 1f;
|
2015-03-19 03:20:37 +01:00
|
|
|
if (lastObjectIndex < objectIndex)
|
|
|
|
progress = (float) (trackPosition - lastObjectEndTime) / (objectStartTime - lastObjectEndTime);
|
2015-03-19 03:57:44 +01:00
|
|
|
else {
|
|
|
|
alpha = Utils.clamp((1f - ((objectStartTime - trackPosition) / (float) approachTime)) * 2f, 0, 1);
|
|
|
|
followPoint.setAlpha(alpha);
|
|
|
|
}
|
|
|
|
|
2015-03-19 03:20:37 +01:00
|
|
|
float step = 1f / (numPoints + 1);
|
|
|
|
float t = step;
|
|
|
|
for (int i = 0; i < numPoints; i++) {
|
2015-09-05 19:53:42 +02:00
|
|
|
float x = startPoint.x + xDiff * t;
|
|
|
|
float y = startPoint.y + yDiff * t;
|
2015-03-19 03:20:37 +01:00
|
|
|
float nextT = t + step;
|
|
|
|
if (lastObjectIndex < objectIndex) { // fade the previous trail
|
|
|
|
if (progress < nextT) {
|
|
|
|
if (progress > t)
|
|
|
|
followPoint.setAlpha(1f - ((progress - t + step) / (step * 2f)));
|
|
|
|
else if (progress > t - step)
|
|
|
|
followPoint.setAlpha(1f - ((progress - (t - step)) / (step * 2f)));
|
2015-03-19 03:57:44 +01:00
|
|
|
else
|
|
|
|
followPoint.setAlpha(1f);
|
2015-11-30 03:58:00 +01:00
|
|
|
followPoint.drawCentered(x, y);
|
2015-03-19 03:20:37 +01:00
|
|
|
}
|
|
|
|
} else
|
2015-11-30 03:58:00 +01:00
|
|
|
followPoint.drawCentered(x, y);
|
2015-03-19 03:20:37 +01:00
|
|
|
t = nextT;
|
|
|
|
}
|
2015-03-19 03:57:44 +01:00
|
|
|
followPoint.setAlpha(1f);
|
2015-03-19 03:20:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
lastObjectIndex = index;
|
|
|
|
}
|
2015-03-15 20:38:04 +01:00
|
|
|
|
2015-11-19 22:56:28 +01:00
|
|
|
// draw hit objects
|
2015-11-18 22:26:03 +01:00
|
|
|
while (!stack.isEmpty()){
|
2015-11-19 22:56:28 +01:00
|
|
|
int idx = stack.pop();
|
|
|
|
GameObject gameObj = gameObjects[idx];
|
2015-11-18 22:26:03 +01:00
|
|
|
|
2015-11-19 22:56:28 +01:00
|
|
|
// normal case
|
|
|
|
if (!loseState)
|
|
|
|
gameObj.draw(g, trackPosition);
|
2015-11-18 22:26:03 +01:00
|
|
|
|
2015-11-19 22:56:28 +01:00
|
|
|
// death: make objects "fall" off the screen
|
|
|
|
else {
|
|
|
|
// time the object began falling
|
|
|
|
int objTime = Math.max(beatmap.objects[idx].getTime() - approachTime, failTrackTime);
|
|
|
|
float dt = (trackPosition - objTime) / (float) (MUSIC_FADEOUT_TIME);
|
2015-11-18 22:26:03 +01:00
|
|
|
|
2015-11-19 22:56:28 +01:00
|
|
|
// would the object already be visible?
|
|
|
|
if (dt <= 0)
|
|
|
|
continue;
|
2015-11-18 22:26:03 +01:00
|
|
|
|
2015-11-19 22:56:28 +01:00
|
|
|
// generate rotation speeds for each objects
|
|
|
|
final float rotSpeed;
|
|
|
|
if (rotations.containsKey(gameObj)) {
|
|
|
|
rotSpeed = rotations.get(gameObj);
|
|
|
|
} else {
|
|
|
|
rotSpeed = (float) (2.0f * (Math.random() - 0.5f) * MAX_ROTATION);
|
|
|
|
rotations.put(gameObj, rotSpeed);
|
2015-11-18 22:26:03 +01:00
|
|
|
}
|
2015-11-19 22:56:28 +01:00
|
|
|
|
|
|
|
g.pushTransform();
|
|
|
|
|
|
|
|
// translate and rotate the object
|
|
|
|
g.translate(0, dt * dt * container.getHeight());
|
|
|
|
Vec2f rotationCenter = gameObj.getPointAt(beatmap.objects[idx].getTime());
|
|
|
|
g.rotate(rotationCenter.x, rotationCenter.y, rotSpeed * dt);
|
|
|
|
gameObj.draw(g, trackPosition);
|
|
|
|
|
|
|
|
g.popTransform();
|
2015-11-18 22:26:03 +01:00
|
|
|
}
|
|
|
|
}
|
2015-03-15 20:38:04 +01:00
|
|
|
|
2015-11-19 22:56:28 +01:00
|
|
|
// draw result objects
|
2015-08-08 23:17:25 +02:00
|
|
|
data.drawHitResults(trackPosition);
|
2015-03-15 20:38:04 +01:00
|
|
|
}
|
|
|
|
|
2015-03-12 01:52:51 +01:00
|
|
|
/**
|
2015-05-17 03:25:19 +02:00
|
|
|
* Loads all required data from a beatmap.
|
|
|
|
* @param beatmap the beatmap to load
|
2015-03-12 01:52:51 +01:00
|
|
|
*/
|
2015-05-17 03:25:19 +02:00
|
|
|
public void loadBeatmap(Beatmap beatmap) {
|
|
|
|
this.beatmap = beatmap;
|
|
|
|
Display.setTitle(String.format("%s - %s", game.getTitle(), beatmap.toString()));
|
2015-06-08 23:23:45 +02:00
|
|
|
if (beatmap.timingPoints == null)
|
2015-05-17 03:25:19 +02:00
|
|
|
BeatmapDB.load(beatmap, BeatmapDB.LOAD_ARRAY);
|
2015-05-29 09:07:58 +02:00
|
|
|
BeatmapParser.parseHitObjects(beatmap);
|
2015-05-17 03:25:19 +02:00
|
|
|
HitSound.setDefaultSampleSet(beatmap.sampleSet);
|
2015-03-12 01:52:51 +01:00
|
|
|
}
|
|
|
|
|
2015-01-15 22:47:55 +01:00
|
|
|
/**
|
|
|
|
* Resets all game data and structures.
|
|
|
|
*/
|
|
|
|
public void resetGameData() {
|
2015-01-27 09:19:39 +01:00
|
|
|
data.clear();
|
2015-01-15 22:47:55 +01:00
|
|
|
objectIndex = 0;
|
|
|
|
breakIndex = 0;
|
|
|
|
breakTime = 0;
|
|
|
|
breakSound = false;
|
|
|
|
timingPointIndex = 0;
|
|
|
|
beatLengthBase = beatLength = 1;
|
|
|
|
pauseTime = -1;
|
2015-09-05 19:53:42 +02:00
|
|
|
pausedMousePosition = null;
|
2015-01-15 22:47:55 +01:00
|
|
|
countdownReadySound = false;
|
|
|
|
countdown3Sound = false;
|
|
|
|
countdown1Sound = false;
|
|
|
|
countdown2Sound = false;
|
|
|
|
countdownGoSound = false;
|
|
|
|
checkpointLoaded = false;
|
|
|
|
deaths = 0;
|
|
|
|
deathTime = -1;
|
2015-03-11 03:37:23 +01:00
|
|
|
replayFrames = null;
|
2015-03-19 08:04:35 +01:00
|
|
|
lastReplayTime = 0;
|
2015-09-05 19:53:42 +02:00
|
|
|
autoMousePosition = new Vec2f();
|
2015-03-18 00:03:50 +01:00
|
|
|
autoMousePressed = false;
|
2015-03-24 06:03:58 +01:00
|
|
|
flashlightRadius = container.getHeight() * 2 / 3;
|
2015-06-30 02:22:38 +02:00
|
|
|
|
2015-01-15 22:47:55 +01:00
|
|
|
System.gc();
|
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* Skips the beginning of a track.
|
2015-05-29 12:06:37 +02:00
|
|
|
* @return {@code true} if skipped, {@code false} otherwise
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
2015-03-10 05:48:04 +01:00
|
|
|
private synchronized boolean skipIntro() {
|
2015-05-17 03:25:19 +02:00
|
|
|
int firstObjectTime = beatmap.objects[0].getTime();
|
2014-06-30 04:17:04 +02:00
|
|
|
int trackPosition = MusicController.getPosition();
|
2015-03-09 23:32:43 +01:00
|
|
|
if (objectIndex == 0 && trackPosition < firstObjectTime - SKIP_OFFSET) {
|
2014-06-30 04:17:04 +02:00
|
|
|
if (isLeadIn()) {
|
|
|
|
leadInTime = 0;
|
|
|
|
MusicController.resume();
|
|
|
|
}
|
2015-03-13 23:03:44 +01:00
|
|
|
MusicController.setPosition(firstObjectTime - SKIP_OFFSET);
|
2015-04-04 00:08:35 +02:00
|
|
|
MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier());
|
2015-03-11 03:37:23 +01:00
|
|
|
replaySkipTime = (isReplay) ? -1 : trackPosition;
|
2015-03-16 04:05:27 +01:00
|
|
|
if (isReplay) {
|
2015-03-13 23:03:44 +01:00
|
|
|
replayX = (int) skipButton.getX();
|
|
|
|
replayY = (int) skipButton.getY();
|
|
|
|
}
|
2015-01-08 01:29:51 +01:00
|
|
|
SoundController.playSound(SoundEffect.MENUHIT);
|
2014-06-30 04:17:04 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-07-04 22:41:52 +02:00
|
|
|
/**
|
|
|
|
* Loads all game images.
|
|
|
|
*/
|
2015-01-18 23:01:13 +01:00
|
|
|
private void loadImages() {
|
2014-07-04 22:41:52 +02:00
|
|
|
int width = container.getWidth();
|
|
|
|
int height = container.getHeight();
|
|
|
|
|
|
|
|
// set images
|
2015-05-17 03:25:19 +02:00
|
|
|
File parent = beatmap.getFile().getParentFile();
|
2015-01-08 04:36:39 +01:00
|
|
|
for (GameImage img : GameImage.values()) {
|
2015-08-24 03:41:09 +02:00
|
|
|
if (img.isBeatmapSkinnable()) {
|
2015-03-06 20:39:49 +01:00
|
|
|
img.setDefaultImage();
|
2015-08-24 03:41:09 +02:00
|
|
|
img.setBeatmapSkinImage(parent);
|
2015-03-06 20:39:49 +01:00
|
|
|
}
|
2015-01-08 04:36:39 +01:00
|
|
|
}
|
2014-07-04 22:41:52 +02:00
|
|
|
|
|
|
|
// skip button
|
2015-02-19 01:35:26 +01:00
|
|
|
if (GameImage.SKIP.getImages() != null) {
|
2015-02-19 06:33:32 +01:00
|
|
|
Animation skip = GameImage.SKIP.getAnimation(120);
|
2015-02-19 01:35:26 +01:00
|
|
|
skipButton = new MenuButton(skip, width - skip.getWidth() / 2f, height - (skip.getHeight() / 2f));
|
|
|
|
} else {
|
|
|
|
Image skip = GameImage.SKIP.getImage();
|
|
|
|
skipButton = new MenuButton(skip, width - skip.getWidth() / 2f, height - (skip.getHeight() / 2f));
|
2015-02-18 04:03:11 +01:00
|
|
|
}
|
2015-08-06 05:28:14 +02:00
|
|
|
skipButton.setHoverAnimationDuration(350);
|
|
|
|
skipButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_BACK);
|
2015-02-19 01:35:26 +01:00
|
|
|
skipButton.setHoverExpand(1.1f, MenuButton.Expand.UP_LEFT);
|
|
|
|
|
2014-07-05 01:59:57 +02:00
|
|
|
// load other images...
|
2014-07-04 22:41:52 +02:00
|
|
|
((GamePauseMenu) game.getState(Opsu.STATE_GAMEPAUSEMENU)).loadImages();
|
2015-01-28 09:47:24 +01:00
|
|
|
data.loadImages();
|
2014-07-04 22:41:52 +02:00
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* Set map modifiers.
|
|
|
|
*/
|
|
|
|
private void setMapModifiers() {
|
2015-01-22 00:56:53 +01:00
|
|
|
// map-based properties, re-initialized each game
|
2015-04-04 18:30:23 +02:00
|
|
|
float multiplier = GameMod.getDifficultyMultiplier();
|
2015-05-17 03:25:19 +02:00
|
|
|
float circleSize = Math.min(beatmap.circleSize * multiplier, 10f);
|
|
|
|
float approachRate = Math.min(beatmap.approachRate * multiplier, 10f);
|
|
|
|
float overallDifficulty = Math.min(beatmap.overallDifficulty * multiplier, 10f);
|
2015-06-22 04:57:30 +02:00
|
|
|
float HPDrainRate = Math.min(beatmap.HPDrainRate * multiplier, 10f);
|
|
|
|
|
2015-01-22 00:56:53 +01:00
|
|
|
// 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();
|
|
|
|
|
2015-03-28 13:11:43 +01:00
|
|
|
// Stack modifier scales with hit object size
|
2015-03-30 12:02:38 +02:00
|
|
|
// StackOffset = HitObjectRadius / 10
|
2015-08-31 02:01:40 +02:00
|
|
|
//int diameter = (int) (104 - (circleSize * 8));
|
|
|
|
float diameter = 108.848f - (circleSize * 8.9646f);
|
2015-05-17 03:42:03 +02:00
|
|
|
HitObject.setStackOffset(diameter * STACK_OFFSET_MODIFIER);
|
2015-03-28 13:11:43 +01:00
|
|
|
|
2015-01-22 00:56:53 +01:00
|
|
|
// initialize objects
|
2015-08-31 02:01:40 +02:00
|
|
|
Circle.init(container, diameter);
|
|
|
|
Slider.init(container, diameter, beatmap);
|
2015-06-14 18:59:12 +02:00
|
|
|
Spinner.init(container, overallDifficulty);
|
2015-08-31 02:01:40 +02:00
|
|
|
Curve.init(container.getWidth(), container.getHeight(), diameter, (Options.isBeatmapSkinIgnored()) ?
|
2015-06-08 22:42:54 +02:00
|
|
|
Options.getSkin().getSliderBorderColor() : beatmap.getSliderBorderColor());
|
2015-01-22 00:56:53 +01:00
|
|
|
|
2015-08-29 04:29:21 +02:00
|
|
|
// approachRate (hit object approach time)
|
|
|
|
if (approachRate < 5)
|
|
|
|
approachTime = (int) (1800 - (approachRate * 120));
|
|
|
|
else
|
|
|
|
approachTime = (int) (1200 - ((approachRate - 5) * 150));
|
|
|
|
|
2015-01-22 00:56:53 +01:00
|
|
|
// overallDifficulty (hit result time offsets)
|
2015-01-27 09:19:39 +01:00
|
|
|
hitResultOffset = new int[GameData.HIT_MAX];
|
2015-08-31 02:01:40 +02:00
|
|
|
hitResultOffset[GameData.HIT_300] = (int) (79.5f - (overallDifficulty * 6));
|
|
|
|
hitResultOffset[GameData.HIT_100] = (int) (139.5f - (overallDifficulty * 8));
|
|
|
|
hitResultOffset[GameData.HIT_50] = (int) (199.5f - (overallDifficulty * 10));
|
2015-01-27 09:19:39 +01:00
|
|
|
hitResultOffset[GameData.HIT_MISS] = (int) (500 - (overallDifficulty * 10));
|
2015-06-30 02:22:38 +02:00
|
|
|
//final float mult = 0.608f;
|
|
|
|
//hitResultOffset[GameData.HIT_300] = (int) ((128 - (overallDifficulty * 9.6)) * mult);
|
|
|
|
//hitResultOffset[GameData.HIT_100] = (int) ((224 - (overallDifficulty * 12.8)) * mult);
|
|
|
|
//hitResultOffset[GameData.HIT_50] = (int) ((320 - (overallDifficulty * 16)) * mult);
|
|
|
|
//hitResultOffset[GameData.HIT_MISS] = (int) ((1000 - (overallDifficulty * 10)) * mult);
|
2015-02-14 19:45:14 +01:00
|
|
|
data.setHitResultOffset(hitResultOffset);
|
2015-06-14 18:59:12 +02:00
|
|
|
|
2015-06-22 04:57:30 +02:00
|
|
|
// HPDrainRate (health change)
|
|
|
|
data.setDrainRate(HPDrainRate);
|
|
|
|
|
2015-06-14 18:59:12 +02:00
|
|
|
// difficulty multiplier (scoring)
|
|
|
|
data.calculateDifficultyMultiplier(beatmap.HPDrainRate, beatmap.circleSize, beatmap.overallDifficulty);
|
2015-08-29 04:12:47 +02:00
|
|
|
|
|
|
|
// hit object fade-in time (TODO: formula)
|
|
|
|
fadeInTime = Math.min(375, (int) (approachTime / 2.5f));
|
|
|
|
|
|
|
|
// fade times ("Hidden" mod)
|
|
|
|
// TODO: find the actual formulas for this
|
|
|
|
hiddenDecayTime = (int) (approachTime / 3.6f);
|
|
|
|
hiddenTimeDiff = (int) (approachTime / 3.3f);
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-05-29 12:06:37 +02:00
|
|
|
* Sets the restart state.
|
|
|
|
* @param restart the new restart state
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
2015-01-15 06:56:30 +01:00
|
|
|
public void setRestart(Restart restart) { this.restart = restart; }
|
2015-05-29 12:06:37 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the current restart state.
|
|
|
|
*/
|
2015-01-15 06:56:30 +01:00
|
|
|
public Restart getRestart() { return restart; }
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns whether or not the track is in the lead-in time state.
|
|
|
|
*/
|
2014-07-09 19:36:42 +02:00
|
|
|
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; }
|
2015-01-16 21:44:13 +01:00
|
|
|
|
2015-08-29 04:12:47 +02:00
|
|
|
/**
|
|
|
|
* Returns the amount of time for hit objects to fade in, in milliseconds.
|
|
|
|
*/
|
|
|
|
public int getFadeInTime() { return fadeInTime; }
|
|
|
|
|
2015-08-08 21:12:02 +02:00
|
|
|
/**
|
2015-08-09 02:15:49 +02:00
|
|
|
* Returns the object decay time in the "Hidden" mod, in milliseconds.
|
2015-08-08 21:12:02 +02:00
|
|
|
*/
|
2015-08-29 04:12:47 +02:00
|
|
|
public int getHiddenDecayTime() { return hiddenDecayTime; }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the time before the hit object time by which the objects have
|
|
|
|
* completely faded in the "Hidden" mod, in milliseconds.
|
|
|
|
*/
|
|
|
|
public int getHiddenTimeDiff() { return hiddenTimeDiff; }
|
2015-08-09 02:15:49 +02:00
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
2015-01-27 09:19:39 +01:00
|
|
|
* Returns an array of hit result offset times, in milliseconds (indexed by GameData.HIT_* constants).
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
|
|
|
public int[] getHitResultOffsets() { return hitResultOffset; }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the beat length.
|
|
|
|
*/
|
|
|
|
public float getBeatLength() { return beatLength; }
|
|
|
|
|
2015-03-28 19:49:25 +01:00
|
|
|
/**
|
|
|
|
* Sets the beat length fields based on a given timing point.
|
2015-04-10 18:34:18 +02:00
|
|
|
* @param timingPoint the timing point
|
|
|
|
* @param setSampleSet whether to set the hit sample set based on the timing point
|
2015-03-28 19:49:25 +01:00
|
|
|
*/
|
2015-05-16 23:58:32 +02:00
|
|
|
private void setBeatLength(TimingPoint timingPoint, boolean setSampleSet) {
|
2015-03-28 19:49:25 +01:00
|
|
|
if (!timingPoint.isInherited())
|
|
|
|
beatLengthBase = beatLength = timingPoint.getBeatLength();
|
|
|
|
else
|
|
|
|
beatLength = beatLengthBase * timingPoint.getSliderMultiplier();
|
2015-04-10 18:34:18 +02:00
|
|
|
if (setSampleSet) {
|
|
|
|
HitSound.setDefaultSampleSet(timingPoint.getSampleType());
|
|
|
|
SoundController.setSampleVolume(timingPoint.getSampleVolume());
|
|
|
|
}
|
2015-03-28 19:49:25 +01:00
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* Returns the slider multiplier given by the current timing point.
|
|
|
|
*/
|
|
|
|
public float getTimingPointMultiplier() { return beatLength / beatLengthBase; }
|
2015-03-09 23:32:43 +01:00
|
|
|
|
|
|
|
/**
|
2015-03-11 20:53:19 +01:00
|
|
|
* Sets a replay to view, or resets the replay if null.
|
2015-03-09 23:32:43 +01:00
|
|
|
* @param replay the replay
|
|
|
|
*/
|
2015-03-10 05:48:04 +01:00
|
|
|
public void setReplay(Replay replay) {
|
2015-03-11 20:53:19 +01:00
|
|
|
if (replay == null) {
|
|
|
|
this.isReplay = false;
|
|
|
|
this.replay = null;
|
|
|
|
} else {
|
|
|
|
if (replay.frames == null) {
|
2015-03-12 06:18:50 +01:00
|
|
|
ErrorHandler.error("Attempting to set a replay with no frames.", null, false);
|
2015-03-11 20:53:19 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.isReplay = true;
|
|
|
|
this.replay = replay;
|
2015-03-11 03:37:23 +01:00
|
|
|
}
|
2015-03-10 05:48:04 +01:00
|
|
|
}
|
2015-03-19 08:04:35 +01:00
|
|
|
|
2015-03-19 00:40:47 +01:00
|
|
|
/**
|
2015-03-19 08:04:35 +01:00
|
|
|
* Adds a replay frame to the list, if possible, and runs it.
|
2015-03-19 00:40:47 +01:00
|
|
|
* @param x the cursor x coordinate
|
|
|
|
* @param y the cursor y coordinate
|
|
|
|
* @param keys the keys pressed
|
|
|
|
* @param time the time of the replay Frame
|
|
|
|
*/
|
|
|
|
public synchronized void addReplayFrameAndRun(int x, int y, int keys, int time){
|
2015-03-19 08:04:35 +01:00
|
|
|
// "auto" and "autopilot" mods: use automatic cursor coordinates
|
2015-03-19 00:40:47 +01:00
|
|
|
if (GameMod.AUTO.isActive() || GameMod.AUTOPILOT.isActive()) {
|
2015-09-05 19:53:42 +02:00
|
|
|
x = (int) autoMousePosition.x;
|
|
|
|
y = (int) autoMousePosition.y;
|
2015-03-19 00:40:47 +01:00
|
|
|
}
|
2015-03-19 08:04:35 +01:00
|
|
|
|
2015-03-15 19:15:34 +01:00
|
|
|
ReplayFrame frame = addReplayFrame(x, y, keys, time);
|
2015-03-19 08:04:35 +01:00
|
|
|
if (frame != null)
|
2015-03-19 00:40:47 +01:00
|
|
|
runReplayFrame(frame);
|
2015-03-15 19:15:34 +01:00
|
|
|
}
|
2015-03-11 03:37:23 +01:00
|
|
|
|
|
|
|
/**
|
2015-03-19 08:04:35 +01:00
|
|
|
* Runs a replay frame.
|
2015-03-19 00:40:47 +01:00
|
|
|
* @param frame the frame to run
|
|
|
|
*/
|
|
|
|
private void runReplayFrame(ReplayFrame frame){
|
2015-03-15 19:15:34 +01:00
|
|
|
int keys = frame.getKeys();
|
|
|
|
int replayX = frame.getScaledX();
|
|
|
|
int replayY = frame.getScaledY();
|
2015-03-19 08:04:35 +01:00
|
|
|
int deltaKeys = (keys & ~lastReplayKeys); // keys that turned on
|
|
|
|
if (deltaKeys != ReplayFrame.KEY_NONE) // send a key press
|
|
|
|
sendGameKeyPress(deltaKeys, replayX, replayY, frame.getTime());
|
|
|
|
else if (keys != lastReplayKeys)
|
|
|
|
; // do nothing
|
|
|
|
else
|
2015-03-15 19:15:34 +01:00
|
|
|
updateGame(replayX, replayY, frame.getTimeDiff(), frame.getTime(), keys);
|
2015-03-19 00:40:47 +01:00
|
|
|
lastReplayKeys = keys;
|
|
|
|
}
|
2015-03-19 08:04:35 +01:00
|
|
|
|
2015-03-19 05:50:39 +01:00
|
|
|
/**
|
2015-03-19 08:04:35 +01:00
|
|
|
* Sends a game key press and updates the hit objects.
|
2015-03-19 05:50:39 +01:00
|
|
|
* @param trackPosition the track position
|
2015-03-19 08:04:35 +01:00
|
|
|
* @param x the cursor x coordinate
|
|
|
|
* @param y the cursor y coordinate
|
|
|
|
* @param keys the keys that are pressed
|
2015-03-19 05:50:39 +01:00
|
|
|
*/
|
2015-03-19 08:04:35 +01:00
|
|
|
private void sendGameKeyPress(int keys, int x, int y, int trackPosition) {
|
2015-05-17 03:42:03 +02:00
|
|
|
if (objectIndex >= gameObjects.length) // nothing to do here
|
2015-03-19 00:40:47 +01:00
|
|
|
return;
|
|
|
|
|
2015-05-17 03:42:03 +02:00
|
|
|
HitObject hitObject = beatmap.objects[objectIndex];
|
2015-03-19 00:40:47 +01:00
|
|
|
|
|
|
|
// circles
|
2015-05-17 03:42:03 +02:00
|
|
|
if (hitObject.isCircle() && gameObjects[objectIndex].mousePressed(x, y, trackPosition))
|
2015-03-19 00:40:47 +01:00
|
|
|
objectIndex++; // circle hit
|
|
|
|
|
|
|
|
// sliders
|
|
|
|
else if (hitObject.isSlider())
|
2015-05-17 03:42:03 +02:00
|
|
|
gameObjects[objectIndex].mousePressed(x, y, trackPosition);
|
2015-03-15 19:15:34 +01:00
|
|
|
}
|
2015-03-19 08:04:35 +01:00
|
|
|
|
2015-03-11 03:37:23 +01:00
|
|
|
/**
|
2015-03-19 08:04:35 +01:00
|
|
|
* Adds a replay frame to the list, if possible.
|
2015-03-11 03:37:23 +01:00
|
|
|
* @param x the cursor x coordinate
|
|
|
|
* @param y the cursor y coordinate
|
|
|
|
* @param keys the keys pressed
|
2015-03-19 08:04:35 +01:00
|
|
|
* @param time the time of the replay frame
|
|
|
|
* @return a ReplayFrame representing the data
|
2015-03-11 03:37:23 +01:00
|
|
|
*/
|
2015-03-15 19:15:34 +01:00
|
|
|
private ReplayFrame addReplayFrame(int x, int y, int keys, int time) {
|
2015-03-11 03:37:23 +01:00
|
|
|
int timeDiff = time - lastReplayTime;
|
|
|
|
lastReplayTime = time;
|
2015-05-17 03:42:03 +02:00
|
|
|
int cx = (int) ((x - HitObject.getXOffset()) / HitObject.getXMultiplier());
|
|
|
|
int cy = (int) ((y - HitObject.getYOffset()) / HitObject.getYMultiplier());
|
2015-03-19 08:04:35 +01:00
|
|
|
ReplayFrame frame = new ReplayFrame(timeDiff, time, cx, cy, keys);
|
|
|
|
if (replayFrames != null)
|
|
|
|
replayFrames.add(frame);
|
|
|
|
return frame;
|
2015-03-11 03:37:23 +01:00
|
|
|
}
|
2015-03-17 19:48:13 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the point at the t value between a start and end point.
|
|
|
|
* @param startX the starting x coordinate
|
|
|
|
* @param startY the starting y coordinate
|
|
|
|
* @param endX the ending x coordinate
|
|
|
|
* @param endY the ending y coordinate
|
|
|
|
* @param t the t value [0, 1]
|
2015-09-05 19:53:42 +02:00
|
|
|
* @return the position vector
|
2015-03-17 19:48:13 +01:00
|
|
|
*/
|
2015-09-05 19:53:42 +02:00
|
|
|
private Vec2f getPointAt(float startX, float startY, float endX, float endY, float t) {
|
2015-03-18 00:03:50 +01:00
|
|
|
// "autopilot" mod: move quicker between objects
|
|
|
|
if (GameMod.AUTOPILOT.isActive())
|
|
|
|
t = Utils.clamp(t * 2f, 0f, 1f);
|
2015-09-05 19:53:42 +02:00
|
|
|
return new Vec2f(startX + (endX - startX) * t, startY + (endY - startY) * t);
|
2015-03-17 19:48:13 +01:00
|
|
|
}
|
2015-03-19 08:04:35 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the current visible area radius (if the "flashlight" mod is enabled).
|
|
|
|
* @param delta the delta interval
|
|
|
|
* @param trackPosition the track position
|
|
|
|
*/
|
|
|
|
private void updateFlashlightRadius(int delta, int trackPosition) {
|
|
|
|
if (!GameMod.FLASHLIGHT.isActive())
|
|
|
|
return;
|
|
|
|
|
|
|
|
int width = container.getWidth(), height = container.getHeight();
|
2015-05-17 03:25:19 +02:00
|
|
|
boolean firstObject = (objectIndex == 0 && trackPosition < beatmap.objects[0].getTime());
|
2015-03-19 08:04:35 +01:00
|
|
|
if (isLeadIn()) {
|
|
|
|
// lead-in: expand area
|
2015-05-17 03:25:19 +02:00
|
|
|
float progress = Math.max((float) (leadInTime - beatmap.audioLeadIn) / approachTime, 0f);
|
2015-03-19 08:04:35 +01:00
|
|
|
flashlightRadius = width - (int) ((width - (height * 2 / 3)) * progress);
|
|
|
|
} else if (firstObject) {
|
|
|
|
// before first object: shrink area
|
2015-05-17 03:25:19 +02:00
|
|
|
int timeDiff = beatmap.objects[0].getTime() - trackPosition;
|
2015-03-19 08:04:35 +01:00
|
|
|
flashlightRadius = width;
|
|
|
|
if (timeDiff < approachTime) {
|
|
|
|
float progress = (float) timeDiff / approachTime;
|
|
|
|
flashlightRadius -= (width - (height * 2 / 3)) * (1 - progress);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// gameplay: size based on combo
|
|
|
|
int targetRadius;
|
|
|
|
int combo = data.getComboStreak();
|
|
|
|
if (combo < 100)
|
|
|
|
targetRadius = height * 2 / 3;
|
|
|
|
else if (combo < 200)
|
|
|
|
targetRadius = height / 2;
|
|
|
|
else
|
|
|
|
targetRadius = height / 3;
|
2015-05-17 03:25:19 +02:00
|
|
|
if (beatmap.breaks != null && breakIndex < beatmap.breaks.size() && breakTime > 0) {
|
2015-03-19 08:04:35 +01:00
|
|
|
// breaks: expand at beginning, shrink at end
|
|
|
|
flashlightRadius = targetRadius;
|
2015-05-17 03:25:19 +02:00
|
|
|
int endTime = beatmap.breaks.get(breakIndex);
|
2015-03-19 08:04:35 +01:00
|
|
|
int breakLength = endTime - breakTime;
|
|
|
|
if (breakLength > approachTime * 3) {
|
|
|
|
float progress = 1f;
|
|
|
|
if (trackPosition - breakTime < approachTime)
|
|
|
|
progress = (float) (trackPosition - breakTime) / approachTime;
|
|
|
|
else if (endTime - trackPosition < approachTime)
|
|
|
|
progress = (float) (endTime - trackPosition) / approachTime;
|
|
|
|
flashlightRadius += (width - flashlightRadius) * progress;
|
|
|
|
}
|
|
|
|
} else if (flashlightRadius != targetRadius) {
|
|
|
|
// radius size change
|
|
|
|
float radiusDiff = height * delta / 2000f;
|
|
|
|
if (flashlightRadius > targetRadius) {
|
|
|
|
flashlightRadius -= radiusDiff;
|
|
|
|
if (flashlightRadius < targetRadius)
|
|
|
|
flashlightRadius = targetRadius;
|
|
|
|
} else {
|
|
|
|
flashlightRadius += radiusDiff;
|
|
|
|
if (flashlightRadius > targetRadius)
|
|
|
|
flashlightRadius = targetRadius;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-03-31 05:06:52 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs stacking calculations on all hit objects, and updates their
|
|
|
|
* positions if necessary.
|
2015-03-31 05:58:11 +02:00
|
|
|
* @author peppy (https://gist.github.com/peppy/1167470)
|
2015-03-31 05:06:52 +02:00
|
|
|
*/
|
|
|
|
private void calculateStacks() {
|
|
|
|
// reverse pass for stack calculation
|
2015-05-17 03:42:03 +02:00
|
|
|
for (int i = gameObjects.length - 1; i > 0; i--) {
|
|
|
|
HitObject hitObjectI = beatmap.objects[i];
|
2015-03-31 05:06:52 +02:00
|
|
|
|
|
|
|
// already calculated
|
|
|
|
if (hitObjectI.getStack() != 0 || hitObjectI.isSpinner())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// search for hit objects in stack
|
2015-03-31 05:58:11 +02:00
|
|
|
for (int n = i - 1; n >= 0; n--) {
|
2015-05-17 03:42:03 +02:00
|
|
|
HitObject hitObjectN = beatmap.objects[n];
|
2015-03-31 05:06:52 +02:00
|
|
|
if (hitObjectN.isSpinner())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// check if in range stack calculation
|
2015-05-17 03:25:19 +02:00
|
|
|
float timeI = hitObjectI.getTime() - (STACK_TIMEOUT * beatmap.stackLeniency);
|
2015-05-17 03:42:03 +02:00
|
|
|
float timeN = hitObjectN.isSlider() ? gameObjects[n].getEndTime() : hitObjectN.getTime();
|
2015-03-31 05:06:52 +02:00
|
|
|
if (timeI > timeN)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// possible special case: if slider end in the stack,
|
|
|
|
// all next hit objects in stack move right down
|
|
|
|
if (hitObjectN.isSlider()) {
|
2015-09-05 19:53:42 +02:00
|
|
|
Vec2f p1 = gameObjects[i].getPointAt(hitObjectI.getTime());
|
|
|
|
Vec2f p2 = gameObjects[n].getPointAt(gameObjects[n].getEndTime());
|
|
|
|
float distance = Utils.distance(p1.x, p1.y, p2.x, p2.y);
|
2015-03-31 05:06:52 +02:00
|
|
|
|
|
|
|
// check if hit object part of this stack
|
2015-05-17 03:42:03 +02:00
|
|
|
if (distance < STACK_LENIENCE * HitObject.getXMultiplier()) {
|
2015-03-31 05:06:52 +02:00
|
|
|
int offset = hitObjectI.getStack() - hitObjectN.getStack() + 1;
|
|
|
|
for (int j = n + 1; j <= i; j++) {
|
2015-05-17 03:42:03 +02:00
|
|
|
HitObject hitObjectJ = beatmap.objects[j];
|
|
|
|
p1 = gameObjects[j].getPointAt(hitObjectJ.getTime());
|
2015-09-05 19:53:42 +02:00
|
|
|
distance = Utils.distance(p1.x, p1.y, p2.x, p2.y);
|
2015-03-31 05:06:52 +02:00
|
|
|
|
|
|
|
// hit object below slider end
|
2015-05-17 03:42:03 +02:00
|
|
|
if (distance < STACK_LENIENCE * HitObject.getXMultiplier())
|
2015-03-31 05:06:52 +02:00
|
|
|
hitObjectJ.setStack(hitObjectJ.getStack() - offset);
|
|
|
|
}
|
|
|
|
break; // slider end always start of the stack: reset calculation
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// not a special case: stack moves up left
|
|
|
|
float distance = Utils.distance(
|
|
|
|
hitObjectI.getX(), hitObjectI.getY(),
|
|
|
|
hitObjectN.getX(), hitObjectN.getY()
|
|
|
|
);
|
|
|
|
if (distance < STACK_LENIENCE) {
|
|
|
|
hitObjectN.setStack(hitObjectI.getStack() + 1);
|
|
|
|
hitObjectI = hitObjectN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// update hit object positions
|
2015-05-17 03:42:03 +02:00
|
|
|
for (int i = 0; i < gameObjects.length; i++) {
|
2015-05-17 03:25:19 +02:00
|
|
|
if (beatmap.objects[i].getStack() != 0)
|
2015-05-17 03:42:03 +02:00
|
|
|
gameObjects[i].updatePosition();
|
2015-03-31 05:06:52 +02:00
|
|
|
}
|
|
|
|
}
|
2015-07-03 05:16:14 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if the coordinates are within the music position bar bounds.
|
|
|
|
* @param cx the x coordinate
|
|
|
|
* @param cy the y coordinate
|
|
|
|
*/
|
|
|
|
private boolean musicPositionBarContains(float cx, float cy) {
|
|
|
|
return ((cx > musicBarX && cx < musicBarX + musicBarWidth) &&
|
|
|
|
(cy > musicBarY && cy < musicBarY + musicBarHeight));
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|