diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 00f7c640..f60a4050 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -4,3 +4,11 @@ - APK: https://github.com/fluddokt/opsu/issues - Play Store: https://github.com/AnirudhRahul/opsu-Android/issues --> + +**Operations:** What did you do + +**Expected behavior:** What do you expect the program to do + +**Actual behaviour:** What did the program actually do + +**Extra information:** Platform, version, screenshots, logs, etc. diff --git a/.gitignore b/.gitignore index 77a7f941..148c5b18 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /SongPacks/ /Songs/ /Temp/ +*.log /.opsu.log /.opsu.cfg /.opsu.db* diff --git a/README.md b/README.md index cd70e83b..6aa806d8 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,7 @@ The following files and folders will be created by opsu! as needed: ## Building -opsu! is distributed as both a [Maven](https://maven.apache.org/) and -[Gradle](https://gradle.org/) project. +opsu! is distributed as both a [Maven](https://maven.apache.org/) and [Gradle](https://gradle.org/) project. ### Maven diff --git a/build.gradle b/build.gradle index 5f8756ff..7ca0c974 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ apply plugin: 'application' import org.apache.tools.ant.filters.* group = 'itdelatrisu' -version = '0.16.1' +version = '0.16.2' mainClassName = 'itdelatrisu.opsu.Opsu' buildDir = new File(rootProject.projectDir, "build/") diff --git a/pom.xml b/pom.xml index f73e8eb9..882a9d9a 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 itdelatrisu opsu - 0.16.1 + 0.16.2 ${project.version} ${maven.build.timestamp} diff --git a/res/search-background.jpg b/res/search-background.jpg deleted file mode 100644 index aee382af..00000000 Binary files a/res/search-background.jpg and /dev/null differ diff --git a/res/selection-mod-cinema.png b/res/selection-mod-cinema.png new file mode 100644 index 00000000..035d4051 Binary files /dev/null and b/res/selection-mod-cinema.png differ diff --git a/res/selection-mod-nightcore.png b/res/selection-mod-nightcore.png new file mode 100644 index 00000000..8df73d48 Binary files /dev/null and b/res/selection-mod-nightcore.png differ diff --git a/res/selection-mod-perfect.png b/res/selection-mod-perfect.png new file mode 100644 index 00000000..36e1965a Binary files /dev/null and b/res/selection-mod-perfect.png differ diff --git a/res/selection-mod-target.png b/res/selection-mod-target.png new file mode 100644 index 00000000..7a2b3c1c Binary files /dev/null and b/res/selection-mod-target.png differ diff --git a/res/version b/res/version index 399a7f01..48ed926d 100644 --- a/res/version +++ b/res/version @@ -1,3 +1,3 @@ version=${version} -file=https://github.com/itdelatrisu/opsu/releases/download/${version}/opsu-${version}.jar +file=https://github.com/clonewith/opsu/releases/download/${version}/opsu-${version}.jar build.date=${timestamp} \ No newline at end of file diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index 1e794f59..b8e3d3e8 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -1402,7 +1402,12 @@ private void resetComboStreak() { SoundController.playSound(SoundEffect.COMBOBREAK); } combo = 0; - if (GameMod.SUDDEN_DEATH.isActive()) + if (GameMod.SUDDEN_DEATH.isActive() || GameMod.PERFECT.isActive()) + health.setHealth(0f); + } + + private void detectPerfectStreak() { + if (GameMod.PERFECT.isActive()) health.setHealth(0f); } @@ -1577,10 +1582,12 @@ private int handleHitResult(int time, int result, float x, float y, Color color, case HIT_100: hitValue = 100; comboEnd |= 1; + detectPerfectStreak(); break; case HIT_50: hitValue = 50; comboEnd |= 2; + detectPerfectStreak(); break; case HIT_MISS: hitValue = 0; diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index 23a50906..80b5cf14 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -34,6 +34,9 @@ * Game images. */ public enum GameImage { + // Launch + // WELCOME_TEXT ("welcome-text", "png"), + // Cursor CURSOR ("cursor", "png"), CURSOR_MIDDLE ("cursormiddle", "png"), @@ -81,6 +84,7 @@ protected Image process_sub(Image img, int w, int h) { FOLLOWPOINT ("followpoint", "png"), // Game Pause/Fail + // PAUSE_ARROW ("arrow-pause", "png"), PAUSE_CONTINUE ("pause-continue", "png"), PAUSE_RETRY ("pause-retry", "png"), PAUSE_BACK ("pause-back", "png"), @@ -113,7 +117,7 @@ protected Image process_sub(Image img, int w, int h) { REVERSEARROW ("reversearrow", "png"), SLIDER_TICK ("sliderscorepoint", "png"), - // Spinner + // Old Spinner SPINNER_CIRCLE ("spinner-circle", "png"), SPINNER_APPROACHCIRCLE ("spinner-approachcircle", "png") { @Override @@ -137,6 +141,12 @@ protected Image process_sub(Image img, int w, int h) { } }, + // New Spinner + // SPINNER_TOP ("spinner-top", "png"), + // SPINNER_MIDDLE ("spinner-middle", "png"), + // SPINNER_BOTTOM ("spinner-bottom", "png"), + // SPINNER_GROW ("spinner-grow", "png"), + // Game Data COMBO_BURST ("comboburst", "comboburst-%d", "png"), SCOREBAR_BG ("scorebar-bg", "png"), @@ -207,14 +217,18 @@ protected Image process_sub(Image img, int w, int h) { MOD_NO_FAIL ("selection-mod-nofail", "png", false, false), MOD_HARD_ROCK ("selection-mod-hardrock", "png", false, false), MOD_SUDDEN_DEATH ("selection-mod-suddendeath", "png", false, false), + MOD_PERFECT ("selection-mod-perfect", "png", false, false), MOD_SPUN_OUT ("selection-mod-spunout", "png", false, false), MOD_AUTO ("selection-mod-autoplay", "png", false, false), MOD_HALF_TIME ("selection-mod-halftime", "png", false, false), MOD_DOUBLE_TIME ("selection-mod-doubletime", "png", false, false), + MOD_NIGHTCORE ("selection-mod-nightcore", "png", false, false), MOD_HIDDEN ("selection-mod-hidden", "png", false, false), MOD_FLASHLIGHT ("selection-mod-flashlight", "png", false, false), MOD_RELAX ("selection-mod-relax", "png", false, false), MOD_AUTOPILOT ("selection-mod-relax2", "png", false, false), + MOD_CINEMA ("selection-mod-cinema", "png", false, false), + MOD_TARGET ("selection-mod-target", "png", false, false), // Selection Buttons SELECTION_MODS ("selection-mods", "png", false, false), @@ -316,13 +330,6 @@ protected Image process_sub(Image img, int w, int h) { return img.getScaledCopy((h * 0.45f) / img.getHeight()); } }, - SEARCH_BG ("search-background", "png|jpg", false, true) { - @Override - protected Image process_sub(Image img, int w, int h) { - img.setAlpha(0.8f); - return img.getScaledCopy(w, h); - } - }, DELETE ("delete", "png", false, false) { @Override protected Image process_sub(Image img, int w, int h) { @@ -864,4 +871,4 @@ private void process() { } else setImage(process_sub(getImage(), unscaledWidth, UNSCALED_HEIGHT).getScaledCopy(getUIscale())); } -} \ No newline at end of file +} diff --git a/src/itdelatrisu/opsu/GameMod.java b/src/itdelatrisu/opsu/GameMod.java index 5e390a56..1fd3e43c 100644 --- a/src/itdelatrisu/opsu/GameMod.java +++ b/src/itdelatrisu/opsu/GameMod.java @@ -33,6 +33,7 @@ * Game mods. */ public enum GameMod { + // TODO: Process PF, NC and TP approxmiately EASY (Category.EASY, 0, GameImage.MOD_EASY, "EZ", 2, Input.KEY_Q, 0.5f, "Easy", "Reduces overall difficulty - larger circles, more forgiving HP drain, less accuracy required."), NO_FAIL (Category.EASY, 1, GameImage.MOD_NO_FAIL, "NF", 1, Input.KEY_W, 0.5f, @@ -43,24 +44,26 @@ public enum GameMod { "HardRock", "Everything just got a bit harder..."), SUDDEN_DEATH (Category.HARD, 1, GameImage.MOD_SUDDEN_DEATH, "SD", 32, Input.KEY_S, 1f, "SuddenDeath", "Miss a note and fail."), -// PERFECT (Category.HARD, 1, GameImage.MOD_PERFECT, "PF", 64, Input.KEY_S, 1f, -// "Perfect", "SS or quit."), - DOUBLE_TIME (Category.HARD, 2, GameImage.MOD_DOUBLE_TIME, "DT", 64, Input.KEY_D, 1.12f, + PERFECT (Category.HARD, 2, GameImage.MOD_PERFECT, "PF", 64, Input.KEY_P, 1f, "Perfect", "SS or quit."), + DOUBLE_TIME (Category.HARD, 3, GameImage.MOD_DOUBLE_TIME, "DT", 64, Input.KEY_D, 1.12f, "DoubleTime", "Zoooooooooom."), -// NIGHTCORE (Category.HARD, 2, GameImage.MOD_NIGHTCORE, "NT", 64, Input.KEY_D, 1.12f, -// "Nightcore", "uguuuuuuuu"), - HIDDEN (Category.HARD, 3, GameImage.MOD_HIDDEN, "HD", 8, Input.KEY_F, 1.06f, + NIGHTCORE (Category.HARD, 4, GameImage.MOD_NIGHTCORE, "NT", 64, Input.KEY_N, 1.12f, "Nightcore", "uguuuuuuuu"), + HIDDEN (Category.HARD, 5, GameImage.MOD_HIDDEN, "HD", 8, Input.KEY_F, 1.06f, "Hidden", "Play with no approach circles and fading notes for a slight score advantage."), - FLASHLIGHT (Category.HARD, 4, GameImage.MOD_FLASHLIGHT, "FL", 1024, Input.KEY_G, 1.12f, + FLASHLIGHT (Category.HARD, 6, GameImage.MOD_FLASHLIGHT, "FL", 1024, Input.KEY_G, 1.12f, "Flashlight", "Restricted view area."), RELAX (Category.SPECIAL, 0, GameImage.MOD_RELAX, "RL", 128, Input.KEY_Z, 0f, "Relax", "You don't need to click.\nGive your clicking/tapping finger a break from the heat of things.\n**UNRANKED**"), AUTOPILOT (Category.SPECIAL, 1, GameImage.MOD_AUTOPILOT, "AP", 8192, Input.KEY_X, 0f, "Relax2", "Automatic cursor movement - just follow the rhythm.\n**UNRANKED**"), - SPUN_OUT (Category.SPECIAL, 2, GameImage.MOD_SPUN_OUT, "SO", 4096, Input.KEY_C, 0.9f, + Target (Category.SPECIAL, 2, GameImage.MOD_TARGET, "AP", 8388608, Input.KEY_T, 1f, + "Target", "Timing practice!"), + SPUN_OUT (Category.SPECIAL, 3, GameImage.MOD_SPUN_OUT, "SO", 4096, Input.KEY_C, 0.9f, "SpunOut", "Spinners will be automatically completed."), - AUTO (Category.SPECIAL, 3, GameImage.MOD_AUTO, "", 2048, Input.KEY_V, 1f, - "Autoplay", "Watch a perfect automated play through the song."); + AUTO (Category.SPECIAL, 4, GameImage.MOD_AUTO, "", 2048, Input.KEY_V, 1f, + "Autoplay", "Watch a perfect automated play through the song."), + CINEMA (Category.SPECIAL, 5, GameImage.MOD_CINEMA, "", 4194304, Input.KEY_M, 1f, "Autoplay", "Watch the video without being distubed by objects."); + /** Mod categories. */ public enum Category { @@ -192,7 +195,7 @@ public static void init(int width, int height) { // create buttons float baseX = Category.EASY.getX() + Fonts.LARGE.getWidth(Category.EASY.getName()) * 1.25f; - float offsetX = GameImage.MOD_EASY.getImage().getWidth() * 2.1f; + float offsetX = GameImage.MOD_EASY.getImage().getWidth() * 1.1f; for (GameMod mod : GameMod.values()) { Image img = mod.image.getImage(); mod.button = new MenuButton(img, @@ -371,16 +374,20 @@ public void toggle(boolean checkInverse) { if (this == AUTO) { SPUN_OUT.active = false; SUDDEN_DEATH.active = false; + PERFECT.active = false; RELAX.active = false; AUTOPILOT.active = false; - } else if (this == SPUN_OUT || this == SUDDEN_DEATH || this == RELAX || this == AUTOPILOT) + CINEMA.active = false; + } else if (this == SPUN_OUT || this == SUDDEN_DEATH || this == PERFECT || this == RELAX || this == AUTOPILOT || this == CINEMA) this.active = false; } - if (active && (this == SUDDEN_DEATH || this == NO_FAIL || this == RELAX || this == AUTOPILOT)) { + if (active && (this == SUDDEN_DEATH || this == PERFECT || this == NO_FAIL || this == RELAX || this == AUTOPILOT)) { SUDDEN_DEATH.active = false; + PERFECT.active = false; NO_FAIL.active = false; RELAX.active = false; AUTOPILOT.active = false; + CINEMA.active = false; active = true; } if (AUTOPILOT.isActive() && SPUN_OUT.isActive()) { @@ -395,12 +402,20 @@ public void toggle(boolean checkInverse) { else EASY.active = false; } - if (HALF_TIME.isActive() && DOUBLE_TIME.isActive()) { - if (this == HALF_TIME) + if (HALF_TIME.isActive() && (DOUBLE_TIME.isActive() || NIGHTCORE.isActive())) { + if (this == HALF_TIME){ DOUBLE_TIME.active = false; + NIGHTCORE.active = false; + } else HALF_TIME.active = false; } + if (DOUBLE_TIME.isActive() && NIGHTCORE.isActive()){ + if (this == DOUBLE_TIME) + NIGHTCORE.active = false; + else + DOUBLE_TIME.active = false; + } } } diff --git a/src/itdelatrisu/opsu/OpsuConstants.java b/src/itdelatrisu/opsu/OpsuConstants.java index 44090ccb..610e0f1a 100644 --- a/src/itdelatrisu/opsu/OpsuConstants.java +++ b/src/itdelatrisu/opsu/OpsuConstants.java @@ -30,25 +30,25 @@ public class OpsuConstants { public static final String PROJECT_NAME = "opsu!"; /** Project author. */ - public static final String PROJECT_AUTHOR = "@itdelatrisu"; + public static final String PROJECT_AUTHOR = "@itdelatrisu + @CloneWith"; /** Website address. */ - public static final URI WEBSITE_URI = URI.create("https://itdelatrisu.github.io/opsu/"); + public static final URI WEBSITE_URI = URI.create("https://clonewith.github.io/opsu/"); /** Repository address. */ - public static final URI REPOSITORY_URI = URI.create("https://github.com/itdelatrisu/opsu"); + public static final URI REPOSITORY_URI = URI.create("https://github.com/clonewith/opsu"); /** Credits address. */ - public static final URI CREDITS_URI = URI.create("https://github.com/itdelatrisu/opsu/blob/master/CREDITS.md"); + public static final URI CREDITS_URI = URI.create("https://github.com/clonewith/opsu/blob/master/CREDITS.md"); /** Issue reporting address. */ - public static final String ISSUES_URL = "https://github.com/itdelatrisu/opsu/issues/new?title=%s&body=%s"; + public static final String ISSUES_URL = "https://github.com/clonewith/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"; /** Changelog address. */ - private static final String CHANGELOG_URL = "https://github.com/itdelatrisu/opsu/releases/tag/%s"; + private static final String CHANGELOG_URL = "https://github.com/clonewith/opsu/releases/tag/%s"; /** Returns the changelog URI for the given version. */ public static URI getChangelogURI(String version) { diff --git a/src/itdelatrisu/opsu/audio/MusicController.java b/src/itdelatrisu/opsu/audio/MusicController.java index e006172f..db78f338 100644 --- a/src/itdelatrisu/opsu/audio/MusicController.java +++ b/src/itdelatrisu/opsu/audio/MusicController.java @@ -321,6 +321,17 @@ public static void resume() { } } + /** + * Always play the current track from the start. + */ + public static void replay() { + if (trackExists()) { + pauseTime = 0f; + player.resume(); + player.setVolume(1.0f); + } + } + /** * Stops the current track. */ diff --git a/src/itdelatrisu/opsu/downloads/servers/DownloadServer.java b/src/itdelatrisu/opsu/downloads/servers/DownloadServer.java index e01f1df2..1103c7a8 100644 --- a/src/itdelatrisu/opsu/downloads/servers/DownloadServer.java +++ b/src/itdelatrisu/opsu/downloads/servers/DownloadServer.java @@ -27,6 +27,10 @@ * Abstract class for beatmap download servers. */ public abstract class DownloadServer { + /** + * For mirror download servers, there may be a better solution. + * For example, use visible .conf (maybe) files instead coding servers insider, just for convenience and need for maintenance. + */ /** Track preview URL. */ private static final String PREVIEW_URL = "http://b.ppy.sh/preview/%d.mp3"; diff --git a/src/itdelatrisu/opsu/options/Options.java b/src/itdelatrisu/opsu/options/Options.java index 3c52eeee..39fc7b7c 100644 --- a/src/itdelatrisu/opsu/options/Options.java +++ b/src/itdelatrisu/opsu/options/Options.java @@ -365,17 +365,26 @@ public void toggle(GameContainer container) { }, SKIN ("Skin", "Skin", "") { private String[] itemList = null; + private String[] DirList = null; @Override public boolean isRestartRequired() { return true; } + // Can we directly load skins without restart here? /** Creates the list of available skins. */ private void createSkinList() { File[] dirs = SkinLoader.getSkinDirectories(getSkinRootDir()); + // Here we use skin directory names as display names, need to change it. itemList = new String[dirs.length + 1]; + DirList = new String[dirs.length + 1]; itemList[0] = Skin.DEFAULT_SKIN_NAME; - for (int i = 0; i < dirs.length; i++) - itemList[i + 1] = dirs[i].getName(); + DirList[0] = null; + for (int i = 0; i < dirs.length; i++){ + Skin r = SkinLoader.loadSkin(dirs[i]); + // itemList[i + 1] = r.getName() + "(" + r.getAuthor() + ")"; + itemList[i + 1] = r.getName(); + DirList[i + 1] = dirs[i].getName(); + } } @Override @@ -968,6 +977,9 @@ public boolean hasFullscreenDisplayMode() { /** The current skin. */ private static Skin skin; + /** The directory of the skin. */ + // private static File skinDir = skin.getDirectory(); + /** Frame limiters. */ private static final int[] targetFPS = { 60, 120, 240, -1 /* Unlimited */ }; @@ -1519,6 +1531,9 @@ public static void loadSkin() { else { // load the skin skin = SkinLoader.loadSkin(skinDir); + // String info = skin.getName() + " (" + skin.getAuthor() + ")"; + // ErrorHandler.error(String.format("You are using the skin '%s'.", info), null, false); + // Show the real name and author in skin.ini ResourceLoader.addResourceLocation(new FileSystemLocation(skinDir)); } ResourceLoader.addResourceLocation(new ClasspathLocation()); @@ -1544,6 +1559,9 @@ public static void loadSkin() { public static File getSkinDir() { File root = getSkinRootDir(); File dir = new File(root, skinName); + // File dir = skinDir; + // File dir = new File(root, skinDir); + // TODO: Need to adjust return (dir.isDirectory()) ? dir : null; } diff --git a/src/itdelatrisu/opsu/skins/Skin.java b/src/itdelatrisu/opsu/skins/Skin.java index f2655628..0468cb47 100644 --- a/src/itdelatrisu/opsu/skins/Skin.java +++ b/src/itdelatrisu/opsu/skins/Skin.java @@ -28,9 +28,20 @@ * Skin configuration (skin.ini). */ public class Skin { + // TODO: Add playwarning arrow tinting support (on/off) + // TODO: Pulsing effect for arrows and pass/fail indicator + // TODO: Animation improvement for combo bursts + // TODO: Clarify usage of star2 (partof / zoom) + // TODO: Difference between hit results and results + // TODO: Spinner versions + // TODO: Adjust locations and anchors of elements + // TODO: Improve support for transparent images /** The default skin name. */ public static final String DEFAULT_SKIN_NAME = "Default"; + /** The status of skin.ini. If it exists it is true. */ + public Boolean INI_STATUS = false; + /** Slider styles. */ public static final byte STYLE_PEPPYSLIDER = 1, // fallback @@ -87,7 +98,7 @@ public class Skin { protected String name = OpsuConstants.PROJECT_NAME + " Default Skin"; /** The skin author. */ - protected String author = "[various authors]"; + protected String author = "various authors"; /** The skin version. */ protected float version = LATEST_VERSION; @@ -206,6 +217,11 @@ public Skin(File dir) { */ public File getDirectory() { return dir; } + /** + * Returns the skin directory. + */ + public Boolean getINIStatus() { return INI_STATUS; } + /** * Returns the name of the skin. */ diff --git a/src/itdelatrisu/opsu/skins/SkinLoader.java b/src/itdelatrisu/opsu/skins/SkinLoader.java index ddc4c49f..22868124 100644 --- a/src/itdelatrisu/opsu/skins/SkinLoader.java +++ b/src/itdelatrisu/opsu/skins/SkinLoader.java @@ -20,6 +20,9 @@ import itdelatrisu.opsu.ErrorHandler; import itdelatrisu.opsu.Utils; +import itdelatrisu.opsu.ui.Colors; +import itdelatrisu.opsu.ui.NotificationManager; +import itdelatrisu.opsu.ui.UI; import java.io.BufferedReader; import java.io.File; @@ -66,18 +69,22 @@ public static File[] getSkinDirectories(File root) { public static Skin loadSkin(File dir) { File skinFile = new File(dir, CONFIG_FILENAME); Skin skin = new Skin(dir); - if (!skinFile.isFile()) // missing skin.ini + if (!skinFile.isFile()){ // missing skin.ini return skin; + } try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(skinFile), "UTF-8"))) { String line = in.readLine(); String tokens[] = null; + skin.INI_STATUS = true; while (line != null) { line = line.trim(); if (!isValidLine(line)) { line = in.readLine(); continue; } + // TODO: This implied minimal skin settings. Read osu!wiki for details and additions. + // TODO: can add the use of commands "Author" and "Name" to show accurate skin name and author instead of folder name switch (line) { case "[General]": while ((line = in.readLine()) != null) { @@ -173,6 +180,7 @@ public static Skin loadSkin(File dir) { break; if ((tokens = tokenize(line)) == null) continue; + // TODO: Is there valid support for RGBA format? don't know try { String[] rgb = tokens[1].split(","); Color color = new Color( diff --git a/src/itdelatrisu/opsu/states/DownloadsMenu.java b/src/itdelatrisu/opsu/states/DownloadsMenu.java index 6579fd19..df841ec3 100644 --- a/src/itdelatrisu/opsu/states/DownloadsMenu.java +++ b/src/itdelatrisu/opsu/states/DownloadsMenu.java @@ -25,6 +25,7 @@ import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundEffect; +import itdelatrisu.opsu.beatmap.Beatmap; import itdelatrisu.opsu.beatmap.BeatmapParser; import itdelatrisu.opsu.beatmap.BeatmapSetList; import itdelatrisu.opsu.beatmap.BeatmapSetNode; @@ -45,6 +46,8 @@ import itdelatrisu.opsu.ui.MenuButton; import itdelatrisu.opsu.ui.NotificationManager.NotificationListener; import itdelatrisu.opsu.ui.UI; +import itdelatrisu.opsu.ui.animations.AnimatedValue; +import itdelatrisu.opsu.ui.animations.AnimationEquation; import java.awt.Desktop; import java.io.File; @@ -76,6 +79,13 @@ * from this state. */ public class DownloadsMenu extends BasicGameState { + + /** Max alpha level of the menu background. */ + private static final float BG_MAX_ALPHA = 0.5f; + + /** Background alpha level (for fade-in effect). */ + private AnimatedValue bgAlpha = new AnimatedValue(1100, 0f, BG_MAX_ALPHA, AnimationEquation.LINEAR); + /** Delay time, in milliseconds, between each search. */ private static final int SEARCH_DELAY = 700; @@ -422,15 +432,28 @@ public void render(GameContainer container, StateBasedGame game, Graphics g) boolean inDropdownMenu = serverMenu.contains(mouseX, mouseY); // background - Image bg = GameImage.SEARCH_BG.getImage(); + Beatmap beatmap = MusicController.getBeatmap(); + + float parallaxX = 0, parallaxY = 0; if (Options.isParallaxEnabled()) { int offset = (int) (height * (GameImage.PARALLAX_SCALE - 1f)); - float parallaxX = -offset / 2f * (mouseX - width / 2) / (width / 2); - float parallaxY = -offset / 2f * (mouseY - height / 2) / (height / 2); - bg = bg.getScaledCopy(GameImage.PARALLAX_SCALE); - bg.drawCentered(width / 2 + parallaxX, height / 2 + parallaxY); - } else - bg.drawCentered(width / 2, height / 2); + parallaxX = -offset / 2f * (mouseX - width / 2) / (width / 2); + parallaxY = -offset / 2f * (mouseY - height / 2) / (height / 2); + } + if (Options.isDynamicBackgroundEnabled() && beatmap != null && + beatmap.drawBackground(width, height, parallaxX, parallaxY, bgAlpha.getValue(), true)) + ; + else { + Image bg = GameImage.MENU_BG.getImage(); + if (Options.isParallaxEnabled()) { + bg = bg.getScaledCopy(GameImage.PARALLAX_SCALE); + bg.setAlpha(bgAlpha.getValue()); + bg.drawCentered(width / 2 + parallaxX, height / 2 + parallaxY); + } else { + bg.setAlpha(bgAlpha.getValue()); + bg.drawCentered(width / 2, height / 2); + } + } // title Fonts.LARGE.drawString(width * 0.024f, height * 0.03f, "Download Beatmaps!", Color.white); @@ -618,6 +641,11 @@ else if (rankedButton.contains(mouseX, mouseY)) UI.updateTooltip(delta, "Toggle the display of unranked maps.\nSome download servers may not support this option.", true); else if (serverMenu.baseContains(mouseX, mouseY)) UI.updateTooltip(delta, "Select a download server.", false); + + // fade in background + Beatmap beatmap = MusicController.getBeatmap(); + if (!(Options.isDynamicBackgroundEnabled() && beatmap != null && beatmap.isBackgroundLoading())) + bgAlpha.update(delta); } @Override diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index e97c18d5..44765e6f 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -976,6 +976,25 @@ else if (!gameFinished) { } } + private void retry() { + // TODO: need another step of process + int trackPosition = MusicController.getPosition(true); + if (gameFinished) + return; + try { + if (trackPosition < beatmap.objects[0].getTime()) + retries--; // don't count this retry (cancel out later increment) + playState = PlayState.RETRY; + game.enterState(Opsu.STATE_GAME, + new DelayedFadeOutTransition(Color.black, LOSE_FADEOUT_TIME, 0), + new FadeInTransition()); + enter(container, game); + if(!GameMod.PERFECT.isActive()) skipIntro(); + } catch (SlickException e) { + ErrorHandler.error("Failed to restart game.", e, false); + } + } + /** * Updates the game. * @param mouseX the mouse x coordinate @@ -1102,8 +1121,22 @@ else if (replayFrames != null) { } // game over, force a restart + // TODO: If using PF mod, needs a quick restart if (!isReplay) { + // record to stats + User user = UserList.get().getCurrentUser(); + user.add(data.getScore()); + ScoreDB.updateUser(user); + if (playState != PlayState.LOSE) { + if (GameMod.PERFECT.isActive()) { + // game.enterState(Opsu.STATE_GAME, + // new DelayedFadeOutTransition(Color.black, MUSIC_FADEOUT_TIME, MUSIC_FADEOUT_TIME - LOSE_FADEOUT_TIME), + // new FadeInTransition()); + // new DelayedFadeOutTransition(Color.black, MUSIC_FADEOUT_TIME, MUSIC_FADEOUT_TIME - LOSE_FADEOUT_TIME); + retry(); + return; + } playState = PlayState.LOSE; failTime = System.currentTimeMillis(); failTrackTime = MusicController.getPosition(true); @@ -1112,11 +1145,6 @@ else if (replayFrames != null) { rotations = new IdentityHashMap(); SoundController.playSound(SoundEffect.FAIL); - // record to stats - User user = UserList.get().getCurrentUser(); - user.add(data.getScore()); - ScoreDB.updateUser(user); - // fade to pause menu game.enterState(Opsu.STATE_GAMEPAUSEMENU, new DelayedFadeOutTransition(Color.black, MUSIC_FADEOUT_TIME, MUSIC_FADEOUT_TIME - LOSE_FADEOUT_TIME), @@ -1219,17 +1247,7 @@ else if (key == Options.getGameKeyRight()) // fall through case Input.KEY_GRAVE: // restart - if (gameFinished) - break; - try { - if (trackPosition < beatmap.objects[0].getTime()) - retries--; // don't count this retry (cancel out later increment) - playState = PlayState.RETRY; - enter(container, game); - skipIntro(); - } catch (SlickException e) { - ErrorHandler.error("Failed to restart game.", e, false); - } + retry(); break; case Input.KEY_S: // save checkpoint diff --git a/src/itdelatrisu/opsu/states/MainMenu.java b/src/itdelatrisu/opsu/states/MainMenu.java index d98aa781..8542c213 100644 --- a/src/itdelatrisu/opsu/states/MainMenu.java +++ b/src/itdelatrisu/opsu/states/MainMenu.java @@ -99,7 +99,7 @@ private enum LogoState { DEFAULT, OPENING, OPEN, CLOSING } private MenuButton playButton, exitButton; /** Music control buttons. */ - private MenuButton musicPlay, musicPause, musicNext, musicPrevious; + private MenuButton musicPlay, musicPause, musicNext, musicPrevious, musicReplay; /** Button linking to Downloads menu. */ private MenuButton downloadsButton; @@ -230,10 +230,12 @@ public void init(GameContainer container, StateBasedGame game) int musicInfoOffset = (int) (musicInfoHeight * 0.6f); int musicWidth = GameImage.MUSIC_PLAY.getImage().getWidth(); int musicHeight = GameImage.MUSIC_PLAY.getImage().getHeight(); + musicReplay = new MenuButton(GameImage.MUSIC_PLAY.getImage(), width - (3 * musicWidth), musicInfoOffset + musicHeight / 1.5f); musicPlay = new MenuButton(GameImage.MUSIC_PLAY.getImage(), width - (2 * musicWidth), musicInfoOffset + musicHeight / 1.5f); musicPause = new MenuButton(GameImage.MUSIC_PAUSE.getImage(), width - (2 * musicWidth), musicInfoOffset + musicHeight / 1.5f); musicNext = new MenuButton(GameImage.MUSIC_NEXT.getImage(), width - musicWidth, musicInfoOffset + musicHeight / 1.5f); - musicPrevious = new MenuButton(GameImage.MUSIC_PREVIOUS.getImage(), width - (3 * musicWidth), musicInfoOffset + musicHeight / 1.5f); + musicPrevious = new MenuButton(GameImage.MUSIC_PREVIOUS.getImage(), width - (4 * musicWidth), musicInfoOffset + musicHeight / 1.5f); + musicReplay.setHoverExpand(1.5f); musicPlay.setHoverExpand(1.5f); musicPause.setHoverExpand(1.5f); musicNext.setHoverExpand(1.5f); @@ -417,6 +419,7 @@ public void render(GameContainer container, StateBasedGame game, Graphics g) musicPause.draw(); else musicPlay.draw(); + musicReplay.draw(); musicNext.draw(); musicPrevious.draw(); @@ -520,6 +523,7 @@ public void update(GameContainer container, StateBasedGame game, int delta) // ensure only one button is in hover state at once boolean noHoverUpdate = musicPositionBarContains(mouseX, mouseY); boolean contains = musicPlay.contains(mouseX, mouseY); + musicReplay.hoverUpdate(delta, !noHoverUpdate && musicReplay.contains(mouseX, mouseY)); musicPlay.hoverUpdate(delta, !noHoverUpdate && contains); musicPause.hoverUpdate(delta, !noHoverUpdate && contains); noHoverUpdate |= contains; @@ -619,8 +623,10 @@ public void update(GameContainer container, StateBasedGame game, int delta) // tooltips if (musicPositionBarContains(mouseX, mouseY)) UI.updateTooltip(delta, "Click to seek to a specific point in the song.", false); + else if (musicReplay.contains(mouseX, mouseY)) + UI.updateTooltip(delta, "Play", false); else if (musicPlay.contains(mouseX, mouseY)) - UI.updateTooltip(delta, (MusicController.isPlaying()) ? "Pause" : "Play", false); + UI.updateTooltip(delta, (MusicController.isPlaying()) ? "Pause" : "Resume", false); else if (musicNext.contains(mouseX, mouseY)) UI.updateTooltip(delta, "Next track", false); else if (musicPrevious.contains(mouseX, mouseY)) @@ -686,6 +692,8 @@ public void click() { playButton.resetHover(); if (!exitButton.contains(mouseX, mouseY, 0.25f)) exitButton.resetHover(); + if (!musicReplay.contains(mouseX, mouseY)) + musicReplay.resetHover(); if (!musicPlay.contains(mouseX, mouseY)) musicPlay.resetHover(); if (!musicPause.contains(mouseX, mouseY)) @@ -754,7 +762,7 @@ public void mousePressed(int button, int x, int y) { UI.getNotificationManager().sendBarNotification("Pause"); } else if (!MusicController.isTrackLoading()) { MusicController.resume(); - UI.getNotificationManager().sendBarNotification("Play"); + UI.getNotificationManager().sendBarNotification("Resume"); } return; } else if (musicNext.contains(x, y)) { @@ -765,6 +773,10 @@ public void mousePressed(int button, int x, int y) { previousTrack(); UI.getNotificationManager().sendBarNotification("<< Prev"); return; + } else if (musicReplay.contains(x, y)) { + MusicController.playAt(0, false); + musicInfoProgress.setTime(0); + UI.getNotificationManager().sendBarNotification("Play"); } // downloads button actions @@ -946,6 +958,7 @@ public void reset() { logo.resetHover(); playButton.resetHover(); exitButton.resetHover(); + musicReplay.resetHover(); musicPlay.resetHover(); musicPause.resetHover(); musicNext.resetHover(); diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index 75ca7130..cfe61e3f 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -460,11 +460,13 @@ public boolean menuClicked(int index) { search.setMaxLength(60); // selection buttons - Image selectionMods = GameImage.SELECTION_MODS.getImage(); - float selectX = width * 0.183f + selectionMods.getWidth() / 2f; - float selectY = height - selectionMods.getHeight() / 2f; - float selectOffset = selectionMods.getWidth() * 1.05f; - selectModsButton = new MenuButton(GameImage.SELECTION_MODS_OVERLAY.getImage(), + // TODO: For special cases (e.g. 5 digit skin), need to process each image separately + // Image selectionMods = GameImage.SELECTION_MODS.getImage(); + Image selectionModsOverlay = GameImage.SELECTION_MODS_OVERLAY.getImage(); + float selectX = width * 0.183f + selectionModsOverlay.getWidth() / 2f; + float selectY = height - selectionModsOverlay.getHeight() / 2f; + float selectOffset = selectionModsOverlay.getWidth() * 1.05f; + selectModsButton = new MenuButton(selectionModsOverlay, selectX, selectY); selectRandomButton = new MenuButton(GameImage.SELECTION_RANDOM_OVERLAY.getImage(), selectX + selectOffset, selectY); diff --git a/src/itdelatrisu/opsu/ui/VisualSetting.java b/src/itdelatrisu/opsu/ui/VisualSetting.java new file mode 100644 index 00000000..6631539b --- /dev/null +++ b/src/itdelatrisu/opsu/ui/VisualSetting.java @@ -0,0 +1,48 @@ +/* + * opsu! - an open-source osu! client + * Copyright (C) 2014-2017 Jeffrey Han + * Copyright (C) 2023-2024 CloneWith + * + * 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 . + */ + +package itdelatrisu.opsu.ui; + +import itdelatrisu.opsu.GameImage; +import itdelatrisu.opsu.Opsu; +import itdelatrisu.opsu.Utils; +import itdelatrisu.opsu.audio.SoundController; +import itdelatrisu.opsu.options.Options; +import itdelatrisu.opsu.replay.ReplayImporter; +import itdelatrisu.opsu.skins.SkinUnpacker; +import itdelatrisu.opsu.ui.animations.AnimatedValue; +import itdelatrisu.opsu.ui.animations.AnimationEquation; + +import javax.swing.JOptionPane; +import javax.swing.UIManager; + +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.state.StateBasedGame; +import org.newdawn.slick.util.Log; + +/** + * Draws the visual settings panel. + */ +public class VisualSetting { + +} diff --git a/src/itdelatrisu/opsu/user/UserSelectOverlay.java b/src/itdelatrisu/opsu/user/UserSelectOverlay.java index 0b608abe..94548430 100644 --- a/src/itdelatrisu/opsu/user/UserSelectOverlay.java +++ b/src/itdelatrisu/opsu/user/UserSelectOverlay.java @@ -382,7 +382,9 @@ private void renderUserEdit(Graphics g, float alpha) { editUserButton.draw(g, alpha); // delete button - deleteUserButton.draw(g, alpha); + if (UserList.get().size() != 1) { + deleteUserButton.draw(g, alpha); + } // user icons int cy = (int) (y + usersStartY + (UserButton.getHeight() + usersPaddingY) * 2); @@ -511,14 +513,10 @@ public void mouseReleased(int button, int x, int y) { String name = user.getName(); if (button == Input.MOUSE_RIGHT_BUTTON) { // right click: edit user - if (name.equals(UserList.DEFAULT_USER_NAME)) { - UI.getNotificationManager().sendBarNotification("This user can't be edited."); - } else { - state = State.EDIT_USER; - prevState = State.USER_SELECT; - stateChangeProgress.setTime(0); - prepareUserEdit(selectedButton.getUser()); - } + state = State.EDIT_USER; + prevState = State.USER_SELECT; + stateChangeProgress.setTime(0); + prepareUserEdit(selectedButton.getUser()); } else { // left click: select user if (!name.equals(UserList.get().getCurrentUser().getName())) {