diff --git a/src/awlex/ospu/FakeGameObject.java b/src/awlex/ospu/FakeGameObject.java index cfb701ee..b746b023 100644 --- a/src/awlex/ospu/FakeGameObject.java +++ b/src/awlex/ospu/FakeGameObject.java @@ -101,4 +101,9 @@ public class FakeGameObject extends GameObject { public void setTime(int time) { this.halfTime = time; } + + @Override + public void updateColor() { + } + } \ No newline at end of file diff --git a/src/itdelatrisu/opsu/Container.java b/src/itdelatrisu/opsu/Container.java index 0be55d73..e5d6c234 100644 --- a/src/itdelatrisu/opsu/Container.java +++ b/src/itdelatrisu/opsu/Container.java @@ -62,6 +62,8 @@ public class Container extends AppGameContainer { getDelta(); while (running()) gameLoop(); + } catch(Exception e) { + e.printStackTrace(); } finally { // destroy the game container close_sub(); diff --git a/src/itdelatrisu/opsu/objects/Circle.java b/src/itdelatrisu/opsu/objects/Circle.java index 684c644a..44718a38 100644 --- a/src/itdelatrisu/opsu/objects/Circle.java +++ b/src/itdelatrisu/opsu/objects/Circle.java @@ -60,6 +60,8 @@ public class Circle extends GameObject { /** Whether or not the circle result ends the combo streak. */ private boolean comboEnd; + private int comboColorIndex; + /** * Initializes the Circle data type with map modifiers, images, and dimensions. * @param container the game container @@ -86,9 +88,9 @@ public class Circle extends GameObject { this.game = game; this.data = data; this.comboEnd = comboEnd; + this.comboColorIndex = comboColorIndex; + updateColor(); updatePosition(); - color = Dancer.colorOverride.getColor(comboColorIndex); - mirrorColor = Dancer.colorMirrorOverride.getColor(comboColorIndex); } public Circle(float x, float y, int time) { @@ -265,4 +267,10 @@ public class Circle extends GameObject { return mirrorColor; } + @Override + public void updateColor() { + color = Dancer.colorOverride.getColor(comboColorIndex); + mirrorColor = Dancer.colorMirrorOverride.getColor(comboColorIndex); + } + } diff --git a/src/itdelatrisu/opsu/objects/DummyObject.java b/src/itdelatrisu/opsu/objects/DummyObject.java index ce5adebd..f545d501 100644 --- a/src/itdelatrisu/opsu/objects/DummyObject.java +++ b/src/itdelatrisu/opsu/objects/DummyObject.java @@ -100,4 +100,8 @@ public class DummyObject extends GameObject { return null; } + @Override + public void updateColor() { + } + } diff --git a/src/itdelatrisu/opsu/objects/GameObject.java b/src/itdelatrisu/opsu/objects/GameObject.java index 1f9f49dd..b819ac32 100644 --- a/src/itdelatrisu/opsu/objects/GameObject.java +++ b/src/itdelatrisu/opsu/objects/GameObject.java @@ -101,4 +101,6 @@ public abstract class GameObject { public abstract Color getColor(); public abstract Color getMirroredColor(); + public abstract void updateColor(); + } diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index 5700713c..b71cb234 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -117,6 +117,8 @@ public class Slider extends GameObject { public float pixelLength; + private int comboColorIndex; + /** * Initializes the Slider data type with images and dimensions. * @param container the game container @@ -162,8 +164,8 @@ public class Slider extends GameObject { this.game = game; this.data = data; this.comboEnd = comboEnd; - color = Dancer.colorOverride.getColor(comboColorIndex); - mirrorColor = Dancer.colorMirrorOverride.getColor(comboColorIndex); + this.comboColorIndex = comboColorIndex; + updateColor(); updatePosition(); this.pixelLength = hitObject.getPixelLength(); @@ -710,4 +712,10 @@ public class Slider extends GameObject { return ticks; } + @Override + public void updateColor() { + color = Dancer.colorOverride.getColor(comboColorIndex); + mirrorColor = Dancer.colorMirrorOverride.getColor(comboColorIndex); + } + } diff --git a/src/itdelatrisu/opsu/objects/Spinner.java b/src/itdelatrisu/opsu/objects/Spinner.java index 251f4e0d..e507e080 100644 --- a/src/itdelatrisu/opsu/objects/Spinner.java +++ b/src/itdelatrisu/opsu/objects/Spinner.java @@ -438,4 +438,7 @@ public class Spinner extends GameObject { return null; } + @Override + public void updateColor() { + } } diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 03b4eb4b..3c8364df 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -72,6 +72,7 @@ import org.newdawn.slick.state.transition.EasedFadeOutTransition; import org.newdawn.slick.state.transition.EmptyTransition; import org.newdawn.slick.state.transition.FadeInTransition; import yugecin.opsudance.*; +import yugecin.opsudance.ui.SBOverlay; /** * "Game" state. @@ -271,10 +272,59 @@ public class Game extends BasicGameState { private final int state; private final Cursor mirrorCursor; + private final SBOverlay sbOverlay; public Game(int state) { this.state = state; mirrorCursor = new Cursor(true); + sbOverlay = new SBOverlay(this); + } + + public void setObjectIndex(int newObjIndex) { + try { + /* + + restart = Restart.MANUAL; + enter(container, game); + checkpointLoaded = true; + if (isLeadIn()) { + leadInTime = 0; + MusicController.resume(); + } + SoundController.playSound(SoundEffect.MENUHIT); + UI.sendBarNotification("Checkpoint loaded."); + + // skip to checkpoint + MusicController.setPosition(checkpoint); + MusicController.setPitch(GameMod.getSpeedMultiplier() * playbackSpeed.getModifier()); + while (objectIndex < gameObjects.length && + beatmap.objects[objectIndex++].getTime() <= checkpoint) + ; + objectIndex--; + lastReplayTime = beatmap.objects[objectIndex].getTime(); + } catch (SlickException e) { + ErrorHandler.error("Failed to load checkpoint.", e, false); + } + */ + restart = Restart.MANUAL; + enter(container, game); + checkpointLoaded = true; + if (isLeadIn()) { + leadInTime = 0; + MusicController.resume(); + } + int checkpoint = gameObjects[newObjIndex].getTime(); + // skip to checkpoint + MusicController.setPosition(checkpoint); + while (objectIndex < gameObjects.length && beatmap.objects[objectIndex].getTime() <= checkpoint) { + objectIndex++; + } + objectIndex--; + sbOverlay.updateIndex(objectIndex); + lastReplayTime = beatmap.objects[objectIndex].getTime(); + } catch (SlickException e) { + e.printStackTrace(); + } } @Override @@ -287,6 +337,8 @@ public class Game extends BasicGameState { int width = container.getWidth(); int height = container.getHeight(); + sbOverlay.init(container, input, width, height); + // create offscreen graphics offscreen = new Image(width, height); gOffscreen = offscreen.getGraphics(); @@ -635,6 +687,8 @@ public class Game extends BasicGameState { else UI.draw(g); + sbOverlay.render(container, game, g); + if (!Dancer.hidewatermark) { Fonts.SMALL.drawString(0.3f, 0.3f, "opsu!dance " + Updater.get().getCurrentVersion() + " by robin_be | https://github.com/yugecin/opsu-dance"); } @@ -647,6 +701,7 @@ public class Game extends BasicGameState { Pippi.update(delta); yugecin.opsudance.spinners.Spinner.update(delta); int mouseX = input.getMouseX(), mouseY = input.getMouseY(); + sbOverlay.update(mouseX, mouseY); skipButton.hoverUpdate(delta, mouseX, mouseY); if (isReplay || GameMod.AUTO.isActive()) playbackSpeed.getButton().hoverUpdate(delta, mouseX, mouseY); @@ -910,6 +965,7 @@ public class Game extends BasicGameState { // update hit object and check completion status if (gameObjects[objectIndex].update(overlap, delta, mouseX, mouseY, keyPressed, trackPosition)) { objectIndex++; // done, so increment object index + sbOverlay.updateIndex(objectIndex); if (objectIndex >= mirrorTo) { Dancer.mirror = false; } @@ -924,6 +980,11 @@ public class Game extends BasicGameState { @Override public void keyPressed(int key, char c) { + + if (sbOverlay.keyPressed(key, c)) { + return; + } + int trackPosition = MusicController.getPosition(); int mouseX = input.getMouseX(); int mouseY = input.getMouseY(); @@ -1064,8 +1125,16 @@ public class Game extends BasicGameState { } } + @Override + public void mouseDragged(int oldx, int oldy, int newx, int newy) { + sbOverlay.mouseDragged(oldx, oldy, newx, newy); + } + @Override public void mousePressed(int button, int x, int y) { + if (sbOverlay.mousePressed(button, x, y)) { + return; + } // watching replay if (isReplay || GameMod.AUTO.isActive()) { if (button == Input.MOUSE_MIDDLE_BUTTON) @@ -1158,6 +1227,10 @@ public class Game extends BasicGameState { @Override public void mouseReleased(int button, int x, int y) { + if (sbOverlay.mouseReleased(button, x, y)) { + return; + } + if (Options.isMouseDisabled()) return; @@ -1200,6 +1273,9 @@ public class Game extends BasicGameState { @Override public void mouseWheelMoved(int newValue) { + if (sbOverlay.mouseWheelMoved(newValue)) { + return; + } if (Options.isMouseWheelDisabled() || Options.isMouseDisabled()) return; @@ -1228,6 +1304,7 @@ public class Game extends BasicGameState { // grab the mouse (not working for touchscreen) // container.setMouseGrabbed(true); + // restart the game if (restart != Restart.FALSE) { // load mods @@ -1369,6 +1446,13 @@ public class Game extends BasicGameState { SoundController.mute(false); } + + sbOverlay.setGameObjects(gameObjects); + if (!checkpointLoaded) { + sbOverlay.enter(); + sbOverlay.updateIndex(0); + } + Pippi.reset(); mirrorFrom = 0; mirrorTo = gameObjects.length; @@ -1384,6 +1468,8 @@ public class Game extends BasicGameState { throws SlickException { // container.setMouseGrabbed(false); + sbOverlay.leave(); + Cursor.lastObjColor = Color.white; Cursor.lastMirroredObjColor = Color.white; diff --git a/src/yugecin/opsudance/ui/OptionsOverlay.java b/src/yugecin/opsudance/ui/OptionsOverlay.java new file mode 100644 index 00000000..7a2bd82c --- /dev/null +++ b/src/yugecin/opsudance/ui/OptionsOverlay.java @@ -0,0 +1,212 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2016 yugecin + * + * opsu!dance 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!dance 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!dance. If not, see . + */ +package yugecin.opsudance.ui; + +import itdelatrisu.opsu.Options; +import itdelatrisu.opsu.ui.Colors; +import itdelatrisu.opsu.ui.Fonts; +import org.newdawn.slick.Color; +import org.newdawn.slick.GameContainer; +import org.newdawn.slick.Graphics; +import org.newdawn.slick.Input; +import org.newdawn.slick.state.StateBasedGame; + +import java.util.Observable; +import java.util.Observer; + +public class OptionsOverlay { + + private int width; + private int height; + + private static Options.GameOption[] options = new Options.GameOption[] { + Options.GameOption.DANCE_MOVER, + Options.GameOption.DANCE_MOVER_DIRECTION, + Options.GameOption.DANCE_SLIDER_MOVER_TYPE, + Options.GameOption.DANCE_SPINNER, + Options.GameOption.DANCE_SPINNER_DELAY, + Options.GameOption.DANCE_LAZY_SLIDERS, + Options.GameOption.DANCE_CIRCLE_STREAMS, + Options.GameOption.DANCE_ONLY_CIRCLE_STACKS, + Options.GameOption.DANCE_CIRLCE_IN_SLOW_SLIDERS, + Options.GameOption.DANCE_CIRLCE_IN_LAZY_SLIDERS, + Options.GameOption.DANCE_MIRROR, + Options.GameOption.DANCE_DRAW_APPROACH, + Options.GameOption.DANCE_OBJECT_COLOR_OVERRIDE, + Options.GameOption.DANCE_OBJECT_COLOR_OVERRIDE_MIRRORED, + Options.GameOption.DANCE_RGB_OBJECT_INC, + Options.GameOption.DANCE_CURSOR_COLOR_OVERRIDE, + Options.GameOption.DANCE_CURSOR_MIRROR_COLOR_OVERRIDE, + Options.GameOption.DANCE_CURSOR_ONLY_COLOR_TRAIL, + Options.GameOption.DANCE_RGB_CURSOR_INC, + Options.GameOption.DANCE_CURSOR_TRAIL_OVERRIDE, + Options.GameOption.DANCE_REMOVE_BG, + Options.GameOption.DANCE_HIDE_OBJECTS, + Options.GameOption.DANCE_HIDE_UI, + Options.GameOption.PIPPI_ENABLE, + Options.GameOption.PIPPI_ANGLE_INC_MUL, + Options.GameOption.PIPPI_ANGLE_INC_MUL_SLIDER, + Options.GameOption.PIPPI_SLIDER_FOLLOW_EXPAND, + Options.GameOption.PIPPI_PREVENT_WOBBLY_STREAMS, + }; + + private int textHeight; + private Input input; + private final ItemList list; + private GameContainer container; + private Options.GameOption selectedOption; + private final SBOverlay overlay; + + public OptionsOverlay(SBOverlay overlay) { + this.overlay = overlay; + list = new ItemList(); + } + + public Options.GameOption[] getSavedOptionList() { + return options; + } + + public void init(GameContainer container, Input input, int width, int height) { + list.init(container); + this.input = input; + this.width = width; + this.height = height; + this.container = container; + textHeight = Fonts.SMALL.getLineHeight(); + } + + public void render(GameContainer container, StateBasedGame game, Graphics g) { + int hoverIdx = getOptionIdxAt(input.getMouseY()); + float a = Color.black.a; + Color.black.a = 0.8f; + g.setColor(Color.black); + g.fillRect(0, 0, width, height); + Color.black.a = a; + for (int i = 0; i < options.length; i++) { + drawOption(g, options[i], i, selectedOption == null ? hoverIdx == i : selectedOption == options[i]); + } + if (list.isVisible()) { + list.render(container, game, g); + } + } + + // I know... kill me + private void drawOption(Graphics g, Options.GameOption option, int pos, boolean focus) { + float y = pos * (textHeight + 5); + Color color = (focus) ? Color.cyan : Color.white; + + Fonts.MEDIUM.drawString(width / 6 * 2, y, option.getName(), color); + Fonts.MEDIUM.drawString(width / 3 * 2, y, option.getValueString(), color); + g.setColor(Colors.WHITE_ALPHA); + g.drawLine(0, y + textHeight + 3, width, y + textHeight + 3 + 1); + } + + private int getOptionIdxAt(int y) { + int index = y / (textHeight + 5); + if (index >= options.length) { + return -1; + } + return index; + } + + public void update(int mouseX, int mouseY) { + + } + + public boolean mousePressed(int button, int x, int y) { + if (list.isVisible()) { + list.mousePressed(button, x, y); + return true; + } + int idx = getOptionIdxAt(y); + if (idx < options.length) { + final Options.GameOption option = options[idx]; + selectedOption = option; + Object[] listItems = option.getListItems(); + if (listItems == null) { + option.click(container); + } else { + list.setItems(listItems); + list.setClickListener(new Observer() { + @Override + public void update(Observable o, Object arg) { + option.clickListItem((int) arg); + overlay.saveOption(option); + } + }); + list.show(); + } + } + return true; + } + + + public void mouseDragged(int oldx, int oldy, int newx, int newy) { + if (list.isVisible()) { + list.mouseDragged(oldx, oldy, newx, newy); + return; + } + + int multiplier; + if (input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON)) + multiplier = 4; + else if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)) + multiplier = 1; + else + return; + + // get direction + int diff = newx - oldx; + if (diff == 0) + return; + diff = ((diff > 0) ? 1 : -1) * multiplier; + + // options (drag only) + if (selectedOption != null) { + selectedOption.drag(container, diff); + } + } + + public boolean mouseReleased(int button, int x, int y) { + if (selectedOption != null) { + overlay.saveOption(selectedOption); + } + selectedOption = null; + if (list.isVisible()) { + list.mouseReleased(button, x, y); + return true; + } + return true; + } + + public boolean mouseWheenMoved(int newValue) { + if (list.isVisible()) { + list.mouseWheelMoved(newValue); + return true; + } + return true; + } + + public boolean keyPressed(int key, char c) { + if (list.isVisible()) { + list.keyPressed(key, c); + return true; + } + return false; + } +} diff --git a/src/yugecin/opsudance/ui/SBOverlay.java b/src/yugecin/opsudance/ui/SBOverlay.java new file mode 100644 index 00000000..63a434ed --- /dev/null +++ b/src/yugecin/opsudance/ui/SBOverlay.java @@ -0,0 +1,237 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2016 yugecin + * + * opsu!dance 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!dance 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!dance. If not, see . + */ +package yugecin.opsudance.ui; + +import itdelatrisu.opsu.Options; +import itdelatrisu.opsu.audio.MusicController; +import itdelatrisu.opsu.objects.GameObject; +import itdelatrisu.opsu.states.Game; +import itdelatrisu.opsu.ui.Fonts; +import org.newdawn.slick.Color; +import org.newdawn.slick.GameContainer; +import org.newdawn.slick.Graphics; +import org.newdawn.slick.Input; +import org.newdawn.slick.state.StateBasedGame; + +import java.util.HashMap; +import java.util.Map; + +@SuppressWarnings("unchecked") +public class SBOverlay { + + public static boolean isActive = true; + + private boolean hide; + private boolean menu; + + private int width; + private int height; + + private int speed; + private GameObject[] gameObjects; + private HashMap[] optionsMap; + private HashMap initialOptions; + + private int index; + + private final Game game; + private final OptionsOverlay options; + + public SBOverlay(Game game) { + this.game = game; + options = new OptionsOverlay(this); + initialOptions = new HashMap<>(); + } + + public void init(GameContainer container, Input input, int width, int height) { + this.width = width; + this.height = height; + speed = 10; + gameObjects = new GameObject[0]; + options.init(container, input, width, height); + } + + public void render(GameContainer container, StateBasedGame game, Graphics g) { + if (!isActive || hide) { + return; + } + int lh = Fonts.SMALL.getLineHeight(); + Fonts.SMALL.drawString(10, height - 50, "speed: C " + (speed / 10f) + " V", Color.cyan); + Fonts.SMALL.drawString(10, height - 50 - lh, "Menu: N", Color.cyan); + Fonts.SMALL.drawString(10, height - 50 - lh * 2, "HIDE: H", Color.cyan); + Fonts.SMALL.drawString(10, height - 50 - lh * 3, "obj: J " + index + " K", Color.cyan); + if (index < optionsMap.length && optionsMap[index] != null) { + int i = 0; + for (Object o : optionsMap[index].entrySet()) { + Map.Entry option = (Map.Entry) o; + Fonts.SMALL.drawString(10, 50 + i * lh, option.getKey().getDisplayName(), Color.cyan); + } + } + if (menu) { + options.render(container, game, g); + } + } + + public void update(int mouseX, int mouseY) { + if (!isActive) { + return; + } + if (menu) { + options.update(mouseX, mouseY); + } + } + + public boolean keyPressed(int key, char c) { + if (!isActive) { + return false; + } + if (options.keyPressed(key, c)) { + return true; + } + if (key == Input.KEY_C && speed > 0) { + speed -= 1; + if (speed == 0) { + MusicController.pause(); + } else { + MusicController.setPitch(speed / 10f); + } + } else if (key == Input.KEY_V && speed < 21) { + if (speed == 0) { + MusicController.resume(); + } + speed += 1; + MusicController.setPitch(speed / 10f); + } else if (key == Input.KEY_H) { + hide = !hide; + } else if (key == Input.KEY_N) { + menu = !menu; + if (menu && speed != 0) { + MusicController.pause(); + } else if (!menu && speed != 0) { + MusicController.resume(); + } + } else if (key == Input.KEY_J && index > 0) { + index--; + setMusicPosition(); + goBackOneSBIndex(); + } else if (key == Input.KEY_K && index < gameObjects.length - 1) { + index++; + setMusicPosition(); + updateIndex(index); + } else if (key == Input.KEY_ESCAPE && menu) { + menu = false; + if (speed != 0) { + MusicController.resume(); + } + return true; + } + return false; + } + + private void goBackOneSBIndex() { + if (optionsMap[index + 1] != null) { + // new options on previous index, so to revert then we have to reload them all to this point.. + final int thisIndex = index; + for (int i = 0; i <= thisIndex; i++) { + updateIndex(thisIndex); + } + } + } + + private void setMusicPosition() { + game.setObjectIndex(index); + if (speed != 0) { + MusicController.setPitch(speed / 10f); + MusicController.resume(); + } else { + MusicController.pause(); + } + } + + public void setGameObjects(GameObject[] gameObjects) { + if (this.gameObjects.length != gameObjects.length) { + optionsMap = new HashMap[gameObjects.length]; + } + this.gameObjects = gameObjects; + } + + public void saveOption(Options.GameOption option) { + if (optionsMap[index] == null) { + optionsMap[index] = new HashMap<>(); + } + optionsMap[index].put(option, option.write()); + } + + public boolean mousePressed(int button, int x, int y) { + return menu && options.mousePressed(button, x, y); + } + + public void mouseDragged(int oldx, int oldy, int newx, int newy) { + if (menu) options.mouseDragged(oldx, oldy, newx, newy); + } + + public void updateIndex(int index) { + this.index = index; + if (index >= optionsMap.length) { + return; + } + HashMap options = optionsMap[index]; + if (options != null) { + for (Object o : options.entrySet()) { + Map.Entry next = (Map.Entry) o; + next.getKey().read(next.getValue()); + readOption(next.getKey()); + } + } + } + + public boolean mouseReleased(int button, int x, int y) { + return menu && options.mouseReleased(button, x, y); + } + + public boolean mouseWheelMoved(int newValue) { + return menu && options.mouseWheenMoved(newValue); + } + + public void enter() { + // enter, save current settings + for (Options.GameOption o : options.getSavedOptionList()) { + initialOptions.put(o, o.write()); + } + } + + public void leave() { + // leave, revert the settings saved before entering + for (Options.GameOption o : options.getSavedOptionList()) { + if (initialOptions.containsKey(o)) { + o.read(initialOptions.get(o)); + readOption(o); + } + } + } + + // needed for object color overrides... + private void readOption(Options.GameOption o) { + if (o == Options.GameOption.DANCE_OBJECT_COLOR_OVERRIDE || o == Options.GameOption.DANCE_OBJECT_COLOR_OVERRIDE_MIRRORED) { + for (int i = index; i < gameObjects.length; i++) { + gameObjects[i].updateColor(); + } + } + } + +}