Snaking sliders initial implementation.

Draws curves in a range from 0 to x (where x is a value between 0 and 1) while blending them in (if enabled in the options)
Cache a unit-cone once and re-use that with scaling and translation instead of generating it each time again.
This is more for readability than performance (since presumably feeding it mostly constants like before would be faster)

Only draws the cones in the curve once if possible
(only possible if consective calls to draw with the intervals x_1 before x_2 are made and [0, x_1] [0, x_2] with x_1 < x_2 holds true)

minor refactoring and cleanup

Omg my code is such a mess. I feel like I've committed a crime against humanity by just randomly putting that
random vbo id into the class called "Rendertarget". But there's really no good place for it now (that has a way to clean it up).
But if "Rendertarget" will ever be used by anything else but the sliders I'm gonna pull that out.

added blending in for return arrows and ticks
This commit is contained in:
Peter Tissen 2015-09-16 17:19:23 +02:00
parent e0da6a2444
commit 131138ea8c
7 changed files with 203 additions and 104 deletions

View File

@ -907,7 +907,7 @@ public class GameData {
float oldColorAlpha = hitResult.color.a;
Colors.WHITE_FADE.a = alpha;
hitResult.color.a = alpha;
hitResult.curve.draw(hitResult.color);
hitResult.curve.draw(hitResult.color,1.0f);
Colors.WHITE_FADE.a = oldWhiteAlpha;
hitResult.color.a = oldColorAlpha;
}

View File

@ -417,6 +417,7 @@ public class Options {
BACKGROUND_DIM ("Background Dim", "DimLevel", "Percentage to dim the background image during gameplay.", 50, 0, 100),
FORCE_DEFAULT_PLAYFIELD ("Force Default Playfield", "ForceDefaultPlayfield", "Override the song background with the default playfield background.", false),
IGNORE_BEATMAP_SKINS ("Ignore All Beatmap Skins", "IgnoreBeatmapSkins", "Never use skin element overrides provided by beatmaps.", false),
SNAKING_SLIDERS ("Snaking sliders", "SnakingSliders", "Sliders gradually snake out from their starting point.", true),
SHOW_HIT_LIGHTING ("Show Hit Lighting", "HitLighting", "Adds an effect behind hit explosions.", true),
SHOW_COMBO_BURSTS ("Show Combo Bursts", "ComboBurst", "A character image is displayed at combo milestones.", true),
SHOW_PERFECT_HIT ("Show Perfect Hits", "PerfectHit", "Whether to show perfect hit result bursts (300s, slider ticks).", true),
@ -941,6 +942,12 @@ public class Options {
*/
public static boolean isBeatmapSkinIgnored() { return GameOption.IGNORE_BEATMAP_SKINS.getBooleanValue(); }
/**
* Returns whether or not sliders should snake in or just appear fully at once.
* @return true if sliders should snake in
*/
public static boolean isSliderSnaking() { return GameOption.SNAKING_SLIDERS.getBooleanValue(); }
/**
* Returns the fixed circle size override, if any.
* @return the CS value (0, 10], 0f if disabled

View File

@ -30,6 +30,7 @@ import itdelatrisu.opsu.objects.curves.Curve;
import itdelatrisu.opsu.objects.curves.Vec2f;
import itdelatrisu.opsu.states.Game;
import itdelatrisu.opsu.ui.Colors;
import itdelatrisu.opsu.ui.animations.AnimationEquation;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
@ -179,20 +180,27 @@ public class Slider implements GameObject {
float approachScale = 1 + scale * 3;
float fadeinScale = (timeDiff - approachTime + fadeInTime) / (float) fadeInTime;
float alpha = Utils.clamp(1 - fadeinScale, 0, 1);
float decorationsAlpha = Utils.clamp(-2.0f * fadeinScale, 0, 1);
boolean overlayAboveNumber = Options.getSkin().isHitCircleOverlayAboveNumber();
float oldAlpha = Colors.WHITE_FADE.a;
Colors.WHITE_FADE.a = color.a = alpha;
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
Image hitCircle = GameImage.HITCIRCLE.getImage();
Vec2f endPos = curve.pointAt(1);
curve.draw(color);
float curveInterval;
if(Options.isSliderSnaking()){
curveInterval = alpha;
} else {
curveInterval = 1.0f;
}
curve.draw(color,curveInterval);
color.a = alpha;
// end circle
hitCircle.drawCentered(endPos.x, endPos.y, color);
hitCircleOverlay.drawCentered(endPos.x, endPos.y, Colors.WHITE_FADE);
Vec2f endCircPos = curve.pointAt(curveInterval);
hitCircle.drawCentered(endCircPos.x, endCircPos.y, color);
hitCircleOverlay.drawCentered(endCircPos.x, endCircPos.y, Colors.WHITE_FADE);
// start circle
hitCircle.drawCentered(x, y, color);
@ -201,10 +209,13 @@ public class Slider implements GameObject {
// ticks
if (ticksT != null) {
Image tick = GameImage.SLIDER_TICK.getImage();
float tickScale = 0.5f + 0.5f*AnimationEquation.OUT_BACK.calc(decorationsAlpha);
Image tick = GameImage.SLIDER_TICK.getImage().getScaledCopy(tickScale);
for (int i = 0; i < ticksT.length; i++) {
Vec2f c = curve.pointAt(ticksT[i]);
Colors.WHITE_FADE.a = decorationsAlpha;
tick.drawCentered(c.x , c.y , Colors.WHITE_FADE);
Colors.WHITE_FADE.a = alpha;
}
}
if (GameMod.HIDDEN.isActive()) {
@ -224,16 +235,23 @@ public class Slider implements GameObject {
hitCircleOverlay.drawCentered(x, y, Colors.WHITE_FADE);
// repeats
if (curveInterval == 1.0f) {
for (int tcurRepeat = currentRepeats; tcurRepeat <= currentRepeats + 1; tcurRepeat++) {
if (hitObject.getRepeatCount() - 1 > tcurRepeat) {
Image arrow = GameImage.REVERSEARROW.getImage();
if (tcurRepeat != currentRepeats) {
if (sliderTime == 0)
if (sliderTime == 0) {
continue;
}
float t = Math.max(getT(trackPosition, true), 0);
arrow.setAlpha((float) (t - Math.floor(t)));
} else
} else {
if(Options.isSliderSnaking()){
arrow.setAlpha(decorationsAlpha);
} else {
arrow.setAlpha(1f);
}
}
if (tcurRepeat % 2 == 0) {
// last circle
arrow.setRotation(curve.getEndAngle());
@ -245,6 +263,7 @@ public class Slider implements GameObject {
}
}
}
}
if (timeDiff >= 0) {
// approach circle

View File

@ -20,6 +20,7 @@ package itdelatrisu.opsu.objects.curves;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.beatmap.HitObject;
import itdelatrisu.opsu.render.CurveRenderState;
import itdelatrisu.opsu.skins.Skin;
@ -111,28 +112,31 @@ public abstract class Curve {
public abstract Vec2f pointAt(float t);
/**
* Draws the full curve to the graphics context.
* Draws the curve in the range [0, t] (where the full range is [0, 1]) to the graphics context.
* @param color the color filter
* @param t set the curve interval to [0, t]
*/
public void draw(Color color) {
public void draw(Color color, float t) {
if (curve == null)
return;
t = Utils.clamp(t, 0.0f, 1.0f);
// peppysliders
if (Options.getSkin().getSliderStyle() == Skin.STYLE_PEPPYSLIDER || !mmsliderSupported) {
int drawUpTo = (int)(curve.length*t);
Image hitCircle = GameImage.HITCIRCLE.getImage();
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
for (int i = 0; i < curve.length; i++)
for (int i = 0; i < drawUpTo; i++)
hitCircleOverlay.drawCentered(curve[i].x, curve[i].y, Colors.WHITE_FADE);
for (int i = 0; i < curve.length; i++)
for (int i = 0; i < drawUpTo; i++)
hitCircle.drawCentered(curve[i].x, curve[i].y, color);
}
// mmsliders
else {
if (renderState == null)
renderState = new CurveRenderState(hitObject);
renderState.draw(color, borderColor, curve);
renderState = new CurveRenderState(hitObject,curve);
renderState.draw(color, borderColor, t);
}
}

View File

@ -18,9 +18,9 @@
package itdelatrisu.opsu.render;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.beatmap.HitObject;
import itdelatrisu.opsu.objects.curves.Vec2f;
import itdelatrisu.opsu.ui.Colors;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
@ -59,6 +59,12 @@ public class CurveRenderState {
/** The HitObject associated with the curve to be drawn. */
protected HitObject hitObject;
/** the points along the curve to be drawn */
protected Vec2f[] curve;
/** The point to which the curve has last been rendered into the texture (as an index into {@code curve}) */
private int lastPointDrawn;
/**
* Set the width and height of the container that Curves get drawn into.
* Should be called before any curves are drawn.
@ -74,11 +80,12 @@ public class CurveRenderState {
scale = (int) (circleDiameter * HitObject.getXMultiplier()); // convert from Osupixels (640x480)
//scale = scale * 118 / 128; //for curves exactly as big as the sliderball
FrameBufferCache.init(width, height);
NewCurveStyleState.initUnitCone();
}
/**
* Undo the static state. Static state setup caused by calls to
* {@link #draw(org.newdawn.slick.Color, org.newdawn.slick.Color, itdelatrisu.opsu.objects.curves.Vec2f[])}
* {@link #draw(org.newdawn.slick.Color, org.newdawn.slick.Color, float)}
* are undone.
*/
public static void shutdown() {
@ -89,10 +96,12 @@ public class CurveRenderState {
/**
* Creates an object to hold the render state that's necessary to draw a curve.
* @param hitObject the HitObject that represents this curve, just used as a unique ID
* @param curve the points along the curve to be drawn
*/
public CurveRenderState(HitObject hitObject) {
public CurveRenderState(HitObject hitObject, Vec2f[] curve) {
fbo = null;
this.hitObject = hitObject;
this.curve = curve;
}
/**
@ -101,9 +110,10 @@ public class CurveRenderState {
* runs it just draws the cached copy to the screen.
* @param color tint of the curve
* @param borderColor the curve border color
* @param curve the points along the curve to be drawn
* @param t the point up to which the curve should be drawn (in the interval [0, 1])
*/
public void draw(Color color, Color borderColor, Vec2f[] curve) {
public void draw(Color color, Color borderColor, float t) {
t = Utils.clamp(t, 0.0f, 1.0f);
float alpha = color.a;
// if this curve hasn't been drawn, draw it and cache the result
@ -113,26 +123,40 @@ public class CurveRenderState {
if (mapping == null)
mapping = cache.insert(hitObject);
fbo = mapping;
createVertexBuffer(fbo.getVbo());
//write impossible value to make sure the fbo is cleared
lastPointDrawn = -1;
}
int drawUpTo = (int) (t * curve.length);
if (lastPointDrawn != drawUpTo) {
if (drawUpTo == lastPointDrawn) {
return;
}
int oldFb = GL11.glGetInteger(EXTFramebufferObject.GL_FRAMEBUFFER_BINDING_EXT);
int oldTex = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
//glGetInteger requires a buffer of size 16, even though just 4
//values are returned in this specific case
IntBuffer oldViewport = BufferUtils.createIntBuffer(16);
GL11.glGetInteger(GL11.GL_VIEWPORT, oldViewport);
EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, fbo.getID());
GL11.glViewport(0, 0, fbo.width, fbo.height);
if (lastPointDrawn <= 0 || lastPointDrawn > drawUpTo) {
lastPointDrawn = 0;
GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
Colors.WHITE_FADE.a = 1.0f;
this.draw_curve(color, borderColor, curve);
}
this.renderCurve(color, borderColor, lastPointDrawn, drawUpTo);
lastPointDrawn = drawUpTo;
color.a = 1f;
GL11.glBindTexture(GL11.GL_TEXTURE_2D, oldTex);
EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, oldFb);
GL11.glViewport(oldViewport.get(0), oldViewport.get(1), oldViewport.get(2), oldViewport.get(3));
Colors.WHITE_FADE.a = alpha;
}
// draw a fullscreen quad with the texture that contains the curve
@ -180,7 +204,7 @@ public class CurveRenderState {
* Backup the current state of the relevant OpenGL state and change it to
* what's needed to draw the curve.
*/
private RenderState startRender() {
private RenderState saveRenderState() {
RenderState state = new RenderState();
state.smoothedPoly = GL11.glGetBoolean(GL11.GL_POLYGON_SMOOTH);
state.blendEnabled = GL11.glGetBoolean(GL11.GL_BLEND);
@ -219,7 +243,7 @@ public class CurveRenderState {
* Restore the old OpenGL state that's backed up in {@code state}.
* @param state the old state to restore
*/
private void endRender(RenderState state) {
private void restoreRenderState(RenderState state) {
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glPopMatrix();
GL11.glMatrixMode(GL11.GL_MODELVIEW);
@ -241,24 +265,18 @@ public class CurveRenderState {
}
/**
* Do the actual drawing of the curve into the currently bound framebuffer.
* @param color the color of the curve
* @param borderColor the curve border color
* @param curve the points along the curve
* Write the vertices and (with position and texture coordinates) for the full
* curve into the OpenGL buffer with the ID specified by {@code bufferID}
* @param bufferID the buffer ID for the OpenGL buffer the vertices should be written into
*/
private void draw_curve(Color color, Color borderColor, Vec2f[] curve) {
staticState.initGradient();
RenderState state = startRender();
int vtx_buf;
// the size is: floatsize * (position + texture coordinates) * (number of cones) * (vertices in a cone)
private void createVertexBuffer(int bufferID)
{
int arrayBufferBinding = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING);
FloatBuffer buff = BufferUtils.createByteBuffer(4 * (4 + 2) * (2 * curve.length - 1) * (NewCurveStyleState.DIVIDES + 2)).asFloatBuffer();
staticState.initShaderProgram();
vtx_buf = GL15.glGenBuffers();
for (int i = 0; i < curve.length; ++i) {
float x = curve[i].x;
float y = curve[i].y;
//if (i == 0 || i == curve.length - 1){
fillCone(buff, x, y, NewCurveStyleState.DIVIDES);
fillCone(buff, x, y);
if (i != 0) {
float last_x = curve[i - 1].x;
float last_y = curve[i - 1].y;
@ -266,12 +284,25 @@ public class CurveRenderState {
double diff_y = y - last_y;
x = (float) (x - diff_x / 2);
y = (float) (y - diff_y / 2);
fillCone(buff, x, y, NewCurveStyleState.DIVIDES);
fillCone(buff, x, y);
}
}
buff.flip();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vtx_buf);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferID);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buff, GL15.GL_STATIC_DRAW);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, arrayBufferBinding);
}
/**
* Do the actual drawing of the curve into the currently bound framebuffer.
* @param color the color of the curve
* @param borderColor the curve border color
*/
private void renderCurve(Color color, Color borderColor, int from, int to) {
staticState.initGradient();
RenderState state = saveRenderState();
staticState.initShaderProgram();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, fbo.getVbo());
GL20.glUseProgram(staticState.program);
GL20.glEnableVertexAttribArray(staticState.attribLoc);
GL20.glEnableVertexAttribArray(staticState.texCoordLoc);
@ -282,63 +313,37 @@ public class CurveRenderState {
//2*4 is for skipping the first 2 floats (u,v)
GL20.glVertexAttribPointer(staticState.attribLoc, 4, GL11.GL_FLOAT, false, 6 * 4, 2 * 4);
GL20.glVertexAttribPointer(staticState.texCoordLoc, 2, GL11.GL_FLOAT, false, 6 * 4, 0);
for (int i = 0; i < curve.length * 2 - 1; ++i)
for (int i = from*2; i < to * 2 - 1; ++i)
GL11.glDrawArrays(GL11.GL_TRIANGLE_FAN, i * (NewCurveStyleState.DIVIDES + 2), NewCurveStyleState.DIVIDES + 2);
GL11.glFlush();
GL20.glDisableVertexAttribArray(staticState.texCoordLoc);
GL20.glDisableVertexAttribArray(staticState.attribLoc);
GL15.glDeleteBuffers(vtx_buf);
endRender(state);
restoreRenderState(state);
}
/**
* Fill {@code buff} with the texture coordinates and positions for a cone
* with {@code DIVIDES} ground corners that has its center at the coordinates
* {@code (x1,y1)}.
* that has its center at the coordinates {@code (x1,y1)}.
* @param buff the buffer to be filled
* @param x1 x-coordinate of the cone
* @param y1 y-coordinate of the cone
* @param DIVIDES the base of the cone is a regular polygon with this many sides
*/
protected void fillCone(FloatBuffer buff, float x1, float y1, final int DIVIDES) {
protected void fillCone(FloatBuffer buff, float x1, float y1) {
float divx = containerWidth / 2.0f;
float divy = containerHeight / 2.0f;
float offx = -1.0f;
float offy = 1.0f;
float x, y;
float radius = scale / 2;
buff.put(1.0f);
buff.put(0.5f);
//GL11.glTexCoord2d(1.0, 0.5);
x = offx + x1 / divx;
y = offy - y1 / divy;
buff.put(x);
buff.put(y);
buff.put(0f);
buff.put(1f);
//GL11.glVertex4f(x, y, 0.0f, 1.0f);
for (int j = 0; j < DIVIDES; ++j) {
double phase = j * (float) Math.PI * 2 / DIVIDES;
buff.put(0.0f);
buff.put(0.5f);
//GL11.glTexCoord2d(0.0, 0.5);
x = (x1 + radius * (float) Math.sin(phase)) / divx;
y = (y1 + radius * (float) Math.cos(phase)) / divy;
buff.put((offx + x));
buff.put((offy - y));
buff.put(1f);
buff.put(1f);
//GL11.glVertex4f(x + 90 * (float) Math.sin(phase), y + 90 * (float) Math.cos(phase), 1.0f, 1.0f);
for(int i = 0; i<NewCurveStyleState.unitCone.length/6; ++i)
{
buff.put(NewCurveStyleState.unitCone[i*6+0]);
buff.put(NewCurveStyleState.unitCone[i*6+1]);
buff.put(offx + (x1 + radius * NewCurveStyleState.unitCone[i*6+2])/divx);
buff.put(offy - (y1 + radius * NewCurveStyleState.unitCone[i*6+3])/divy);
buff.put(NewCurveStyleState.unitCone[i*6+4]);
buff.put(NewCurveStyleState.unitCone[i*6+5]);
}
buff.put(0.0f);
buff.put(0.5f);
//GL11.glTexCoord2d(0.0, 0.5);
x = (x1 + radius * (float) Math.sin(0.0)) / divx;
y = (y1 + radius * (float) Math.cos(0.0)) / divy;
buff.put((offx + x));
buff.put((offy - y));
buff.put(1f);
buff.put(1f);
//GL11.glVertex4f(x + 90 * (float) Math.sin(0.0), y + 90 * (float) Math.cos(0.0), 1.0f, 1.0f);
}
/**
@ -354,6 +359,13 @@ public class CurveRenderState {
*/
protected static final int DIVIDES = 30;
/**
* Array to hold the dummy vertex data (texture coordinates and position)
* of a cone with DIVIDES vertices at its base, that is centered around
* (0,0) and has a radius of 1 (so that it can be translated and scaled easily).
*/
protected static float[] unitCone = new float[(DIVIDES+2)*6];
/** OpenGL shader program ID used to draw and recolor the curve. */
protected int program = 0;
@ -398,6 +410,47 @@ public class CurveRenderState {
}
}
/**
* Write the data into {@code unitCone} if it hasn't already been initialized.
*/
public static void initUnitCone() {
int index = 0;
//check if initialization has already happened
if (unitCone[0] == 0.0f) {
//tip of the cone
//vec2 texture coordinates
unitCone[index++] = 1.0f;
unitCone[index++] = 0.5f;
//vec4 position
unitCone[index++] = 0.0f;
unitCone[index++] = 0.0f;
unitCone[index++] = 0.0f;
unitCone[index++] = 1.0f;
for (int j = 0; j < NewCurveStyleState.DIVIDES; ++j) {
double phase = j * (float) Math.PI * 2 / NewCurveStyleState.DIVIDES;
//vec2 texture coordinates
unitCone[index++] = 0.0f;
unitCone[index++] = 0.5f;
//vec4 positon
unitCone[index++] = (float) Math.sin(phase);
unitCone[index++] = (float) Math.cos(phase);
unitCone[index++] = 1.0f;
unitCone[index++] = 1.0f;
}
//vec2 texture coordinates
unitCone[index++] = 0.0f;
unitCone[index++] = 0.5f;
//vec4 positon
unitCone[index++] = (float) Math.sin(0.0f);
unitCone[index++] = (float) Math.cos(0.0f);
unitCone[index++] = 1.0f;
unitCone[index++] = 1.0f;
}
}
/**
* Compiles and links the shader program for the new style curve objects
* if it hasn't already been compiled and linked.

View File

@ -21,6 +21,7 @@ import java.nio.ByteBuffer;
import org.lwjgl.opengl.EXTFramebufferObject;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
/**
* Represents a rendertarget. For now this maps to an OpenGL FBO via LWJGL.
@ -31,6 +32,9 @@ public class Rendertarget {
/** The dimensions. */
public final int width, height;
/** ID of the vertex buffer associated with this rendertarget*/
private final int vboID;
/** The FBO ID. */
private final int fboID;
@ -49,6 +53,7 @@ public class Rendertarget {
this.width = width;
this.height = height;
fboID = EXTFramebufferObject.glGenFramebuffersEXT();
vboID = GL15.glGenBuffers();
textureID = GL11.glGenTextures();
depthBufferID = EXTFramebufferObject.glGenRenderbuffersEXT();
}
@ -60,19 +65,27 @@ public class Rendertarget {
EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, fboID);
}
/**
* Returns the FBO ID.
* Get the ID of the VBO associated with this Rendertarget.
* @return OpenGL buffer ID for the VBO
*/
public int getVbo() {
return vboID;
}
/**
* Get the FBO ID.
* @return the OpenGL FBO ID
*/
// NOTE: use judiciously, try to avoid if possible and consider adding a
// method to this class if you find yourself calling this repeatedly.
public int getID() {
return fboID;
}
/**
* Returns the texture ID.
* Get the texture ID of the texture this rendertarget renders into.
* @return the OpenGL texture ID
*/
// NOTE: try not to use, could be moved into separate class.
public int getTextureID() {
return textureID;
}
@ -89,6 +102,7 @@ public class Rendertarget {
* and a renderbuffer that it renders the depth to.
* @param width the width
* @param height the height
* @return the newly created Rendertarget instance
*/
public static Rendertarget createRTTFramebuffer(int width, int height) {
int old_framebuffer = GL11.glGetInteger(EXTFramebufferObject.GL_FRAMEBUFFER_BINDING_EXT);
@ -122,5 +136,6 @@ public class Rendertarget {
EXTFramebufferObject.glDeleteFramebuffersEXT(fboID);
EXTFramebufferObject.glDeleteRenderbuffersEXT(depthBufferID);
GL11.glDeleteTextures(textureID);
GL15.glDeleteBuffers(vboID);
}
}

View File

@ -76,6 +76,7 @@ public class OptionsMenu extends BasicGameState {
GameOption.BACKGROUND_DIM,
GameOption.FORCE_DEFAULT_PLAYFIELD,
GameOption.IGNORE_BEATMAP_SKINS,
GameOption.SNAKING_SLIDERS,
GameOption.SHOW_HIT_LIGHTING,
GameOption.SHOW_COMBO_BURSTS,
GameOption.SHOW_PERFECT_HIT,