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/>.
* /
2015-01-21 05:56:10 +01:00
package itdelatrisu.opsu ;
2014-06-30 04:17:04 +02:00
2015-08-29 04:29:21 +02:00
import itdelatrisu.opsu.audio.MusicController ;
import itdelatrisu.opsu.beatmap.Beatmap ;
import itdelatrisu.opsu.skins.Skin ;
import itdelatrisu.opsu.skins.SkinLoader ;
import itdelatrisu.opsu.ui.Fonts ;
import itdelatrisu.opsu.ui.UI ;
2014-06-30 04:17:04 +02:00
import java.io.BufferedReader ;
import java.io.BufferedWriter ;
import java.io.File ;
import java.io.FileOutputStream ;
import java.io.FileReader ;
import java.io.IOException ;
import java.io.OutputStreamWriter ;
2015-01-16 19:42:54 +01:00
import java.net.URI ;
2014-06-30 04:17:04 +02:00
import java.text.SimpleDateFormat ;
import java.util.Date ;
2015-06-12 22:04:20 +02:00
import java.util.HashMap ;
2014-07-19 09:31:54 +02:00
import java.util.Locale ;
2014-07-09 04:17:48 +02:00
import java.util.concurrent.TimeUnit ;
2015-08-27 21:14:04 +02:00
import java.util.jar.Attributes ;
import java.util.jar.JarFile ;
import java.util.jar.Manifest ;
2015-11-01 03:20:08 +01:00
import java.util.regex.Matcher ;
import java.util.regex.Pattern ;
2014-06-30 04:17:04 +02:00
2014-07-18 05:58:37 +02:00
import org.lwjgl.input.Keyboard ;
2014-06-30 04:17:04 +02:00
import org.newdawn.slick.GameContainer ;
import org.newdawn.slick.Input ;
import org.newdawn.slick.SlickException ;
2015-05-24 05:48:28 +02:00
import org.newdawn.slick.util.ClasspathLocation ;
import org.newdawn.slick.util.FileSystemLocation ;
2014-06-30 04:17:04 +02:00
import org.newdawn.slick.util.Log ;
2015-05-24 05:48:28 +02:00
import org.newdawn.slick.util.ResourceLoader ;
2014-06-30 04:17:04 +02:00
2015-11-01 03:20:08 +01:00
import com.sun.jna.platform.win32.Advapi32Util ;
import com.sun.jna.platform.win32.Win32Exception ;
import com.sun.jna.platform.win32.WinReg ;
2014-06-30 04:17:04 +02:00
/ * *
2015-01-21 05:56:10 +01:00
* Handles all user options .
2014-06-30 04:17:04 +02:00
* /
2015-01-21 05:56:10 +01:00
public class Options {
2015-08-27 21:14:04 +02:00
/** Whether to use XDG directories. */
2015-09-03 02:23:23 +02:00
public static final boolean USE_XDG = checkXDGFlag ( ) ;
2015-08-27 21:14:04 +02:00
2015-02-13 21:03:17 +01:00
/** The config directory. */
private static final File CONFIG_DIR = getXDGBaseDir ( " XDG_CONFIG_HOME " , " .config " ) ;
/** The data directory. */
private static final File DATA_DIR = getXDGBaseDir ( " XDG_DATA_HOME " , " .local/share " ) ;
2015-08-30 21:31:01 +02:00
/** The cache directory. */
private static final File CACHE_DIR = getXDGBaseDir ( " XDG_CACHE_HOME " , " .cache " ) ;
2015-01-22 06:44:45 +01:00
/** File for logging errors. */
2015-02-13 21:03:17 +01:00
public static final File LOG_FILE = new File ( CONFIG_DIR , " .opsu.log " ) ;
2014-06-30 04:17:04 +02:00
2015-01-22 06:44:45 +01:00
/** File for storing user options. */
2015-02-13 21:03:17 +01:00
private static final File OPTIONS_FILE = new File ( CONFIG_DIR , " .opsu.cfg " ) ;
2014-07-02 07:53:42 +02:00
2015-11-01 03:20:08 +01:00
/** The default beatmap directory (unless an osu! installation is detected). */
private static final File BEATMAP_DIR = new File ( DATA_DIR , " Songs/ " ) ;
2014-06-30 04:17:04 +02:00
2015-11-01 03:20:08 +01:00
/** The default skin directory (unless an osu! installation is detected). */
private static final File SKIN_ROOT_DIR = new File ( DATA_DIR , " Skins/ " ) ;
2015-05-24 05:48:28 +02:00
2015-03-05 03:03:06 +01:00
/** Cached beatmap database name. */
2015-05-17 03:25:19 +02:00
public static final File BEATMAP_DB = new File ( DATA_DIR , " .opsu.db " ) ;
2015-03-05 03:03:06 +01:00
2015-01-28 09:47:24 +01:00
/** Score database name. */
2015-02-13 21:03:17 +01:00
public static final File SCORE_DB = new File ( DATA_DIR , " .opsu_scores.db " ) ;
2015-01-28 09:47:24 +01:00
2015-08-30 21:31:01 +02:00
/** Directory where natives are unpacked. */
public static final File NATIVE_DIR = new File ( CACHE_DIR , " Natives/ " ) ;
2016-10-13 10:12:47 +02:00
/** Directory where temporary files are stored (deleted on exit). */
public static final File TEMP_DIR = new File ( CACHE_DIR , " Temp/ " ) ;
2015-01-22 06:44:45 +01:00
/** Font file name. */
2015-05-15 07:33:53 +02:00
public static final String FONT_NAME = " DroidSansFallback.ttf " ;
2014-08-25 18:47:10 +02:00
2015-03-07 10:17:19 +01:00
/** Version file name. */
public static final String VERSION_FILE = " version " ;
2015-01-22 06:44:45 +01:00
/** Repository address. */
2015-03-07 10:17:19 +01:00
public static final URI REPOSITORY_URI = URI . create ( " https://github.com/itdelatrisu/opsu " ) ;
2015-01-16 19:42:54 +01:00
2015-01-22 06:44:45 +01:00
/** Issue reporting address. */
2015-03-07 10:17:19 +01:00
public static final String ISSUES_URL = " https://github.com/itdelatrisu/opsu/issues/new?title=%s&body=%s " ;
/** Address containing the latest version file. */
public static final String VERSION_REMOTE = " https://raw.githubusercontent.com/itdelatrisu/opsu/gh-pages/version " ;
2015-01-16 19:42:54 +01:00
2015-01-22 06:44:45 +01:00
/** The beatmap directory. */
2014-06-30 04:17:04 +02:00
private static File beatmapDir ;
2015-01-22 06:44:45 +01:00
/** The OSZ archive directory. */
2014-07-06 07:58:44 +02:00
private static File oszDir ;
2015-01-22 06:44:45 +01:00
/** The screenshot directory (created when needed). */
2014-07-18 06:56:37 +02:00
private static File screenshotDir ;
2015-03-12 01:52:51 +01:00
/** The replay directory (created when needed). */
private static File replayDir ;
2015-04-02 04:10:36 +02:00
/** The replay import directory. */
private static File replayImportDir ;
2015-05-24 05:48:28 +02:00
/** The root skin directory. */
private static File skinRootDir ;
2014-06-30 04:17:04 +02:00
2015-03-03 06:40:51 +01:00
/** Port binding. */
private static int port = 49250 ;
2015-08-27 21:14:04 +02:00
/ * *
* Returns whether the XDG flag in the manifest ( if any ) is set to " true " .
* @return true if XDG directories are enabled , false otherwise
* /
private static boolean checkXDGFlag ( ) {
JarFile jarFile = Utils . getJarFile ( ) ;
if ( jarFile = = null )
return false ;
try {
Manifest manifest = jarFile . getManifest ( ) ;
if ( manifest = = null )
return false ;
Attributes attributes = manifest . getMainAttributes ( ) ;
String value = attributes . getValue ( " Use-XDG " ) ;
return ( value ! = null & & value . equalsIgnoreCase ( " true " ) ) ;
} catch ( IOException e ) {
return false ;
}
}
2015-02-13 21:03:17 +01:00
/ * *
* Returns the directory based on the XDG base directory specification for
2015-08-27 21:14:04 +02:00
* Unix - like operating systems , only if the " XDG " flag is enabled .
2015-02-13 21:03:17 +01:00
* @param env the environment variable to check ( XDG_ * _ * )
* @param fallback the fallback directory relative to ~ home
* @return the XDG base directory , or the working directory if unavailable
* /
private static File getXDGBaseDir ( String env , String fallback ) {
2015-08-27 21:14:04 +02:00
if ( ! USE_XDG )
2015-02-13 21:03:17 +01:00
return new File ( " ./ " ) ;
String OS = System . getProperty ( " os.name " ) . toLowerCase ( ) ;
if ( OS . indexOf ( " nix " ) > = 0 | | OS . indexOf ( " nux " ) > = 0 | | OS . indexOf ( " aix " ) > 0 ) {
String rootPath = System . getenv ( env ) ;
if ( rootPath = = null ) {
String home = System . getProperty ( " user.home " ) ;
if ( home = = null )
return new File ( " ./ " ) ;
rootPath = String . format ( " %s/%s " , home , fallback ) ;
}
File dir = new File ( rootPath , " opsu " ) ;
2015-07-11 17:51:52 +02:00
if ( ! dir . isDirectory ( ) & & ! dir . mkdir ( ) )
ErrorHandler . error ( String . format ( " Failed to create configuration folder at '%s/opsu'. " , rootPath ) , null , false ) ;
2015-02-13 21:03:17 +01:00
return dir ;
} else
return new File ( " ./ " ) ;
}
2015-11-01 03:20:08 +01:00
/ * *
* Returns the osu ! installation directory .
* @return the directory , or null if not found
* /
private static File getOsuInstallationDirectory ( ) {
if ( ! System . getProperty ( " os.name " ) . startsWith ( " Win " ) )
return null ; // only works on Windows
// registry location
final WinReg . HKEY rootKey = WinReg . HKEY_CLASSES_ROOT ;
final String regKey = " osu \\ DefaultIcon " ;
final String regValue = null ; // default value
final String regPathPattern = " \" (.+) \\ \\ [^ \\ /]+ \\ .exe \" " ;
String value ;
try {
value = Advapi32Util . registryGetStringValue ( rootKey , regKey , regValue ) ;
} catch ( Win32Exception e ) {
return null ; // key/value not found
}
Pattern pattern = Pattern . compile ( regPathPattern ) ;
Matcher m = pattern . matcher ( value ) ;
if ( ! m . find ( ) )
return null ;
File dir = new File ( m . group ( 1 ) ) ;
return ( dir . isDirectory ( ) ) ? dir : null ;
}
2014-12-21 03:35:18 +01:00
/ * *
* The theme song string :
2015-01-22 06:44:45 +01:00
* { @code filename , title , artist , length ( ms ) }
2014-12-21 03:35:18 +01:00
* /
2015-02-04 09:43:23 +01:00
private static String themeString = " theme.ogg,On the Bach,Jingle Punks,66000 " ;
2014-12-21 03:35:18 +01:00
2015-01-22 06:44:45 +01:00
/** Game options. */
public enum GameOption {
2015-06-12 22:04:20 +02:00
// internal options (not displayed in-game)
BEATMAP_DIRECTORY ( " BeatmapDirectory " ) {
@Override
public String write ( ) { return getBeatmapDir ( ) . getAbsolutePath ( ) ; }
@Override
public void read ( String s ) { beatmapDir = new File ( s ) ; }
} ,
OSZ_DIRECTORY ( " OSZDirectory " ) {
@Override
public String write ( ) { return getOSZDir ( ) . getAbsolutePath ( ) ; }
@Override
public void read ( String s ) { oszDir = new File ( s ) ; }
} ,
SCREENSHOT_DIRECTORY ( " ScreenshotDirectory " ) {
@Override
public String write ( ) { return getScreenshotDir ( ) . getAbsolutePath ( ) ; }
@Override
public void read ( String s ) { screenshotDir = new File ( s ) ; }
} ,
REPLAY_DIRECTORY ( " ReplayDirectory " ) {
@Override
public String write ( ) { return getReplayDir ( ) . getAbsolutePath ( ) ; }
@Override
public void read ( String s ) { replayDir = new File ( s ) ; }
} ,
2015-06-30 03:18:28 +02:00
REPLAY_IMPORT_DIRECTORY ( " ReplayImportDirectory " ) {
@Override
public String write ( ) { return getReplayImportDir ( ) . getAbsolutePath ( ) ; }
@Override
public void read ( String s ) { replayImportDir = new File ( s ) ; }
} ,
2015-06-12 22:04:20 +02:00
SKIN_DIRECTORY ( " SkinDirectory " ) {
@Override
public String write ( ) { return getSkinRootDir ( ) . getAbsolutePath ( ) ; }
@Override
public void read ( String s ) { skinRootDir = new File ( s ) ; }
} ,
THEME_SONG ( " ThemeSong " ) {
@Override
public String write ( ) { return themeString ; }
@Override
public void read ( String s ) { themeString = s ; }
} ,
PORT ( " Port " ) {
@Override
public String write ( ) { return Integer . toString ( port ) ; }
@Override
public void read ( String s ) {
int i = Integer . parseInt ( s ) ;
2015-08-13 05:00:58 +02:00
if ( i > 0 & & i < = 65535 )
2015-06-12 22:04:20 +02:00
port = i ;
}
} ,
// in-game options
SCREEN_RESOLUTION ( " Screen Resolution " , " ScreenResolution " , " Restart (Ctrl+Shift+F5) to apply resolution changes. " ) {
2015-01-11 19:05:32 +01:00
@Override
2015-01-15 07:52:16 +01:00
public String getValueString ( ) { return resolution . toString ( ) ; }
2015-01-11 19:05:32 +01:00
@Override
public void click ( GameContainer container ) {
do {
2015-01-15 07:52:16 +01:00
resolution = resolution . next ( ) ;
2015-06-12 22:04:20 +02:00
} while ( resolution ! = Resolution . RES_800_600 & & (
container . getScreenWidth ( ) < resolution . getWidth ( ) | |
container . getScreenHeight ( ) < resolution . getHeight ( ) ) ) ;
}
@Override
public void read ( String s ) {
try {
Resolution res = Resolution . valueOf ( String . format ( " RES_%s " , s . replace ( 'x' , '_' ) ) ) ;
resolution = res ;
} catch ( IllegalArgumentException e ) { }
2015-01-11 19:05:32 +01:00
}
} ,
2015-06-12 22:04:20 +02:00
// FULLSCREEN ("Fullscreen Mode", "Fullscreen", "Restart to apply changes.", false),
SKIN ( " Skin " , " Skin " , " Restart (Ctrl+Shift+F5) to apply skin changes. " ) {
2015-05-24 05:48:28 +02:00
@Override
public String getValueString ( ) { return skinName ; }
@Override
public void click ( GameContainer container ) {
skinDirIndex = ( skinDirIndex + 1 ) % skinDirs . length ;
skinName = skinDirs [ skinDirIndex ] ;
}
2015-06-12 22:04:20 +02:00
@Override
public void read ( String s ) { skinName = s ; }
2015-05-24 05:48:28 +02:00
} ,
2015-06-12 22:04:20 +02:00
TARGET_FPS ( " Frame Limiter " , " FrameSync " , " Higher values may cause high CPU usage. " ) {
2015-01-11 19:05:32 +01:00
@Override
2015-01-15 07:52:16 +01:00
public String getValueString ( ) {
return String . format ( ( getTargetFPS ( ) = = 60 ) ? " %dfps (vsync) " : " %dfps " , getTargetFPS ( ) ) ;
}
2015-01-11 19:05:32 +01:00
@Override
public void click ( GameContainer container ) {
targetFPSindex = ( targetFPSindex + 1 ) % targetFPS . length ;
container . setTargetFrameRate ( getTargetFPS ( ) ) ;
2015-01-15 07:52:16 +01:00
container . setVSync ( getTargetFPS ( ) = = 60 ) ;
2015-01-11 19:05:32 +01:00
}
2015-06-12 22:04:20 +02:00
@Override
public String write ( ) { return Integer . toString ( targetFPS [ targetFPSindex ] ) ; }
@Override
public void read ( String s ) {
int i = Integer . parseInt ( s ) ;
for ( int j = 0 ; j < targetFPS . length ; j + + ) {
if ( i = = targetFPS [ j ] ) {
targetFPSindex = j ;
break ;
}
}
}
2015-01-11 19:05:32 +01:00
} ,
2015-06-12 22:12:21 +02:00
SHOW_FPS ( " Show FPS Counter " , " FpsCounter " , " Show an FPS counter in the bottom-right hand corner. " , true ) ,
SHOW_UNICODE ( " Prefer Non-English Metadata " , " ShowUnicode " , " Where available, song titles will be shown in their native language. " , false ) {
@Override
public void click ( GameContainer container ) {
super . click ( container ) ;
if ( bool ) {
try {
2015-08-21 03:40:07 +02:00
Fonts . LARGE . loadGlyphs ( ) ;
Fonts . MEDIUM . loadGlyphs ( ) ;
Fonts . DEFAULT . loadGlyphs ( ) ;
2015-06-12 22:12:21 +02:00
} catch ( SlickException e ) {
Log . warn ( " Failed to load glyphs. " , e ) ;
}
}
}
} ,
SCREENSHOT_FORMAT ( " Screenshot Format " , " ScreenshotFormat " , " Press F12 to take a screenshot. " ) {
@Override
public String getValueString ( ) { return screenshotFormat [ screenshotFormatIndex ] . toUpperCase ( ) ; }
@Override
public void click ( GameContainer container ) { screenshotFormatIndex = ( screenshotFormatIndex + 1 ) % screenshotFormat . length ; }
@Override
public String write ( ) { return Integer . toString ( screenshotFormatIndex ) ; }
@Override
public void read ( String s ) {
int i = Integer . parseInt ( s ) ;
if ( i > = 0 & & i < screenshotFormat . length )
screenshotFormatIndex = i ;
}
} ,
2015-06-14 19:30:33 +02:00
CURSOR_SIZE ( " Cursor Size " , " CursorSize " , " Change the cursor scale. " , 100 , 50 , 200 ) {
@Override
public String getValueString ( ) { return String . format ( " %.2fx " , val / 100f ) ; }
@Override
public String write ( ) { return String . format ( Locale . US , " %.2f " , val / 100f ) ; }
@Override
public void read ( String s ) {
int i = ( int ) ( Float . parseFloat ( s ) * 100f ) ;
if ( i > = 50 & & i < = 200 )
val = i ;
}
} ,
2015-06-12 22:12:21 +02:00
NEW_CURSOR ( " Enable New Cursor " , " NewCursor " , " Use the new cursor style (may cause higher CPU usage). " , true ) {
@Override
public void click ( GameContainer container ) {
super . click ( container ) ;
UI . getCursor ( ) . reset ( ) ;
}
} ,
DYNAMIC_BACKGROUND ( " Enable Dynamic Backgrounds " , " DynamicBackground " , " The song background will be used as the main menu background. " , true ) ,
LOAD_VERBOSE ( " Show Detailed Loading Progress " , " LoadVerbose " , " Display more specific loading information in the splash screen. " , false ) ,
2015-06-12 22:04:20 +02:00
MASTER_VOLUME ( " Master Volume " , " VolumeUniversal " , " Global volume level. " , 35 , 0 , 100 ) {
2015-01-20 20:52:02 +01:00
@Override
public void drag ( GameContainer container , int d ) {
2015-03-03 06:40:51 +01:00
super . drag ( container , d ) ;
2015-01-20 20:52:02 +01:00
container . setMusicVolume ( getMasterVolume ( ) * getMusicVolume ( ) ) ;
}
} ,
2015-06-12 22:04:20 +02:00
MUSIC_VOLUME ( " Music Volume " , " VolumeMusic " , " Volume of music. " , 80 , 0 , 100 ) {
2015-01-11 19:05:32 +01:00
@Override
public void drag ( GameContainer container , int d ) {
2015-03-03 06:40:51 +01:00
super . drag ( container , d ) ;
2015-01-20 20:52:02 +01:00
container . setMusicVolume ( getMasterVolume ( ) * getMusicVolume ( ) ) ;
2015-01-11 19:05:32 +01:00
}
2015-06-12 22:04:20 +02:00
} ,
2015-06-13 01:25:19 +02:00
EFFECT_VOLUME ( " Effect Volume " , " VolumeEffect " , " Volume of menu and game sounds. " , 70 , 0 , 100 ) ,
HITSOUND_VOLUME ( " Hit Sound Volume " , " VolumeHitSound " , " Volume of hit sounds. " , 30 , 0 , 100 ) ,
2015-06-12 22:04:20 +02:00
MUSIC_OFFSET ( " Music Offset " , " Offset " , " Adjust this value if hit objects are out of sync. " , - 75 , - 500 , 500 ) {
2015-01-11 19:05:32 +01:00
@Override
2015-03-03 06:40:51 +01:00
public String getValueString ( ) { return String . format ( " %dms " , val ) ; }
2015-01-11 19:05:32 +01:00
} ,
2015-06-12 22:12:21 +02:00
DISABLE_SOUNDS ( " Disable All Sound Effects " , " DisableSound " , " May resolve Linux sound driver issues. Requires a restart. " ,
2015-07-11 17:51:52 +02:00
( System . getProperty ( " os.name " ) . toLowerCase ( ) . contains ( " linux " ) ) ) ,
2015-06-12 22:12:21 +02:00
KEY_LEFT ( " Left Game Key " , " keyOsuLeft " , " Select this option to input a key. " ) {
2015-01-11 19:05:32 +01:00
@Override
2015-06-12 22:12:21 +02:00
public String getValueString ( ) { return Keyboard . getKeyName ( getGameKeyLeft ( ) ) ; }
2015-01-11 19:05:32 +01:00
@Override
2015-06-12 22:12:21 +02:00
public String write ( ) { return Keyboard . getKeyName ( getGameKeyLeft ( ) ) ; }
2015-06-12 22:04:20 +02:00
@Override
2015-06-12 22:12:21 +02:00
public void read ( String s ) { setGameKeyLeft ( Keyboard . getKeyIndex ( s ) ) ; }
} ,
KEY_RIGHT ( " Right Game Key " , " keyOsuRight " , " Select this option to input a key. " ) {
@Override
public String getValueString ( ) { return Keyboard . getKeyName ( getGameKeyRight ( ) ) ; }
2015-06-12 22:04:20 +02:00
@Override
2015-06-12 22:12:21 +02:00
public String write ( ) { return Keyboard . getKeyName ( getGameKeyRight ( ) ) ; }
2015-01-11 19:05:32 +01:00
@Override
2015-06-12 22:12:21 +02:00
public void read ( String s ) { setGameKeyRight ( Keyboard . getKeyIndex ( s ) ) ; }
2015-01-11 19:05:32 +01:00
} ,
2015-06-12 22:12:21 +02:00
DISABLE_MOUSE_WHEEL ( " Disable mouse wheel in play mode " , " MouseDisableWheel " , " During play, you can use the mouse wheel to adjust the volume and pause the game. \ nThis will disable that functionality. " , false ) ,
DISABLE_MOUSE_BUTTONS ( " Disable mouse buttons in play mode " , " MouseDisableButtons " , " This option will disable all mouse buttons. \ nSpecifically for people who use their keyboard to click. " , false ) ,
2015-09-11 17:43:19 +02:00
DISABLE_CURSOR ( " Disable Cursor " , " DisableCursor " , " Hide the cursor sprite. " , false ) ,
2015-06-13 01:25:19 +02:00
BACKGROUND_DIM ( " Background Dim " , " DimLevel " , " Percentage to dim the background image during gameplay. " , 50 , 0 , 100 ) ,
2015-06-12 22:04:20 +02:00
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 ) ,
2015-09-16 17:19:23 +02:00
SNAKING_SLIDERS ( " Snaking sliders " , " SnakingSliders " , " Sliders gradually snake out from their starting point. " , true ) ,
2015-06-12 22:12:21 +02:00
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 ) ,
SHOW_FOLLOW_POINTS ( " Show Follow Points " , " FollowPoints " , " Whether to show follow points between hit objects. " , true ) ,
SHOW_HIT_ERROR_BAR ( " Show Hit Error Bar " , " ScoreMeter " , " Shows precisely how accurate you were with each hit. " , false ) ,
LOAD_HD_IMAGES ( " Load HD Images " , " LoadHDImages " , String . format ( " Loads HD (%s) images when available. Increases memory usage and loading times. " , GameImage . HD_SUFFIX ) , true ) ,
2015-06-12 22:04:20 +02:00
FIXED_CS ( " Fixed Circle Size (CS) " , " FixedCS " , " Determines the size of circles and sliders. " , 0 , 0 , 100 ) {
2015-01-11 19:05:32 +01:00
@Override
2015-03-03 06:40:51 +01:00
public String getValueString ( ) { return ( val = = 0 ) ? " Disabled " : String . format ( " %.1f " , val / 10f ) ; }
2015-06-12 22:04:20 +02:00
@Override
public String write ( ) { return String . format ( Locale . US , " %.1f " , val / 10f ) ; }
@Override
public void read ( String s ) {
int i = ( int ) ( Float . parseFloat ( s ) * 10f ) ;
if ( i > = 0 & & i < = 100 )
val = i ;
}
2015-01-11 19:05:32 +01:00
} ,
2015-06-12 22:04:20 +02:00
FIXED_HP ( " Fixed HP Drain Rate (HP) " , " FixedHP " , " Determines the rate at which health decreases. " , 0 , 0 , 100 ) {
2015-01-11 19:05:32 +01:00
@Override
2015-03-03 06:40:51 +01:00
public String getValueString ( ) { return ( val = = 0 ) ? " Disabled " : String . format ( " %.1f " , val / 10f ) ; }
2015-06-12 22:04:20 +02:00
@Override
public String write ( ) { return String . format ( Locale . US , " %.1f " , val / 10f ) ; }
@Override
public void read ( String s ) {
int i = ( int ) ( Float . parseFloat ( s ) * 10f ) ;
if ( i > = 0 & & i < = 100 )
val = i ;
}
2015-01-11 19:05:32 +01:00
} ,
2015-06-12 22:04:20 +02:00
FIXED_AR ( " Fixed Approach Rate (AR) " , " FixedAR " , " Determines how long hit circles stay on the screen. " , 0 , 0 , 100 ) {
2015-01-11 19:05:32 +01:00
@Override
2015-03-03 06:40:51 +01:00
public String getValueString ( ) { return ( val = = 0 ) ? " Disabled " : String . format ( " %.1f " , val / 10f ) ; }
2015-06-12 22:04:20 +02:00
@Override
public String write ( ) { return String . format ( Locale . US , " %.1f " , val / 10f ) ; }
@Override
public void read ( String s ) {
int i = ( int ) ( Float . parseFloat ( s ) * 10f ) ;
if ( i > = 0 & & i < = 100 )
val = i ;
}
2015-01-11 19:05:32 +01:00
} ,
2015-06-12 22:04:20 +02:00
FIXED_OD ( " Fixed Overall Difficulty (OD) " , " FixedOD " , " Determines the time window for hit results. " , 0 , 0 , 100 ) {
2015-01-11 19:05:32 +01:00
@Override
2015-03-03 06:40:51 +01:00
public String getValueString ( ) { return ( val = = 0 ) ? " Disabled " : String . format ( " %.1f " , val / 10f ) ; }
2015-06-12 22:04:20 +02:00
@Override
public String write ( ) { return String . format ( Locale . US , " %.1f " , val / 10f ) ; }
@Override
public void read ( String s ) {
int i = ( int ) ( Float . parseFloat ( s ) * 10f ) ;
if ( i > = 0 & & i < = 100 )
val = i ;
}
2015-01-11 19:05:32 +01:00
} ,
2015-06-12 22:04:20 +02:00
CHECKPOINT ( " Track Checkpoint " , " Checkpoint " , " Press Ctrl+L while playing to load a checkpoint, and Ctrl+S to set one. " , 0 , 0 , 3599 ) {
2015-01-11 19:05:32 +01:00
@Override
public String getValueString ( ) {
2015-03-03 06:40:51 +01:00
return ( val = = 0 ) ? " Disabled " : String . format ( " %02d:%02d " ,
TimeUnit . SECONDS . toMinutes ( val ) ,
val - TimeUnit . MINUTES . toSeconds ( TimeUnit . SECONDS . toMinutes ( val ) ) ) ;
2015-01-11 19:05:32 +01:00
}
} ,
2015-07-03 05:16:14 +02:00
ENABLE_THEME_SONG ( " Enable Theme Song " , " MenuMusic " , " Whether to play the theme song upon starting opsu! " , true ) ,
2015-07-08 02:03:54 +02:00
REPLAY_SEEKING ( " Replay Seeking " , " ReplaySeeking " , " Enable a seeking bar on the left side of the screen during replays. " , false ) ,
2015-08-21 17:25:52 +02:00
DISABLE_UPDATER ( " Disable Automatic Updates " , " DisableUpdater " , " Disable automatic checking for updates upon starting opsu!. " , false ) ,
ENABLE_WATCH_SERVICE ( " Enable Watch Service " , " WatchService " , " Watch the beatmap directory for changes. Requires a restart. " , false ) ;
2015-01-11 19:05:32 +01:00
2015-01-22 06:44:45 +01:00
/** Option name. */
2015-08-21 04:11:55 +02:00
private final String name ;
2015-01-11 19:05:32 +01:00
2015-06-12 22:04:20 +02:00
/** Option name, as displayed in the configuration file. */
2015-08-21 04:11:55 +02:00
private final String displayName ;
2015-06-12 22:04:20 +02:00
2015-01-22 06:44:45 +01:00
/** Option description. */
2015-08-21 04:11:55 +02:00
private final String description ;
2015-01-11 19:05:32 +01:00
2015-03-03 06:40:51 +01:00
/** The boolean value for the option (if applicable). */
protected boolean bool ;
/** The integer value for the option (if applicable). */
protected int val ;
/** The upper and lower bounds on the integer value (if applicable). */
private int max , min ;
2015-06-12 22:04:20 +02:00
/** Option types. */
private enum OptionType { BOOLEAN , NUMERIC , OTHER } ;
2015-03-03 06:40:51 +01:00
/** Whether or not this is a numeric option. */
2015-06-12 22:04:20 +02:00
private OptionType type = OptionType . OTHER ;
2015-03-03 06:40:51 +01:00
2015-01-11 19:05:32 +01:00
/ * *
2015-06-12 22:04:20 +02:00
* Constructor for internal options ( not displayed in - game ) .
* @param displayName the option name , as displayed in the configuration file
* /
GameOption ( String displayName ) {
2015-08-21 04:11:55 +02:00
this ( null , displayName , null ) ;
2015-06-12 22:04:20 +02:00
}
/ * *
* Constructor for other option types .
2015-01-11 19:05:32 +01:00
* @param name the option name
2015-06-12 22:04:20 +02:00
* @param displayName the option name , as displayed in the configuration file
2015-01-11 19:05:32 +01:00
* @param description the option description
* /
2015-06-12 22:04:20 +02:00
GameOption ( String name , String displayName , String description ) {
2015-01-11 19:05:32 +01:00
this . name = name ;
2015-06-12 22:04:20 +02:00
this . displayName = displayName ;
2015-01-11 19:05:32 +01:00
this . description = description ;
}
2015-03-03 06:40:51 +01:00
/ * *
2015-06-12 22:04:20 +02:00
* Constructor for boolean options .
2015-03-03 06:40:51 +01:00
* @param name the option name
2015-06-12 22:04:20 +02:00
* @param displayName the option name , as displayed in the configuration file
2015-03-03 06:40:51 +01:00
* @param description the option description
* @param value the default boolean value
* /
2015-06-12 22:04:20 +02:00
GameOption ( String name , String displayName , String description , boolean value ) {
this ( name , displayName , description ) ;
2015-03-03 06:40:51 +01:00
this . bool = value ;
2015-06-12 22:04:20 +02:00
this . type = OptionType . BOOLEAN ;
2015-03-03 06:40:51 +01:00
}
/ * *
2015-06-12 22:04:20 +02:00
* Constructor for numeric options .
2015-03-03 06:40:51 +01:00
* @param name the option name
2015-06-12 22:04:20 +02:00
* @param displayName the option name , as displayed in the configuration file
2015-03-03 06:40:51 +01:00
* @param description the option description
* @param value the default integer value
* /
2015-06-12 22:04:20 +02:00
GameOption ( String name , String displayName , String description , int value , int min , int max ) {
this ( name , displayName , description ) ;
2015-03-03 06:40:51 +01:00
this . val = value ;
this . min = min ;
this . max = max ;
2015-06-12 22:04:20 +02:00
this . type = OptionType . NUMERIC ;
2015-03-03 06:40:51 +01:00
}
2015-01-11 19:05:32 +01:00
/ * *
* Returns the option name .
* @return the name string
* /
public String getName ( ) { return name ; }
2015-06-12 22:04:20 +02:00
/ * *
* Returns the option name , as displayed in the configuration file .
* @return the display name string
* /
public String getDisplayName ( ) { return displayName ; }
2015-01-11 19:05:32 +01:00
/ * *
2015-03-03 06:40:51 +01:00
* Returns the option description .
2015-01-11 19:05:32 +01:00
* @return the description string
* /
public String getDescription ( ) { return description ; }
2015-03-03 06:40:51 +01:00
/ * *
* Returns the boolean value for the option , if applicable .
* @return the boolean value
* /
public boolean getBooleanValue ( ) { return bool ; }
/ * *
* Returns the integer value for the option , if applicable .
* @return the integer value
* /
public int getIntegerValue ( ) { return val ; }
/ * *
* Sets the boolean value for the option .
* @param value the new boolean value
* /
public void setValue ( boolean value ) { this . bool = value ; }
/ * *
* Sets the integer value for the option .
* @param value the new integer value
* /
public void setValue ( int value ) { this . val = value ; }
2015-01-11 19:05:32 +01:00
/ * *
* Returns the value of the option as a string ( via override ) .
2015-03-03 06:40:51 +01:00
* < p >
2015-06-12 22:04:20 +02:00
* By default , this returns " {@code val}% " for numeric options ,
* " Yes " or " No " based on the { @code bool } field for boolean options ,
* and an empty string otherwise .
2015-01-11 19:05:32 +01:00
* @return the value string
* /
2015-03-03 06:40:51 +01:00
public String getValueString ( ) {
2015-06-12 22:04:20 +02:00
if ( type = = OptionType . NUMERIC )
2015-03-03 06:40:51 +01:00
return String . format ( " %d%% " , val ) ;
2015-06-12 22:04:20 +02:00
else if ( type = = OptionType . BOOLEAN )
2015-03-03 06:40:51 +01:00
return ( bool ) ? " Yes " : " No " ;
2015-06-12 22:04:20 +02:00
else
return " " ;
2015-03-03 06:40:51 +01:00
}
2015-01-11 19:05:32 +01:00
/ * *
* Processes a mouse click action ( via override ) .
2015-03-03 06:40:51 +01:00
* < p >
* By default , this inverts the current { @code bool } field .
2015-01-11 19:05:32 +01:00
* @param container the game container
* /
2015-03-03 06:40:51 +01:00
public void click ( GameContainer container ) { bool = ! bool ; }
2015-01-11 19:05:32 +01:00
/ * *
* Processes a mouse drag action ( via override ) .
2015-03-03 06:40:51 +01:00
* < p >
2015-06-12 22:04:20 +02:00
* By default , only if this is a numeric option , the { @code val } field
* will be shifted by { @code d } within the given bounds .
2015-01-11 19:05:32 +01:00
* @param container the game container
* @param d the dragged distance ( modified by multiplier )
* /
2015-03-03 06:40:51 +01:00
public void drag ( GameContainer container , int d ) {
2015-06-12 22:04:20 +02:00
if ( type = = OptionType . NUMERIC )
2015-08-31 00:56:05 +02:00
val = Utils . clamp ( val + d , min , max ) ;
2015-03-03 06:40:51 +01:00
}
2015-06-12 22:04:20 +02:00
/ * *
* Returns the string to write to the configuration file ( via override ) .
* < p >
* By default , this returns " {@code val} " for numeric options ,
* " true " or " false " based on the { @code bool } field for boolean options ,
* and { @link # getValueString ( ) } otherwise .
* @return the string to write
* /
public String write ( ) {
if ( type = = OptionType . NUMERIC )
return Integer . toString ( val ) ;
else if ( type = = OptionType . BOOLEAN )
return Boolean . toString ( bool ) ;
else
return getValueString ( ) ;
}
/ * *
* Reads the value of the option from the configuration file ( via override ) .
* < p >
2015-06-13 01:25:19 +02:00
* By default , this sets { @code val } for numeric options only if the
* value is between the min and max bounds , sets { @code bool } for
* boolean options , and does nothing otherwise .
2015-06-12 22:04:20 +02:00
* @param s the value string read from the configuration file
* /
public void read ( String s ) {
2015-06-13 01:25:19 +02:00
if ( type = = OptionType . NUMERIC ) {
int i = Integer . parseInt ( s ) ;
if ( i > = min & & i < = max )
val = i ;
} else if ( type = = OptionType . BOOLEAN )
2015-06-12 22:04:20 +02:00
bool = Boolean . parseBoolean ( s ) ;
}
2014-07-03 00:24:19 +02:00
} ;
2015-06-12 22:04:20 +02:00
/** Map of option display names to GameOptions. */
private static HashMap < String , GameOption > optionMap ;
2015-01-22 06:44:45 +01:00
/** Screen resolutions. */
2015-01-15 07:52:16 +01:00
private enum Resolution {
RES_800_600 ( 800 , 600 ) ,
RES_1024_600 ( 1024 , 600 ) ,
RES_1024_768 ( 1024 , 768 ) ,
2015-02-14 20:10:47 +01:00
RES_1280_720 ( 1280 , 720 ) ,
2015-01-15 07:52:16 +01:00
RES_1280_800 ( 1280 , 800 ) ,
RES_1280_960 ( 1280 , 960 ) ,
2015-02-14 20:10:47 +01:00
RES_1280_1024 ( 1280 , 1024 ) ,
2015-01-15 07:52:16 +01:00
RES_1366_768 ( 1366 , 768 ) ,
RES_1440_900 ( 1440 , 900 ) ,
RES_1600_900 ( 1600 , 900 ) ,
2015-02-10 03:40:38 +01:00
RES_1600_1200 ( 1600 , 1200 ) ,
2015-02-10 05:22:58 +01:00
RES_1680_1050 ( 1680 , 1050 ) ,
2015-01-15 07:52:16 +01:00
RES_1920_1080 ( 1920 , 1080 ) ,
RES_1920_1200 ( 1920 , 1200 ) ,
RES_2560_1440 ( 2560 , 1440 ) ,
2015-02-14 20:10:47 +01:00
RES_2560_1600 ( 2560 , 1600 ) ,
RES_3840_2160 ( 3840 , 2160 ) ;
2015-01-15 07:52:16 +01:00
2015-01-22 06:44:45 +01:00
/** Screen dimensions. */
2015-01-15 07:52:16 +01:00
private int width , height ;
2015-01-22 06:44:45 +01:00
/** Enum values. */
2015-01-15 07:52:16 +01:00
private static Resolution [ ] values = Resolution . values ( ) ;
/ * *
* Constructor .
* @param width the screen width
* @param height the screen height
* /
Resolution ( int width , int height ) {
this . width = width ;
this . height = height ;
}
/ * *
* Returns the screen width .
* /
public int getWidth ( ) { return width ; }
/ * *
* Returns the screen height .
* /
public int getHeight ( ) { return height ; }
/ * *
* Returns the next ( larger ) Resolution .
* /
public Resolution next ( ) { return values [ ( this . ordinal ( ) + 1 ) % values . length ] ; }
@Override
public String toString ( ) { return String . format ( " %sx%s " , width , height ) ; }
}
2014-06-30 04:17:04 +02:00
2015-01-22 06:44:45 +01:00
/** Current screen resolution. */
2015-01-16 19:42:54 +01:00
private static Resolution resolution = Resolution . RES_1024_768 ;
2014-06-30 04:17:04 +02:00
2015-05-24 05:48:28 +02:00
/** The available skin directories. */
private static String [ ] skinDirs ;
/** The index in the skinDirs array. */
private static int skinDirIndex = 0 ;
/** The name of the skin. */
private static String skinName = " Default " ;
/** The current skin. */
private static Skin skin ;
2015-01-22 06:44:45 +01:00
/** Frame limiters. */
2014-06-30 04:17:04 +02:00
private static final int [ ] targetFPS = { 60 , 120 , 240 } ;
2015-01-16 21:44:13 +01:00
2015-01-22 06:44:45 +01:00
/** Index in targetFPS[] array. */
2014-06-30 04:17:04 +02:00
private static int targetFPSindex = 0 ;
2015-01-22 06:44:45 +01:00
/** Screenshot file formats. */
2014-06-30 04:17:04 +02:00
private static String [ ] screenshotFormat = { " png " , " jpg " , " bmp " } ;
2015-01-22 06:44:45 +01:00
/** Index in screenshotFormat[] array. */
2014-06-30 04:17:04 +02:00
private static int screenshotFormatIndex = 0 ;
2015-01-22 06:44:45 +01:00
/** Left and right game keys. */
2014-07-18 05:58:37 +02:00
private static int
keyLeft = Keyboard . KEY_NONE ,
keyRight = Keyboard . KEY_NONE ;
2015-01-21 05:56:10 +01:00
// This class should not be instantiated.
private Options ( ) { }
2014-06-30 04:17:04 +02:00
/ * *
2014-07-02 01:32:03 +02:00
* Returns the target frame rate .
* @return the target FPS
2014-06-30 04:17:04 +02:00
* /
2014-07-02 01:32:03 +02:00
public static int getTargetFPS ( ) { return targetFPS [ targetFPSindex ] ; }
2014-06-30 04:17:04 +02:00
2015-03-05 20:40:57 +01:00
/ * *
* Sets the target frame rate to the next available option , and sends a
* bar notification about the action .
* @param container the game container
* /
public static void setNextFPS ( GameContainer container ) {
GameOption . TARGET_FPS . click ( container ) ;
UI . sendBarNotification ( String . format ( " Frame limiter: %s " , GameOption . TARGET_FPS . getValueString ( ) ) ) ;
}
2015-01-20 20:52:02 +01:00
/ * *
* Returns the master volume level .
* @return the volume [ 0 , 1 ]
* /
2015-03-03 06:40:51 +01:00
public static float getMasterVolume ( ) { return GameOption . MASTER_VOLUME . getIntegerValue ( ) / 100f ; }
2015-01-20 20:52:02 +01:00
/ * *
* Sets the master volume level ( if within valid range ) .
* @param container the game container
* @param volume the volume [ 0 , 1 ]
* /
public static void setMasterVolume ( GameContainer container , float volume ) {
if ( volume > = 0f & & volume < = 1f ) {
2015-03-03 06:40:51 +01:00
GameOption . MASTER_VOLUME . setValue ( ( int ) ( volume * 100f ) ) ;
2015-03-27 01:45:33 +01:00
MusicController . setVolume ( getMasterVolume ( ) * getMusicVolume ( ) ) ;
2015-01-20 20:52:02 +01:00
}
}
2014-06-30 04:17:04 +02:00
/ * *
* Returns the default music volume .
* @return the volume [ 0 , 1 ]
* /
2015-03-03 06:40:51 +01:00
public static float getMusicVolume ( ) { return GameOption . MUSIC_VOLUME . getIntegerValue ( ) / 100f ; }
2014-06-30 04:17:04 +02:00
2014-07-01 07:14:03 +02:00
/ * *
* Returns the default sound effect volume .
* @return the sound volume [ 0 , 1 ]
* /
2015-03-03 06:40:51 +01:00
public static float getEffectVolume ( ) { return GameOption . EFFECT_VOLUME . getIntegerValue ( ) / 100f ; }
2014-07-01 07:14:03 +02:00
2014-07-03 00:24:19 +02:00
/ * *
2014-07-09 19:36:42 +02:00
* Returns the default hit sound volume .
* @return the hit sound volume [ 0 , 1 ]
2014-07-03 00:24:19 +02:00
* /
2015-03-03 06:40:51 +01:00
public static float getHitSoundVolume ( ) { return GameOption . HITSOUND_VOLUME . getIntegerValue ( ) / 100f ; }
2014-07-03 00:24:19 +02:00
2014-06-30 04:17:04 +02:00
/ * *
* Returns the music offset time .
* @return the offset ( in milliseconds )
* /
2015-03-03 06:40:51 +01:00
public static int getMusicOffset ( ) { return GameOption . MUSIC_OFFSET . getIntegerValue ( ) ; }
2014-06-30 04:17:04 +02:00
/ * *
2014-07-02 01:32:03 +02:00
* Returns the screenshot file format .
* @return the file extension ( " png " , " jpg " , " bmp " )
2014-06-30 04:17:04 +02:00
* /
2014-07-02 01:32:03 +02:00
public static String getScreenshotFormat ( ) { return screenshotFormat [ screenshotFormatIndex ] ; }
2014-06-30 04:17:04 +02:00
/ * *
2014-07-11 02:35:07 +02:00
* Sets the container size and makes the window borderless if the container
* size is identical to the screen resolution .
* < p >
* If the configured resolution is larger than the screen size , the smallest
* available resolution will be used .
* @param app the game container
* /
2015-01-22 00:56:53 +01:00
public static void setDisplayMode ( Container app ) {
2014-07-11 02:35:07 +02:00
int screenWidth = app . getScreenWidth ( ) ;
int screenHeight = app . getScreenHeight ( ) ;
2015-01-18 21:26:00 +01:00
2015-01-15 07:52:16 +01:00
// check for larger-than-screen dimensions
if ( screenWidth < resolution . getWidth ( ) | | screenHeight < resolution . getHeight ( ) )
resolution = Resolution . RES_800_600 ;
2015-01-22 00:56:53 +01:00
try {
app . setDisplayMode ( resolution . getWidth ( ) , resolution . getHeight ( ) , false ) ;
} catch ( SlickException e ) {
ErrorHandler . error ( " Failed to set display mode. " , e , true ) ;
}
2015-01-15 07:52:16 +01:00
// set borderless window if dimensions match screen size
2015-01-21 01:01:18 +01:00
boolean borderless = ( screenWidth = = resolution . getWidth ( ) & & screenHeight = = resolution . getHeight ( ) ) ;
System . setProperty ( " org.lwjgl.opengl.Window.undecorated " , Boolean . toString ( borderless ) ) ;
2014-07-11 02:35:07 +02:00
}
2014-06-30 04:17:04 +02:00
// /**
// * Returns whether or not fullscreen mode is enabled.
// * @return true if enabled
// */
// public static boolean isFullscreen() { return fullscreen; }
2014-07-02 01:32:03 +02:00
/ * *
* Returns whether or not the FPS counter display is enabled .
* @return true if enabled
* /
2015-03-03 06:40:51 +01:00
public static boolean isFPSCounterEnabled ( ) { return GameOption . SHOW_FPS . getBooleanValue ( ) ; }
2014-07-02 01:32:03 +02:00
2014-06-30 04:17:04 +02:00
/ * *
* Returns whether or not hit lighting effects are enabled .
* @return true if enabled
* /
2015-03-03 06:40:51 +01:00
public static boolean isHitLightingEnabled ( ) { return GameOption . SHOW_HIT_LIGHTING . getBooleanValue ( ) ; }
2014-06-30 04:17:04 +02:00
/ * *
* Returns whether or not combo burst effects are enabled .
* @return true if enabled
* /
2015-03-03 06:40:51 +01:00
public static boolean isComboBurstEnabled ( ) { return GameOption . SHOW_COMBO_BURSTS . getBooleanValue ( ) ; }
2014-06-30 04:17:04 +02:00
2014-06-30 18:37:37 +02:00
/ * *
* Returns the port number to bind to .
* @return the port
* /
2014-07-02 01:32:03 +02:00
public static int getPort ( ) { return port ; }
2015-06-14 19:30:33 +02:00
/ * *
* Returns the cursor scale .
* @return the scale [ 0 . 5 , 2 ]
* /
public static float getCursorScale ( ) { return GameOption . CURSOR_SIZE . getIntegerValue ( ) / 100f ; }
2014-07-02 01:32:03 +02:00
/ * *
* Returns whether or not the new cursor type is enabled .
* @return true if enabled
* /
2015-03-03 06:40:51 +01:00
public static boolean isNewCursorEnabled ( ) { return GameOption . NEW_CURSOR . getBooleanValue ( ) ; }
2014-06-30 18:37:37 +02:00
2014-07-02 09:02:11 +02:00
/ * *
* Returns whether or not the main menu background should be the current track image .
* @return true if enabled
* /
2015-03-03 06:40:51 +01:00
public static boolean isDynamicBackgroundEnabled ( ) { return GameOption . DYNAMIC_BACKGROUND . getBooleanValue ( ) ; }
2014-07-02 09:02:11 +02:00
2014-07-03 00:24:19 +02:00
/ * *
* Returns whether or not to show perfect hit result bursts .
* @return true if enabled
* /
2015-03-03 06:40:51 +01:00
public static boolean isPerfectHitBurstEnabled ( ) { return GameOption . SHOW_PERFECT_HIT . getBooleanValue ( ) ; }
2014-07-03 00:24:19 +02:00
2015-03-19 04:23:34 +01:00
/ * *
* Returns whether or not to show follow points .
* @return true if enabled
* /
public static boolean isFollowPointEnabled ( ) { return GameOption . SHOW_FOLLOW_POINTS . getBooleanValue ( ) ; }
2014-07-03 00:24:19 +02:00
/ * *
* Returns the background dim level .
* @return the alpha level [ 0 , 1 ]
* /
2015-03-03 06:40:51 +01:00
public static float getBackgroundDim ( ) { return ( 100 - GameOption . BACKGROUND_DIM . getIntegerValue ( ) ) / 100f ; }
2014-07-03 00:24:19 +02:00
2014-07-03 07:05:23 +02:00
/ * *
* Returns whether or not to override the song background with the default playfield background .
* @return true if forced
* /
2015-03-03 06:40:51 +01:00
public static boolean isDefaultPlayfieldForced ( ) { return GameOption . FORCE_DEFAULT_PLAYFIELD . getBooleanValue ( ) ; }
2014-07-03 07:05:23 +02:00
2014-07-04 22:41:52 +02:00
/ * *
* Returns whether or not beatmap skins are ignored .
* @return true if ignored
* /
2015-03-03 06:40:51 +01:00
public static boolean isBeatmapSkinIgnored ( ) { return GameOption . IGNORE_BEATMAP_SKINS . getBooleanValue ( ) ; }
2014-07-04 22:41:52 +02:00
2015-09-16 17:19:23 +02:00
/ * *
* 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 ( ) ; }
2014-07-05 07:24:01 +02:00
/ * *
* Returns the fixed circle size override , if any .
2015-03-03 06:40:51 +01:00
* @return the CS value ( 0 , 10 ] , 0f if disabled
2014-07-05 07:24:01 +02:00
* /
2015-03-03 06:40:51 +01:00
public static float getFixedCS ( ) { return GameOption . FIXED_CS . getIntegerValue ( ) / 10f ; }
2014-07-05 07:24:01 +02:00
/ * *
* Returns the fixed HP drain rate override , if any .
2015-03-03 06:40:51 +01:00
* @return the HP value ( 0 , 10 ] , 0f if disabled
2014-07-05 07:24:01 +02:00
* /
2015-03-03 06:40:51 +01:00
public static float getFixedHP ( ) { return GameOption . FIXED_HP . getIntegerValue ( ) / 10f ; }
2014-07-05 07:24:01 +02:00
/ * *
* Returns the fixed approach rate override , if any .
2015-03-03 06:40:51 +01:00
* @return the AR value ( 0 , 10 ] , 0f if disabled
2014-07-05 07:24:01 +02:00
* /
2015-03-03 06:40:51 +01:00
public static float getFixedAR ( ) { return GameOption . FIXED_AR . getIntegerValue ( ) / 10f ; }
2014-07-05 07:24:01 +02:00
/ * *
* Returns the fixed overall difficulty override , if any .
2015-03-03 06:40:51 +01:00
* @return the OD value ( 0 , 10 ] , 0f if disabled
2014-07-05 07:24:01 +02:00
* /
2015-03-03 06:40:51 +01:00
public static float getFixedOD ( ) { return GameOption . FIXED_OD . getIntegerValue ( ) / 10f ; }
2014-07-05 07:24:01 +02:00
2014-07-06 03:00:52 +02:00
/ * *
* Returns whether or not to render loading text in the splash screen .
* @return true if enabled
* /
2015-03-03 06:40:51 +01:00
public static boolean isLoadVerbose ( ) { return GameOption . LOAD_VERBOSE . getBooleanValue ( ) ; }
2014-07-06 03:00:52 +02:00
2014-07-09 04:17:48 +02:00
/ * *
* Returns the track checkpoint time .
* @return the checkpoint time ( in ms )
* /
2015-03-03 06:40:51 +01:00
public static int getCheckpoint ( ) { return GameOption . CHECKPOINT . getIntegerValue ( ) * 1000 ; }
2014-07-09 04:17:48 +02:00
2014-07-11 04:01:39 +02:00
/ * *
* Returns whether or not all sound effects are disabled .
* @return true if disabled
* /
2015-03-03 06:40:51 +01:00
public static boolean isSoundDisabled ( ) { return GameOption . DISABLE_SOUNDS . getBooleanValue ( ) ; }
2014-07-11 04:01:39 +02:00
2014-08-25 05:48:52 +02:00
/ * *
* Returns whether or not to use non - English metadata where available .
* @return true if Unicode preferred
* /
2015-03-03 06:40:51 +01:00
public static boolean useUnicodeMetadata ( ) { return GameOption . SHOW_UNICODE . getBooleanValue ( ) ; }
2014-08-25 05:48:52 +02:00
2014-12-21 00:17:04 +01:00
/ * *
* Returns whether or not to play the theme song .
* @return true if enabled
* /
2015-03-03 06:40:51 +01:00
public static boolean isThemeSongEnabled ( ) { return GameOption . ENABLE_THEME_SONG . getBooleanValue ( ) ; }
2014-12-21 00:17:04 +01:00
2015-07-03 05:16:14 +02:00
/ * *
* Returns whether or not replay seeking is enabled .
* @return true if enabled
* /
public static boolean isReplaySeekingEnabled ( ) { return GameOption . REPLAY_SEEKING . getBooleanValue ( ) ; }
2015-07-08 02:03:54 +02:00
/ * *
* Returns whether or not automatic checking for updates is disabled .
* @return true if disabled
* /
public static boolean isUpdaterDisabled ( ) { return GameOption . DISABLE_UPDATER . getBooleanValue ( ) ; }
2015-08-21 17:25:52 +02:00
/ * *
* Returns whether or not the beatmap watch service is enabled .
* @return true if enabled
* /
public static boolean isWatchServiceEnabled ( ) { return GameOption . ENABLE_WATCH_SERVICE . getBooleanValue ( ) ; }
2014-07-09 04:17:48 +02:00
/ * *
* Sets the track checkpoint time , if within bounds .
* @param time the track position ( in ms )
* @return true if within bounds
* /
public static boolean setCheckpoint ( int time ) {
if ( time > = 0 & & time < 3600 ) {
2015-03-03 06:40:51 +01:00
GameOption . CHECKPOINT . setValue ( time ) ;
2014-07-09 04:17:48 +02:00
return true ;
}
return false ;
}
2015-02-15 07:40:01 +01:00
/ * *
* Returns whether or not to show the hit error bar .
* @return true if enabled
* /
2015-03-03 06:40:51 +01:00
public static boolean isHitErrorBarEnabled ( ) { return GameOption . SHOW_HIT_ERROR_BAR . getBooleanValue ( ) ; }
2015-03-14 13:11:33 +01:00
/ * *
2015-03-15 04:28:50 +01:00
* Returns whether or not to load HD ( @2x ) images .
* @return true if HD images are enabled , false if only SD images should be loaded
2015-03-14 13:11:33 +01:00
* /
2015-03-15 04:28:50 +01:00
public static boolean loadHDImages ( ) { return GameOption . LOAD_HD_IMAGES . getBooleanValue ( ) ; }
2015-02-15 07:40:01 +01:00
2015-03-03 04:12:57 +01:00
/ * *
* Returns whether or not the mouse wheel is disabled during gameplay .
* @return true if disabled
* /
2015-03-03 06:40:51 +01:00
public static boolean isMouseWheelDisabled ( ) { return GameOption . DISABLE_MOUSE_WHEEL . getBooleanValue ( ) ; }
2015-03-03 04:12:57 +01:00
/ * *
* Returns whether or not the mouse buttons are disabled during gameplay .
* @return true if disabled
* /
2015-03-03 06:40:51 +01:00
public static boolean isMouseDisabled ( ) { return GameOption . DISABLE_MOUSE_BUTTONS . getBooleanValue ( ) ; }
2015-03-03 04:12:57 +01:00
2015-03-05 20:40:57 +01:00
/ * *
* Toggles the mouse button enabled / disabled state during gameplay and
* sends a bar notification about the action .
* /
public static void toggleMouseDisabled ( ) {
GameOption . DISABLE_MOUSE_BUTTONS . click ( null ) ;
UI . sendBarNotification ( ( GameOption . DISABLE_MOUSE_BUTTONS . getBooleanValue ( ) ) ?
" Mouse buttons are disabled. " : " Mouse buttons are enabled. " ) ;
}
2015-09-11 17:43:19 +02:00
/ * *
* Returns whether or not the cursor sprite should be hidden .
* @return true if disabled
* /
public static boolean isCursorDisabled ( ) { return GameOption . DISABLE_CURSOR . getBooleanValue ( ) ; }
2014-07-18 05:58:37 +02:00
/ * *
* Returns the left game key .
* @return the left key code
* /
public static int getGameKeyLeft ( ) {
if ( keyLeft = = Keyboard . KEY_NONE )
2015-03-07 05:49:59 +01:00
setGameKeyLeft ( Input . KEY_Z ) ;
2014-07-18 05:58:37 +02:00
return keyLeft ;
}
/ * *
* Returns the right game key .
* @return the right key code
* /
public static int getGameKeyRight ( ) {
if ( keyRight = = Keyboard . KEY_NONE )
2015-03-07 05:49:59 +01:00
setGameKeyRight ( Input . KEY_X ) ;
2014-07-18 05:58:37 +02:00
return keyRight ;
}
2015-01-21 05:56:10 +01:00
/ * *
* Sets the left game key .
2015-03-07 20:24:26 +01:00
* This will not be set to the same key as the right game key , nor to any
* reserved keys ( see { @link # isValidGameKey ( int ) } ) .
2015-01-21 05:56:10 +01:00
* @param key the keyboard key
2015-03-07 05:49:59 +01:00
* @return { @code true } if the key was set , { @code false } if it was rejected
* /
public static boolean setGameKeyLeft ( int key ) {
2015-03-07 20:24:26 +01:00
if ( ( key = = keyRight & & key ! = Keyboard . KEY_NONE ) | | ! isValidGameKey ( key ) )
2015-03-07 05:49:59 +01:00
return false ;
keyLeft = key ;
return true ;
}
2015-01-21 05:56:10 +01:00
/ * *
* Sets the right game key .
2015-03-07 20:24:26 +01:00
* This will not be set to the same key as the left game key , nor to any
* reserved keys ( see { @link # isValidGameKey ( int ) } ) .
2015-01-21 05:56:10 +01:00
* @param key the keyboard key
2015-03-07 05:49:59 +01:00
* @return { @code true } if the key was set , { @code false } if it was rejected
* /
public static boolean setGameKeyRight ( int key ) {
2015-03-07 20:24:26 +01:00
if ( ( key = = keyLeft & & key ! = Keyboard . KEY_NONE ) | | ! isValidGameKey ( key ) )
2015-03-07 05:49:59 +01:00
return false ;
keyRight = key ;
return true ;
}
2015-01-21 05:56:10 +01:00
2015-03-07 20:24:26 +01:00
/ * *
* Checks if the given key is a valid game key .
* @param key the keyboard key
* @return { @code true } if valid , { @code false } otherwise
* /
private static boolean isValidGameKey ( int key ) {
return ( key ! = Keyboard . KEY_ESCAPE & & key ! = Keyboard . KEY_SPACE & &
key ! = Keyboard . KEY_UP & & key ! = Keyboard . KEY_DOWN & &
key ! = Keyboard . KEY_F7 & & key ! = Keyboard . KEY_F10 & & key ! = Keyboard . KEY_F12 ) ;
}
2014-06-30 04:17:04 +02:00
/ * *
2014-07-06 07:58:44 +02:00
* Returns the beatmap directory .
2014-06-30 04:17:04 +02:00
* If invalid , this will attempt to search for the directory ,
* and if nothing found , will create one .
2014-07-02 07:53:42 +02:00
* @return the beatmap directory
2014-06-30 04:17:04 +02:00
* /
public static File getBeatmapDir ( ) {
if ( beatmapDir ! = null & & beatmapDir . isDirectory ( ) )
return beatmapDir ;
2015-11-01 03:20:08 +01:00
// use osu! installation directory, if found
File osuDir = getOsuInstallationDirectory ( ) ;
if ( osuDir ! = null ) {
beatmapDir = new File ( osuDir , BEATMAP_DIR . getName ( ) ) ;
2014-07-02 01:32:03 +02:00
if ( beatmapDir . isDirectory ( ) )
2014-06-30 04:17:04 +02:00
return beatmapDir ;
}
2015-07-11 17:51:52 +02:00
2015-11-01 03:20:08 +01:00
// use default directory
beatmapDir = BEATMAP_DIR ;
if ( ! beatmapDir . isDirectory ( ) & & ! beatmapDir . mkdir ( ) )
2015-07-11 17:51:52 +02:00
ErrorHandler . error ( String . format ( " Failed to create beatmap directory at '%s'. " , beatmapDir . getAbsolutePath ( ) ) , null , false ) ;
2014-06-30 04:17:04 +02:00
return beatmapDir ;
}
2014-07-06 07:58:44 +02:00
/ * *
* Returns the OSZ archive directory .
2014-07-18 06:56:37 +02:00
* If invalid , this will create and return a " SongPacks " directory .
2014-07-06 07:58:44 +02:00
* @return the OSZ archive directory
* /
public static File getOSZDir ( ) {
if ( oszDir ! = null & & oszDir . isDirectory ( ) )
return oszDir ;
2015-02-13 21:03:17 +01:00
oszDir = new File ( DATA_DIR , " SongPacks/ " ) ;
2015-07-11 17:51:52 +02:00
if ( ! oszDir . isDirectory ( ) & & ! oszDir . mkdir ( ) )
ErrorHandler . error ( String . format ( " Failed to create song packs directory at '%s'. " , oszDir . getAbsolutePath ( ) ) , null , false ) ;
2014-07-06 07:58:44 +02:00
return oszDir ;
}
2015-06-30 02:22:38 +02:00
2015-04-02 04:10:36 +02:00
/ * *
* Returns the replay import directory.
2015-04-05 17:24:05 +02:00
* If invalid , this will create and return a " ReplayImport " directory .
* @return the replay import directory
2015-04-02 04:10:36 +02:00
* /
public static File getReplayImportDir ( ) {
if ( replayImportDir ! = null & & replayImportDir . isDirectory ( ) )
return replayImportDir ;
2014-07-06 07:58:44 +02:00
2015-04-02 04:10:36 +02:00
replayImportDir = new File ( DATA_DIR , " ReplayImport/ " ) ;
2015-07-11 17:51:52 +02:00
if ( ! replayImportDir . isDirectory ( ) & & ! replayImportDir . mkdir ( ) )
ErrorHandler . error ( String . format ( " Failed to create replay import directory at '%s'. " , replayImportDir . getAbsolutePath ( ) ) , null , false ) ;
2015-04-02 04:10:36 +02:00
return replayImportDir ;
}
2015-06-30 02:22:38 +02:00
2014-07-18 06:56:37 +02:00
/ * *
* Returns the screenshot directory .
* If invalid , this will return a " Screenshot " directory .
* @return the screenshot directory
* /
public static File getScreenshotDir ( ) {
if ( screenshotDir ! = null & & screenshotDir . isDirectory ( ) )
return screenshotDir ;
2015-02-13 21:03:17 +01:00
screenshotDir = new File ( DATA_DIR , " Screenshots/ " ) ;
2014-07-18 06:56:37 +02:00
return screenshotDir ;
}
2015-03-12 01:52:51 +01:00
/ * *
* Returns the replay directory .
* If invalid , this will return a " Replay " directory .
* @return the replay directory
* /
public static File getReplayDir ( ) {
if ( replayDir ! = null & & replayDir . isDirectory ( ) )
return replayDir ;
replayDir = new File ( DATA_DIR , " Replays/ " ) ;
return replayDir ;
}
2014-07-02 07:53:42 +02:00
/ * *
* Returns the current skin directory .
* If invalid , this will create a " Skins " folder in the root directory .
* @return the skin directory
* /
2015-05-24 05:48:28 +02:00
public static File getSkinRootDir ( ) {
if ( skinRootDir ! = null & & skinRootDir . isDirectory ( ) )
return skinRootDir ;
2014-07-02 07:53:42 +02:00
2015-11-01 03:20:08 +01:00
// use osu! installation directory, if found
File osuDir = getOsuInstallationDirectory ( ) ;
if ( osuDir ! = null ) {
skinRootDir = new File ( osuDir , SKIN_ROOT_DIR . getName ( ) ) ;
2015-05-24 05:48:28 +02:00
if ( skinRootDir . isDirectory ( ) )
return skinRootDir ;
}
2015-07-11 17:51:52 +02:00
2015-11-01 03:20:08 +01:00
// use default directory
skinRootDir = SKIN_ROOT_DIR ;
if ( ! skinRootDir . isDirectory ( ) & & ! skinRootDir . mkdir ( ) )
2015-07-11 17:51:52 +02:00
ErrorHandler . error ( String . format ( " Failed to create skins directory at '%s'. " , skinRootDir . getAbsolutePath ( ) ) , null , false ) ;
2015-05-24 05:48:28 +02:00
return skinRootDir ;
2014-07-02 07:53:42 +02:00
}
2014-07-02 01:32:03 +02:00
2015-05-24 05:48:28 +02:00
/ * *
* Loads the skin given by the current skin directory .
* If the directory is invalid , the default skin will be loaded .
* /
public static void loadSkin ( ) {
2015-06-03 12:23:23 +02:00
File skinDir = getSkinDir ( ) ;
if ( skinDir = = null ) // invalid skin name
2015-05-24 05:48:28 +02:00
skinName = Skin . DEFAULT_SKIN_NAME ;
// create available skins list
2015-06-03 12:23:23 +02:00
File [ ] dirs = SkinLoader . getSkinDirectories ( getSkinRootDir ( ) ) ;
2015-05-24 05:48:28 +02:00
skinDirs = new String [ dirs . length + 1 ] ;
skinDirs [ 0 ] = Skin . DEFAULT_SKIN_NAME ;
for ( int i = 0 ; i < dirs . length ; i + + )
skinDirs [ i + 1 ] = dirs [ i ] . getName ( ) ;
// set skin and modify resource locations
ResourceLoader . removeAllResourceLocations ( ) ;
if ( skinDir = = null )
skin = new Skin ( null ) ;
else {
// set skin index
for ( int i = 1 ; i < skinDirs . length ; i + + ) {
if ( skinDirs [ i ] . equals ( skinName ) ) {
skinDirIndex = i ;
break ;
}
}
// load the skin
skin = SkinLoader . loadSkin ( skinDir ) ;
ResourceLoader . addResourceLocation ( new FileSystemLocation ( skinDir ) ) ;
}
ResourceLoader . addResourceLocation ( new ClasspathLocation ( ) ) ;
ResourceLoader . addResourceLocation ( new FileSystemLocation ( new File ( " . " ) ) ) ;
ResourceLoader . addResourceLocation ( new FileSystemLocation ( new File ( " ./res/ " ) ) ) ;
}
/ * *
* Returns the current skin .
* @return the skin , or null if no skin is loaded ( see { @link # loadSkin ( ) } )
* /
public static Skin getSkin ( ) { return skin ; }
2015-06-03 12:23:23 +02:00
/ * *
* Returns the current skin directory .
* < p >
* NOTE : This directory will differ from that of the currently loaded skin
* if { @link # loadSkin ( ) } has not been called after a directory change .
* Use { @link Skin # getDirectory ( ) } to get the directory of the currently
* loaded skin .
* @return the skin directory , or null for the default skin
* /
public static File getSkinDir ( ) {
File root = getSkinRootDir ( ) ;
File dir = new File ( root , skinName ) ;
return ( dir . isDirectory ( ) ) ? dir : null ;
}
2014-12-21 03:35:18 +01:00
/ * *
2015-05-17 03:25:19 +02:00
* Returns a dummy Beatmap containing the theme song .
* @return the theme song beatmap
2014-12-21 03:35:18 +01:00
* /
2015-05-17 03:25:19 +02:00
public static Beatmap getThemeBeatmap ( ) {
2014-12-21 03:35:18 +01:00
String [ ] tokens = themeString . split ( " , " ) ;
if ( tokens . length ! = 4 ) {
2015-01-16 03:55:26 +01:00
ErrorHandler . error ( " Theme song string is malformed. " , null , false ) ;
2014-12-21 03:35:18 +01:00
return null ;
}
2015-05-17 03:25:19 +02:00
Beatmap beatmap = new Beatmap ( null ) ;
beatmap . audioFilename = new File ( tokens [ 0 ] ) ;
beatmap . title = tokens [ 1 ] ;
beatmap . artist = tokens [ 2 ] ;
2014-12-21 03:35:18 +01:00
try {
2015-05-17 03:25:19 +02:00
beatmap . endTime = Integer . parseInt ( tokens [ 3 ] ) ;
2014-12-21 03:35:18 +01:00
} catch ( NumberFormatException e ) {
2015-01-16 03:55:26 +01:00
ErrorHandler . error ( " Theme song length is not a valid integer " , e , false ) ;
2014-12-21 03:35:18 +01:00
return null ;
}
2015-05-17 03:25:19 +02:00
return beatmap ;
2014-12-21 03:35:18 +01:00
}
2014-06-30 04:17:04 +02:00
/ * *
* Reads user options from the options file , if it exists .
* /
public static void parseOptions ( ) {
// if no config file, use default settings
2014-07-02 07:53:42 +02:00
if ( ! OPTIONS_FILE . isFile ( ) ) {
2014-06-30 04:17:04 +02:00
saveOptions ( ) ;
return ;
}
2015-06-12 22:04:20 +02:00
// create option map
if ( optionMap = = null ) {
optionMap = new HashMap < String , GameOption > ( ) ;
for ( GameOption option : GameOption . values ( ) )
optionMap . put ( option . getDisplayName ( ) , option ) ;
}
// read file
2014-07-02 07:53:42 +02:00
try ( BufferedReader in = new BufferedReader ( new FileReader ( OPTIONS_FILE ) ) ) {
2014-06-30 04:17:04 +02:00
String line ;
while ( ( line = in . readLine ( ) ) ! = null ) {
line = line . trim ( ) ;
if ( line . length ( ) < 2 | | line . charAt ( 0 ) = = '#' )
continue ;
int index = line . indexOf ( '=' ) ;
if ( index = = - 1 )
continue ;
2015-06-12 22:04:20 +02:00
// read option
String name = line . substring ( 0 , index ) . trim ( ) ;
GameOption option = optionMap . get ( name ) ;
if ( option ! = null ) {
try {
String value = line . substring ( index + 1 ) . trim ( ) ;
option . read ( value ) ;
} catch ( NumberFormatException e ) {
Log . warn ( String . format ( " Format error in options file for line: '%s'. " , line ) , e ) ;
2014-07-18 05:58:37 +02:00
}
2014-06-30 04:17:04 +02:00
}
}
} catch ( IOException e ) {
2015-01-16 03:55:26 +01:00
ErrorHandler . error ( String . format ( " Failed to read file '%s'. " , OPTIONS_FILE . getAbsolutePath ( ) ) , e , false ) ;
2014-06-30 04:17:04 +02:00
}
}
/ * *
* ( Over ) writes user options to a file .
* /
public static void saveOptions ( ) {
try ( BufferedWriter writer = new BufferedWriter ( new OutputStreamWriter (
new FileOutputStream ( OPTIONS_FILE ) , " utf-8 " ) ) ) {
// header
SimpleDateFormat dateFormat = new SimpleDateFormat ( " EEEE, MMMM dd, yyyy " ) ;
String date = dateFormat . format ( new Date ( ) ) ;
writer . write ( " # opsu! configuration " ) ;
writer . newLine ( ) ;
writer . write ( " # last updated on " ) ;
writer . write ( date ) ;
writer . newLine ( ) ;
writer . newLine ( ) ;
// options
2015-06-12 22:04:20 +02:00
for ( GameOption option : GameOption . values ( ) ) {
writer . write ( option . getDisplayName ( ) ) ;
writer . write ( " = " ) ;
writer . write ( option . write ( ) ) ;
writer . newLine ( ) ;
}
2014-06-30 04:17:04 +02:00
writer . close ( ) ;
} catch ( IOException e ) {
2015-01-16 03:55:26 +01:00
ErrorHandler . error ( String . format ( " Failed to write to file '%s'. " , OPTIONS_FILE . getAbsolutePath ( ) ) , e , false ) ;
2014-06-30 04:17:04 +02:00
}
}
}