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/). [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 If osu! is already installed, this application will attempt to load songs
directly from the osu! program folder. Otherwise, run this application from directly from the osu! program folder. Otherwise, place songs in the generated
one directory above the root song directory, or place songs in the generated `Songs` folder or set the `BeatmapDirectory` value in the generated
`songs` folder. This path can be changed at any time by editing the configuration file to the path of the root song directory.
`BeatmapDirectory` value in the generated configuration file.
### First Run ### First Run
The `Music Offset` value will likely need to be adjusted when playing for the 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 itdelatrisu.opsu.states.Options;
import java.io.File;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
@ -28,7 +29,6 @@ import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics; import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image; import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException; import org.newdawn.slick.SlickException;
import org.newdawn.slick.util.Log;
/** /**
* Holds score data and renders all score-related elements. * Holds score data and renders all score-related elements.
@ -179,26 +179,6 @@ public class GameScore {
*/ */
private float difficulty = 5f; 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. * Default text symbol images.
*/ */
@ -233,20 +213,7 @@ public class GameScore {
this.width = width; this.width = width;
this.height = height; 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(); 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 * @throws SlickException
*/ */
private void initializeImages() throws SlickException { public void loadImages() throws SlickException {
// scorebar File dir = MusicController.getOsuFile().getFile().getParentFile();
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");
// combo burst images // combo burst images
for (int i = 0; i <= 3; i++) if (comboBurstImages != null) {
comboBurstImages[i] = new Image(String.format("comboburst-%d.png", i)); 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 // lighting image
try { if (lighting != null && !lighting.isDestroyed()) {
lighting = new Image("lighting.png"); lighting.destroy();
lighting1 = new Image("lighting1.png"); lighting = null;
} catch (Exception e) {
// optional
} }
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 // letter grade images
String[] grades = { "X", "XH", "S", "SH", "A", "B", "C", "D" }; gradesLarge = new Image[GRADE_MAX];
for (int i = 0; i < grades.length; i++) { gradesSmall = new Image[GRADE_MAX];
gradesLarge[i] = new Image(String.format("ranking-%s.png", grades[i])); gradesLarge[GRADE_SS] = GameImage.RANKING_SS.getImage();
gradesSmall[i] = new Image(String.format("ranking-%s-small.png", grades[i])); 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 // ranking screen elements
setRankingImage( Image rankingPanel = GameImage.RANKING_PANEL.getImage();
new Image("ranking-panel.png"), Image rankingPerfect = GameImage.RANKING_PERFECT.getImage();
new Image("ranking-perfect.png"), Image rankingTitle = GameImage.RANKING_TITLE.getImage();
new Image("ranking-title.png"), Image rankingMaxCombo = GameImage.RANKING_MAXCOMBO.getImage();
new Image("ranking-maxcombo.png"), Image rankingAccuracy = GameImage.RANKING_ACCURACY.getImage();
new Image("ranking-accuracy.png") 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()));
* 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());
} }
/** /**
@ -469,15 +486,19 @@ public class GameScore {
if (firstObjectTime >= 1500 && trackPosition < firstObjectTime - 500) if (firstObjectTime >= 1500 && trackPosition < firstObjectTime - 500)
healthRatio = (float) trackPosition / (firstObjectTime - 500); healthRatio = (float) trackPosition / (firstObjectTime - 500);
} }
bgImage.draw(0, 0); GameImage.SCOREBAR_BG.getImage().draw(0, 0);
Image colourCropped = colourImage.getSubImage(0, 0, (int) (colourImage.getWidth() * healthRatio), colourImage.getHeight()); Image colour = GameImage.SCOREBAR_COLOUR.getImage();
colourCropped.draw(0, bgImage.getHeight() / 4f); Image colourCropped = colour.getSubImage(0, 0, (int) (colour.getWidth() * healthRatio), colour.getHeight());
colourCropped.draw(0, GameImage.SCOREBAR_BG.getImage().getHeight() / 4f);
if (health >= 50f) 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) 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 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 // combo burst
if (comboBurstIndex != -1 && comboBurstAlpha > 0f) { if (comboBurstIndex != -1 && comboBurstAlpha > 0f) {
@ -513,12 +534,14 @@ public class GameScore {
grade.draw(width - grade.getWidth(), height * 0.09f); grade.draw(width - grade.getWidth(), height * 0.09f);
// header & "Ranking" text // 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.setColor(Utils.COLOR_BLACK_ALPHA);
g.fillRect(0, 0, width, rankingHeight); g.fillRect(0, 0, width, rankingHeight);
rankingImage.draw((width * 0.97f) - rankingImage.getWidth(), 0); rankingTitle.draw((width * 0.97f) - rankingTitle.getWidth(), 0);
// ranking panel // ranking panel
Image rankingPanel = GameImage.RANKING_PANEL.getImage();
int rankingPanelWidth = rankingPanel.getWidth(); int rankingPanelWidth = rankingPanel.getWidth();
int rankingPanelHeight = rankingPanel.getHeight(); int rankingPanelHeight = rankingPanel.getHeight();
rankingPanel.draw(0, rankingHeight - (rankingHeight / 10f)); rankingPanel.draw(0, rankingHeight - (rankingHeight / 10f));
@ -557,19 +580,25 @@ public class GameScore {
} }
// combo and accuracy // combo and accuracy
Image rankingMaxCombo = GameImage.RANKING_MAXCOMBO.getImage();
Image rankingAccuracy = GameImage.RANKING_ACCURACY.getImage();
float textY = rankingHeight + (rankingPanelHeight * 0.87f) - (rankingHeight / 10f); float textY = rankingHeight + (rankingPanelHeight * 0.87f) - (rankingHeight / 10f);
float numbersX = comboImage.getWidth() * .07f; float numbersX = rankingMaxCombo.getWidth() * .07f;
float numbersY = textY + comboImage.getHeight() * 0.7f; float numbersY = textY + rankingMaxCombo.getHeight() * 0.7f;
comboImage.draw(width * 0.01f, textY); rankingMaxCombo.draw(width * 0.01f, textY);
accuracyImage.draw(rankingPanelWidth / 2f, textY); rankingAccuracy.draw(rankingPanelWidth / 2f, textY);
drawSymbolString(String.format("%dx", comboMax), drawSymbolString(String.format("%dx", comboMax),
(int) (width * 0.01f + numbersX), (int) numbersY, symbolTextScale, false); (int) (width * 0.01f + numbersX), (int) numbersY, symbolTextScale, false);
drawSymbolString(String.format("%02.2f%%", getScorePercent()), drawSymbolString(String.format("%02.2f%%", getScorePercent()),
(int) (rankingPanelWidth / 2f + numbersX), (int) numbersY, symbolTextScale, false); (int) (rankingPanelWidth / 2f + numbersX), (int) numbersY, symbolTextScale, false);
// full combo // full combo
if (combo == fullObjectCount) if (combo == fullObjectCount) {
perfectImage.draw(width * 0.08f, (height * 0.99f) - perfectImage.getHeight()); 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 // game-related variables
private static GameContainer container; private static GameContainer container;
// private static StateBasedGame game;
private static Input input; private static Input input;
// This class should not be instantiated. // This class should not be instantiated.
@ -119,7 +118,6 @@ public class Utils {
public static void init(GameContainer container, StateBasedGame game) public static void init(GameContainer container, StateBasedGame game)
throws SlickException { throws SlickException {
Utils.container = container; Utils.container = container;
// Utils.game = game;
Utils.input = container.getInput(); Utils.input = container.getInput();
// game settings // game settings

View File

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

View File

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

View File

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

View File

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

View File

@ -19,6 +19,7 @@
package itdelatrisu.opsu.states; package itdelatrisu.opsu.states;
import itdelatrisu.opsu.GUIMenuButton; import itdelatrisu.opsu.GUIMenuButton;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.MusicController; import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.Opsu; import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.SoundController; import itdelatrisu.opsu.SoundController;
@ -27,7 +28,6 @@ import itdelatrisu.opsu.Utils;
import org.newdawn.slick.Color; import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer; import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics; import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input; import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException; import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState; import org.newdawn.slick.state.BasicGameState;
@ -36,7 +36,7 @@ import org.newdawn.slick.state.transition.FadeInTransition;
import org.newdawn.slick.state.transition.FadeOutTransition; import org.newdawn.slick.state.transition.FadeOutTransition;
/** /**
* "Game Paused" state. * "Game Pause/Fail" state.
* <ul> * <ul>
* <li>[Continue] - unpause game (return to game state) * <li>[Continue] - unpause game (return to game state)
* <li>[Retry] - restart 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; 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 // game-related variables
private StateBasedGame game; private StateBasedGame game;
private GameContainer container;
private int state; private int state;
public GamePauseMenu(int state) { public GamePauseMenu(int state) {
@ -80,43 +71,18 @@ public class GamePauseMenu extends BasicGameState {
@Override @Override
public void init(GameContainer container, StateBasedGame game) public void init(GameContainer container, StateBasedGame game)
throws SlickException { throws SlickException {
this.container = container;
this.game = game; 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 @Override
public void render(GameContainer container, StateBasedGame game, Graphics g) public void render(GameContainer container, StateBasedGame game, Graphics g)
throws SlickException { throws SlickException {
// background // background
if (backgroundImage != null && Game.getRestart() != Game.RESTART_LOSE) if (Game.getRestart() != Game.RESTART_LOSE)
backgroundImage.draw(); GameImage.PAUSE_OVERLAY.getImage().draw();
else if (failImage != null && Game.getRestart() == Game.RESTART_LOSE)
failImage.draw();
else else
g.setBackground(Color.black); GameImage.FAIL_BACKGROUND.getImage().draw();
// draw buttons // draw buttons
if (Game.getRestart() != Game.RESTART_LOSE) if (Game.getRestart() != Game.RESTART_LOSE)
@ -202,4 +168,25 @@ public class GamePauseMenu extends BasicGameState {
Game.setRestart(restart); Game.setRestart(restart);
game.enterState(Opsu.STATE_GAME); 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, DYNAMIC_BACKGROUND,
SHOW_PERFECT_HIT, SHOW_PERFECT_HIT,
BACKGROUND_DIM, 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 = { private static final GameOption[] gameplayOptions = {
GameOption.BACKGROUND_DIM, GameOption.BACKGROUND_DIM,
GameOption.FORCE_DEFAULT_PLAYFIELD, GameOption.FORCE_DEFAULT_PLAYFIELD,
GameOption.IGNORE_BEATMAP_SKINS,
GameOption.SHOW_HIT_LIGHTING, GameOption.SHOW_HIT_LIGHTING,
GameOption.SHOW_COMBO_BURSTS, GameOption.SHOW_COMBO_BURSTS,
GameOption.SHOW_PERFECT_HIT GameOption.SHOW_PERFECT_HIT
@ -311,6 +313,11 @@ public class Options extends BasicGameState {
*/ */
private static boolean forceDefaultPlayfield = false; 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). * Game option coordinate modifiers (for drawing).
*/ */
@ -545,6 +552,9 @@ public class Options extends BasicGameState {
case FORCE_DEFAULT_PLAYFIELD: case FORCE_DEFAULT_PLAYFIELD:
forceDefaultPlayfield = !forceDefaultPlayfield; forceDefaultPlayfield = !forceDefaultPlayfield;
break; break;
case IGNORE_BEATMAP_SKINS:
ignoreBeatmapSkins = !ignoreBeatmapSkins;
break;
default: default:
break; break;
} }
@ -721,6 +731,12 @@ public class Options extends BasicGameState {
"Override the song background with the default playfield background." "Override the song background with the default playfield background."
); );
break; 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: case SHOW_HIT_LIGHTING:
drawOption(pos, "Show Hit Lighting", drawOption(pos, "Show Hit Lighting",
showHitLighting ? "Yes" : "No", showHitLighting ? "Yes" : "No",
@ -920,6 +936,12 @@ public class Options extends BasicGameState {
*/ */
public static boolean isDefaultPlayfieldForced() { return forceDefaultPlayfield; } 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. * Returns the current beatmap directory.
* If invalid, this will attempt to search for the directory, * If invalid, this will attempt to search for the directory,
@ -1044,6 +1066,9 @@ public class Options extends BasicGameState {
case "ForceDefaultPlayfield": case "ForceDefaultPlayfield":
forceDefaultPlayfield = Boolean.parseBoolean(value); forceDefaultPlayfield = Boolean.parseBoolean(value);
break; break;
case "IgnoreBeatmapSkins":
ignoreBeatmapSkins = Boolean.parseBoolean(value);
break;
case "HitLighting": case "HitLighting":
showHitLighting = Boolean.parseBoolean(value); showHitLighting = Boolean.parseBoolean(value);
break; break;
@ -1112,6 +1137,8 @@ public class Options extends BasicGameState {
writer.newLine(); writer.newLine();
writer.write(String.format("ForceDefaultPlayfield = %b", forceDefaultPlayfield)); writer.write(String.format("ForceDefaultPlayfield = %b", forceDefaultPlayfield));
writer.newLine(); writer.newLine();
writer.write(String.format("IgnoreBeatmapSkins = %b", ignoreBeatmapSkins));
writer.newLine();
writer.write(String.format("HitLighting = %b", showHitLighting)); writer.write(String.format("HitLighting = %b", showHitLighting));
writer.newLine(); writer.newLine();
writer.write(String.format("ComboBurst = %b", showComboBursts)); writer.write(String.format("ComboBurst = %b", showComboBursts));