From 69f5aa5748ccb8ed021a3b372e97b360edeec3c9 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Mon, 16 Feb 2015 17:53:24 -0500 Subject: [PATCH] Added a separate Game Mods menu. - Replaces the mods being shown in the options menu. - Added all the (base) mod icons to the menu, with the unimplemented ones grayed out. - Added a rotation effect to MenuButton, and now multiple effects can be set at once. Signed-off-by: Jeffrey Han --- src/itdelatrisu/opsu/GameData.java | 9 +- src/itdelatrisu/opsu/GameImage.java | 14 +- src/itdelatrisu/opsu/GameMod.java | 247 +++++++++++++----- src/itdelatrisu/opsu/MenuButton.java | 209 ++++++++------- src/itdelatrisu/opsu/Utils.java | 31 ++- src/itdelatrisu/opsu/states/ButtonMenu.java | 133 +++++++++- .../opsu/states/DownloadsMenu.java | 1 + src/itdelatrisu/opsu/states/OptionsMenu.java | 48 +--- src/itdelatrisu/opsu/states/SongMenu.java | 11 +- 9 files changed, 471 insertions(+), 232 deletions(-) diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index 80617239..6c80ead5 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -1007,13 +1007,6 @@ public class GameData { if (hitValue > 0) { SoundController.playHitSound(hitSound); - // game mod score multipliers - float modMultiplier = 1f; - for (GameMod mod : GameMod.values()) { - if (mod.isActive()) - modMultiplier *= mod.getMultiplier(); - } - /** * [SCORE FORMULA] * Score = Hit Value + Hit Value * (Combo * Difficulty * Mod) / 25 @@ -1022,7 +1015,7 @@ public class GameData { * - Difficulty: the beatmap difficulty * - Mod: mod multipliers */ - score += (hitValue + (hitValue * (Math.max(combo - 1, 0) * difficulty * modMultiplier) / 25)); + score += (hitValue + (hitValue * (Math.max(combo - 1, 0) * difficulty * GameMod.getScoreMultiplier()) / 25)); incrementComboStreak(); } hitResultCount[result]++; diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index 2dfe20da..d3cf4d2b 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -309,7 +309,7 @@ public enum GameImage { MOD_EASY ("selection-mod-easy", "png", false, false) { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy((h * 0.12f) / img.getHeight()); + return img.getScaledCopy((h / 12f) / img.getHeight()); } }, MOD_NO_FAIL ("selection-mod-nofail", "png", false, false) { @@ -366,6 +366,18 @@ public enum GameImage { return MOD_EASY.process_sub(img, w, h); } }, + MOD_RELAX ("selection-mod-relax", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return MOD_EASY.process_sub(img, w, h); + } + }, + MOD_AUTOPILOT ("selection-mod-relax2", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return MOD_EASY.process_sub(img, w, h); + } + }, // Selection Buttons SELECTION_MODS ("selection-mods", "png", false, false) { diff --git a/src/itdelatrisu/opsu/GameMod.java b/src/itdelatrisu/opsu/GameMod.java index e9b34f9b..037ed70b 100644 --- a/src/itdelatrisu/opsu/GameMod.java +++ b/src/itdelatrisu/opsu/GameMod.java @@ -21,6 +21,7 @@ package itdelatrisu.opsu; import java.util.Arrays; import java.util.Collections; +import org.newdawn.slick.Color; import org.newdawn.slick.Image; import org.newdawn.slick.Input; @@ -28,29 +29,103 @@ import org.newdawn.slick.Input; * Game mods. */ public enum GameMod { - EASY (0, GameImage.MOD_EASY, "EZ", 2, Input.KEY_Q, 0.5f, + EASY (Category.EASY, 0, GameImage.MOD_EASY, "EZ", 2, Input.KEY_Q, 0.5f, "Reduces overall difficulty - larger circles, more forgiving HP drain, less accuracy required."), - NO_FAIL (1, GameImage.MOD_NO_FAIL, "NF", 1, Input.KEY_W, 0.5f, + NO_FAIL (Category.EASY, 1, GameImage.MOD_NO_FAIL, "NF", 1, Input.KEY_W, 0.5f, "You can't fail. No matter what."), - HARD_ROCK (2, GameImage.MOD_HARD_ROCK, "HR", 16, Input.KEY_A, 1.06f, + HALF_TIME (Category.EASY, 2, GameImage.MOD_HALF_TIME, "HT", 256, Input.KEY_E, 0.3f, false, + "Less zoom."), + HARD_ROCK (Category.HARD, 0, GameImage.MOD_HARD_ROCK, "HR", 16, Input.KEY_A, 1.06f, "Everything just got a bit harder..."), - SUDDEN_DEATH (3, GameImage.MOD_SUDDEN_DEATH, "SD", 32, Input.KEY_S, + SUDDEN_DEATH (Category.HARD, 1, GameImage.MOD_SUDDEN_DEATH, "SD", 32, Input.KEY_S, 1f, "Miss a note and fail."), - SPUN_OUT (4, GameImage.MOD_SPUN_OUT, "SO", 4096, Input.KEY_V, 0.9f, +// PERFECT (Category.HARD, 1, GameImage.MOD_PERFECT, "PF", 64, Input.KEY_S, 1f, +// "SS or quit."), + DOUBLE_TIME (Category.HARD, 2, GameImage.MOD_DOUBLE_TIME, "DT", 64, Input.KEY_D, 1.12f, false, + "Zoooooooooom."), +// NIGHTCORE (Category.HARD, 2, GameImage.MOD_NIGHTCORE, "NT", 64, Input.KEY_D, 1.12f, +// "uguuuuuuuu"), + HIDDEN (Category.HARD, 3, GameImage.MOD_HIDDEN, "HD", 8, Input.KEY_F, 1.06f, false, + "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, false, + "Restricted view area."), + RELAX (Category.SPECIAL, 0, GameImage.MOD_RELAX, "RL", 128, Input.KEY_Z, 0f, false, + "You don't need to click. Give your clicking/tapping finger a break from the heat of things. **UNRANKED**"), + AUTOPILOT (Category.SPECIAL, 1, GameImage.MOD_AUTOPILOT, "AP", 8192, Input.KEY_X, 0f, false, + "Automatic cursor movement - just follow the rhythm. **UNRANKED**"), + SPUN_OUT (Category.SPECIAL, 2, GameImage.MOD_SPUN_OUT, "SO", 4096, Input.KEY_V, 0.9f, "Spinners will be automatically completed."), - AUTO (5, GameImage.MOD_AUTO, "", 2048, Input.KEY_B, + AUTO (Category.SPECIAL, 3, GameImage.MOD_AUTO, "", 2048, Input.KEY_B, 1f, "Watch a perfect automated play through the song."); -// HALF_TIME (6, GameImage.MOD_HALF_TIME, "HT", 256, Input.KEY_E, 0.3f, -// "Less zoom."), -// DOUBLE_TIME (7, GameImage.MOD_DOUBLE_TIME, "DT", 64, Input.KEY_D, 1.12f, -// "Zoooooooooom."), -// HIDDEN (8, GameImage.MOD_HIDDEN, "HD", 8, Input.KEY_F, 1.06f, -// "Play with no approach circles and fading notes for a slight score advantage."), -// FLASHLIGHT (9, GameImage.MOD_FLASHLIGHT, "FL", 1024, Input.KEY_G, 1.12f, -// "Restricted view area."); - /** The ID of the mod (used for positioning). */ - private int id; + /** Mod categories. */ + public enum Category { + EASY (0, "Difficulty Reduction", Color.green), + HARD (1, "Difficulty Increase", Color.red), + SPECIAL (2, "Special", Color.white); + + /** Drawing index. */ + private int index; + + /** Category name. */ + private String name; + + /** Text color. */ + private Color color; + + /** The coordinates of the category. */ + private float x, y; + + /** + * Constructor. + * @param index the drawing index + * @param name the category name + * @param color the text color + */ + Category(int index, String name, Color color) { + this.index = index; + this.name = name; + this.color = color; + } + + /** + * Initializes the category. + * @param width the container width + * @param height the container height + */ + public void init(int width, int height) { + float multY = Utils.FONT_LARGE.getLineHeight() * 2 + height * 0.06f; + float offsetY = GameImage.MOD_EASY.getImage().getHeight() * 1.5f; + this.x = width / 30f; + this.y = multY + Utils.FONT_LARGE.getLineHeight() * 3f + offsetY * index; + } + + /** + * Returns the category name. + */ + public String getName() { return name; } + + /** + * Returns the text color. + */ + public Color getColor() { return color; } + + /** + * Returns the x coordinate of the category. + */ + public float getX() { return x; } + + /** + * Returns the y coordinate of the category. + */ + public float getY() { return y; } + } + + /** The category for the mod. */ + private Category category; + + /** The index in the category (for positioning). */ + private int categoryIndex; /** The file name of the mod image. */ private GameImage image; @@ -58,18 +133,21 @@ public enum GameMod { /** The abbreviation for the mod. */ private String abbrev; - /** Bit value associated with the mod. */ - private int bit; - /** - * The shortcut key associated with the mod. + * Bit value associated with the mod. * See the osu! API: https://github.com/peppy/osu-api/wiki#mods */ + private int bit; + + /** The shortcut key associated with the mod. */ private int key; /** The score multiplier. */ private float multiplier; + /** Whether or not the mod is implemented. */ + private boolean implemented; + /** The description of the mod. */ private String description; @@ -89,28 +167,56 @@ public enum GameMod { Collections.reverse(Arrays.asList(VALUES_REVERSED)); } + /** The last calculated score multiplier, or -1f if it must be recalculated. */ + private static float scoreMultiplier = -1f; + /** - * Constructor. - * @param id the ID of the mod (for positioning). - * @param image the GameImage - * @param abbrev the two-letter abbreviation - * @param bit the bit - * @param key the shortcut key - * @param description the description + * Initializes the game mods. + * @param width the container width + * @param height the container height */ - GameMod(int id, GameImage image, String abbrev, int bit, int key, String description) { - this.id = id; - this.image = image; - this.abbrev = abbrev; - this.bit = bit; - this.key = key; - this.multiplier = 1f; - this.description = description; + public static void init(int width, int height) { + // initialize categories + for (Category c : Category.values()) + c.init(width, height); + + // create buttons + float baseX = Category.EASY.getX() + Utils.FONT_LARGE.getWidth(Category.EASY.getName()) * 1.25f; + float offsetX = GameImage.MOD_EASY.getImage().getWidth() * 2.1f; + for (GameMod mod : GameMod.values()) { + Image img = mod.image.getImage(); + mod.button = new MenuButton(img, + baseX + (offsetX * mod.categoryIndex) + img.getWidth() / 2f, + mod.category.getY()); + mod.button.setHoverExpand(1.2f); + mod.button.setHoverRotate(10f); + + // reset state + mod.active = false; + } + + scoreMultiplier = -1f; + } + + /** + * Returns the current score multiplier from all active mods. + */ + public static float getScoreMultiplier() { + if (scoreMultiplier < 0f) { + float multiplier = 1f; + for (GameMod mod : GameMod.values()) { + if (mod.isActive()) + multiplier *= mod.getMultiplier(); + } + scoreMultiplier = multiplier; + } + return scoreMultiplier; } /** * Constructor. - * @param id the ID of the mod (for positioning). + * @param category the category for the mod + * @param categoryIndex the index in the category * @param image the GameImage * @param abbrev the two-letter abbreviation * @param bit the bit @@ -118,37 +224,36 @@ public enum GameMod { * @param multiplier the score multiplier * @param description the description */ - GameMod(int id, GameImage image, String abbrev, int bit, int key, float multiplier, String description) { - this.id = id; + GameMod(Category category, int categoryIndex, GameImage image, String abbrev, + int bit, int key, float multiplier, String description) { + this(category, categoryIndex, image, abbrev, bit, key, 1f, true, description); + } + + /** + * Constructor. + * @param category the category for the mod + * @param categoryIndex the index in the category + * @param image the GameImage + * @param abbrev the two-letter abbreviation + * @param bit the bit + * @param key the shortcut key + * @param multiplier the score multiplier + * @param implemented whether the mod is implemented + * @param description the description + */ + GameMod(Category category, int categoryIndex, GameImage image, String abbrev, + int bit, int key, float multiplier, boolean implemented, String description) { + this.category = category; + this.categoryIndex = categoryIndex; this.image = image; this.abbrev = abbrev; this.bit = bit; this.key = key; this.multiplier = multiplier; + this.implemented = implemented; this.description = description; } - /** - * Initializes the game mod. - * @param width the container width - * @param height the container height - */ - public void init(int width, int height) { - Image img = image.getImage(); - - // find coordinates - float offsetX = img.getWidth() * 1.5f; - float x = (width / 2f) - (offsetX * SIZE / 2.75f); - float y = (height * 0.8f) + (img.getHeight() / 2); - - // create button - this.button = new MenuButton(img, x + (offsetX * id), y); - this.button.setHoverExpand(1.15f); - - // reset state - this.active = false; - } - /** * Returns the abbreviated name of the mod. * @return the two-letter abbreviation @@ -185,7 +290,11 @@ public enum GameMod { * @param checkInverse if true, perform checks for mutual exclusivity */ public void toggle(boolean checkInverse) { + if (!implemented) + return; + active = !active; + scoreMultiplier = -1f; if (checkInverse) { if (AUTO.isActive()) { @@ -230,10 +339,12 @@ public enum GameMod { * Draws the game mod. */ public void draw() { - if (!active) - button.getImage().setAlpha(0.5f); - button.draw(); - button.getImage().setAlpha(1.0f); + if (!implemented) { + button.getImage().setAlpha(0.2f); + button.draw(); + button.getImage().setAlpha(1f); + } else + button.draw(); } /** @@ -244,6 +355,16 @@ public enum GameMod { */ public boolean contains(float x, float y) { return button.contains(x, y); } + /** + * Returns the center x coordinate of the button. + */ + public float getButtonX() { return button.getX(); } + + /** + * Returns the center y coordinate of the button. + */ + public float getButtonY() { return button.getY(); } + /** * Resets the hover fields for the button. */ diff --git a/src/itdelatrisu/opsu/MenuButton.java b/src/itdelatrisu/opsu/MenuButton.java index e7308601..cf4d35d9 100644 --- a/src/itdelatrisu/opsu/MenuButton.java +++ b/src/itdelatrisu/opsu/MenuButton.java @@ -24,9 +24,8 @@ import org.newdawn.slick.Font; import org.newdawn.slick.Image; /** - * A convenience class for menu buttons. - * Consists of an image or animation and coordinates. - * Multi-part images and animations currently do not support hover updates. + * A convenience class for menu buttons, consisting of an image or animation + * and coordinates. Multi-part images currently do not support effects. */ public class MenuButton { /** The image associated with the button. */ @@ -53,24 +52,30 @@ public class MenuButton { /** The color to draw the text with. */ private Color color; - /** Hover action types. */ - private enum HoverAction { NONE, EXPAND, FADE }; + /** Effect types. */ + private static final int + EFFECT_EXPAND = 1, + EFFECT_FADE = 2, + EFFECT_ROTATE = 4; - /** The hover action for this button. */ - private HoverAction hoverAction = HoverAction.NONE; + /** The hover actions for this button. */ + private int hoverEffect = 0; - /** The current and max scale of the button (for hovering). */ + /** The current and max scale of the button. */ private float scale = 1f, hoverScale = 1.25f; - /** The current and base alpha level of the button (for hovering). */ + /** The current and base alpha level of the button. */ private float alpha = 1f, baseAlpha = 0.75f; - /** The scaled expansion direction for the button (for hovering). */ + /** The scaled expansion direction for the button. */ private Expand dir = Expand.CENTER; - /** Scaled expansion directions (for hovering). */ + /** Scaled expansion directions. */ public enum Expand { CENTER, UP, RIGHT, LEFT, DOWN, UP_RIGHT, UP_LEFT, DOWN_RIGHT, DOWN_LEFT; } + /** The current and max rotation angles of the button. */ + private float angle = 0f, maxAngle = 30f; + /** * Creates a new button from an Image. * @param img the image @@ -162,43 +167,7 @@ public class MenuButton { /** * Draws the button. */ - public void draw() { - if (img != null) { - if (imgL == null) { - if (hoverAction == HoverAction.EXPAND) { - Image imgScaled = (scale == 1f) ? img : img.getScaledCopy(scale); - imgScaled.setAlpha(img.getAlpha()); - imgScaled.draw(x - xRadius, y - yRadius); - } else if (hoverAction == HoverAction.FADE) { - float a = img.getAlpha(); - img.setAlpha(alpha); - img.draw(x - xRadius, y - yRadius); - img.setAlpha(a); - } else - img.draw(x - xRadius, y - yRadius); - } else { - if (hoverAction == HoverAction.FADE) { - float a = img.getAlpha(), aL = imgL.getAlpha(), aR = imgR.getAlpha(); - img.setAlpha(alpha); - imgL.setAlpha(alpha); - imgR.setAlpha(alpha); - img.draw(x - xRadius + imgL.getWidth(), y - yRadius); - imgL.draw(x - xRadius, y - yRadius); - imgR.draw(x + xRadius - imgR.getWidth(), y - yRadius); - img.setAlpha(a); - imgL.setAlpha(aL); - imgR.setAlpha(aR); - } else { - img.draw(x - xRadius + imgL.getWidth(), y - yRadius); - imgL.draw(x - xRadius, y - yRadius); - imgR.draw(x + xRadius - imgR.getWidth(), y - yRadius); - } - } - } else - anim.draw(x - xRadius, y - yRadius); - if (text != null) - font.drawString(x - font.getWidth(text) / 2f, y - font.getLineHeight() / 2f, text, color); - } + public void draw() { draw(null); } /** * Draw the button with a color filter. @@ -207,37 +176,48 @@ public class MenuButton { public void draw(Color filter) { if (img != null) { if (imgL == null) { - if (hoverAction == HoverAction.EXPAND) { - Image imgScaled = (scale == 1f) ? img : img.getScaledCopy(scale); - imgScaled.setAlpha(img.getAlpha()); - imgScaled.draw(x - xRadius, y - yRadius, filter); - } else if (hoverAction == HoverAction.FADE) { - float a = img.getAlpha(); - img.setAlpha(alpha); - img.draw(x - xRadius, y - yRadius, filter); - img.setAlpha(a); - } else - img.draw(x - xRadius, y - yRadius, filter); + if (hoverEffect == 0) + Utils.draw(img, x - xRadius, y - yRadius, filter); + else { + Image hoverImg = img; + float oldAlpha = img.getAlpha(); + float oldAngle = img.getRotation(); + if ((hoverEffect & EFFECT_EXPAND) > 0) { + if (scale != 1f) { + hoverImg = hoverImg.getScaledCopy(scale); + hoverImg.setAlpha(oldAlpha); + } + } + if ((hoverEffect & EFFECT_FADE) > 0) + hoverImg.setAlpha(alpha); + if ((hoverEffect & EFFECT_ROTATE) > 0) + hoverImg.setRotation(angle); + Utils.draw(hoverImg, x - xRadius, y - yRadius, filter); + if (hoverImg != img) { + hoverImg.setAlpha(oldAlpha); + hoverImg.setRotation(oldAngle); + } + } } else { - if (hoverAction == HoverAction.FADE) { + if (hoverEffect == 0) { + Utils.draw(img, x - xRadius + imgL.getWidth(), y - yRadius, filter); + Utils.draw(imgL, x - xRadius, y - yRadius, filter); + Utils.draw(imgR, x + xRadius - imgR.getWidth(), y - yRadius, filter); + } else if ((hoverEffect & EFFECT_FADE) > 0) { float a = img.getAlpha(), aL = imgL.getAlpha(), aR = imgR.getAlpha(); img.setAlpha(alpha); imgL.setAlpha(alpha); imgR.setAlpha(alpha); - img.draw(x - xRadius + imgL.getWidth(), y - yRadius, filter); - imgL.draw(x - xRadius, y - yRadius, filter); - imgR.draw(x + xRadius - imgR.getWidth(), y - yRadius, filter); + Utils.draw(img, x - xRadius + imgL.getWidth(), y - yRadius, filter); + Utils.draw(imgL, x - xRadius, y - yRadius, filter); + Utils.draw(imgR, x + xRadius - imgR.getWidth(), y - yRadius, filter); img.setAlpha(a); imgL.setAlpha(aL); imgR.setAlpha(aR); - } else { - img.draw(x - xRadius + imgL.getWidth(), y - yRadius, filter); - imgL.draw(x - xRadius, y - yRadius, filter); - imgR.draw(x + xRadius - imgR.getWidth(), y - yRadius, filter); } } } else - anim.draw(x - xRadius, y - yRadius, filter); + Utils.draw(anim, x - xRadius, y - yRadius, filter); if (text != null) font.drawString(x - font.getWidth(text) / 2f, y - font.getLineHeight() / 2f, text, color); } @@ -256,55 +236,77 @@ public class MenuButton { * Resets the hover fields for the button. */ public void resetHover() { - if (hoverAction == HoverAction.EXPAND) { + if ((hoverEffect & EFFECT_EXPAND) > 0) { this.scale = 1f; setHoverRadius(); - } else if (hoverAction == HoverAction.FADE) + } + if ((hoverEffect & EFFECT_FADE) > 0) this.alpha = baseAlpha; + if ((hoverEffect & EFFECT_ROTATE) > 0) + this.angle = 0f; } /** - * Sets the hover action to "expand". + * Removes all hover effects that have been set for the button. */ - public void setHoverExpand() { this.hoverAction = HoverAction.EXPAND; } + public void removeHoverEffects() { hoverEffect = 0; } /** - * Sets the hover action to "expand". + * Sets the "expand" hover effect. + */ + public void setHoverExpand() { hoverEffect |= EFFECT_EXPAND; } + + /** + * Sets the "expand" hover effect. * @param scale the maximum scale factor (default 1.25f) */ public void setHoverExpand(float scale) { setHoverExpand(scale, this.dir); } /** - * Sets the hover action to "expand". + * Sets the "expand" hover effect. * @param dir the expansion direction */ public void setHoverExpand(Expand dir) { setHoverExpand(this.hoverScale, dir); } /** - * Sets the hover action to "expand". + * Sets the "expand" hover effect. * @param scale the maximum scale factor (default 1.25f) * @param dir the expansion direction */ public void setHoverExpand(float scale, Expand dir) { - this.hoverAction = HoverAction.EXPAND; + hoverEffect |= EFFECT_EXPAND; this.hoverScale = scale; this.dir = dir; } /** - * Sets the hover action to "fade". + * Sets the "fade" hover effect. */ - public void setHoverFade() { this.hoverAction = HoverAction.FADE; } + public void setHoverFade() { hoverEffect |= EFFECT_FADE; } /** - * Sets the hover action to "fade". + * Sets the "fade" hover effect. * @param baseAlpha the base alpha level to fade in from (default 0.7f) */ public void setHoverFade(float baseAlpha) { - this.hoverAction = HoverAction.FADE; + hoverEffect |= EFFECT_FADE; this.baseAlpha = baseAlpha; } + /** + * Sets the "rotate" hover effect. + */ + public void setHoverRotate() { hoverEffect |= EFFECT_ROTATE; } + + /** + * Sets the "rotate" hover effect. + * @param maxAngle the maximum rotation angle, in degrees (default 30f) + */ + public void setHoverRotate(float maxAngle) { + hoverEffect |= EFFECT_ROTATE; + this.maxAngle = maxAngle; + } + /** * Processes a hover action depending on whether or not the cursor * is hovering over the button. @@ -313,31 +315,48 @@ public class MenuButton { * @param cy the y coordinate */ public void hoverUpdate(int delta, float cx, float cy) { - if (hoverAction == HoverAction.NONE) + if (hoverEffect == 0) return; - boolean isHover = contains(cx, cy); - if (hoverAction == HoverAction.EXPAND) { - // scale the button - int sign; + + // scale the button + if ((hoverEffect & EFFECT_EXPAND) > 0) { + int sign = 0; if (isHover && scale < hoverScale) sign = 1; else if (!isHover && scale > 1f) sign = -1; - else - return; - scale = Utils.getBoundedValue(scale, sign * (hoverScale - 1f) * delta / 100f, 1, hoverScale); - setHoverRadius(); - } else { - // fade the button - int sign; + if (sign != 0) { + scale = Utils.getBoundedValue(scale, sign * (hoverScale - 1f) * delta / 100f, 1, hoverScale); + setHoverRadius(); + } + } + + // fade the button + if ((hoverEffect & EFFECT_FADE) > 0) { + int sign = 0; if (isHover && alpha < 1f) sign = 1; else if (!isHover && alpha > baseAlpha) sign = -1; - else - return; - alpha = Utils.getBoundedValue(alpha, sign * (1f - baseAlpha) * delta / 200f, baseAlpha, 1f); + if (sign != 0) + alpha = Utils.getBoundedValue(alpha, sign * (1f - baseAlpha) * delta / 200f, baseAlpha, 1f); + } + + // rotate the button + if ((hoverEffect & EFFECT_ROTATE) > 0) { + int sign = 0; + boolean right = (maxAngle > 0); + if (isHover && angle != maxAngle) + sign = (right) ? 1 : -1; + else if (!isHover && angle != 0) + sign = (right) ? -1 : 1; + if (sign != 0) { + float diff = sign * Math.abs(maxAngle) * delta / 125f; + angle = (right) ? + Utils.getBoundedValue(angle, diff, 0, maxAngle) : + Utils.getBoundedValue(angle, diff, maxAngle, 0); + } } } diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index 2d126c37..9d3ce39c 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -213,8 +213,7 @@ public class Utils { } // initialize game mods - for (GameMod mod : GameMod.values()) - mod.init(width, height); + GameMod.init(width, height); // initialize hit objects OsuHitObject.init(width, height); @@ -281,6 +280,34 @@ public class Utils { anim.draw(x - (anim.getWidth() / 2f), y - (anim.getHeight() / 2f)); } + /** + * Draws an image at the given location. + * @param img the image to draw + * @param x the x coordinate + * @param y the y coordinate + * @param color the color filter to apply + */ + public static void draw(Image img, float x, float y, Color color) { + if (color == null) + img.draw(x, y); + else + img.draw(x, y, color); + } + + /** + * Draws an animation at the given location. + * @param anim the animation to draw + * @param x the x coordinate + * @param y the y coordinate + * @param color the color filter to apply + */ + public static void draw(Animation anim, float x, float y, Color color) { + if (color == null) + anim.draw(x, y); + else + anim.draw(x, y, color); + } + /** * Returns a bounded value for a base value and displacement. * @param base the initial value diff --git a/src/itdelatrisu/opsu/states/ButtonMenu.java b/src/itdelatrisu/opsu/states/ButtonMenu.java index f9efe3d4..76d33a53 100644 --- a/src/itdelatrisu/opsu/states/ButtonMenu.java +++ b/src/itdelatrisu/opsu/states/ButtonMenu.java @@ -19,6 +19,7 @@ package itdelatrisu.opsu.states; import itdelatrisu.opsu.GameImage; +import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.MenuButton; import itdelatrisu.opsu.Opsu; import itdelatrisu.opsu.OsuGroupList; @@ -121,6 +122,95 @@ public class ButtonMenu extends BasicGameState { public void leave(GameContainer container, StateBasedGame game) { Button.CLOSE.click(container, game); } + }, + MODS (new Button[] { Button.RESET_MODS, Button.CLOSE }) { + @Override + public String[] getTitle(GameContainer container, StateBasedGame game) { + return new String[] { + "Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun." + }; + } + + @Override + protected float getBaseY(GameContainer container, StateBasedGame game) { + return container.getHeight() * 2f / 3; + } + + @Override + public void enter(GameContainer container, StateBasedGame game) { + super.enter(container, game); + for (GameMod mod : GameMod.values()) + mod.resetHover(); + } + + @Override + public void leave(GameContainer container, StateBasedGame game) { + Button.CLOSE.click(container, game); + } + + @Override + public void draw(GameContainer container, StateBasedGame game, Graphics g) { + super.draw(container, game, g); + + int width = container.getWidth(); + int height = container.getHeight(); + + // score multiplier (TODO: fade in color changes) + float mult = GameMod.getScoreMultiplier(); + String multString = String.format("Score Multiplier: %.2fx", mult); + Color multColor = (mult == 1f) ? Color.white : (mult > 1f) ? Color.green : Color.red; + float multY = Utils.FONT_LARGE.getLineHeight() * 2 + height * 0.06f; + Utils.FONT_LARGE.drawString( + (width - Utils.FONT_LARGE.getWidth(multString)) / 2f, + multY, multString, multColor); + + // category text + for (GameMod.Category category : GameMod.Category.values()) { + Utils.FONT_LARGE.drawString(category.getX(), + category.getY() - Utils.FONT_LARGE.getLineHeight() / 2f, + category.getName(), category.getColor()); + } + + // buttons (TODO: draw descriptions when hovering) + for (GameMod mod : GameMod.values()) + mod.draw(); + } + + @Override + public void update(GameContainer container, int delta, int mouseX, int mouseY) { + super.update(container, delta, mouseX, mouseY); + for (GameMod mod : GameMod.values()) { + if (mod.isActive()) + mod.hoverUpdate(delta, mod.getButtonX(), mod.getButtonY()); + else + mod.hoverUpdate(delta, -1, -1); + } + } + + @Override + public void keyPress(GameContainer container, StateBasedGame game, int key, char c) { + super.keyPress(container, game, key, c); + for (GameMod mod : GameMod.values()) { + if (key == mod.getKey()) { + mod.toggle(true); + break; + } + } + } + + @Override + public void click(GameContainer container, StateBasedGame game, int cx, int cy) { + super.click(container, game, cx, cy); + for (GameMod mod : GameMod.values()) { + if (mod.contains(cx, cy)) { + boolean prevState = mod.isActive(); + mod.toggle(true); + if (mod.isActive() != prevState) + SoundController.playSound(SoundEffect.MENUCLICK); + return; + } + } + } }; /** The buttons in the state. */ @@ -153,23 +243,29 @@ public class ButtonMenu extends BasicGameState { */ public void init(GameContainer container, StateBasedGame game, Image button, Image buttonL, Image buttonR) { float center = container.getWidth() / 2f; - float centerOffset = container.getWidth() * OFFSET_WIDTH_RATIO; - float baseY = container.getHeight() * 0.2f; - baseY += ((getTitle(container, game).length - 1) * Utils.FONT_LARGE.getLineHeight()); + float baseY = getBaseY(container, game); float offsetY = button.getHeight() * 1.25f; menuButtons = new MenuButton[buttons.length]; for (int i = 0; i < buttons.length; i++) { - MenuButton b = new MenuButton(button, buttonL, buttonR, - center + ((i % 2 == 0) ? centerOffset * -1 : centerOffset), - baseY + (i * offsetY)); - b.setText(String.format("%d. %s", i + 1, buttons[i].getText()), - Utils.FONT_XLARGE, Color.white); + MenuButton b = new MenuButton(button, buttonL, buttonR, center, baseY + (i * offsetY)); + b.setText(String.format("%d. %s", i + 1, buttons[i].getText()), Utils.FONT_XLARGE, Color.white); b.setHoverFade(); menuButtons[i] = b; } } + /** + * Returns the base Y coordinate for the buttons. + * @param container the game container + * @param game the game + */ + protected float getBaseY(GameContainer container, StateBasedGame game) { + float baseY = container.getHeight() * 0.2f; + baseY += ((getTitle(container, game).length - 1) * Utils.FONT_LARGE.getLineHeight()); + return baseY; + } + /** * Draws the title and buttons to the graphics context. * @param container the game container @@ -231,13 +327,14 @@ public class ButtonMenu extends BasicGameState { } /** - * Processes a key press action (numeric digits only). + * Processes a key press action. * @param container the game container * @param game the game - * @param digit the digit pressed + * @param key the key code that was pressed (see {@link org.newdawn.slick.Input}) + * @param c the character of the key that was pressed */ - public void keyPress(GameContainer container, StateBasedGame game, int digit) { - int index = digit - 1; + public void keyPress(GameContainer container, StateBasedGame game, int key, char c) { + int index = Character.getNumericValue(c) - 1; if (index >= 0 && index < buttons.length) buttons[index].click(container, game); } @@ -384,6 +481,16 @@ public class ButtonMenu extends BasicGameState { public void click(GameContainer container, StateBasedGame game) { CANCEL.click(container, game); } + }, + RESET_MODS ("Reset All Mods", Color.red) { + @Override + public void click(GameContainer container, StateBasedGame game) { + SoundController.playSound(SoundEffect.MENUHIT); + for (GameMod mod : GameMod.values()) { + if (mod.isActive()) + mod.toggle(false); + } + } }; /** The text to show on the button. */ @@ -500,7 +607,7 @@ public class ButtonMenu extends BasicGameState { break; default: if (menuState != null) - menuState.keyPress(container, game, Character.getNumericValue(c)); + menuState.keyPress(container, game, key, c); break; } } diff --git a/src/itdelatrisu/opsu/states/DownloadsMenu.java b/src/itdelatrisu/opsu/states/DownloadsMenu.java index 11848d09..ab578dc0 100644 --- a/src/itdelatrisu/opsu/states/DownloadsMenu.java +++ b/src/itdelatrisu/opsu/states/DownloadsMenu.java @@ -229,6 +229,7 @@ public class DownloadsMenu extends BasicGameState { // search g.setColor(Color.white); + g.setLineWidth(2f); search.render(container, g); Utils.FONT_BOLD.drawString( search.getX() + search.getWidth() * 0.01f, search.getY() + search.getHeight() * 1.3f, diff --git a/src/itdelatrisu/opsu/states/OptionsMenu.java b/src/itdelatrisu/opsu/states/OptionsMenu.java index 11fae815..4e4afc5a 100644 --- a/src/itdelatrisu/opsu/states/OptionsMenu.java +++ b/src/itdelatrisu/opsu/states/OptionsMenu.java @@ -19,7 +19,6 @@ package itdelatrisu.opsu.states; import itdelatrisu.opsu.GameImage; -import itdelatrisu.opsu.GameMod; import itdelatrisu.opsu.MenuButton; import itdelatrisu.opsu.Opsu; import itdelatrisu.opsu.Options; @@ -171,10 +170,6 @@ public class OptionsMenu extends BasicGameState { int width = container.getWidth(); int height = container.getHeight(); - // game option coordinate modifiers - textY = 20 + (Utils.FONT_XLARGE.getLineHeight() * 3 / 2); - offsetY = (int) (((height * 0.8f) - textY) / maxOptionsScreen); - // option tabs Image tabImage = GameImage.MENU_TAB.getImage(); int subtextWidth = Utils.FONT_DEFAULT.getWidth("Click or drag an option to change it."); @@ -184,6 +179,10 @@ public class OptionsMenu extends BasicGameState { ((width - subtextWidth - tabImage.getWidth()) / 2) / OptionTab.SIZE); for (OptionTab tab : OptionTab.values()) tab.button = new MenuButton(tabImage, tabX + (tab.ordinal() * tabOffset), tabY); + + // game option coordinate modifiers + textY = (int) (tabY + tabImage.getHeight()); + offsetY = (height - textY - GameImage.MENU_BACK.getImage().getHeight()) / maxOptionsScreen; } @Override @@ -232,21 +231,6 @@ public class OptionsMenu extends BasicGameState { g.drawLine(0, lineY, width, lineY); g.resetLineWidth(); - // game mods - Utils.FONT_LARGE.drawString(width / 30, height * 0.8f, "Game Mods:", Color.white); - boolean descDrawn = false; - for (GameMod mod : GameMod.values()) { - mod.draw(); - if (!descDrawn && mod.contains(mouseX, mouseY)) { - Utils.FONT_DEFAULT.drawString( - (width - Utils.FONT_DEFAULT.getWidth(mod.getDescription())) / 2, - height * 0.975f - Utils.FONT_DEFAULT.getLineHeight(), - mod.getDescription(), Color.white - ); - descDrawn = true; - } - } - Utils.getBackButton().draw(); // key entry state @@ -272,8 +256,6 @@ public class OptionsMenu extends BasicGameState { Utils.updateVolumeDisplay(delta); int mouseX = input.getMouseX(), mouseY = input.getMouseY(); Utils.getBackButton().hoverUpdate(delta, mouseX, mouseY); - for (GameMod mod : GameMod.values()) - mod.hoverUpdate(delta, mouseX, mouseY); } @Override @@ -309,17 +291,6 @@ public class OptionsMenu extends BasicGameState { } } - // game mods - for (GameMod mod : GameMod.values()) { - if (mod.contains(x, y)) { - boolean prevState = mod.isActive(); - mod.toggle(true); - if (mod.isActive() != prevState) - SoundController.playSound(SoundEffect.MENUCLICK); - return; - } - } - // options (click only) GameOption option = getClickedOption(y); if (option != GameOption.NULL) @@ -400,15 +371,6 @@ public class OptionsMenu extends BasicGameState { currentTab = currentTab.next(); SoundController.playSound(SoundEffect.MENUCLICK); break; - default: - // check mod shortcut keys - for (GameMod mod : GameMod.values()) { - if (key == mod.getKey()) { - mod.toggle(true); - break; - } - } - break; } } @@ -417,8 +379,6 @@ public class OptionsMenu extends BasicGameState { throws SlickException { currentTab = OptionTab.DISPLAY; Utils.getBackButton().resetHover(); - for (GameMod mod : GameMod.values()) - mod.resetHover(); } /** diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index d616125c..84f0c1cd 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -359,9 +359,8 @@ public class SongMenu extends BasicGameState { } // selection buttons - // TODO -// GameImage.SELECTION_MODS.getImage().drawCentered(selectModsButton.getX(), selectModsButton.getY()); -// selectModsButton.draw(); + GameImage.SELECTION_MODS.getImage().drawCentered(selectModsButton.getX(), selectModsButton.getY()); + selectModsButton.draw(); GameImage.SELECTION_RANDOM.getImage().drawCentered(selectRandomButton.getX(), selectRandomButton.getY()); selectRandomButton.draw(); GameImage.SELECTION_OPTIONS.getImage().drawCentered(selectMapOptionsButton.getX(), selectMapOptionsButton.getY()); @@ -714,9 +713,9 @@ public class SongMenu extends BasicGameState { } break; case Input.KEY_F1: - // TODO: mods menu -// SoundController.playSound(SoundEffect.MENUHIT); -// game.enterState(); + SoundController.playSound(SoundEffect.MENUHIT); + ((ButtonMenu) game.getState(Opsu.STATE_BUTTONMENU)).setMenuState(MenuState.MODS); + game.enterState(Opsu.STATE_BUTTONMENU); break; case Input.KEY_F2: if (focusNode == null)