2014-06-30 04:17:04 +02:00
|
|
|
/*
|
|
|
|
* opsu! - an open-source osu! client
|
2015-01-16 18:05:44 +01:00
|
|
|
* Copyright (C) 2014, 2015 Jeffrey Han
|
2014-06-30 04:17:04 +02:00
|
|
|
*
|
|
|
|
* 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;
|
|
|
|
|
2015-01-08 01:29:51 +01:00
|
|
|
import itdelatrisu.opsu.audio.HitSound;
|
|
|
|
import itdelatrisu.opsu.audio.MusicController;
|
|
|
|
import itdelatrisu.opsu.audio.SoundController;
|
|
|
|
import itdelatrisu.opsu.audio.SoundEffect;
|
2015-03-10 23:10:51 +01:00
|
|
|
import itdelatrisu.opsu.downloads.Updater;
|
|
|
|
import itdelatrisu.opsu.replay.Replay;
|
|
|
|
import itdelatrisu.opsu.replay.ReplayFrame;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-03-12 01:52:51 +01:00
|
|
|
import java.io.File;
|
2015-03-10 23:10:51 +01:00
|
|
|
import java.util.Date;
|
2014-06-30 04:17:04 +02:00
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Iterator;
|
2015-03-10 05:48:04 +01:00
|
|
|
import java.util.concurrent.LinkedBlockingDeque;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-01-26 00:36:54 +01:00
|
|
|
import org.newdawn.slick.Animation;
|
2014-06-30 04:17:04 +02:00
|
|
|
import org.newdawn.slick.Color;
|
|
|
|
import org.newdawn.slick.Graphics;
|
|
|
|
import org.newdawn.slick.Image;
|
|
|
|
|
|
|
|
/**
|
2015-01-27 09:19:39 +01:00
|
|
|
* Holds game data and renders all related elements.
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
2015-01-27 09:19:39 +01:00
|
|
|
public class GameData {
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Delta multiplier for steady HP drain. */
|
2014-07-16 22:56:23 +02:00
|
|
|
public static final float HP_DRAIN_MULTIPLIER = 1 / 200f;
|
|
|
|
|
2015-02-20 22:56:41 +01:00
|
|
|
/** Time, in milliseconds, for a hit result to fade. */
|
|
|
|
public static final int HITRESULT_FADE_TIME = 500;
|
|
|
|
|
2015-03-13 23:03:44 +01:00
|
|
|
/** Duration, in milliseconds, of a combo pop effect. */
|
2015-03-04 07:30:28 +01:00
|
|
|
private static final int COMBO_POP_TIME = 250;
|
|
|
|
|
2015-02-15 07:40:01 +01:00
|
|
|
/** Time, in milliseconds, for a hit error tick to fade. */
|
|
|
|
private static final int HIT_ERROR_FADE_TIME = 5000;
|
|
|
|
|
2015-01-22 21:12:15 +01:00
|
|
|
/** Letter grades. */
|
2015-01-28 09:47:24 +01:00
|
|
|
public enum Grade {
|
2015-01-22 21:12:15 +01:00
|
|
|
NULL (null, null),
|
|
|
|
SS (GameImage.RANKING_SS, GameImage.RANKING_SS_SMALL),
|
|
|
|
SSH (GameImage.RANKING_SSH, GameImage.RANKING_SSH_SMALL), // silver
|
|
|
|
S (GameImage.RANKING_S, GameImage.RANKING_S_SMALL),
|
|
|
|
SH (GameImage.RANKING_SH, GameImage.RANKING_SH_SMALL), // silver
|
|
|
|
A (GameImage.RANKING_A, GameImage.RANKING_A_SMALL),
|
|
|
|
B (GameImage.RANKING_B, GameImage.RANKING_B_SMALL),
|
|
|
|
C (GameImage.RANKING_C, GameImage.RANKING_C_SMALL),
|
|
|
|
D (GameImage.RANKING_D, GameImage.RANKING_D_SMALL);
|
|
|
|
|
|
|
|
/** GameImages associated with this grade (large and small sizes). */
|
|
|
|
private GameImage large, small;
|
|
|
|
|
2015-01-29 01:23:02 +01:00
|
|
|
/** Large-size image scaled for use in song menu. */
|
|
|
|
private Image menuImage;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clears all image references.
|
|
|
|
* This does NOT destroy images, so be careful of memory leaks!
|
|
|
|
*/
|
|
|
|
public static void clearReferences() {
|
|
|
|
for (Grade grade : Grade.values())
|
|
|
|
grade.menuImage = null;
|
|
|
|
}
|
|
|
|
|
2015-01-22 21:12:15 +01:00
|
|
|
/**
|
|
|
|
* Constructor.
|
|
|
|
* @param large the large size image
|
|
|
|
* @param small the small size image
|
|
|
|
*/
|
|
|
|
Grade(GameImage large, GameImage small) {
|
|
|
|
this.large = large;
|
|
|
|
this.small = small;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the large size grade image.
|
|
|
|
*/
|
|
|
|
public Image getLargeImage() { return large.getImage(); }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the small size grade image.
|
|
|
|
*/
|
|
|
|
public Image getSmallImage() { return small.getImage(); }
|
2015-01-29 01:23:02 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the large size grade image scaled for song menu use.
|
|
|
|
*/
|
|
|
|
public Image getMenuImage() {
|
|
|
|
if (menuImage != null)
|
|
|
|
return menuImage;
|
|
|
|
|
|
|
|
Image img = getSmallImage();
|
|
|
|
if (!small.hasSkinImage()) // save default image only
|
|
|
|
this.menuImage = img;
|
|
|
|
return img;
|
|
|
|
}
|
2015-01-22 21:12:15 +01:00
|
|
|
}
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Hit result types. */
|
2014-06-30 04:17:04 +02:00
|
|
|
public static final int
|
|
|
|
HIT_MISS = 0,
|
|
|
|
HIT_50 = 1,
|
|
|
|
HIT_100 = 2,
|
|
|
|
HIT_300 = 3,
|
|
|
|
HIT_100K = 4, // 100-Katu
|
|
|
|
HIT_300K = 5, // 300-Katu
|
|
|
|
HIT_300G = 6, // Geki
|
|
|
|
HIT_SLIDER10 = 7,
|
|
|
|
HIT_SLIDER30 = 8,
|
|
|
|
HIT_MAX = 9; // not a hit result
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Hit result-related images (indexed by HIT_* constants). */
|
2014-06-30 04:17:04 +02:00
|
|
|
private Image[] hitResults;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Counts of each hit result so far. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private int[] hitResultCount;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Total objects including slider hits/ticks (for determining Full Combo status). */
|
2014-06-30 04:17:04 +02:00
|
|
|
private int fullObjectCount;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** The current combo streak. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private int combo;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** The max combo streak obtained. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private int comboMax;
|
|
|
|
|
2015-03-04 07:30:28 +01:00
|
|
|
/** The current combo pop timer, in milliseconds. */
|
|
|
|
private int comboPopTime;
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* Hit result types accumulated this streak (bitmask), for Katu/Geki status.
|
|
|
|
* <ul>
|
|
|
|
* <li>&1: 100
|
|
|
|
* <li>&2: 50/Miss
|
|
|
|
* </ul>
|
|
|
|
*/
|
|
|
|
private byte comboEnd;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Combo burst images. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private Image[] comboBurstImages;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Index of the current combo burst image. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private int comboBurstIndex;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Alpha level of the current combo burst image (for fade out). */
|
2014-06-30 04:17:04 +02:00
|
|
|
private float comboBurstAlpha;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Current x coordinate of the combo burst image (for sliding animation). */
|
2015-03-14 13:11:33 +01:00
|
|
|
private float comboBurstX;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-02-15 07:40:01 +01:00
|
|
|
/** Time offsets for obtaining each hit result (indexed by HIT_* constants). */
|
|
|
|
private int[] hitResultOffset;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** List of hit result objects associated with hit objects. */
|
2015-03-10 05:48:04 +01:00
|
|
|
private LinkedBlockingDeque<OsuHitObjectResult> hitResultList;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-02-15 04:15:41 +01:00
|
|
|
/**
|
2015-02-15 07:40:01 +01:00
|
|
|
* Class to store hit error information.
|
2015-02-15 04:15:41 +01:00
|
|
|
* @author fluddokt
|
|
|
|
*/
|
2015-02-15 07:40:01 +01:00
|
|
|
private class HitErrorInfo {
|
|
|
|
/** The correct hit time. */
|
|
|
|
private int time;
|
|
|
|
|
|
|
|
/** The coordinates of the hit. */
|
|
|
|
@SuppressWarnings("unused")
|
|
|
|
private int x, y;
|
|
|
|
|
|
|
|
/** The difference between the correct and actual hit times. */
|
|
|
|
private int timeDiff;
|
2015-02-14 19:45:14 +01:00
|
|
|
|
2015-02-15 07:40:01 +01:00
|
|
|
/**
|
|
|
|
* Constructor.
|
|
|
|
* @param time the correct hit time
|
|
|
|
* @param x the x coordinate of the hit
|
|
|
|
* @param y the y coordinate of the hit
|
|
|
|
* @param timeDiff the difference between the correct and actual hit times
|
|
|
|
*/
|
2015-02-15 04:15:41 +01:00
|
|
|
public HitErrorInfo(int time, int x, int y, int timeDiff) {
|
2015-02-14 19:45:14 +01:00
|
|
|
this.time = time;
|
|
|
|
this.x = x;
|
|
|
|
this.y = y;
|
|
|
|
this.timeDiff = timeDiff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-15 07:40:01 +01:00
|
|
|
/** List containing recent hit error information. */
|
2015-03-10 05:48:04 +01:00
|
|
|
private LinkedBlockingDeque<HitErrorInfo> hitErrorList;
|
2015-02-14 19:45:14 +01:00
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* Hit result helper class.
|
|
|
|
*/
|
|
|
|
private class OsuHitObjectResult {
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Object start time. */
|
|
|
|
public int time;
|
|
|
|
|
|
|
|
/** Hit result. */
|
|
|
|
public int result;
|
|
|
|
|
|
|
|
/** Object coordinates. */
|
|
|
|
public float x, y;
|
|
|
|
|
|
|
|
/** Combo color. */
|
|
|
|
public Color color;
|
|
|
|
|
2015-02-20 22:56:41 +01:00
|
|
|
/** Whether the hit object was a spinner. */
|
|
|
|
public boolean isSpinner;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Alpha level (for fading out). */
|
|
|
|
public float alpha = 1f;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor.
|
|
|
|
* @param time the result's starting track position
|
|
|
|
* @param result the hit result (HIT_* constants)
|
|
|
|
* @param x the center x coordinate
|
|
|
|
* @param y the center y coordinate
|
|
|
|
* @param color the color of the hit object
|
2015-02-20 22:56:41 +01:00
|
|
|
* @param isSpinner whether the hit object was a spinner
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
2015-02-20 22:56:41 +01:00
|
|
|
public OsuHitObjectResult(int time, int result, float x, float y, Color color, boolean isSpinner) {
|
2014-06-30 04:17:04 +02:00
|
|
|
this.time = time;
|
|
|
|
this.result = result;
|
|
|
|
this.x = x;
|
|
|
|
this.y = y;
|
|
|
|
this.color = color;
|
2015-02-20 22:56:41 +01:00
|
|
|
this.isSpinner = isSpinner;
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Current game score. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private long score;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Displayed game score (for animation, slightly behind score). */
|
2014-07-03 08:03:56 +02:00
|
|
|
private long scoreDisplay;
|
|
|
|
|
2015-02-20 22:56:41 +01:00
|
|
|
/** Displayed game score percent (for animation, slightly behind score percent). */
|
|
|
|
private float scorePercentDisplay;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Current health bar percentage. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private float health;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Displayed health (for animation, slightly behind health). */
|
2014-12-30 07:17:05 +01:00
|
|
|
private float healthDisplay;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Beatmap HPDrainRate value. (0:easy ~ 10:hard) */
|
2014-06-30 10:03:39 +02:00
|
|
|
private float drainRate = 5f;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Beatmap OverallDifficulty value. (0:easy ~ 10:hard) */
|
2014-06-30 10:03:39 +02:00
|
|
|
private float difficulty = 5f;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Default text symbol images. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private Image[] defaultSymbols;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Score text symbol images. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private HashMap<Character, Image> scoreSymbols;
|
|
|
|
|
2015-01-26 00:36:54 +01:00
|
|
|
/** Scorebar animation. */
|
|
|
|
private Animation scorebarColour;
|
|
|
|
|
2015-01-28 09:47:24 +01:00
|
|
|
/** The associated score data. */
|
|
|
|
private ScoreData scoreData;
|
|
|
|
|
2015-03-10 23:10:51 +01:00
|
|
|
/** The associated replay. */
|
|
|
|
private Replay replay;
|
|
|
|
|
2015-01-28 09:47:24 +01:00
|
|
|
/** Whether this object is used for gameplay (true) or score viewing (false). */
|
|
|
|
private boolean gameplay;
|
|
|
|
|
2015-01-22 06:44:45 +01:00
|
|
|
/** Container dimensions. */
|
2014-06-30 04:17:04 +02:00
|
|
|
private int width, height;
|
|
|
|
|
|
|
|
/**
|
2015-01-28 09:47:24 +01:00
|
|
|
* Constructor for gameplay.
|
2014-06-30 04:17:04 +02:00
|
|
|
* @param width container width
|
|
|
|
* @param height container height
|
|
|
|
*/
|
2015-01-27 09:19:39 +01:00
|
|
|
public GameData(int width, int height) {
|
2014-06-30 04:17:04 +02:00
|
|
|
this.width = width;
|
|
|
|
this.height = height;
|
2015-01-28 09:47:24 +01:00
|
|
|
this.gameplay = true;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
clear();
|
|
|
|
}
|
|
|
|
|
2015-01-28 09:47:24 +01:00
|
|
|
/**
|
|
|
|
* Constructor for score viewing.
|
|
|
|
* This will initialize all parameters and images needed for the
|
2015-01-29 09:17:53 +01:00
|
|
|
* {@link #drawRankingElements(Graphics, OsuFile)} method.
|
2015-01-28 09:47:24 +01:00
|
|
|
* @param s the ScoreData object
|
|
|
|
* @param width container width
|
|
|
|
* @param height container height
|
|
|
|
*/
|
|
|
|
public GameData(ScoreData s, int width, int height) {
|
|
|
|
this.width = width;
|
|
|
|
this.height = height;
|
|
|
|
this.gameplay = false;
|
|
|
|
|
|
|
|
this.scoreData = s;
|
|
|
|
this.score = s.score;
|
|
|
|
this.comboMax = s.combo;
|
|
|
|
this.fullObjectCount = (s.perfect) ? s.combo : -1;
|
|
|
|
this.hitResultCount = new int[HIT_MAX];
|
|
|
|
hitResultCount[HIT_300] = s.hit300;
|
|
|
|
hitResultCount[HIT_100] = s.hit100;
|
|
|
|
hitResultCount[HIT_50] = s.hit50;
|
|
|
|
hitResultCount[HIT_300G] = s.geki;
|
|
|
|
hitResultCount[HIT_300K] = 0;
|
|
|
|
hitResultCount[HIT_100K] = s.katu;
|
|
|
|
hitResultCount[HIT_MISS] = s.miss;
|
2015-03-12 01:52:51 +01:00
|
|
|
this.replay = (s.replayString == null) ? null :
|
|
|
|
new Replay(new File(Options.getReplayDir(), String.format("%s.osr", s.replayString)));
|
2015-01-28 09:47:24 +01:00
|
|
|
|
|
|
|
loadImages();
|
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* Clears all data and re-initializes object.
|
|
|
|
*/
|
|
|
|
public void clear() {
|
|
|
|
score = 0;
|
2014-07-03 08:03:56 +02:00
|
|
|
scoreDisplay = 0;
|
2015-02-20 22:56:41 +01:00
|
|
|
scorePercentDisplay = 0f;
|
2014-06-30 04:17:04 +02:00
|
|
|
health = 100f;
|
2014-12-30 07:17:05 +01:00
|
|
|
healthDisplay = 100f;
|
2014-06-30 04:17:04 +02:00
|
|
|
hitResultCount = new int[HIT_MAX];
|
2015-03-10 05:48:04 +01:00
|
|
|
hitResultList = new LinkedBlockingDeque<OsuHitObjectResult>();
|
|
|
|
hitErrorList = new LinkedBlockingDeque<HitErrorInfo>();
|
2014-06-30 04:17:04 +02:00
|
|
|
fullObjectCount = 0;
|
|
|
|
combo = 0;
|
|
|
|
comboMax = 0;
|
2015-03-04 07:30:28 +01:00
|
|
|
comboPopTime = COMBO_POP_TIME;
|
2014-06-30 04:17:04 +02:00
|
|
|
comboEnd = 0;
|
|
|
|
comboBurstIndex = -1;
|
2015-01-28 09:47:24 +01:00
|
|
|
scoreData = null;
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-07-04 22:41:52 +02:00
|
|
|
* Loads all game score images.
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
2015-01-28 09:47:24 +01:00
|
|
|
public void loadImages() {
|
|
|
|
// gameplay-specific images
|
|
|
|
if (isGameplay()) {
|
|
|
|
// combo burst images
|
|
|
|
if (GameImage.COMBO_BURST.hasSkinImages() ||
|
|
|
|
(!GameImage.COMBO_BURST.hasSkinImage() && GameImage.COMBO_BURST.getImages() != null))
|
|
|
|
comboBurstImages = GameImage.COMBO_BURST.getImages();
|
|
|
|
else
|
|
|
|
comboBurstImages = new Image[]{ GameImage.COMBO_BURST.getImage() };
|
2015-01-30 02:36:23 +01:00
|
|
|
|
2015-01-28 09:47:24 +01:00
|
|
|
// scorebar-colour animation
|
|
|
|
Image[] scorebar = GameImage.SCOREBAR_COLOUR.getImages();
|
|
|
|
scorebarColour = (scorebar != null) ? new Animation(scorebar, 60) : null;
|
2015-01-30 02:36:23 +01:00
|
|
|
|
2015-01-28 09:47:24 +01:00
|
|
|
// 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();
|
|
|
|
}
|
2014-07-04 22:41:52 +02:00
|
|
|
|
|
|
|
// 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();
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a default/score text symbol image for a character.
|
|
|
|
*/
|
|
|
|
public Image getDefaultSymbolImage(int i) { return defaultSymbols[i]; }
|
|
|
|
public Image getScoreSymbolImage(char c) { return scoreSymbols.get(c); }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets or returns the health drain rate.
|
|
|
|
*/
|
2014-06-30 10:03:39 +02:00
|
|
|
public void setDrainRate(float drainRate) { this.drainRate = drainRate; }
|
|
|
|
public float getDrainRate() { return drainRate; }
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets or returns the difficulty.
|
|
|
|
*/
|
2014-06-30 10:03:39 +02:00
|
|
|
public void setDifficulty(float difficulty) { this.difficulty = difficulty; }
|
|
|
|
public float getDifficulty() { return difficulty; }
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-02-15 07:40:01 +01:00
|
|
|
/**
|
|
|
|
* Sets the array of hit result offsets.
|
|
|
|
*/
|
|
|
|
public void setHitResultOffset(int[] hitResultOffset) { this.hitResultOffset = hitResultOffset; }
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* Draws a number with defaultSymbols.
|
|
|
|
* @param n the number to draw
|
|
|
|
* @param x the center x coordinate
|
|
|
|
* @param y the center y coordinate
|
|
|
|
* @param scale the scale to apply
|
|
|
|
*/
|
2015-03-30 16:06:16 +02:00
|
|
|
public void drawSymbolNumber(int n, float x, float y, float scale, Color color) {
|
2014-06-30 04:17:04 +02:00
|
|
|
int length = (int) (Math.log10(n) + 1);
|
|
|
|
float digitWidth = getDefaultSymbolImage(0).getWidth() * scale;
|
|
|
|
float cx = x + ((length - 1) * (digitWidth / 2));
|
|
|
|
|
|
|
|
for (int i = 0; i < length; i++) {
|
2015-03-30 16:06:16 +02:00
|
|
|
getDefaultSymbolImage(n % 10).getScaledCopy(scale).drawCentered(cx, y, color);
|
2014-06-30 04:17:04 +02:00
|
|
|
cx -= digitWidth;
|
|
|
|
n /= 10;
|
|
|
|
}
|
|
|
|
}
|
2014-07-16 22:56:23 +02:00
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* Draws a string of scoreSymbols.
|
|
|
|
* @param str the string to draw
|
|
|
|
* @param x the starting x coordinate
|
|
|
|
* @param y the y coordinate
|
|
|
|
* @param scale the scale to apply
|
2015-03-04 07:30:28 +01:00
|
|
|
* @param alpha the alpha level
|
2014-06-30 04:17:04 +02:00
|
|
|
* @param rightAlign align right (true) or left (false)
|
|
|
|
*/
|
2015-03-04 07:30:28 +01:00
|
|
|
public void drawSymbolString(String str, float x, float y, float scale, float alpha, boolean rightAlign) {
|
2014-06-30 04:17:04 +02:00
|
|
|
char[] c = str.toCharArray();
|
2015-03-04 07:30:28 +01:00
|
|
|
float cx = x;
|
2014-06-30 04:17:04 +02:00
|
|
|
if (rightAlign) {
|
|
|
|
for (int i = c.length - 1; i >= 0; i--) {
|
|
|
|
Image digit = getScoreSymbolImage(c[i]);
|
|
|
|
if (scale != 1.0f)
|
|
|
|
digit = digit.getScaledCopy(scale);
|
|
|
|
cx -= digit.getWidth();
|
2015-03-04 07:30:28 +01:00
|
|
|
digit.setAlpha(alpha);
|
2014-06-30 04:17:04 +02:00
|
|
|
digit.draw(cx, y);
|
2015-03-04 07:30:28 +01:00
|
|
|
digit.setAlpha(1f);
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (int i = 0; i < c.length; i++) {
|
|
|
|
Image digit = getScoreSymbolImage(c[i]);
|
|
|
|
if (scale != 1.0f)
|
|
|
|
digit = digit.getScaledCopy(scale);
|
2015-03-04 07:30:28 +01:00
|
|
|
digit.setAlpha(alpha);
|
2014-06-30 04:17:04 +02:00
|
|
|
digit.draw(cx, y);
|
2015-03-04 07:30:28 +01:00
|
|
|
digit.setAlpha(1f);
|
2014-06-30 04:17:04 +02:00
|
|
|
cx += digit.getWidth();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-02-19 01:35:26 +01:00
|
|
|
|
2015-02-16 00:51:07 +01:00
|
|
|
/**
|
2015-02-19 01:35:26 +01:00
|
|
|
* Draws a string of scoreSymbols of fixed width.
|
2015-02-16 00:51:07 +01:00
|
|
|
* @param str the string to draw
|
|
|
|
* @param x the starting x coordinate
|
|
|
|
* @param y the y coordinate
|
|
|
|
* @param scale the scale to apply
|
2015-02-16 23:05:01 +01:00
|
|
|
* @param fixedsize the width to use for all symbols
|
2015-02-16 00:51:07 +01:00
|
|
|
* @param rightAlign align right (true) or left (false)
|
|
|
|
*/
|
2015-03-04 07:30:28 +01:00
|
|
|
public void drawFixedSizeSymbolString(String str, float x, float y, float scale, float fixedsize, boolean rightAlign) {
|
2015-02-16 00:51:07 +01:00
|
|
|
char[] c = str.toCharArray();
|
2015-03-04 07:30:28 +01:00
|
|
|
float cx = x;
|
2015-02-16 00:51:07 +01:00
|
|
|
if (rightAlign) {
|
|
|
|
for (int i = c.length - 1; i >= 0; i--) {
|
|
|
|
Image digit = getScoreSymbolImage(c[i]);
|
|
|
|
if (scale != 1.0f)
|
|
|
|
digit = digit.getScaledCopy(scale);
|
|
|
|
cx -= fixedsize;
|
2015-02-19 01:35:26 +01:00
|
|
|
digit.draw(cx + (fixedsize - digit.getWidth()) / 2, y);
|
2015-02-16 00:51:07 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (int i = 0; i < c.length; i++) {
|
|
|
|
Image digit = getScoreSymbolImage(c[i]);
|
|
|
|
if (scale != 1.0f)
|
|
|
|
digit = digit.getScaledCopy(scale);
|
2015-02-19 01:35:26 +01:00
|
|
|
digit.draw(cx + (fixedsize - digit.getWidth()) / 2, y);
|
2015-02-16 00:51:07 +01:00
|
|
|
cx += fixedsize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
/**
|
2014-07-08 19:38:16 +02:00
|
|
|
* Draws game elements:
|
|
|
|
* scorebar, score, score percentage, map progress circle,
|
2015-02-15 07:40:01 +01:00
|
|
|
* mod icons, combo count, combo burst, hit error bar, and grade.
|
2014-06-30 04:17:04 +02:00
|
|
|
* @param g the graphics context
|
|
|
|
* @param breakPeriod if true, will not draw scorebar and combo elements, and will draw grade
|
|
|
|
* @param firstObject true if the first hit object's start time has not yet passed
|
|
|
|
*/
|
2015-02-19 01:35:26 +01:00
|
|
|
@SuppressWarnings("deprecation")
|
2014-07-18 03:16:15 +02:00
|
|
|
public void drawGameElements(Graphics g, boolean breakPeriod, boolean firstObject) {
|
2015-03-06 06:25:48 +01:00
|
|
|
boolean relaxAutoPilot = (GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive());
|
2015-03-04 07:30:28 +01:00
|
|
|
int margin = (int) (width * 0.008f);
|
2015-03-13 04:53:25 +01:00
|
|
|
float uiScale = GameImage.getUIscale();
|
2015-01-16 21:44:13 +01:00
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
// score
|
2015-03-06 06:25:48 +01:00
|
|
|
if (!relaxAutoPilot)
|
|
|
|
drawFixedSizeSymbolString((scoreDisplay < 100000000) ? String.format("%08d", scoreDisplay) : Long.toString(scoreDisplay),
|
|
|
|
width - margin, 0, 1.0f, getScoreSymbolImage('0').getWidth() - 2, true);
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
// score percentage
|
2014-07-08 19:38:16 +02:00
|
|
|
int symbolHeight = getScoreSymbolImage('0').getHeight();
|
2015-03-06 06:25:48 +01:00
|
|
|
if (!relaxAutoPilot)
|
|
|
|
drawSymbolString(
|
|
|
|
String.format((scorePercentDisplay < 10f) ? "0%.2f%%" : "%.2f%%", scorePercentDisplay),
|
|
|
|
width - margin, symbolHeight, 0.60f, 1f, true);
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
// map progress circle
|
2015-03-06 06:25:48 +01:00
|
|
|
OsuFile osu = MusicController.getOsuFile();
|
|
|
|
int firstObjectTime = osu.objects[0].getTime();
|
|
|
|
int trackPosition = MusicController.getPosition();
|
2015-02-16 00:51:07 +01:00
|
|
|
float circleDiameter = symbolHeight * 0.60f;
|
2015-03-04 07:30:28 +01:00
|
|
|
int circleX = (int) (width - margin - ( // max width: "100.00%"
|
2015-01-16 21:44:13 +01:00
|
|
|
getScoreSymbolImage('1').getWidth() +
|
|
|
|
getScoreSymbolImage('0').getWidth() * 4 +
|
|
|
|
getScoreSymbolImage('.').getWidth() +
|
|
|
|
getScoreSymbolImage('%').getWidth()
|
2015-02-16 00:51:07 +01:00
|
|
|
) * 0.60f - circleDiameter);
|
2015-03-06 06:25:48 +01:00
|
|
|
if (!relaxAutoPilot) {
|
|
|
|
g.setAntiAlias(true);
|
|
|
|
g.setLineWidth(2f);
|
|
|
|
g.setColor(Color.white);
|
|
|
|
g.drawOval(circleX, symbolHeight, circleDiameter, circleDiameter);
|
|
|
|
if (trackPosition > firstObjectTime) {
|
|
|
|
// map progress (white)
|
|
|
|
g.fillArc(circleX, symbolHeight, circleDiameter, circleDiameter,
|
|
|
|
-90, -90 + (int) (360f * (trackPosition - firstObjectTime) / (osu.endTime - firstObjectTime))
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
// lead-in time (yellow)
|
|
|
|
g.setColor(Utils.COLOR_YELLOW_ALPHA);
|
|
|
|
g.fillArc(circleX, symbolHeight, circleDiameter, circleDiameter,
|
|
|
|
-90 + (int) (360f * trackPosition / firstObjectTime), -90
|
|
|
|
);
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
2014-07-08 19:38:16 +02:00
|
|
|
// mod icons
|
2014-07-16 22:01:36 +02:00
|
|
|
if ((firstObject && trackPosition < firstObjectTime) || GameMod.AUTO.isActive()) {
|
|
|
|
int modWidth = GameMod.AUTO.getImage().getWidth();
|
2014-07-08 19:38:16 +02:00
|
|
|
float modX = (width * 0.98f) - modWidth;
|
2014-07-16 22:01:36 +02:00
|
|
|
int modCount = 0;
|
2015-01-22 06:44:45 +01:00
|
|
|
for (GameMod mod : GameMod.VALUES_REVERSED) {
|
2014-07-16 22:01:36 +02:00
|
|
|
if (mod.isActive()) {
|
|
|
|
mod.getImage().draw(
|
2014-07-08 19:38:16 +02:00
|
|
|
modX - (modCount * (modWidth / 2f)),
|
|
|
|
symbolHeight + circleDiameter + 10
|
|
|
|
);
|
|
|
|
modCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-20 21:08:16 +01:00
|
|
|
// hit error bar
|
|
|
|
if (Options.isHitErrorBarEnabled() && !hitErrorList.isEmpty()) {
|
|
|
|
// fade out with last tick
|
|
|
|
float hitErrorAlpha = 1f;
|
|
|
|
Color white = new Color(Color.white);
|
|
|
|
if (trackPosition - hitErrorList.getFirst().time > HIT_ERROR_FADE_TIME * 0.9f)
|
|
|
|
hitErrorAlpha = (HIT_ERROR_FADE_TIME - (trackPosition - hitErrorList.getFirst().time)) / (HIT_ERROR_FADE_TIME * 0.1f);
|
|
|
|
|
|
|
|
// draw bar
|
2015-03-13 04:53:25 +01:00
|
|
|
float hitErrorX = width / uiScale / 2;
|
|
|
|
float hitErrorY = height / uiScale - margin - 10;
|
|
|
|
float barY = (hitErrorY - 3) * uiScale, barHeight = 6 * uiScale;
|
|
|
|
float tickY = (hitErrorY - 10) * uiScale, tickHeight = 20 * uiScale;
|
2015-02-20 21:08:16 +01:00
|
|
|
float oldAlphaBlack = Utils.COLOR_BLACK_ALPHA.a;
|
|
|
|
Utils.COLOR_BLACK_ALPHA.a = hitErrorAlpha;
|
|
|
|
g.setColor(Utils.COLOR_BLACK_ALPHA);
|
2015-03-13 04:53:25 +01:00
|
|
|
g.fillRect((hitErrorX - 3 - hitResultOffset[HIT_50]) * uiScale, tickY,
|
|
|
|
(hitResultOffset[HIT_50] * 2) * uiScale, tickHeight);
|
2015-02-20 21:08:16 +01:00
|
|
|
Utils.COLOR_BLACK_ALPHA.a = oldAlphaBlack;
|
|
|
|
Utils.COLOR_LIGHT_ORANGE.a = hitErrorAlpha;
|
|
|
|
g.setColor(Utils.COLOR_LIGHT_ORANGE);
|
2015-03-13 04:53:25 +01:00
|
|
|
g.fillRect((hitErrorX - 3 - hitResultOffset[HIT_50]) * uiScale, barY,
|
|
|
|
(hitResultOffset[HIT_50] * 2) * uiScale, barHeight);
|
2015-02-20 21:08:16 +01:00
|
|
|
Utils.COLOR_LIGHT_ORANGE.a = 1f;
|
|
|
|
Utils.COLOR_LIGHT_GREEN.a = hitErrorAlpha;
|
|
|
|
g.setColor(Utils.COLOR_LIGHT_GREEN);
|
2015-03-13 04:53:25 +01:00
|
|
|
g.fillRect((hitErrorX - 3 - hitResultOffset[HIT_100]) * uiScale, barY,
|
|
|
|
(hitResultOffset[HIT_100] * 2) * uiScale, barHeight);
|
2015-02-20 21:08:16 +01:00
|
|
|
Utils.COLOR_LIGHT_GREEN.a = 1f;
|
|
|
|
Utils.COLOR_LIGHT_BLUE.a = hitErrorAlpha;
|
|
|
|
g.setColor(Utils.COLOR_LIGHT_BLUE);
|
2015-03-13 04:53:25 +01:00
|
|
|
g.fillRect((hitErrorX - 3 - hitResultOffset[HIT_300]) * uiScale, barY,
|
|
|
|
(hitResultOffset[HIT_300] * 2) * uiScale, barHeight);
|
2015-02-20 21:08:16 +01:00
|
|
|
Utils.COLOR_LIGHT_BLUE.a = 1f;
|
|
|
|
white.a = hitErrorAlpha;
|
|
|
|
g.setColor(white);
|
2015-03-13 04:53:25 +01:00
|
|
|
g.fillRect((hitErrorX - 1.5f) * uiScale, tickY, 3 * uiScale, tickHeight);
|
2015-02-20 21:08:16 +01:00
|
|
|
|
|
|
|
// draw ticks
|
2015-03-13 04:53:25 +01:00
|
|
|
float tickWidth = 2 * uiScale;
|
2015-02-20 21:08:16 +01:00
|
|
|
for (HitErrorInfo info : hitErrorList) {
|
|
|
|
int time = info.time;
|
|
|
|
float alpha = 1 - ((float) (trackPosition - time) / HIT_ERROR_FADE_TIME);
|
|
|
|
white.a = alpha * hitErrorAlpha;
|
|
|
|
g.setColor(white);
|
2015-03-13 04:53:25 +01:00
|
|
|
g.fillRect((hitErrorX + info.timeDiff - 1) * uiScale, tickY, tickWidth, tickHeight);
|
2015-02-20 21:08:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-06 06:25:48 +01:00
|
|
|
if (!breakPeriod && !relaxAutoPilot) {
|
2014-06-30 04:17:04 +02:00
|
|
|
// scorebar
|
2014-12-30 07:17:05 +01:00
|
|
|
float healthRatio = healthDisplay / 100f;
|
2014-06-30 04:17:04 +02:00
|
|
|
if (firstObject) { // gradually move ki before map begins
|
|
|
|
if (firstObjectTime >= 1500 && trackPosition < firstObjectTime - 500)
|
|
|
|
healthRatio = (float) trackPosition / (firstObjectTime - 500);
|
|
|
|
}
|
2015-01-26 00:36:54 +01:00
|
|
|
Image scorebar = GameImage.SCOREBAR_BG.getImage();
|
2015-02-16 17:34:35 +01:00
|
|
|
Image colour;
|
2015-02-19 01:35:26 +01:00
|
|
|
if (scorebarColour != null) {
|
|
|
|
scorebarColour.updateNoDraw(); // TODO deprecated method
|
2015-02-16 17:34:35 +01:00
|
|
|
colour = scorebarColour.getCurrentFrame();
|
2015-02-19 01:35:26 +01:00
|
|
|
} else
|
2015-02-16 17:34:35 +01:00
|
|
|
colour = GameImage.SCOREBAR_COLOUR.getImage();
|
2015-03-13 04:53:25 +01:00
|
|
|
float colourX = 4 * uiScale, colourY = 15 * uiScale;
|
|
|
|
Image colourCropped = colour.getSubImage(0, 0, (int) (645 * uiScale * healthRatio), colour.getHeight());
|
2015-02-19 01:35:26 +01:00
|
|
|
|
2015-02-16 17:34:35 +01:00
|
|
|
scorebar.setAlpha(1f);
|
2015-01-26 00:36:54 +01:00
|
|
|
scorebar.draw(0, 0);
|
|
|
|
colourCropped.draw(colourX, colourY);
|
2015-02-19 01:35:26 +01:00
|
|
|
|
2014-07-08 19:38:16 +02:00
|
|
|
Image ki = null;
|
2014-06-30 04:17:04 +02:00
|
|
|
if (health >= 50f)
|
2014-07-08 19:38:16 +02:00
|
|
|
ki = GameImage.SCOREBAR_KI.getImage();
|
2014-06-30 04:17:04 +02:00
|
|
|
else if (health >= 25f)
|
2014-07-08 19:38:16 +02:00
|
|
|
ki = GameImage.SCOREBAR_KI_DANGER.getImage();
|
2014-06-30 04:17:04 +02:00
|
|
|
else
|
2014-07-08 19:38:16 +02:00
|
|
|
ki = GameImage.SCOREBAR_KI_DANGER2.getImage();
|
2015-03-13 23:03:44 +01:00
|
|
|
if (comboPopTime < COMBO_POP_TIME)
|
|
|
|
ki = ki.getScaledCopy(1f + (0.45f * (1f - (float) comboPopTime / COMBO_POP_TIME)));
|
2015-02-16 23:05:01 +01:00
|
|
|
ki.drawCentered(colourX + colourCropped.getWidth(), colourY);
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
// combo burst
|
|
|
|
if (comboBurstIndex != -1 && comboBurstAlpha > 0f) {
|
|
|
|
Image comboBurst = comboBurstImages[comboBurstIndex];
|
|
|
|
comboBurst.setAlpha(comboBurstAlpha);
|
|
|
|
comboBurstImages[comboBurstIndex].draw(comboBurstX, height - comboBurst.getHeight());
|
|
|
|
}
|
|
|
|
|
|
|
|
// combo count
|
2015-03-04 07:30:28 +01:00
|
|
|
if (combo > 0) {
|
|
|
|
float comboPop = 1 - ((float) comboPopTime / COMBO_POP_TIME);
|
|
|
|
float comboPopBack = 1 + comboPop * 0.45f;
|
|
|
|
float comboPopFront = 1 + comboPop * 0.08f;
|
|
|
|
String comboString = String.format("%dx", combo);
|
|
|
|
if (comboPopTime != COMBO_POP_TIME)
|
|
|
|
drawSymbolString(comboString, margin, height - margin - (symbolHeight * comboPopBack), comboPopBack, 0.5f, false);
|
|
|
|
drawSymbolString(comboString, margin, height - margin - (symbolHeight * comboPopFront), comboPopFront, 1f, false);
|
|
|
|
}
|
2015-03-06 06:25:48 +01:00
|
|
|
} else if (!relaxAutoPilot) {
|
2014-06-30 04:17:04 +02:00
|
|
|
// grade
|
2015-01-22 21:12:15 +01:00
|
|
|
Grade grade = getGrade();
|
|
|
|
if (grade != Grade.NULL) {
|
|
|
|
Image gradeImage = grade.getSmallImage();
|
2014-07-09 04:17:48 +02:00
|
|
|
float gradeScale = symbolHeight * 0.75f / gradeImage.getHeight();
|
|
|
|
gradeImage.getScaledCopy(gradeScale).draw(
|
|
|
|
circleX - gradeImage.getWidth(), symbolHeight
|
|
|
|
);
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-01-28 09:47:24 +01:00
|
|
|
* Draws ranking elements: score, results, ranking, game mods.
|
2014-06-30 04:17:04 +02:00
|
|
|
* @param g the graphics context
|
2015-01-28 09:47:24 +01:00
|
|
|
* @param osu the OsuFile
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
2015-01-28 09:47:24 +01:00
|
|
|
public void drawRankingElements(Graphics g, OsuFile osu) {
|
2015-02-19 01:35:26 +01:00
|
|
|
// TODO Version 2 skins
|
2015-02-16 23:05:01 +01:00
|
|
|
float rankingHeight = 75;
|
2015-02-16 17:34:35 +01:00
|
|
|
float scoreTextScale = 1.0f;
|
|
|
|
float symbolTextScale = 1.15f;
|
2015-02-16 23:05:01 +01:00
|
|
|
float rankResultScale = 0.5f;
|
2015-03-13 04:53:25 +01:00
|
|
|
float uiScale = GameImage.getUIscale();
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-02-19 01:35:26 +01:00
|
|
|
// ranking panel
|
2015-03-13 04:53:25 +01:00
|
|
|
GameImage.RANKING_PANEL.getImage().draw(0, (int) (rankingHeight * uiScale));
|
2015-02-19 01:35:26 +01:00
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
// score
|
2015-02-16 23:05:01 +01:00
|
|
|
drawFixedSizeSymbolString(
|
|
|
|
(score < 100000000) ? String.format("%08d", score) : Long.toString(score),
|
2015-03-13 04:53:25 +01:00
|
|
|
210 * uiScale, (rankingHeight + 50) * uiScale,
|
|
|
|
scoreTextScale, getScoreSymbolImage('0').getWidth() * scoreTextScale - 2, false
|
|
|
|
);
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
// result counts
|
2015-02-16 17:34:35 +01:00
|
|
|
float resultInitialX = 130;
|
|
|
|
float resultInitialY = rankingHeight + 140;
|
|
|
|
float resultHitInitialX = 65;
|
2015-02-19 01:35:26 +01:00
|
|
|
float resultHitInitialY = rankingHeight + 182;
|
2015-02-16 17:34:35 +01:00
|
|
|
float resultOffsetX = 320;
|
|
|
|
float resultOffsetY = 96;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
int[] rankDrawOrder = { HIT_300, HIT_300G, HIT_100, HIT_100K, HIT_50, HIT_MISS };
|
|
|
|
int[] rankResultOrder = {
|
|
|
|
hitResultCount[HIT_300], hitResultCount[HIT_300G],
|
|
|
|
hitResultCount[HIT_100], hitResultCount[HIT_100K] + hitResultCount[HIT_300K],
|
|
|
|
hitResultCount[HIT_50], hitResultCount[HIT_MISS]
|
|
|
|
};
|
|
|
|
|
|
|
|
for (int i = 0; i < rankDrawOrder.length; i += 2) {
|
2015-02-16 17:34:35 +01:00
|
|
|
hitResults[rankDrawOrder[i]].getScaledCopy(rankResultScale).drawCentered(
|
2015-03-13 04:53:25 +01:00
|
|
|
resultHitInitialX * uiScale,
|
|
|
|
(resultHitInitialY + (resultOffsetY * (i / 2))) * uiScale);
|
2015-02-16 17:34:35 +01:00
|
|
|
hitResults[rankDrawOrder[i+1]].getScaledCopy(rankResultScale).drawCentered(
|
2015-03-13 04:53:25 +01:00
|
|
|
(resultHitInitialX + resultOffsetX) * uiScale,
|
|
|
|
(resultHitInitialY + (resultOffsetY * (i / 2))) * uiScale);
|
2014-06-30 04:17:04 +02:00
|
|
|
drawSymbolString(String.format("%dx", rankResultOrder[i]),
|
2015-03-13 04:53:25 +01:00
|
|
|
resultInitialX * uiScale,
|
|
|
|
(resultInitialY + (resultOffsetY * (i / 2))) * uiScale,
|
2015-03-04 07:30:28 +01:00
|
|
|
symbolTextScale, 1f, false);
|
2014-06-30 04:17:04 +02:00
|
|
|
drawSymbolString(String.format("%dx", rankResultOrder[i+1]),
|
2015-03-13 04:53:25 +01:00
|
|
|
(resultInitialX + resultOffsetX) * uiScale,
|
|
|
|
(resultInitialY + (resultOffsetY * (i / 2))) * uiScale,
|
2015-03-04 07:30:28 +01:00
|
|
|
symbolTextScale, 1f, false);
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// combo and accuracy
|
2015-02-16 23:05:01 +01:00
|
|
|
float accuracyX = 295;
|
2015-02-16 17:34:35 +01:00
|
|
|
float textY = rankingHeight + 425;
|
|
|
|
float numbersY = textY + 30;
|
2014-06-30 04:17:04 +02:00
|
|
|
drawSymbolString(String.format("%dx", comboMax),
|
2015-03-13 04:53:25 +01:00
|
|
|
25 * uiScale, numbersY * uiScale, symbolTextScale, 1f, false);
|
2014-06-30 04:17:04 +02:00
|
|
|
drawSymbolString(String.format("%02.2f%%", getScorePercent()),
|
2015-03-13 04:53:25 +01:00
|
|
|
(accuracyX + 20) * uiScale, numbersY * uiScale, symbolTextScale, 1f, false);
|
|
|
|
GameImage.RANKING_MAXCOMBO.getImage().draw(10 * uiScale, textY * uiScale);
|
|
|
|
GameImage.RANKING_ACCURACY.getImage().draw(accuracyX * uiScale, textY * uiScale);
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
// full combo
|
2015-01-28 09:47:24 +01:00
|
|
|
if (comboMax == fullObjectCount) {
|
2014-07-04 22:41:52 +02:00
|
|
|
GameImage.RANKING_PERFECT.getImage().draw(
|
|
|
|
width * 0.08f,
|
|
|
|
(height * 0.99f) - GameImage.RANKING_PERFECT.getImage().getHeight()
|
|
|
|
);
|
|
|
|
}
|
2015-02-19 01:35:26 +01:00
|
|
|
|
2015-02-16 00:51:07 +01:00
|
|
|
// grade
|
|
|
|
Grade grade = getGrade();
|
|
|
|
if (grade != Grade.NULL)
|
2015-02-19 01:35:26 +01:00
|
|
|
grade.getLargeImage().draw(width - grade.getLargeImage().getWidth(), rankingHeight);
|
2015-01-28 09:47:24 +01:00
|
|
|
|
2015-02-19 01:35:26 +01:00
|
|
|
// header
|
|
|
|
Image rankingTitle = GameImage.RANKING_TITLE.getImage();
|
2015-02-16 17:34:35 +01:00
|
|
|
g.setColor(Utils.COLOR_BLACK_ALPHA);
|
2015-03-13 04:53:25 +01:00
|
|
|
g.fillRect(0, 0, width, 100 * uiScale);
|
2015-02-16 17:34:35 +01:00
|
|
|
rankingTitle.draw((width * 0.97f) - rankingTitle.getWidth(), 0);
|
2015-02-19 01:35:26 +01:00
|
|
|
float c = width * 0.01f;
|
|
|
|
Utils.FONT_LARGE.drawString(c, c,
|
2015-02-16 17:34:35 +01:00
|
|
|
String.format("%s - %s [%s]", osu.getArtist(), osu.getTitle(), osu.version), Color.white);
|
2015-02-19 01:35:26 +01:00
|
|
|
Utils.FONT_MEDIUM.drawString(c, c + Utils.FONT_LARGE.getLineHeight() - 6,
|
2015-02-16 17:34:35 +01:00
|
|
|
String.format("Beatmap by %s", osu.creator), Color.white);
|
|
|
|
Utils.FONT_MEDIUM.drawString(
|
2015-02-19 01:35:26 +01:00
|
|
|
c, c + Utils.FONT_LARGE.getLineHeight() + Utils.FONT_MEDIUM.getLineHeight() - 10,
|
2015-02-16 17:34:35 +01:00
|
|
|
String.format("Played on %s.", scoreData.getTimeString()), Color.white);
|
|
|
|
|
2015-01-28 09:47:24 +01:00
|
|
|
// mod icons
|
|
|
|
int modWidth = GameMod.AUTO.getImage().getWidth();
|
|
|
|
float modX = (width * 0.98f) - modWidth;
|
|
|
|
int modCount = 0;
|
|
|
|
for (GameMod mod : GameMod.VALUES_REVERSED) {
|
|
|
|
if ((mod.getBit() & scoreData.mods) > 0) {
|
|
|
|
mod.getImage().draw(modX - (modCount * (modWidth / 2f)), height / 2f);
|
|
|
|
modCount++;
|
|
|
|
}
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws stored hit results and removes them from the list as necessary.
|
|
|
|
* @param trackPosition the current track position
|
|
|
|
*/
|
|
|
|
public void drawHitResults(int trackPosition) {
|
|
|
|
Iterator<OsuHitObjectResult> iter = hitResultList.iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
|
|
OsuHitObjectResult hitResult = iter.next();
|
2015-02-20 22:56:41 +01:00
|
|
|
if (hitResult.time + HITRESULT_FADE_TIME > trackPosition) {
|
|
|
|
// hit result
|
2014-06-30 04:17:04 +02:00
|
|
|
hitResults[hitResult.result].setAlpha(hitResult.alpha);
|
|
|
|
hitResults[hitResult.result].drawCentered(hitResult.x, hitResult.y);
|
2015-02-20 22:56:41 +01:00
|
|
|
hitResults[hitResult.result].setAlpha(1f);
|
|
|
|
|
|
|
|
// spinner
|
|
|
|
if (hitResult.isSpinner && hitResult.result != HIT_MISS) {
|
|
|
|
Image spinnerOsu = GameImage.SPINNER_OSU.getImage();
|
|
|
|
spinnerOsu.setAlpha(hitResult.alpha);
|
|
|
|
spinnerOsu.drawCentered(width / 2, height / 4);
|
|
|
|
spinnerOsu.setAlpha(1f);
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
|
|
|
|
// hit lighting
|
2015-02-20 22:56:41 +01:00
|
|
|
else if (Options.isHitLightingEnabled() && hitResult.result != HIT_MISS &&
|
2015-01-21 09:47:47 +01:00
|
|
|
hitResult.result != HIT_SLIDER30 && hitResult.result != HIT_SLIDER10) {
|
2015-02-20 22:56:41 +01:00
|
|
|
float scale = 1f + ((trackPosition - hitResult.time) / (float) HITRESULT_FADE_TIME);
|
2015-01-21 09:47:47 +01:00
|
|
|
Image scaledLighting = GameImage.LIGHTING.getImage().getScaledCopy(scale);
|
|
|
|
Image scaledLighting1 = GameImage.LIGHTING1.getImage().getScaledCopy(scale);
|
2015-02-14 19:45:14 +01:00
|
|
|
scaledLighting.setAlpha(hitResult.alpha);
|
|
|
|
scaledLighting1.setAlpha(hitResult.alpha);
|
2015-02-15 07:40:01 +01:00
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
scaledLighting.draw(hitResult.x - (scaledLighting.getWidth() / 2f),
|
2015-01-21 09:47:47 +01:00
|
|
|
hitResult.y - (scaledLighting.getHeight() / 2f), hitResult.color);
|
|
|
|
scaledLighting1.draw(hitResult.x - (scaledLighting1.getWidth() / 2f),
|
|
|
|
hitResult.y - (scaledLighting1.getHeight() / 2f), hitResult.color);
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
2015-02-20 22:56:41 +01:00
|
|
|
|
|
|
|
hitResult.alpha = 1 - ((float) (trackPosition - hitResult.time) / HITRESULT_FADE_TIME);
|
2014-06-30 04:17:04 +02:00
|
|
|
} else
|
|
|
|
iter.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes health by a given percentage, modified by drainRate.
|
|
|
|
*/
|
|
|
|
public void changeHealth(float percent) {
|
|
|
|
// TODO: drainRate formula
|
|
|
|
health += percent;
|
|
|
|
if (health > 100f)
|
|
|
|
health = 100f;
|
2014-07-16 22:56:23 +02:00
|
|
|
else if (health < 0f)
|
2014-06-30 04:17:04 +02:00
|
|
|
health = 0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns health percentage.
|
|
|
|
*/
|
|
|
|
public float getHealth() { return health; }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns false if health is zero.
|
|
|
|
* If "No Fail" or "Auto" mods are active, this will always return true.
|
|
|
|
*/
|
|
|
|
public boolean isAlive() {
|
2015-03-06 06:25:48 +01:00
|
|
|
return (health > 0f || GameMod.NO_FAIL.isActive() || GameMod.AUTO.isActive() ||
|
|
|
|
GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive());
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes score by a raw value (not affected by other modifiers).
|
|
|
|
*/
|
|
|
|
public void changeScore(int value) { score += value; }
|
|
|
|
|
|
|
|
/**
|
2015-01-28 09:47:24 +01:00
|
|
|
* Returns the raw score percentage.
|
|
|
|
* @param hit300 the number of 300s
|
|
|
|
* @param hit100 the number of 100s
|
|
|
|
* @param hit50 the number of 50s
|
|
|
|
* @param miss the number of misses
|
|
|
|
* @return the percentage
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
2015-01-28 09:47:24 +01:00
|
|
|
public static float getScorePercent(int hit300, int hit100, int hit50, int miss) {
|
2014-06-30 04:17:04 +02:00
|
|
|
float percent = 0;
|
2015-01-28 09:47:24 +01:00
|
|
|
int objectCount = hit300 + hit100 + hit50 + miss;
|
2014-06-30 04:17:04 +02:00
|
|
|
if (objectCount > 0)
|
2015-01-28 09:47:24 +01:00
|
|
|
percent = (hit300 * 300 + hit100 * 100 + hit50 * 50) / (objectCount * 300f) * 100f;
|
2014-06-30 04:17:04 +02:00
|
|
|
return percent;
|
|
|
|
}
|
|
|
|
|
2015-01-28 09:47:24 +01:00
|
|
|
/**
|
|
|
|
* Returns the raw score percentage.
|
|
|
|
*/
|
|
|
|
private float getScorePercent() {
|
|
|
|
return getScorePercent(
|
|
|
|
hitResultCount[HIT_300], hitResultCount[HIT_100],
|
|
|
|
hitResultCount[HIT_50], hitResultCount[HIT_MISS]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
2015-01-22 21:12:15 +01:00
|
|
|
* Returns letter grade based on score data,
|
|
|
|
* or Grade.NULL if no objects have been processed.
|
2015-01-28 09:47:24 +01:00
|
|
|
* @param hit300 the number of 300s
|
|
|
|
* @param hit100 the number of 100s
|
|
|
|
* @param hit50 the number of 50s
|
|
|
|
* @param miss the number of misses
|
2015-03-15 20:38:04 +01:00
|
|
|
* @param silver whether or not a silver SS/S should be awarded (if applicable)
|
2015-01-22 21:12:15 +01:00
|
|
|
* @return the current Grade
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
2015-03-15 20:38:04 +01:00
|
|
|
public static Grade getGrade(int hit300, int hit100, int hit50, int miss, boolean silver) {
|
2015-01-28 09:47:24 +01:00
|
|
|
int objectCount = hit300 + hit100 + hit50 + miss;
|
2014-06-30 04:17:04 +02:00
|
|
|
if (objectCount < 1) // avoid division by zero
|
2015-01-22 21:12:15 +01:00
|
|
|
return Grade.NULL;
|
2014-06-30 04:17:04 +02:00
|
|
|
|
2015-01-28 09:47:24 +01:00
|
|
|
float percent = getScorePercent(hit300, hit100, hit50, miss);
|
|
|
|
float hit300ratio = hit300 * 100f / objectCount;
|
2015-03-15 20:38:04 +01:00
|
|
|
float hit50ratio = hit50 * 100f / objectCount;
|
|
|
|
boolean noMiss = (miss == 0);
|
2014-06-30 04:17:04 +02:00
|
|
|
if (percent >= 100f)
|
2015-03-15 20:38:04 +01:00
|
|
|
return (silver) ? Grade.SSH : Grade.SS;
|
2014-06-30 04:17:04 +02:00
|
|
|
else if (hit300ratio >= 90f && hit50ratio < 1.0f && noMiss)
|
2015-03-15 20:38:04 +01:00
|
|
|
return (silver) ? Grade.SH : Grade.S;
|
2014-06-30 04:17:04 +02:00
|
|
|
else if ((hit300ratio >= 80f && noMiss) || hit300ratio >= 90f)
|
2015-01-22 21:12:15 +01:00
|
|
|
return Grade.A;
|
2014-06-30 04:17:04 +02:00
|
|
|
else if ((hit300ratio >= 70f && noMiss) || hit300ratio >= 80f)
|
2015-01-22 21:12:15 +01:00
|
|
|
return Grade.B;
|
2014-06-30 04:17:04 +02:00
|
|
|
else if (hit300ratio >= 60f)
|
2015-01-22 21:12:15 +01:00
|
|
|
return Grade.C;
|
2014-06-30 04:17:04 +02:00
|
|
|
else
|
2015-01-22 21:12:15 +01:00
|
|
|
return Grade.D;
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
2015-01-28 09:47:24 +01:00
|
|
|
/**
|
|
|
|
* Returns letter grade based on score data,
|
|
|
|
* or Grade.NULL if no objects have been processed.
|
|
|
|
*/
|
|
|
|
private Grade getGrade() {
|
|
|
|
return getGrade(
|
|
|
|
hitResultCount[HIT_300], hitResultCount[HIT_100],
|
2015-03-15 20:38:04 +01:00
|
|
|
hitResultCount[HIT_50], hitResultCount[HIT_MISS],
|
|
|
|
(GameMod.HIDDEN.isActive() || GameMod.FLASHLIGHT.isActive())
|
2015-01-28 09:47:24 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2014-07-03 08:03:56 +02:00
|
|
|
/**
|
2015-02-20 22:56:41 +01:00
|
|
|
* Updates displayed elements based on a delta value.
|
2014-07-03 08:03:56 +02:00
|
|
|
* @param delta the delta interval since the last call
|
|
|
|
*/
|
2014-12-30 07:17:05 +01:00
|
|
|
public void updateDisplays(int delta) {
|
|
|
|
// score display
|
2014-07-03 08:03:56 +02:00
|
|
|
if (scoreDisplay < score) {
|
|
|
|
scoreDisplay += (score - scoreDisplay) * delta / 50 + 1;
|
|
|
|
if (scoreDisplay > score)
|
|
|
|
scoreDisplay = score;
|
|
|
|
}
|
|
|
|
|
2015-02-20 22:56:41 +01:00
|
|
|
// score percent display
|
|
|
|
float scorePercent = getScorePercent();
|
|
|
|
if (scorePercentDisplay != scorePercent) {
|
|
|
|
if (scorePercentDisplay < scorePercent) {
|
|
|
|
scorePercentDisplay += (scorePercent - scorePercentDisplay) * delta / 50f + 0.01f;
|
|
|
|
if (scorePercentDisplay > scorePercent)
|
|
|
|
scorePercentDisplay = scorePercent;
|
|
|
|
} else {
|
|
|
|
scorePercentDisplay -= (scorePercentDisplay - scorePercent) * delta / 50f + 0.01f;
|
|
|
|
if (scorePercentDisplay < scorePercent)
|
|
|
|
scorePercentDisplay = scorePercent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-30 07:17:05 +01:00
|
|
|
// health display
|
|
|
|
if (healthDisplay != health) {
|
|
|
|
float shift = delta / 15f;
|
|
|
|
if (healthDisplay < health) {
|
|
|
|
healthDisplay += shift;
|
|
|
|
if (healthDisplay > health)
|
|
|
|
healthDisplay = health;
|
|
|
|
} else {
|
|
|
|
healthDisplay -= shift;
|
|
|
|
if (healthDisplay < health)
|
|
|
|
healthDisplay = health;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// combo burst
|
2014-06-30 04:17:04 +02:00
|
|
|
if (comboBurstIndex > -1 && Options.isComboBurstEnabled()) {
|
|
|
|
int leftX = 0;
|
|
|
|
int rightX = width - comboBurstImages[comboBurstIndex].getWidth();
|
|
|
|
if (comboBurstX < leftX) {
|
2015-03-14 13:11:33 +01:00
|
|
|
comboBurstX += (delta / 2f) * GameImage.getUIscale();
|
2014-06-30 04:17:04 +02:00
|
|
|
if (comboBurstX > leftX)
|
|
|
|
comboBurstX = leftX;
|
|
|
|
} else if (comboBurstX > rightX) {
|
2015-03-14 13:11:33 +01:00
|
|
|
comboBurstX -= (delta / 2f) * GameImage.getUIscale();
|
2014-06-30 04:17:04 +02:00
|
|
|
if (comboBurstX < rightX)
|
|
|
|
comboBurstX = rightX;
|
|
|
|
} else if (comboBurstAlpha > 0f) {
|
|
|
|
comboBurstAlpha -= (delta / 1200f);
|
|
|
|
if (comboBurstAlpha < 0f)
|
|
|
|
comboBurstAlpha = 0f;
|
|
|
|
}
|
|
|
|
}
|
2015-02-20 21:08:16 +01:00
|
|
|
|
2015-03-04 07:30:28 +01:00
|
|
|
// combo pop
|
|
|
|
comboPopTime += delta;
|
|
|
|
if (comboPopTime > COMBO_POP_TIME)
|
|
|
|
comboPopTime = COMBO_POP_TIME;
|
|
|
|
|
2015-02-20 21:08:16 +01:00
|
|
|
// hit error bar
|
|
|
|
if (Options.isHitErrorBarEnabled()) {
|
|
|
|
int trackPosition = MusicController.getPosition();
|
|
|
|
Iterator<HitErrorInfo> iter = hitErrorList.iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
|
|
HitErrorInfo info = iter.next();
|
|
|
|
if (Math.abs(info.timeDiff) >= hitResultOffset[GameData.HIT_50] ||
|
|
|
|
info.time + HIT_ERROR_FADE_TIME <= trackPosition)
|
|
|
|
iter.remove();
|
|
|
|
}
|
|
|
|
}
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
|
2015-03-15 20:38:04 +01:00
|
|
|
/**
|
|
|
|
* Returns the current combo streak.
|
|
|
|
*/
|
|
|
|
public int getComboStreak() { return combo; }
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* Increases the combo streak by one.
|
|
|
|
*/
|
|
|
|
private void incrementComboStreak() {
|
|
|
|
combo++;
|
2015-03-04 07:30:28 +01:00
|
|
|
comboPopTime = 0;
|
2014-06-30 04:17:04 +02:00
|
|
|
if (combo > comboMax)
|
|
|
|
comboMax = combo;
|
2015-01-16 21:44:13 +01:00
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
// combo bursts (at 30, 60, 100+50x)
|
|
|
|
if (Options.isComboBurstEnabled() &&
|
|
|
|
(combo == 30 || combo == 60 || (combo >= 100 && combo % 50 == 0))) {
|
|
|
|
if (combo == 30)
|
|
|
|
comboBurstIndex = 0;
|
|
|
|
else
|
|
|
|
comboBurstIndex = (comboBurstIndex + 1) % comboBurstImages.length;
|
|
|
|
comboBurstAlpha = 0.8f;
|
|
|
|
if ((comboBurstIndex % 2) == 0)
|
|
|
|
comboBurstX = width;
|
|
|
|
else
|
|
|
|
comboBurstX = comboBurstImages[0].getWidth() * -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-01 07:14:03 +02:00
|
|
|
/**
|
|
|
|
* Resets the combo streak to zero.
|
|
|
|
*/
|
|
|
|
private void resetComboStreak() {
|
2015-03-06 06:25:48 +01:00
|
|
|
if (combo >= 20 && !(GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive()))
|
2015-01-08 01:29:51 +01:00
|
|
|
SoundController.playSound(SoundEffect.COMBOBREAK);
|
2014-07-01 07:14:03 +02:00
|
|
|
combo = 0;
|
2014-07-16 22:01:36 +02:00
|
|
|
if (GameMod.SUDDEN_DEATH.isActive())
|
2014-07-01 07:14:03 +02:00
|
|
|
health = 0f;
|
|
|
|
}
|
|
|
|
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* Handles a slider tick result.
|
|
|
|
* @param time the tick start time
|
|
|
|
* @param result the hit result (HIT_* constants)
|
|
|
|
* @param x the x coordinate
|
|
|
|
* @param y the y coordinate
|
2015-03-01 19:58:03 +01:00
|
|
|
* @param hitObject the hit object
|
|
|
|
* @param repeat the current repeat number
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
2015-03-01 19:58:03 +01:00
|
|
|
public void sliderTickResult(int time, int result, float x, float y, OsuHitObject hitObject, int repeat) {
|
2014-06-30 04:17:04 +02:00
|
|
|
int hitValue = 0;
|
|
|
|
switch (result) {
|
|
|
|
case HIT_SLIDER30:
|
|
|
|
hitValue = 30;
|
|
|
|
incrementComboStreak();
|
|
|
|
changeHealth(1f);
|
2015-03-01 17:06:46 +01:00
|
|
|
SoundController.playHitSound(
|
2015-03-01 19:58:03 +01:00
|
|
|
hitObject.getEdgeHitSoundType(repeat),
|
|
|
|
hitObject.getSampleSet(repeat),
|
|
|
|
hitObject.getAdditionSampleSet(repeat));
|
2014-06-30 04:17:04 +02:00
|
|
|
break;
|
|
|
|
case HIT_SLIDER10:
|
|
|
|
hitValue = 10;
|
|
|
|
incrementComboStreak();
|
2015-01-08 01:29:51 +01:00
|
|
|
SoundController.playHitSound(HitSound.SLIDERTICK);
|
2014-06-30 04:17:04 +02:00
|
|
|
break;
|
|
|
|
case HIT_MISS:
|
2014-07-01 07:14:03 +02:00
|
|
|
resetComboStreak();
|
2014-06-30 04:17:04 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
fullObjectCount++;
|
|
|
|
|
|
|
|
if (hitValue > 0) {
|
|
|
|
score += hitValue;
|
2014-07-03 00:24:19 +02:00
|
|
|
if (!Options.isPerfectHitBurstEnabled())
|
|
|
|
; // hide perfect hit results
|
|
|
|
else
|
2015-02-20 22:56:41 +01:00
|
|
|
hitResultList.add(new OsuHitObjectResult(time, result, x, y, null, false));
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles a hit result.
|
|
|
|
* @param time the object start time
|
|
|
|
* @param result the hit result (HIT_* constants)
|
|
|
|
* @param x the x coordinate
|
|
|
|
* @param y the y coordinate
|
|
|
|
* @param color the combo color
|
|
|
|
* @param end true if this is the last hit object in the combo
|
2015-03-01 19:58:03 +01:00
|
|
|
* @param hitObject the hit object
|
|
|
|
* @param repeat the current repeat number (for sliders, or 0 otherwise)
|
2014-06-30 04:17:04 +02:00
|
|
|
*/
|
2014-07-01 07:14:03 +02:00
|
|
|
public void hitResult(int time, int result, float x, float y, Color color,
|
2015-03-01 19:58:03 +01:00
|
|
|
boolean end, OsuHitObject hitObject, int repeat) {
|
2014-06-30 04:17:04 +02:00
|
|
|
int hitValue = 0;
|
2014-07-03 00:24:19 +02:00
|
|
|
boolean perfectHit = false;
|
2014-06-30 04:17:04 +02:00
|
|
|
switch (result) {
|
|
|
|
case HIT_300:
|
2014-07-03 00:24:19 +02:00
|
|
|
perfectHit = true;
|
2014-06-30 04:17:04 +02:00
|
|
|
hitValue = 300;
|
|
|
|
changeHealth(5f);
|
|
|
|
break;
|
|
|
|
case HIT_100:
|
|
|
|
hitValue = 100;
|
|
|
|
changeHealth(2f);
|
|
|
|
comboEnd |= 1;
|
|
|
|
break;
|
|
|
|
case HIT_50:
|
|
|
|
hitValue = 50;
|
|
|
|
comboEnd |= 2;
|
|
|
|
break;
|
|
|
|
case HIT_MISS:
|
|
|
|
hitValue = 0;
|
|
|
|
changeHealth(-10f);
|
|
|
|
comboEnd |= 2;
|
2014-07-01 07:14:03 +02:00
|
|
|
resetComboStreak();
|
2014-06-30 04:17:04 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (hitValue > 0) {
|
2015-03-01 17:06:46 +01:00
|
|
|
SoundController.playHitSound(
|
2015-03-01 19:58:03 +01:00
|
|
|
hitObject.getEdgeHitSoundType(repeat),
|
|
|
|
hitObject.getSampleSet(repeat),
|
|
|
|
hitObject.getAdditionSampleSet(repeat));
|
2014-06-30 04:17:04 +02:00
|
|
|
/**
|
|
|
|
* [SCORE FORMULA]
|
|
|
|
* Score = Hit Value + Hit Value * (Combo * Difficulty * Mod) / 25
|
|
|
|
* - Hit Value: hit result (50, 100, 300), slider ticks, spinner bonus
|
|
|
|
* - Combo: combo before this hit - 1 (minimum 0)
|
|
|
|
* - Difficulty: the beatmap difficulty
|
|
|
|
* - Mod: mod multipliers
|
|
|
|
*/
|
2015-02-16 23:53:24 +01:00
|
|
|
score += (hitValue + (hitValue * (Math.max(combo - 1, 0) * difficulty * GameMod.getScoreMultiplier()) / 25));
|
2014-06-30 04:17:04 +02:00
|
|
|
incrementComboStreak();
|
|
|
|
}
|
|
|
|
hitResultCount[result]++;
|
|
|
|
fullObjectCount++;
|
|
|
|
|
|
|
|
// last element in combo: check for Geki/Katu
|
|
|
|
if (end) {
|
|
|
|
if (comboEnd == 0) {
|
|
|
|
result = HIT_300G;
|
|
|
|
changeHealth(15f);
|
|
|
|
hitResultCount[result]++;
|
|
|
|
} else if ((comboEnd & 2) == 0) {
|
|
|
|
if (result == HIT_100) {
|
|
|
|
result = HIT_100K;
|
|
|
|
changeHealth(10f);
|
|
|
|
hitResultCount[result]++;
|
|
|
|
} else if (result == HIT_300) {
|
|
|
|
result = HIT_300K;
|
|
|
|
changeHealth(10f);
|
|
|
|
hitResultCount[result]++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
comboEnd = 0;
|
|
|
|
}
|
|
|
|
|
2014-07-03 00:24:19 +02:00
|
|
|
if (perfectHit && !Options.isPerfectHitBurstEnabled())
|
|
|
|
; // hide perfect hit results
|
2015-03-06 06:25:48 +01:00
|
|
|
else if (result == HIT_MISS && (GameMod.RELAX.isActive() || GameMod.AUTOPILOT.isActive()))
|
|
|
|
; // "relax" and "autopilot" mods: hide misses
|
2014-07-03 00:24:19 +02:00
|
|
|
else
|
2015-03-01 19:58:03 +01:00
|
|
|
hitResultList.add(new OsuHitObjectResult(time, result, x, y, color, hitObject.isSpinner()));
|
2014-06-30 04:17:04 +02:00
|
|
|
}
|
2015-01-28 09:47:24 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a ScoreData object encapsulating all game data.
|
|
|
|
* If score data already exists, the existing object will be returned
|
|
|
|
* (i.e. this will not overwrite existing data).
|
|
|
|
* @param osu the OsuFile
|
|
|
|
* @return the ScoreData object
|
|
|
|
*/
|
|
|
|
public ScoreData getScoreData(OsuFile osu) {
|
|
|
|
if (scoreData != null)
|
|
|
|
return scoreData;
|
|
|
|
|
|
|
|
scoreData = new ScoreData();
|
|
|
|
scoreData.timestamp = System.currentTimeMillis() / 1000L;
|
|
|
|
scoreData.MID = osu.beatmapID;
|
|
|
|
scoreData.MSID = osu.beatmapSetID;
|
|
|
|
scoreData.title = osu.title;
|
|
|
|
scoreData.artist = osu.artist;
|
|
|
|
scoreData.creator = osu.creator;
|
|
|
|
scoreData.version = osu.version;
|
|
|
|
scoreData.hit300 = hitResultCount[HIT_300];
|
|
|
|
scoreData.hit100 = hitResultCount[HIT_100];
|
|
|
|
scoreData.hit50 = hitResultCount[HIT_50];
|
|
|
|
scoreData.geki = hitResultCount[HIT_300G];
|
|
|
|
scoreData.katu = hitResultCount[HIT_300K] + hitResultCount[HIT_100K];
|
|
|
|
scoreData.miss = hitResultCount[HIT_MISS];
|
|
|
|
scoreData.score = score;
|
|
|
|
scoreData.combo = comboMax;
|
|
|
|
scoreData.perfect = (comboMax == fullObjectCount);
|
2015-03-09 23:32:43 +01:00
|
|
|
scoreData.mods = GameMod.getModState();
|
2015-03-12 01:52:51 +01:00
|
|
|
scoreData.replayString = (replay == null) ? null : replay.getReplayFilename();
|
2015-01-28 09:47:24 +01:00
|
|
|
return scoreData;
|
|
|
|
}
|
|
|
|
|
2015-03-10 23:10:51 +01:00
|
|
|
/**
|
|
|
|
* Returns a Replay object encapsulating all game data.
|
2015-03-19 08:04:35 +01:00
|
|
|
* If a replay already exists and frames is null, the existing object will be returned.
|
2015-03-10 23:10:51 +01:00
|
|
|
* @param frames the replay frames
|
2015-03-19 08:04:35 +01:00
|
|
|
* @param osu the associated OsuFile
|
2015-03-11 20:53:19 +01:00
|
|
|
* @return the Replay object, or null if none exists and frames is null
|
2015-03-10 23:10:51 +01:00
|
|
|
*/
|
2015-03-19 08:04:35 +01:00
|
|
|
public Replay getReplay(ReplayFrame[] frames, OsuFile osu) {
|
2015-03-11 20:53:19 +01:00
|
|
|
if (replay != null && frames == null)
|
2015-03-10 23:10:51 +01:00
|
|
|
return replay;
|
|
|
|
|
2015-03-11 03:37:23 +01:00
|
|
|
if (frames == null)
|
|
|
|
return null;
|
|
|
|
|
2015-03-10 23:10:51 +01:00
|
|
|
replay = new Replay();
|
|
|
|
replay.mode = OsuFile.MODE_OSU;
|
|
|
|
replay.version = Updater.get().getBuildDate();
|
2015-03-19 08:04:35 +01:00
|
|
|
replay.beatmapHash = (osu == null) ? "" : Utils.getMD5(osu.getFile());
|
2015-03-10 23:10:51 +01:00
|
|
|
replay.playerName = ""; // TODO
|
2015-03-12 01:52:51 +01:00
|
|
|
replay.replayHash = Long.toString(System.currentTimeMillis()); // TODO
|
2015-03-10 23:10:51 +01:00
|
|
|
replay.hit300 = (short) hitResultCount[HIT_300];
|
|
|
|
replay.hit100 = (short) hitResultCount[HIT_100];
|
|
|
|
replay.hit50 = (short) hitResultCount[HIT_50];
|
|
|
|
replay.geki = (short) hitResultCount[HIT_300G];
|
|
|
|
replay.katu = (short) (hitResultCount[HIT_300K] + hitResultCount[HIT_100K]);
|
|
|
|
replay.miss = (short) hitResultCount[HIT_MISS];
|
|
|
|
replay.score = (int) score;
|
|
|
|
replay.combo = (short) comboMax;
|
|
|
|
replay.perfect = (comboMax == fullObjectCount);
|
|
|
|
replay.mods = GameMod.getModState();
|
|
|
|
replay.lifeFrames = null; // TODO
|
|
|
|
replay.timestamp = new Date();
|
|
|
|
replay.frames = frames;
|
|
|
|
replay.seed = 0; // TODO
|
2015-03-11 20:53:19 +01:00
|
|
|
replay.loaded = true;
|
2015-03-12 01:52:51 +01:00
|
|
|
|
2015-03-10 23:10:51 +01:00
|
|
|
return replay;
|
|
|
|
}
|
|
|
|
|
2015-03-13 23:03:44 +01:00
|
|
|
/**
|
|
|
|
* Sets the replay object.
|
|
|
|
*/
|
|
|
|
public void setReplay(Replay replay) { this.replay = replay; }
|
|
|
|
|
2015-01-28 09:47:24 +01:00
|
|
|
/**
|
|
|
|
* Returns whether or not this object is used for gameplay.
|
|
|
|
* @return true if gameplay, false if score viewing
|
|
|
|
*/
|
|
|
|
public boolean isGameplay() { return gameplay; }
|
2015-02-15 07:40:01 +01:00
|
|
|
|
2015-02-15 04:15:41 +01:00
|
|
|
/**
|
2015-02-15 07:40:01 +01:00
|
|
|
* Adds the hit into the list of hit error information.
|
|
|
|
* @param time the correct hit time
|
|
|
|
* @param x the x coordinate of the hit
|
|
|
|
* @param y the y coordinate of the hit
|
|
|
|
* @param timeDiff the difference between the correct and actual hit times
|
2015-02-15 04:15:41 +01:00
|
|
|
*/
|
|
|
|
public void addHitError(int time, int x, int y, int timeDiff) {
|
2015-02-20 21:08:16 +01:00
|
|
|
hitErrorList.addFirst(new HitErrorInfo(time, x, y, timeDiff));
|
2015-02-14 19:45:14 +01:00
|
|
|
}
|
2015-02-18 04:49:19 +01:00
|
|
|
}
|