Added initial support for loading beatmap skins.

- Added a GameImage enum for more organized loading of image resources.
- Game image loading now takes place directly before each beatmap is loaded.
- Added option 'IGNORE_BEATMAP_SKINS' to disable this feature.

Other changes:
- Slight correction in readme file: apparently the JAR will not run in the osu! program folder.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2014-07-04 16:41:52 -04:00
parent c72b9b955a
commit 16afcaf3e6
10 changed files with 536 additions and 346 deletions

View File

@ -17,10 +17,9 @@ opsu! also requires beatmaps to run, which are available for download on the
[osu!Mirror](https://osu.yas-online.net/) or [Bloodcat](http://bloodcat.com/osu/).
If osu! is already installed, this application will attempt to load songs
directly from the osu! program folder. Otherwise, run this application from
one directory above the root song directory, or place songs in the generated
`songs` folder. This path can be changed at any time by editing the
`BeatmapDirectory` value in the generated configuration file.
directly from the osu! program folder. Otherwise, place songs in the generated
`Songs` folder or set the `BeatmapDirectory` value in the generated
configuration file to the path of the root song directory.
### First Run
The `Music Offset` value will likely need to be adjusted when playing for the

View File

@ -0,0 +1,178 @@
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014 Jeffrey Han
*
* opsu! is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* opsu! is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu;
import itdelatrisu.opsu.states.Options;
import java.io.File;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.util.Log;
/**
* Game images.
*/
public enum GameImage {
// Game
SECTION_PASS ("section-pass.png"),
SECTION_FAIL ("section-fail.png"),
WARNINGARROW ("play-warningarrow.png"),
SKIP ("play-skip.png"),
COUNTDOWN_READY ("ready.png"),
COUNTDOWN_3 ("count3.png"),
COUNTDOWN_2 ("count2.png"),
COUNTDOWN_1 ("count1.png"),
COUNTDOWN_GO ("go.png"),
HITCIRCLE_SELECT ("hitcircleselect.png"),
UNRANKED ("play-unranked.png"),
// Game Pause/Fail
PAUSE_CONTINUE ("pause-continue.png"),
PAUSE_RETRY ("pause-retry.png"),
PAUSE_BACK ("pause-back.png"),
PAUSE_OVERLAY ("pause-overlay.png"),
FAIL_BACKGROUND ("fail-background.png"),
// Circle
HITCIRCLE ("hitcircle.png"),
HITCIRCLE_OVERLAY ("hitcircleoverlay.png"),
APPROACHCIRCLE ("approachcircle.png"),
// Slider
SLIDER_FOLLOWCIRCLE ("sliderfollowcircle.png"),
REVERSEARROW ("reversearrow.png"),
SLIDER_TICK ("sliderscorepoint.png"),
// Spinner
SPINNER_CIRCLE ("spinner-circle.png"),
SPINNER_APPROACHCIRCLE ("spinner-approachcircle.png"),
SPINNER_METRE ("spinner-metre.png"),
SPINNER_SPIN ("spinner-spin.png"),
SPINNER_CLEAR ("spinner-clear.png"),
SPINNER_OSU ("spinner-osu.png"),
// Game Score
SCOREBAR_BG ("scorebar-bg.png"),
SCOREBAR_COLOUR ("scorebar-colour.png"),
SCOREBAR_KI ("scorebar-ki.png"),
SCOREBAR_KI_DANGER ("scorebar-kidanger.png"),
SCOREBAR_KI_DANGER2 ("scorebar-kidanger2.png"),
HIT_MISS ("hit0.png"),
HIT_50 ("hit50.png"),
HIT_100 ("hit100.png"),
HIT_300 ("hit300.png"),
HIT_100K ("hit100k.png"),
HIT_300K ("hit300k.png"),
HIT_300G ("hit300g.png"),
HIT_SLIDER10 ("sliderpoint10.png"),
HIT_SLIDER30 ("sliderpoint30.png"),
RANKING_SS ("ranking-X.png"),
RANKING_SS_SMALL ("ranking-X-small.png"),
RANKING_SSH ("ranking-XH.png"),
RANKING_SSH_SMALL ("ranking-XH-small.png"),
RANKING_S ("ranking-S.png"),
RANKING_S_SMALL ("ranking-S-small.png"),
RANKING_SH ("ranking-SH.png"),
RANKING_SH_SMALL ("ranking-SH-small.png"),
RANKING_A ("ranking-A.png"),
RANKING_A_SMALL ("ranking-A-small.png"),
RANKING_B ("ranking-B.png"),
RANKING_B_SMALL ("ranking-B-small.png"),
RANKING_C ("ranking-C.png"),
RANKING_C_SMALL ("ranking-C-small.png"),
RANKING_D ("ranking-D.png"),
RANKING_D_SMALL ("ranking-D-small.png"),
RANKING_PANEL ("ranking-panel.png"),
RANKING_PERFECT ("ranking-perfect.png"),
RANKING_TITLE ("ranking-title.png"),
RANKING_MAXCOMBO ("ranking-maxcombo.png"),
RANKING_ACCURACY ("ranking-accuracy.png"),
DEFAULT_0 ("default-0.png"),
DEFAULT_1 ("default-1.png"),
DEFAULT_2 ("default-2.png"),
DEFAULT_3 ("default-3.png"),
DEFAULT_4 ("default-4.png"),
DEFAULT_5 ("default-5.png"),
DEFAULT_6 ("default-6.png"),
DEFAULT_7 ("default-7.png"),
DEFAULT_8 ("default-8.png"),
DEFAULT_9 ("default-9.png"),
SCORE_0 ("score-0.png"),
SCORE_1 ("score-1.png"),
SCORE_2 ("score-2.png"),
SCORE_3 ("score-3.png"),
SCORE_4 ("score-4.png"),
SCORE_5 ("score-5.png"),
SCORE_6 ("score-6.png"),
SCORE_7 ("score-7.png"),
SCORE_8 ("score-8.png"),
SCORE_9 ("score-9.png"),
SCORE_COMMA ("score-comma.png"),
SCORE_DOT ("score-dot.png"),
SCORE_PERCENT ("score-percent.png"),
SCORE_X ("score-x.png");
/**
* The file name.
*/
private String filename;
/**
* The associated image.
*/
private Image img;
/**
* Constructor.
*/
GameImage(String filename) {
this.filename = filename;
}
/**
* Returns the associated image.
*/
public Image getImage() { return img; }
/**
* Sets an image.
*/
public void setImage(Image img) { this.img = img; }
/**
* Sets an image.
* Scans the path for the image first, then uses the default image.
*/
public void setImage(File dir) {
try {
// destroy the existing image, if any
if (img != null && !img.isDestroyed())
img.destroy();
// set a new image
File file = new File(dir, filename);
if (file.isFile() && !Options.isBeatmapSkinIgnored())
img = new Image(file.getAbsolutePath());
else
img = new Image(filename);
} catch (SlickException e) {
Log.error(String.format("Failed to set image '%s'.", filename), e);
}
}
}

View File

@ -20,6 +20,7 @@ package itdelatrisu.opsu;
import itdelatrisu.opsu.states.Options;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
@ -28,7 +29,6 @@ import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.util.Log;
/**
* Holds score data and renders all score-related elements.
@ -179,26 +179,6 @@ public class GameScore {
*/
private float difficulty = 5f;
/**
* Scorebar-related images.
*/
private Image
bgImage, // background (always rendered)
colourImage, // health bar (cropped)
kiImage, // end image (50~100% health)
kiDangerImage, // end image (25~50% health)
kiDanger2Image; // end image (0~25% health)
/**
* Ranking screen images.
*/
private Image
rankingPanel, // panel to display text in
perfectImage, // display if full combo
rankingImage, // styled text "Ranking"
comboImage, // styled text "Combo"
accuracyImage; // styled text "Accuracy"
/**
* Default text symbol images.
*/
@ -233,20 +213,7 @@ public class GameScore {
this.width = width;
this.height = height;
hitResults = new Image[HIT_MAX];
defaultSymbols = new Image[10];
scoreSymbols = new HashMap<Character, Image>(14);
gradesLarge = new Image[GRADE_MAX];
gradesSmall = new Image[GRADE_MAX];
comboBurstImages = new Image[4];
clear();
try {
initializeImages();
} catch (Exception e) {
Log.error("Failed to initialize images.", e);
}
}
/**
@ -267,100 +234,150 @@ public class GameScore {
}
/**
* Initialize all images tied to this object.
* Loads all game score images.
* @throws SlickException
*/
private void initializeImages() throws SlickException {
// scorebar
setScorebarImage(
new Image("scorebar-bg.png"),
new Image("scorebar-colour.png"),
new Image("scorebar-ki.png"),
new Image("scorebar-kidanger.png"),
new Image("scorebar-kidanger2.png")
);
// text symbol images
for (int i = 0; i <= 9; i++) {
defaultSymbols[i] = new Image(String.format("default-%d.png", i));
scoreSymbols.put(Character.forDigit(i, 10), new Image(String.format("score-%d.png", i)));
}
scoreSymbols.put(',', new Image("score-comma.png"));
scoreSymbols.put('.', new Image("score-dot.png"));
scoreSymbols.put('%', new Image("score-percent.png"));
scoreSymbols.put('x', new Image("score-x.png"));
// hit result images
hitResults[HIT_MISS] = new Image("hit0.png");
hitResults[HIT_50] = new Image("hit50.png");
hitResults[HIT_100] = new Image("hit100.png");
hitResults[HIT_300] = new Image("hit300.png");
hitResults[HIT_100K] = new Image("hit100k.png");
hitResults[HIT_300K] = new Image("hit300k.png");
hitResults[HIT_300G] = new Image("hit300g.png");
hitResults[HIT_SLIDER10] = new Image("sliderpoint10.png");
hitResults[HIT_SLIDER30] = new Image("sliderpoint30.png");
public void loadImages() throws SlickException {
File dir = MusicController.getOsuFile().getFile().getParentFile();
// combo burst images
for (int i = 0; i <= 3; i++)
comboBurstImages[i] = new Image(String.format("comboburst-%d.png", i));
if (comboBurstImages != null) {
for (int i = 0; i < comboBurstImages.length; i++) {
if (!comboBurstImages[i].isDestroyed())
comboBurstImages[i].destroy();
}
}
LinkedList<Image> comboBurst = new LinkedList<Image>();
String comboFormat = "comboburst-%d.png";
int comboIndex = 0;
File comboFile = new File(dir, "comboburst.png");
File comboFileN = new File(dir, String.format(comboFormat, comboIndex));
if (comboFileN.isFile()) { // beatmap provides images
do {
comboBurst.add(new Image(comboFileN.getAbsolutePath()));
comboFileN = new File(dir, String.format(comboFormat, ++comboIndex));
} while (comboFileN.isFile());
} else if (comboFile.isFile()) // beatmap provides single image
comboBurst.add(new Image(comboFile.getAbsolutePath()));
else { // load default images
while (true) {
try {
Image comboImage = new Image(String.format(comboFormat, comboIndex++));
comboBurst.add(comboImage);
} catch (Exception e) {
break;
}
}
}
comboBurstImages = comboBurst.toArray(new Image[comboBurst.size()]);
// lighting image
try {
lighting = new Image("lighting.png");
lighting1 = new Image("lighting1.png");
} catch (Exception e) {
// optional
if (lighting != null && !lighting.isDestroyed()) {
lighting.destroy();
lighting = null;
}
if (lighting1 != null && !lighting1.isDestroyed()) {
lighting1.destroy();
lighting1 = null;
}
File lightingFile = new File(dir, "lighting.png");
File lighting1File = new File(dir, "lighting1.png");
if (lightingFile.isFile()) { // beatmap provides images
try {
lighting = new Image(lightingFile.getAbsolutePath());
lighting1 = new Image(lighting1File.getAbsolutePath());
} catch (Exception e) {
// optional
}
} else { // load default image
try {
lighting = new Image("lighting.png");
lighting1 = new Image("lighting1.png");
} catch (Exception e) {
// optional
}
}
// scorebar
Image bg = GameImage.SCOREBAR_BG.getImage();
Image colour = GameImage.SCOREBAR_COLOUR.getImage();
int bgWidth = width / 2;
GameImage.SCOREBAR_BG.setImage(bg.getScaledCopy(bgWidth, bg.getHeight()));
GameImage.SCOREBAR_COLOUR.setImage(colour.getScaledCopy(bgWidth, colour.getHeight()));
// default symbol images
defaultSymbols = new Image[10];
defaultSymbols[0] = GameImage.DEFAULT_0.getImage();
defaultSymbols[1] = GameImage.DEFAULT_1.getImage();
defaultSymbols[2] = GameImage.DEFAULT_2.getImage();
defaultSymbols[3] = GameImage.DEFAULT_3.getImage();
defaultSymbols[4] = GameImage.DEFAULT_4.getImage();
defaultSymbols[5] = GameImage.DEFAULT_5.getImage();
defaultSymbols[6] = GameImage.DEFAULT_6.getImage();
defaultSymbols[7] = GameImage.DEFAULT_7.getImage();
defaultSymbols[8] = GameImage.DEFAULT_8.getImage();
defaultSymbols[9] = GameImage.DEFAULT_9.getImage();
// score symbol images
scoreSymbols = new HashMap<Character, Image>(14);
scoreSymbols.put('0', GameImage.SCORE_0.getImage());
scoreSymbols.put('1', GameImage.SCORE_1.getImage());
scoreSymbols.put('2', GameImage.SCORE_2.getImage());
scoreSymbols.put('3', GameImage.SCORE_3.getImage());
scoreSymbols.put('4', GameImage.SCORE_4.getImage());
scoreSymbols.put('5', GameImage.SCORE_5.getImage());
scoreSymbols.put('6', GameImage.SCORE_6.getImage());
scoreSymbols.put('7', GameImage.SCORE_7.getImage());
scoreSymbols.put('8', GameImage.SCORE_8.getImage());
scoreSymbols.put('9', GameImage.SCORE_9.getImage());
scoreSymbols.put(',', GameImage.SCORE_COMMA.getImage());
scoreSymbols.put('.', GameImage.SCORE_DOT.getImage());
scoreSymbols.put('%', GameImage.SCORE_PERCENT.getImage());
scoreSymbols.put('x', GameImage.SCORE_X.getImage());
// hit result images
hitResults = new Image[HIT_MAX];
hitResults[HIT_MISS] = GameImage.HIT_MISS.getImage();
hitResults[HIT_50] = GameImage.HIT_50.getImage();
hitResults[HIT_100] = GameImage.HIT_100.getImage();
hitResults[HIT_300] = GameImage.HIT_300.getImage();
hitResults[HIT_100K] = GameImage.HIT_100K.getImage();
hitResults[HIT_300K] = GameImage.HIT_300K.getImage();
hitResults[HIT_300G] = GameImage.HIT_300G.getImage();
hitResults[HIT_SLIDER10] = GameImage.HIT_SLIDER10.getImage();
hitResults[HIT_SLIDER30] = GameImage.HIT_SLIDER30.getImage();
// letter grade images
String[] grades = { "X", "XH", "S", "SH", "A", "B", "C", "D" };
for (int i = 0; i < grades.length; i++) {
gradesLarge[i] = new Image(String.format("ranking-%s.png", grades[i]));
gradesSmall[i] = new Image(String.format("ranking-%s-small.png", grades[i]));
}
gradesLarge = new Image[GRADE_MAX];
gradesSmall = new Image[GRADE_MAX];
gradesLarge[GRADE_SS] = GameImage.RANKING_SS.getImage();
gradesSmall[GRADE_SS] = GameImage.RANKING_SS_SMALL.getImage();
gradesLarge[GRADE_SSH] = GameImage.RANKING_SSH.getImage();
gradesSmall[GRADE_SSH] = GameImage.RANKING_SSH_SMALL.getImage();
gradesLarge[GRADE_S] = GameImage.RANKING_S.getImage();
gradesSmall[GRADE_S] = GameImage.RANKING_S_SMALL.getImage();
gradesLarge[GRADE_SH] = GameImage.RANKING_SH.getImage();
gradesSmall[GRADE_SH] = GameImage.RANKING_SH_SMALL.getImage();
gradesLarge[GRADE_A] = GameImage.RANKING_A.getImage();
gradesSmall[GRADE_A] = GameImage.RANKING_A_SMALL.getImage();
gradesLarge[GRADE_B] = GameImage.RANKING_B.getImage();
gradesSmall[GRADE_B] = GameImage.RANKING_B_SMALL.getImage();
gradesLarge[GRADE_C] = GameImage.RANKING_C.getImage();
gradesSmall[GRADE_C] = GameImage.RANKING_C_SMALL.getImage();
gradesLarge[GRADE_D] = GameImage.RANKING_D.getImage();
gradesSmall[GRADE_D] = GameImage.RANKING_D_SMALL.getImage();
// ranking screen elements
setRankingImage(
new Image("ranking-panel.png"),
new Image("ranking-perfect.png"),
new Image("ranking-title.png"),
new Image("ranking-maxcombo.png"),
new Image("ranking-accuracy.png")
);
}
/**
* Sets a background, health bar, and end image.
* @param bgImage background image
* @param colourImage health bar image
* @param kiImage end image
*/
public void setScorebarImage(Image bg, Image colour,
Image ki, Image kiDanger, Image kiDanger2) {
int bgWidth = width / 2;
this.bgImage = bg.getScaledCopy(bgWidth, bg.getHeight());
this.colourImage = colour.getScaledCopy(bgWidth, colour.getHeight());
this.kiImage = ki;
this.kiDangerImage = kiDanger;
this.kiDanger2Image = kiDanger2;
}
/**
* Sets a ranking panel, full combo, and ranking/combo/accuracy text image.
* @param rankingPanel ranking panel image
* @param perfectImage full combo image
* @param rankingImage styled text "Ranking"
* @param comboImage styled text "Combo"
* @param accuracyImage styled text "Accuracy"
*/
public void setRankingImage(Image rankingPanel, Image perfectImage,
Image rankingImage, Image comboImage, Image accuracyImage) {
this.rankingPanel = rankingPanel.getScaledCopy((height * 0.63f) / rankingPanel.getHeight());
this.perfectImage = perfectImage.getScaledCopy((height * 0.16f) / perfectImage.getHeight());
this.rankingImage = rankingImage.getScaledCopy((height * 0.15f) / rankingImage.getHeight());
this.comboImage = comboImage.getScaledCopy((height * 0.05f) / comboImage.getHeight());
this.accuracyImage = accuracyImage.getScaledCopy((height * 0.05f) / accuracyImage.getHeight());
Image rankingPanel = GameImage.RANKING_PANEL.getImage();
Image rankingPerfect = GameImage.RANKING_PERFECT.getImage();
Image rankingTitle = GameImage.RANKING_TITLE.getImage();
Image rankingMaxCombo = GameImage.RANKING_MAXCOMBO.getImage();
Image rankingAccuracy = GameImage.RANKING_ACCURACY.getImage();
GameImage.RANKING_PANEL.setImage(rankingPanel.getScaledCopy((height * 0.63f) / rankingPanel.getHeight()));
GameImage.RANKING_PERFECT.setImage(rankingPerfect.getScaledCopy((height * 0.16f) / rankingPerfect.getHeight()));
GameImage.RANKING_TITLE.setImage(rankingTitle.getScaledCopy((height * 0.15f) / rankingTitle.getHeight()));
GameImage.RANKING_MAXCOMBO.setImage(rankingMaxCombo.getScaledCopy((height * 0.05f) / rankingMaxCombo.getHeight()));
GameImage.RANKING_ACCURACY.setImage(rankingAccuracy.getScaledCopy((height * 0.05f) / rankingAccuracy.getHeight()));
}
/**
@ -469,15 +486,19 @@ public class GameScore {
if (firstObjectTime >= 1500 && trackPosition < firstObjectTime - 500)
healthRatio = (float) trackPosition / (firstObjectTime - 500);
}
bgImage.draw(0, 0);
Image colourCropped = colourImage.getSubImage(0, 0, (int) (colourImage.getWidth() * healthRatio), colourImage.getHeight());
colourCropped.draw(0, bgImage.getHeight() / 4f);
GameImage.SCOREBAR_BG.getImage().draw(0, 0);
Image colour = GameImage.SCOREBAR_COLOUR.getImage();
Image colourCropped = colour.getSubImage(0, 0, (int) (colour.getWidth() * healthRatio), colour.getHeight());
colourCropped.draw(0, GameImage.SCOREBAR_BG.getImage().getHeight() / 4f);
if (health >= 50f)
kiImage.drawCentered(colourCropped.getWidth(), kiImage.getHeight() / 2f);
GameImage.SCOREBAR_KI.getImage().drawCentered(
colourCropped.getWidth(), GameImage.SCOREBAR_KI.getImage().getHeight() / 2f);
else if (health >= 25f)
kiDangerImage.drawCentered(colourCropped.getWidth(), kiDangerImage.getHeight() / 2f);
GameImage.SCOREBAR_KI_DANGER.getImage().drawCentered(
colourCropped.getWidth(), GameImage.SCOREBAR_KI_DANGER.getImage().getHeight() / 2f);
else
kiDanger2Image.drawCentered(colourCropped.getWidth(), kiDanger2Image.getHeight() / 2f);
GameImage.SCOREBAR_KI_DANGER2.getImage().drawCentered(
colourCropped.getWidth(), GameImage.SCOREBAR_KI_DANGER2.getImage().getHeight() / 2f);
// combo burst
if (comboBurstIndex != -1 && comboBurstAlpha > 0f) {
@ -513,12 +534,14 @@ public class GameScore {
grade.draw(width - grade.getWidth(), height * 0.09f);
// header & "Ranking" text
float rankingHeight = (rankingImage.getHeight() * 0.75f) + 3;
Image rankingTitle = GameImage.RANKING_TITLE.getImage();
float rankingHeight = (rankingTitle.getHeight() * 0.75f) + 3;
g.setColor(Utils.COLOR_BLACK_ALPHA);
g.fillRect(0, 0, width, rankingHeight);
rankingImage.draw((width * 0.97f) - rankingImage.getWidth(), 0);
rankingTitle.draw((width * 0.97f) - rankingTitle.getWidth(), 0);
// ranking panel
Image rankingPanel = GameImage.RANKING_PANEL.getImage();
int rankingPanelWidth = rankingPanel.getWidth();
int rankingPanelHeight = rankingPanel.getHeight();
rankingPanel.draw(0, rankingHeight - (rankingHeight / 10f));
@ -557,19 +580,25 @@ public class GameScore {
}
// combo and accuracy
Image rankingMaxCombo = GameImage.RANKING_MAXCOMBO.getImage();
Image rankingAccuracy = GameImage.RANKING_ACCURACY.getImage();
float textY = rankingHeight + (rankingPanelHeight * 0.87f) - (rankingHeight / 10f);
float numbersX = comboImage.getWidth() * .07f;
float numbersY = textY + comboImage.getHeight() * 0.7f;
comboImage.draw(width * 0.01f, textY);
accuracyImage.draw(rankingPanelWidth / 2f, textY);
float numbersX = rankingMaxCombo.getWidth() * .07f;
float numbersY = textY + rankingMaxCombo.getHeight() * 0.7f;
rankingMaxCombo.draw(width * 0.01f, textY);
rankingAccuracy.draw(rankingPanelWidth / 2f, textY);
drawSymbolString(String.format("%dx", comboMax),
(int) (width * 0.01f + numbersX), (int) numbersY, symbolTextScale, false);
drawSymbolString(String.format("%02.2f%%", getScorePercent()),
(int) (rankingPanelWidth / 2f + numbersX), (int) numbersY, symbolTextScale, false);
// full combo
if (combo == fullObjectCount)
perfectImage.draw(width * 0.08f, (height * 0.99f) - perfectImage.getHeight());
if (combo == fullObjectCount) {
GameImage.RANKING_PERFECT.getImage().draw(
width * 0.08f,
(height * 0.99f) - GameImage.RANKING_PERFECT.getImage().getHeight()
);
}
}
/**

View File

@ -104,7 +104,6 @@ public class Utils {
// game-related variables
private static GameContainer container;
// private static StateBasedGame game;
private static Input input;
// This class should not be instantiated.
@ -119,7 +118,6 @@ public class Utils {
public static void init(GameContainer container, StateBasedGame game)
throws SlickException {
Utils.container = container;
// Utils.game = game;
Utils.input = container.getInput();
// game settings

View File

@ -18,6 +18,7 @@
package itdelatrisu.opsu.objects;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.GameScore;
import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.OsuHitObject;
@ -27,21 +28,12 @@ import itdelatrisu.opsu.states.Options;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
/**
* Data type representing a circle object.
*/
public class Circle {
/**
* Images related to hit circles.
*/
private static Image
hitCircle, // hit circle
hitCircleOverlay, // hit circle overlay
approachCircle; // approach circle
/**
* The associated OsuHitObject.
*/
@ -76,26 +68,11 @@ public class Circle {
public static void init(GameContainer container, float circleSize) throws SlickException {
int diameter = (int) (96 - (circleSize * 8));
diameter = diameter * container.getWidth() / 640; // convert from Osupixels (640x480)
hitCircle = new Image("hitcircle.png").getScaledCopy(diameter, diameter);
hitCircleOverlay = new Image("hitcircleoverlay.png").getScaledCopy(diameter, diameter);
approachCircle = new Image("approachcircle.png").getScaledCopy(diameter, diameter);
GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameter, diameter));
GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(diameter, diameter));
GameImage.APPROACHCIRCLE.setImage(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(diameter, diameter));
}
/**
* Returns the hit circle image.
*/
public static Image getHitCircle() { return hitCircle; }
/**
* Returns the hit circle overlay image.
*/
public static Image getHitCircleOverlay() { return hitCircleOverlay; }
/**
* Returns the approach circle image.
*/
public static Image getApproachCircle() { return approachCircle; }
/**
* Constructor.
* @param hitObject the associated OsuHitObject
@ -121,11 +98,12 @@ public class Circle {
if (timeDiff >= 0) {
float approachScale = 1 + (timeDiff * 2f / game.getApproachTime());
Utils.drawCentered(approachCircle.getScaledCopy(approachScale), hitObject.x, hitObject.y, color);
Utils.drawCentered(hitCircleOverlay, hitObject.x, hitObject.y, Color.white);
Utils.drawCentered(hitCircle, hitObject.x, hitObject.y, color);
Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale),
hitObject.x, hitObject.y, color);
Utils.drawCentered(GameImage.HITCIRCLE_OVERLAY.getImage(), hitObject.x, hitObject.y, Color.white);
Utils.drawCentered(GameImage.HITCIRCLE.getImage(), hitObject.x, hitObject.y, color);
score.drawSymbolNumber(hitObject.comboNumber, hitObject.x, hitObject.y,
hitCircle.getWidth() * 0.40f / score.getDefaultSymbolImage(0).getHeight());
GameImage.HITCIRCLE.getImage().getWidth() * 0.40f / score.getDefaultSymbolImage(0).getHeight());
}
}
@ -162,7 +140,7 @@ public class Circle {
*/
public boolean mousePressed(int x, int y) {
double distance = Math.hypot(hitObject.x - x, hitObject.y - y);
int circleRadius = hitCircle.getWidth() / 2;
int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2;
if (distance < circleRadius) {
int result = hitResult(hitObject.time);
if (result > -1) {

View File

@ -18,6 +18,7 @@
package itdelatrisu.opsu.objects;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.GameScore;
import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.OsuFile;
@ -26,6 +27,8 @@ import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.states.Game;
import itdelatrisu.opsu.states.Options;
import java.io.File;
import org.newdawn.slick.Animation;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
@ -36,14 +39,6 @@ import org.newdawn.slick.SlickException;
* Data type representing a slider object.
*/
public class Slider {
/**
* Images related to sliders.
*/
private static Image
sliderFollowCircle, // slider follow circle
reverseArrow, // reverse arrow (for repeats)
sliderTick; // slider tick
/**
* Slider ball animation.
*/
@ -261,8 +256,8 @@ public class Slider {
* Draws the full Bezier curve to the graphics context.
*/
public void draw() {
Image hitCircle = Circle.getHitCircle();
Image hitCircleOverlay = Circle.getHitCircleOverlay();
Image hitCircle = GameImage.HITCIRCLE.getImage();
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
// draw overlay and hit circle
for (int i = curveX.length - 1; i >= 0; i--)
@ -283,12 +278,38 @@ public class Slider {
int diameter = (int) (96 - (circleSize * 8));
diameter = diameter * container.getWidth() / 640; // convert from Osupixels (640x480)
// slider ball
if (sliderBall != null) {
for (int i = 0; i < sliderBall.getFrameCount(); i++) {
Image img = sliderBall.getImage(i);
if (!img.isDestroyed())
img.destroy();
}
}
sliderBall = new Animation();
for (int i = 0; i <= 9; i++)
sliderBall.addFrame(new Image(String.format("sliderb%d.png", i)).getScaledCopy(diameter * 118 / 128, diameter * 118 / 128), 60);
sliderFollowCircle = new Image("sliderfollowcircle.png").getScaledCopy(diameter * 259 / 128, diameter * 259 / 128);
reverseArrow = new Image("reversearrow.png").getScaledCopy(diameter, diameter);
sliderTick = new Image("sliderscorepoint.png").getScaledCopy(diameter / 4, diameter / 4);
String sliderFormat = "sliderb%d.png";
int sliderIndex = 0;
File dir = MusicController.getOsuFile().getFile().getParentFile();
File slider = new File(dir, String.format(sliderFormat, sliderIndex));
if (slider.isFile()) {
do {
sliderBall.addFrame(new Image(slider.getAbsolutePath()).getScaledCopy(diameter * 118 / 128, diameter * 118 / 128), 60);
slider = new File(dir, String.format(sliderFormat, ++sliderIndex));
} while (slider.isFile());
} else {
while (true) {
try {
Image sliderFrame = new Image(String.format(sliderFormat, sliderIndex++));
sliderBall.addFrame(sliderFrame.getScaledCopy(diameter * 118 / 128, diameter * 118 / 128), 60);
} catch (Exception e) {
break;
}
}
}
GameImage.SLIDER_FOLLOWCIRCLE.setImage(GameImage.SLIDER_FOLLOWCIRCLE.getImage().getScaledCopy(diameter * 259 / 128, diameter * 259 / 128));
GameImage.REVERSEARROW.setImage(GameImage.REVERSEARROW.getImage().getScaledCopy(diameter, diameter));
GameImage.SLIDER_TICK.setImage(GameImage.SLIDER_TICK.getImage().getScaledCopy(diameter / 4, diameter / 4));
sliderMultiplier = osu.sliderMultiplier;
sliderTickRate = osu.sliderTickRate;
@ -310,8 +331,6 @@ public class Slider {
this.comboEnd = comboEnd;
this.bezier = new Bezier();
// calculate slider time and ticks upon first update call
}
/**
@ -322,8 +341,8 @@ public class Slider {
public void draw(int trackPosition, boolean currentObject) {
int timeDiff = hitObject.time - trackPosition;
Image hitCircleOverlay = Circle.getHitCircleOverlay();
Image hitCircle = Circle.getHitCircle();
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
Image hitCircle = GameImage.HITCIRCLE.getImage();
// bezier
bezier.draw();
@ -332,7 +351,7 @@ public class Slider {
if (currentObject && ticksT != null) {
for (int i = 0; i < ticksT.length; i++) {
float[] c = bezier.pointAt(ticksT[i]);
sliderTick.drawCentered(c[0], c[1]);
GameImage.SLIDER_TICK.getImage().drawCentered(c[0], c[1]);
}
}
@ -352,19 +371,20 @@ public class Slider {
// repeats
if (hitObject.repeat - 1 > currentRepeats) {
Image arrow = GameImage.REVERSEARROW.getImage();
if (currentRepeats % 2 == 0) { // last circle
reverseArrow.setRotation(bezier.getEndAngle());
reverseArrow.drawCentered(hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex]);
arrow.setRotation(bezier.getEndAngle());
arrow.drawCentered(hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex]);
} else { // first circle
reverseArrow.setRotation(bezier.getStartAngle());
reverseArrow.drawCentered(hitObject.x, hitObject.y);
arrow.setRotation(bezier.getStartAngle());
arrow.drawCentered(hitObject.x, hitObject.y);
}
}
if (timeDiff >= 0) {
// approach circle
float approachScale = 1 + (timeDiff * 2f / game.getApproachTime());
Utils.drawCentered(Circle.getApproachCircle().getScaledCopy(approachScale),
Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale),
hitObject.x, hitObject.y, color);
} else {
float[] c = bezier.pointAt(getT(trackPosition, false));
@ -374,7 +394,7 @@ public class Slider {
// follow circle
if (followCircleActive)
sliderFollowCircle.drawCentered(c[0], c[1]);
GameImage.SLIDER_FOLLOWCIRCLE.getImage().drawCentered(c[0], c[1]);
}
}
@ -421,7 +441,7 @@ public class Slider {
return false;
double distance = Math.hypot(hitObject.x - x, hitObject.y - y);
int circleRadius = Circle.getHitCircle().getWidth() / 2;
int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2;
if (distance < circleRadius) {
int trackPosition = MusicController.getPosition();
int timeDiff = Math.abs(trackPosition - hitObject.time);
@ -513,7 +533,7 @@ public class Slider {
// check if cursor pressed and within end circle
else if (game.isInputKeyPressed()) {
double distance = Math.hypot(hitObject.sliderX[lastIndex] - mouseX, hitObject.sliderY[lastIndex] - mouseY);
int followCircleRadius = sliderFollowCircle.getWidth() / 2;
int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2;
if (distance < followCircleRadius)
ticksHit++;
}
@ -550,7 +570,7 @@ public class Slider {
// holding slider...
float[] c = bezier.pointAt(getT(trackPosition, false));
double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY);
int followCircleRadius = sliderFollowCircle.getWidth() / 2;
int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2;
if ((game.isInputKeyPressed() && distance < followCircleRadius) || isAutoMod) {
// mouse pressed and within follow circle
followCircleActive = true;

View File

@ -18,6 +18,7 @@
package itdelatrisu.opsu.objects;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.GameScore;
import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.OsuHitObject;
@ -36,17 +37,6 @@ import org.newdawn.slick.SlickException;
* Data type representing a spinner object.
*/
public class Spinner {
/**
* Images related to spinners.
*/
private static Image
spinnerCircle, // spinner
spinnerApproachCircle, // spinner approach circle (for end time)
spinnerMetre, // spinner meter (subimage based on completion ratio)
// spinnerOsuImage, // spinner "OSU!" text (complete)
spinnerSpinImage, // spinner "SPIN!" text (start)
spinnerClearImage; // spinner "CLEAR" text (passed)
/**
* Container dimensions.
*/
@ -91,12 +81,10 @@ public class Spinner {
width = container.getWidth();
height = container.getHeight();
spinnerCircle = new Image("spinner-circle.png").getScaledCopy(height * 9 / 10, height * 9 / 10);
spinnerApproachCircle = new Image("spinner-approachcircle.png").getScaledCopy(spinnerCircle.getWidth(), spinnerCircle.getHeight());
spinnerMetre = new Image("spinner-metre.png").getScaledCopy(width, height);
spinnerSpinImage = new Image("spinner-spin.png");
spinnerClearImage = new Image("spinner-clear.png");
// spinnerOsuImage = new Image("spinner-osu.png");
Image spinnerCircle = GameImage.SPINNER_CIRCLE.getImage();
GameImage.SPINNER_CIRCLE.setImage(spinnerCircle.getScaledCopy(height * 9 / 10, height * 9 / 10));
GameImage.SPINNER_APPROACHCIRCLE.setImage(GameImage.SPINNER_APPROACHCIRCLE.getImage().getScaledCopy(spinnerCircle.getWidth(), spinnerCircle.getHeight()));
GameImage.SPINNER_METRE.setImage(GameImage.SPINNER_METRE.getImage().getScaledCopy(width, height));
}
/**
@ -135,6 +123,7 @@ public class Spinner {
return;
// spinner meter (subimage)
Image spinnerMetre = GameImage.SPINNER_METRE.getImage();
int spinnerMetreY = (spinnerComplete) ? 0 : (int) (spinnerMetre.getHeight() * (1 - (rotations / rotationsNeeded)));
Image spinnerMetreSub = spinnerMetre.getSubImage(
0, spinnerMetreY,
@ -143,12 +132,12 @@ public class Spinner {
spinnerMetreSub.draw(0, height - spinnerMetreSub.getHeight());
// main spinner elements
spinnerCircle.drawCentered(width / 2, height / 2);
spinnerApproachCircle.getScaledCopy(1 - ((float) timeDiff / (hitObject.time - hitObject.endTime))).drawCentered(width / 2, height / 2);
spinnerSpinImage.drawCentered(width / 2, height * 3 / 4);
GameImage.SPINNER_CIRCLE.getImage().drawCentered(width / 2, height / 2);
GameImage.SPINNER_APPROACHCIRCLE.getImage().getScaledCopy(1 - ((float) timeDiff / (hitObject.time - hitObject.endTime))).drawCentered(width / 2, height / 2);
GameImage.SPINNER_SPIN.getImage().drawCentered(width / 2, height * 3 / 4);
if (spinnerComplete) {
spinnerClearImage.drawCentered(width / 2, height / 4);
GameImage.SPINNER_CLEAR.getImage().drawCentered(width / 2, height / 4);
int extraRotations = (int) (rotations - rotationsNeeded);
if (extraRotations > 0)
score.drawSymbolNumber(extraRotations * 1000, width / 2, height * 2 / 3, 1.0f);

View File

@ -19,6 +19,7 @@
package itdelatrisu.opsu.states;
import itdelatrisu.opsu.GUIMenuButton;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.GameScore;
import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.Opsu;
@ -31,6 +32,7 @@ import itdelatrisu.opsu.objects.Circle;
import itdelatrisu.opsu.objects.Slider;
import itdelatrisu.opsu.objects.Spinner;
import java.io.File;
import java.util.HashMap;
import java.util.Stack;
@ -121,16 +123,6 @@ public class Game extends BasicGameState {
*/
private int breakIndex;
/**
* Warning arrows, pointing right and left.
*/
private Image warningArrowR, warningArrowL;
/**
* Section pass and fail images (displayed at start of break, when necessary).
*/
private Image breakStartPass, breakStartFail;
/**
* Break start time (0 if not in break).
*/
@ -161,16 +153,6 @@ public class Game extends BasicGameState {
*/
private float beatLengthBase, beatLength;
/**
* Countdown-related images.
*/
private Image
countdownReady, // "READY?" text
countdown3, // "3" text
countdown1, // "2" text
countdown2, // "1" text
countdownGo; // "GO!" text
/**
* Whether the countdown sound has been played.
*/
@ -178,11 +160,6 @@ public class Game extends BasicGameState {
countdownReadySound, countdown3Sound, countdown1Sound,
countdown2Sound, countdownGoSound;
/**
* Glowing hit circle outline which must be clicked when returning from pause menu.
*/
private Image hitCircleSelect;
/**
* Mouse coordinates before game paused.
*/
@ -204,11 +181,6 @@ public class Game extends BasicGameState {
*/
private Image playfield;
/**
* Image displayed during unranked plays.
*/
private Image unrankedImage;
// game-related variables
private GameContainer container;
private StateBasedGame game;
@ -229,41 +201,8 @@ public class Game extends BasicGameState {
int width = container.getWidth();
int height = container.getHeight();
// spinners have fixed properties, and only need to be initialized once
Spinner.init(container);
// breaks
breakStartPass = new Image("section-pass.png");
breakStartFail = new Image("section-fail.png");
warningArrowR = new Image("play-warningarrow.png");
warningArrowL = warningArrowR.getFlippedCopy(true, false);
// skip button
Image skip = new Image("play-skip.png");
float skipScale = (height * 0.1f) / skip.getHeight();
skip = skip.getScaledCopy(skipScale);
skipButton = new GUIMenuButton(skip,
width - (skip.getWidth() / 2f),
height - (skip.getHeight() / 2f));
// countdown
float countdownHeight = height / 3f;
countdownReady = new Image("ready.png");
countdownReady = countdownReady.getScaledCopy(countdownHeight / countdownReady.getHeight());
countdown3 = new Image("count3.png");
countdown3 = countdown3.getScaledCopy(countdownHeight / countdown3.getHeight());
countdown2 = new Image("count2.png");
countdown2 = countdown2.getScaledCopy(countdownHeight / countdown2.getHeight());
countdown1 = new Image("count1.png");
countdown1 = countdown1.getScaledCopy(countdownHeight / countdown1.getHeight());
countdownGo = new Image("go.png");
countdownGo = countdownGo.getScaledCopy(countdownHeight / countdownGo.getHeight());
// hit circle select
hitCircleSelect = new Image("hitcircleselect.png");
// "unranked" image
unrankedImage = new Image("play-unranked.png");
// create the associated GameScore object
score = new GameScore(width, height);
// playfield background
try {
@ -271,9 +210,6 @@ public class Game extends BasicGameState {
} catch (Exception e) {
// optional
}
// create the associated GameScore object
score = new GameScore(width, height);
}
@Override
@ -317,13 +253,13 @@ public class Game extends BasicGameState {
trackPosition - breakTime < 5000) {
// show break start
if (score.getHealth() >= 50) {
breakStartPass.drawCentered(width / 2f, height / 2f);
GameImage.SECTION_PASS.getImage().drawCentered(width / 2f, height / 2f);
if (!breakSound) {
SoundController.playSound(SoundController.SOUND_SECTIONPASS);
breakSound = true;
}
} else {
breakStartFail.drawCentered(width / 2f, height / 2f);
GameImage.SECTION_FAIL.getImage().drawCentered(width / 2f, height / 2f);
if (!breakSound) {
SoundController.playSound(SoundController.SOUND_SECTIONFAIL);
breakSound = true;
@ -334,15 +270,18 @@ public class Game extends BasicGameState {
int endTimeDiff = endTime - trackPosition;
if ((endTimeDiff > 1500 && endTimeDiff < 2000) ||
(endTimeDiff > 500 && endTimeDiff < 1000)) {
warningArrowR.draw(width * 0.15f, height * 0.15f);
warningArrowR.draw(width * 0.15f, height * 0.75f);
warningArrowL.draw(width * 0.75f, height * 0.15f);
warningArrowL.draw(width * 0.75f, height * 0.75f);
Image arrow = GameImage.WARNINGARROW.getImage();
arrow.setRotation(0);
arrow.draw(width * 0.15f, height * 0.15f);
arrow.draw(width * 0.15f, height * 0.75f);
arrow.setRotation(180);
arrow.draw(width * 0.75f, height * 0.15f);
arrow.draw(width * 0.75f, height * 0.75f);
}
}
if (Options.isModActive(Options.MOD_AUTO))
unrankedImage.drawCentered(width / 2, height * 0.077f);
GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f);
Utils.drawFPS();
Utils.drawCursor();
return;
@ -380,36 +319,37 @@ public class Game extends BasicGameState {
int timeDiff = osu.objects[0].time - trackPosition;
if (timeDiff >= 500 && timeDiff < 3000) {
if (timeDiff >= 1500) {
countdownReady.drawCentered(width / 2, height / 2);
GameImage.COUNTDOWN_READY.getImage().drawCentered(width / 2, height / 2);
if (!countdownReadySound) {
SoundController.playSound(SoundController.SOUND_READY);
countdownReadySound = true;
}
}
if (timeDiff < 2000) {
countdown3.draw(0, 0);
GameImage.COUNTDOWN_3.getImage().draw(0, 0);
if (!countdown3Sound) {
SoundController.playSound(SoundController.SOUND_COUNT3);
countdown3Sound = true;
}
}
if (timeDiff < 1500) {
countdown2.draw(width - countdown2.getWidth(), 0);
GameImage.COUNTDOWN_2.getImage().draw(width - GameImage.COUNTDOWN_2.getImage().getWidth(), 0);
if (!countdown2Sound) {
SoundController.playSound(SoundController.SOUND_COUNT2);
countdown2Sound = true;
}
}
if (timeDiff < 1000) {
countdown1.drawCentered(width / 2, height / 2);
GameImage.COUNTDOWN_1.getImage().drawCentered(width / 2, height / 2);
if (!countdown1Sound) {
SoundController.playSound(SoundController.SOUND_COUNT1);
countdown1Sound = true;
}
}
} else if (timeDiff >= -500 && timeDiff < 500) {
countdownGo.setAlpha((timeDiff < 0) ? 1 - (timeDiff / -1000f) : 1);
countdownGo.drawCentered(width / 2, height / 2);
Image go = GameImage.COUNTDOWN_GO.getImage();
go.setAlpha((timeDiff < 0) ? 1 - (timeDiff / -1000f) : 1);
go.drawCentered(width / 2, height / 2);
if (!countdownGoSound) {
SoundController.playSound(SoundController.SOUND_GO);
countdownGoSound = true;
@ -442,7 +382,7 @@ public class Game extends BasicGameState {
score.drawHitResults(trackPosition);
if (Options.isModActive(Options.MOD_AUTO))
unrankedImage.drawCentered(width / 2, height * 0.077f);
GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f);
// returning from pause screen
if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) {
@ -451,8 +391,8 @@ public class Game extends BasicGameState {
g.fillRect(0, 0, width, height);
// draw glowing hit select circle and pulse effect
int circleRadius = Circle.getHitCircle().getWidth();
Image cursorCircle = hitCircleSelect.getScaledCopy(circleRadius, circleRadius);
int circleRadius = GameImage.HITCIRCLE.getImage().getWidth();
Image cursorCircle = GameImage.HITCIRCLE_SELECT.getImage().getScaledCopy(circleRadius, circleRadius);
cursorCircle.setAlpha(1.0f);
cursorCircle.drawCentered(pausedMouseX, pausedMouseY);
Image cursorCirclePulse = cursorCircle.getScaledCopy(1f + pausePulse);
@ -643,7 +583,7 @@ public class Game extends BasicGameState {
// returning from pause screen
if (pauseTime > -1) {
double distance = Math.hypot(pausedMouseX - x, pausedMouseY - y);
int circleRadius = Circle.getHitCircle().getWidth() / 2;
int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2;
if (distance < circleRadius) {
// unpause the game
pauseTime = -1;
@ -698,6 +638,7 @@ public class Game extends BasicGameState {
if (restart != RESTART_FALSE) {
// new game
if (restart == RESTART_NEW) {
loadImages();
setMapModifiers();
// calculate map length (TODO: end on slider?)
@ -802,6 +743,49 @@ public class Game extends BasicGameState {
input.isKeyDown(Input.KEY_Z) || input.isKeyDown(Input.KEY_X));
}
/**
* Loads all game images.
* @throws SlickException
*/
private void loadImages() throws SlickException {
int width = container.getWidth();
int height = container.getHeight();
// set images
File parent = osu.getFile().getParentFile();
for (GameImage o : GameImage.values())
o.setImage(parent);
// skip button
Image skip = GameImage.SKIP.getImage();
float skipScale = (height * 0.1f) / skip.getHeight();
skip = skip.getScaledCopy(skipScale);
skipButton = new GUIMenuButton(skip,
width - (skip.getWidth() / 2f),
height - (skip.getHeight() / 2f));
// countdown
Image countdownReady = GameImage.COUNTDOWN_READY.getImage();
Image countdown3 = GameImage.COUNTDOWN_3.getImage();
Image countdown2 = GameImage.COUNTDOWN_2.getImage();
Image countdown1 = GameImage.COUNTDOWN_1.getImage();
Image countdownGo = GameImage.COUNTDOWN_GO.getImage();
float countdownHeight = height / 3f;
GameImage.COUNTDOWN_READY.setImage(
countdownReady.getScaledCopy(countdownHeight / countdownReady.getHeight()));
GameImage.COUNTDOWN_3.setImage(
countdown3.getScaledCopy(countdownHeight / countdown3.getHeight()));
GameImage.COUNTDOWN_2.setImage(
countdown2.getScaledCopy(countdownHeight / countdown2.getHeight()));
GameImage.COUNTDOWN_1.setImage(
countdown1.getScaledCopy(countdownHeight / countdown1.getHeight()));
GameImage.COUNTDOWN_GO.setImage(
countdownGo.getScaledCopy(countdownHeight / countdownGo.getHeight()));
((GamePauseMenu) game.getState(Opsu.STATE_GAMEPAUSEMENU)).loadImages();
score.loadImages();
}
/**
* Set map modifiers.
*/
@ -821,6 +805,7 @@ public class Game extends BasicGameState {
Circle.init(container, circleSize);
Slider.init(container, circleSize, osu);
Spinner.init(container);
// approachRate (hit object approach time)
if (approachRate < 5)

View File

@ -19,6 +19,7 @@
package itdelatrisu.opsu.states;
import itdelatrisu.opsu.GUIMenuButton;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.SoundController;
@ -27,7 +28,6 @@ import itdelatrisu.opsu.Utils;
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.SlickException;
import org.newdawn.slick.state.BasicGameState;
@ -36,7 +36,7 @@ import org.newdawn.slick.state.transition.FadeInTransition;
import org.newdawn.slick.state.transition.FadeOutTransition;
/**
* "Game Paused" state.
* "Game Pause/Fail" state.
* <ul>
* <li>[Continue] - unpause game (return to game state)
* <li>[Retry] - restart game (return to game state)
@ -59,18 +59,9 @@ public class GamePauseMenu extends BasicGameState {
*/
private GUIMenuButton continueButton, retryButton, backButton;
/**
* Background image for pause menu (optional).
*/
private Image backgroundImage;
/**
* Background image for fail menu (optional).
*/
private Image failImage;
// game-related variables
private StateBasedGame game;
private GameContainer container;
private int state;
public GamePauseMenu(int state) {
@ -80,43 +71,18 @@ public class GamePauseMenu extends BasicGameState {
@Override
public void init(GameContainer container, StateBasedGame game)
throws SlickException {
this.container = container;
this.game = game;
int width = container.getWidth();
int height = container.getHeight();
// initialize buttons
continueButton = new GUIMenuButton(new Image("pause-continue.png"), width / 2f, height * 0.25f);
retryButton = new GUIMenuButton(new Image("pause-retry.png"), width / 2f, height * 0.5f);
backButton = new GUIMenuButton(new Image("pause-back.png"), width / 2f, height * 0.75f);
// pause background image
try {
backgroundImage = new Image("pause-overlay.png").getScaledCopy(width, height);
backgroundImage.setAlpha(0.7f);
} catch (Exception e) {
// optional
}
// fail image
try {
failImage = new Image("fail-background.png").getScaledCopy(width, height);
failImage.setAlpha(0.7f);
} catch (Exception e) {
// optional
}
}
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g)
throws SlickException {
// background
if (backgroundImage != null && Game.getRestart() != Game.RESTART_LOSE)
backgroundImage.draw();
else if (failImage != null && Game.getRestart() == Game.RESTART_LOSE)
failImage.draw();
if (Game.getRestart() != Game.RESTART_LOSE)
GameImage.PAUSE_OVERLAY.getImage().draw();
else
g.setBackground(Color.black);
GameImage.FAIL_BACKGROUND.getImage().draw();
// draw buttons
if (Game.getRestart() != Game.RESTART_LOSE)
@ -202,4 +168,25 @@ public class GamePauseMenu extends BasicGameState {
Game.setRestart(restart);
game.enterState(Opsu.STATE_GAME);
}
/**
* Loads all game pause/fail menu images.
*/
public void loadImages() {
int width = container.getWidth();
int height = container.getHeight();
// initialize buttons
continueButton = new GUIMenuButton(GameImage.PAUSE_CONTINUE.getImage(), width / 2f, height * 0.25f);
retryButton = new GUIMenuButton(GameImage.PAUSE_RETRY.getImage(), width / 2f, height * 0.5f);
backButton = new GUIMenuButton(GameImage.PAUSE_BACK.getImage(), width / 2f, height * 0.75f);
// pause background image
GameImage.PAUSE_OVERLAY.setImage(GameImage.PAUSE_OVERLAY.getImage().getScaledCopy(width, height));
GameImage.PAUSE_OVERLAY.getImage().setAlpha(0.7f);
// fail image
GameImage.FAIL_BACKGROUND.setImage(GameImage.FAIL_BACKGROUND.getImage().getScaledCopy(width, height));
GameImage.FAIL_BACKGROUND.getImage().setAlpha(0.7f);
}
}

View File

@ -129,7 +129,8 @@ public class Options extends BasicGameState {
DYNAMIC_BACKGROUND,
SHOW_PERFECT_HIT,
BACKGROUND_DIM,
FORCE_DEFAULT_PLAYFIELD;
FORCE_DEFAULT_PLAYFIELD,
IGNORE_BEATMAP_SKINS;
};
/**
@ -189,6 +190,7 @@ public class Options extends BasicGameState {
private static final GameOption[] gameplayOptions = {
GameOption.BACKGROUND_DIM,
GameOption.FORCE_DEFAULT_PLAYFIELD,
GameOption.IGNORE_BEATMAP_SKINS,
GameOption.SHOW_HIT_LIGHTING,
GameOption.SHOW_COMBO_BURSTS,
GameOption.SHOW_PERFECT_HIT
@ -311,6 +313,11 @@ public class Options extends BasicGameState {
*/
private static boolean forceDefaultPlayfield = false;
/**
* Whether or not to ignore resources in the beatmap folders.
*/
private static boolean ignoreBeatmapSkins = false;
/**
* Game option coordinate modifiers (for drawing).
*/
@ -545,6 +552,9 @@ public class Options extends BasicGameState {
case FORCE_DEFAULT_PLAYFIELD:
forceDefaultPlayfield = !forceDefaultPlayfield;
break;
case IGNORE_BEATMAP_SKINS:
ignoreBeatmapSkins = !ignoreBeatmapSkins;
break;
default:
break;
}
@ -721,6 +731,12 @@ public class Options extends BasicGameState {
"Override the song background with the default playfield background."
);
break;
case IGNORE_BEATMAP_SKINS:
drawOption(pos, "Ignore All Beatmap Skins",
ignoreBeatmapSkins ? "Yes" : "No",
"Never use skin element overrides provided by beatmaps."
);
break;
case SHOW_HIT_LIGHTING:
drawOption(pos, "Show Hit Lighting",
showHitLighting ? "Yes" : "No",
@ -920,6 +936,12 @@ public class Options extends BasicGameState {
*/
public static boolean isDefaultPlayfieldForced() { return forceDefaultPlayfield; }
/**
* Returns whether or not beatmap skins are ignored.
* @return true if ignored
*/
public static boolean isBeatmapSkinIgnored() { return ignoreBeatmapSkins; }
/**
* Returns the current beatmap directory.
* If invalid, this will attempt to search for the directory,
@ -1044,6 +1066,9 @@ public class Options extends BasicGameState {
case "ForceDefaultPlayfield":
forceDefaultPlayfield = Boolean.parseBoolean(value);
break;
case "IgnoreBeatmapSkins":
ignoreBeatmapSkins = Boolean.parseBoolean(value);
break;
case "HitLighting":
showHitLighting = Boolean.parseBoolean(value);
break;
@ -1112,6 +1137,8 @@ public class Options extends BasicGameState {
writer.newLine();
writer.write(String.format("ForceDefaultPlayfield = %b", forceDefaultPlayfield));
writer.newLine();
writer.write(String.format("IgnoreBeatmapSkins = %b", ignoreBeatmapSkins));
writer.newLine();
writer.write(String.format("HitLighting = %b", showHitLighting));
writer.newLine();
writer.write(String.format("ComboBurst = %b", showComboBursts));