Compare commits
59 Commits
master
...
kockout-wd
Author | SHA1 | Date | |
---|---|---|---|
f46ee19838 | |||
9a3ecc06aa | |||
|
02448503c7 | ||
|
10e5375231 | ||
|
db5299a32a | ||
|
f01728156a | ||
|
4117357f31 | ||
|
8725e0b31c | ||
|
6ca1f6e800 | ||
|
6076e6e9c9 | ||
|
3debd40aec | ||
|
0a6412e03f | ||
|
2b83657907 | ||
|
a3c1cfad9e | ||
|
f92270fa22 | ||
|
2ae438ced2 | ||
|
e169b0b1f5 | ||
|
ec375965a2 | ||
|
949bc62b47 | ||
|
ee14ac9b2d | ||
|
5a268bc713 | ||
|
3d470cbe7a | ||
|
3913abfcaf | ||
|
8df9732bf6 | ||
|
3b82c7797e | ||
|
a8cacc690f | ||
|
eec3ae3ecd | ||
|
f398b961d7 | ||
|
29640a4ef7 | ||
|
9628149de0 | ||
|
8fed11ba86 | ||
|
9af2b01657 | ||
|
4f68669add | ||
|
8471c43ed5 | ||
|
b4498333df | ||
|
c0f122c74d | ||
|
df1afd98c3 | ||
|
22d2b0be86 | ||
|
085cd719fa | ||
|
3bccdc5540 | ||
|
992ec7afd8 | ||
|
60de7a26c1 | ||
|
eaa46bddd9 | ||
|
92dc59f7b6 | ||
|
408c162741 | ||
|
35769b31c7 | ||
|
5704ebb0c1 | ||
|
e8d0199705 | ||
|
9320767f21 | ||
|
9d9f2df5d8 | ||
|
5f334ca4cb | ||
|
9df139bded | ||
|
3629dfd4d7 | ||
|
e20b55ba59 | ||
|
e270aeb92e | ||
|
00f34296fb | ||
|
0e8bf0b31e | ||
|
a84786987a | ||
|
ba2aa51b7b |
15
res/sh_Cursor.vertex
Normal file
15
res/sh_Cursor.vertex
Normal file
|
@ -0,0 +1,15 @@
|
|||
attribute vec4 m_Position;
|
||||
attribute vec2 m_TexCoord;
|
||||
attribute float m_Time;
|
||||
|
||||
varying vec4 v_Color;
|
||||
varying vec2 v_TexCoord;
|
||||
|
||||
uniform float g_FadeClock;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; //m_Position;
|
||||
v_Color = vec4(1.0, 0.0, 0.0, g_FadeClock);
|
||||
v_TexCoord = m_TexCoord;
|
||||
}
|
13
res/sh_Texture.frag
Normal file
13
res/sh_Texture.frag
Normal file
|
@ -0,0 +1,13 @@
|
|||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
varying vec4 v_Color;
|
||||
varying vec2 v_TexCoord;
|
||||
|
||||
uniform sampler2D m_Sampler;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
gl_FragColor = v_Color * texture2D(m_Sampler, v_TexCoord);
|
||||
}
|
|
@ -4,6 +4,7 @@ package awlex.ospu;
|
|||
* Created by Awlex on 10.10.2016.
|
||||
*/
|
||||
|
||||
import itdelatrisu.opsu.GameData;
|
||||
import itdelatrisu.opsu.objects.GameObject;
|
||||
import itdelatrisu.opsu.objects.curves.Vec2f;
|
||||
import org.newdawn.slick.Color;
|
||||
|
@ -35,6 +36,15 @@ public class FakeGameObject extends GameObject {
|
|||
this.start.y = this.end.y = (start.end.y + end.start.y) / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameObject clone(GameData a) {
|
||||
FakeGameObject o = new FakeGameObject();
|
||||
o.halfTime = this.halfTime;
|
||||
o.start = this.start;
|
||||
o.end = this.end;
|
||||
return o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics g, int trackPosition, boolean mirrored) {
|
||||
|
||||
|
|
92
src/com/osufx/sunpy/PropertyCollection.java
Normal file
92
src/com/osufx/sunpy/PropertyCollection.java
Normal file
|
@ -0,0 +1,92 @@
|
|||
package com.osufx.sunpy;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class PropertyCollection {
|
||||
private HashMap<String, ShaderProperty> properties = new HashMap<>();
|
||||
private Shader owner;
|
||||
|
||||
private boolean changed = false;
|
||||
|
||||
public PropertyCollection(Shader owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
this.changed = true;
|
||||
this.properties.clear();
|
||||
}
|
||||
|
||||
public void Add(ShaderProperty prop) {
|
||||
this.properties.put(prop.Name, prop);
|
||||
}
|
||||
|
||||
public void Replace(ShaderProperty prop) {
|
||||
this.properties.replace(prop.Name, prop);
|
||||
}
|
||||
|
||||
public void Replace(String key, Object value) {
|
||||
if (!this.properties.containsKey(key))
|
||||
return;
|
||||
|
||||
ShaderProperty prop = this.properties.get(key);
|
||||
|
||||
if (value.getClass() != prop.Type){
|
||||
System.out.println(String.format("%s variable %s does not take value type %s", this.owner.name, key, value.getClass()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (prop.Value != null && prop.Value == value)
|
||||
return;
|
||||
|
||||
prop.Value = value;
|
||||
}
|
||||
|
||||
public void Remove(String key) {
|
||||
this.properties.remove(key);
|
||||
}
|
||||
|
||||
public void Set() {
|
||||
if (!this.changed)
|
||||
return;
|
||||
|
||||
for (ShaderProperty prop : this.properties.values()) {
|
||||
prop.Set();
|
||||
}
|
||||
|
||||
this.changed = false;
|
||||
}
|
||||
|
||||
public ShaderProperty get(String propName) {
|
||||
if (!this.properties.containsKey(propName))
|
||||
return null;
|
||||
return this.properties.get(propName);
|
||||
}
|
||||
|
||||
public void set(String propName, Object value) {
|
||||
if (!this.owner.loaded)
|
||||
return;
|
||||
|
||||
if (!this.properties.containsKey(propName)) {
|
||||
System.out.println(String.format("Property %s does not exist!", propName));
|
||||
return;
|
||||
}
|
||||
|
||||
ShaderProperty prop = this.properties.get(propName);
|
||||
|
||||
if (value.getClass() != prop.Type){
|
||||
System.out.println(String.format("%s variable %s does not take value type %s", this.owner.name, propName, value.getClass()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (prop.Value != null && prop.Value == value)
|
||||
return;
|
||||
|
||||
prop.Value = value;
|
||||
|
||||
if (this.owner.started)
|
||||
prop.Set();
|
||||
else
|
||||
this.changed = true;
|
||||
}
|
||||
}
|
213
src/com/osufx/sunpy/Shader.java
Normal file
213
src/com/osufx/sunpy/Shader.java
Normal file
|
@ -0,0 +1,213 @@
|
|||
package com.osufx.sunpy;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.newdawn.slick.util.Log;
|
||||
import org.newdawn.slick.util.ResourceLoader;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.*;
|
||||
|
||||
public class Shader {
|
||||
public String name;
|
||||
public int programId = -1;
|
||||
public boolean loaded = false;
|
||||
public boolean started = false;
|
||||
|
||||
public PropertyCollection Properties;
|
||||
|
||||
public static HashMap<String, Object> globalProperties = new HashMap<>();
|
||||
public static List<Shader> loadedShaders = new ArrayList<>();
|
||||
public static int currentShader = 0;
|
||||
|
||||
public HashMap<String, Integer> attribLoc = new HashMap<>();
|
||||
private static final String[] attribs = new String[] {
|
||||
"m_Position",
|
||||
"m_Color",
|
||||
"m_TexCoord",
|
||||
"m_Time",
|
||||
"m_Direction"
|
||||
};
|
||||
|
||||
private HashMap<Integer, String> shaderParts = new HashMap<>();
|
||||
private int previousShader;
|
||||
|
||||
public Shader(String vertexShader, String fragmentShader) {
|
||||
String vertexString;
|
||||
String fragmentString;
|
||||
|
||||
try {
|
||||
InputStream vertexStream = ResourceLoader.getResourceAsStream(vertexShader);
|
||||
vertexString = convertStreamToString(vertexStream);
|
||||
|
||||
InputStream fragmentStream = ResourceLoader.getResourceAsStream(fragmentShader);
|
||||
fragmentString = convertStreamToString(fragmentStream);
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
this.name = String.format("%s/%s", vertexShader, fragmentShader);
|
||||
this.Properties = new PropertyCollection(this);
|
||||
|
||||
this.programId = GL20.glCreateProgram();
|
||||
|
||||
this.shaderParts.put(GL20.glCreateShader(GL20.GL_VERTEX_SHADER), vertexString);
|
||||
this.shaderParts.put(GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER), fragmentString);
|
||||
this.CompileParts();
|
||||
this.Compile();
|
||||
}
|
||||
|
||||
public Shader(String shader, final int shaderType) {
|
||||
if (shaderType != GL20.GL_FRAGMENT_SHADER && shaderType != GL20.GL_VERTEX_SHADER){
|
||||
System.out.println("Invalid shader type");
|
||||
return;
|
||||
}
|
||||
|
||||
String shaderString;
|
||||
|
||||
try {
|
||||
InputStream shaderStream = ResourceLoader.getResourceAsStream(shader);
|
||||
shaderString = convertStreamToString(shaderStream);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
this.name = String.format("%s/%s",
|
||||
shaderType == GL20.GL_FRAGMENT_SHADER ? "Fragment" : "Vertex",
|
||||
shader);
|
||||
this.Properties = new PropertyCollection(this);
|
||||
|
||||
this.programId = GL20.glCreateProgram();
|
||||
|
||||
this.shaderParts.put(GL20.glCreateShader(shaderType), shaderString);
|
||||
this.CompileParts();
|
||||
this.Compile();
|
||||
}
|
||||
|
||||
public void CompileParts() {
|
||||
for (Map.Entry<Integer, String> entry : this.shaderParts.entrySet()) {
|
||||
int handle = entry.getKey();
|
||||
String source = entry.getValue();
|
||||
|
||||
GL20.glShaderSource(handle, source);
|
||||
GL20.glCompileShader(handle);
|
||||
|
||||
int res = GL20.glGetShaderi(handle, GL20.GL_COMPILE_STATUS);
|
||||
if (res != GL11.GL_TRUE) {
|
||||
String error = GL20.glGetShaderInfoLog(handle, 1024);
|
||||
Log.error("Shader compilation failed.", new Exception(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Compile() {
|
||||
this.Properties.Clear();
|
||||
|
||||
for (int handler : this.shaderParts.keySet()) {
|
||||
GL20.glAttachShader(this.programId, handler);
|
||||
}
|
||||
|
||||
GL20.glLinkProgram(this.programId);
|
||||
|
||||
int res = GL20.glGetProgrami(this.programId, GL20.GL_LINK_STATUS);
|
||||
if (res != GL11.GL_TRUE) {
|
||||
String error = GL20.glGetProgramInfoLog(this.programId, 1024);
|
||||
Log.error("Program linking failed.", new Exception(error));
|
||||
}
|
||||
|
||||
for (int handler : this.shaderParts.keySet()) {
|
||||
GL20.glDeleteShader(handler);
|
||||
}
|
||||
|
||||
for (int i = 0; i < attribs.length; i++) {
|
||||
int attrib = GL20.glGetAttribLocation(this.programId, attribs[i]);
|
||||
this.attribLoc.put(attribs[i], attrib);
|
||||
//GL20.glBindAttribLocation(this.programId, i, attribLoc[i]);
|
||||
}
|
||||
|
||||
/*for (int handler : this.shaderParts.keySet()) {
|
||||
GL20.glDetachShader(this.programId, handler);
|
||||
}*/
|
||||
|
||||
this.loaded = res == GL11.GL_TRUE;
|
||||
|
||||
if (this.loaded) {
|
||||
int uniCount = GL20.glGetProgrami(this.programId, GL20.GL_ACTIVE_UNIFORMS);
|
||||
|
||||
for (int i = 0; i < uniCount; i++) {
|
||||
IntBuffer length = BufferUtils.createIntBuffer(1);
|
||||
IntBuffer size = BufferUtils.createIntBuffer(1);
|
||||
IntBuffer type = BufferUtils.createIntBuffer(1);
|
||||
ByteBuffer name = ByteBuffer.allocateDirect(64);
|
||||
|
||||
GL20.glGetActiveUniform(this.programId, i, length, size, type, name);
|
||||
|
||||
|
||||
byte[] buf = new byte[length.get(0)];
|
||||
|
||||
name.get(buf, 0, buf.length);
|
||||
String propName = new String(buf);
|
||||
int propLocation = GL20.glGetUniformLocation(programId, propName);
|
||||
|
||||
UniformType propUniformType = UniformType.map.get(type.get(0));
|
||||
Class propType = ShaderProperty.ConvertType(propUniformType.name());
|
||||
|
||||
System.out.println(String.format("propName:%s PropUniformType:%s PropType:%s", propName, propUniformType, propType));
|
||||
|
||||
this.Properties.Add(new ShaderProperty(
|
||||
propName,
|
||||
propLocation,
|
||||
propType,
|
||||
propUniformType
|
||||
));
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Object> entry : globalProperties.entrySet()) {
|
||||
this.Properties.Replace(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
loadedShaders.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void Begin(){
|
||||
if (this.started || !this.loaded)
|
||||
return;
|
||||
|
||||
GL20.glUseProgram(this.programId);
|
||||
this.previousShader = currentShader;
|
||||
currentShader = this.programId;
|
||||
|
||||
GL20.glEnableVertexAttribArray(this.attribLoc.get("m_Position"));
|
||||
GL20.glEnableVertexAttribArray(this.attribLoc.get("m_TexCoord"));
|
||||
|
||||
//this.Properties.set("m_Sampler", 0);
|
||||
|
||||
this.started = true;
|
||||
}
|
||||
|
||||
public void End() {
|
||||
if (!this.started)
|
||||
return;
|
||||
|
||||
GL11.glFlush();
|
||||
GL20.glDisableVertexAttribArray(this.attribLoc.get("m_TexCoord"));
|
||||
GL20.glDisableVertexAttribArray(this.attribLoc.get("m_Position"));
|
||||
|
||||
GL20.glUseProgram(this.previousShader);
|
||||
currentShader = this.previousShader;
|
||||
|
||||
this.started = false;
|
||||
}
|
||||
|
||||
static String convertStreamToString(InputStream is) {
|
||||
Scanner s = new Scanner(is).useDelimiter("\\A");
|
||||
return s.hasNext() ? s.next() : "";
|
||||
}
|
||||
}
|
134
src/com/osufx/sunpy/ShaderProperty.java
Normal file
134
src/com/osufx/sunpy/ShaderProperty.java
Normal file
|
@ -0,0 +1,134 @@
|
|||
package com.osufx.sunpy;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public class ShaderProperty {
|
||||
public String Name;
|
||||
public int Location;
|
||||
|
||||
public Class Type;
|
||||
public UniformType ActiveUniformType;
|
||||
|
||||
public Object Value;
|
||||
|
||||
public ShaderProperty(String Name, int Location, Class Type, UniformType ActiveUniformType) {
|
||||
this.Name = Name;
|
||||
this.Location = Location;
|
||||
this.Type = Type;
|
||||
this.ActiveUniformType = ActiveUniformType;
|
||||
}
|
||||
|
||||
public void Set() {
|
||||
if (Value == null)
|
||||
return;
|
||||
|
||||
switch (this.ActiveUniformType) {
|
||||
case Bool:
|
||||
GL20.glUniform1(this.Location, IntBuffer.wrap( new int[]{ (boolean)Value ? 1 : 0 } ));
|
||||
break;
|
||||
case Int:
|
||||
GL20.glUniform1(this.Location, IntBuffer.wrap( new int[]{ (int)Value } ));
|
||||
break;
|
||||
case Float:
|
||||
GL20.glUniform1f(this.Location, (float)Value);
|
||||
break;
|
||||
case BoolVec2:
|
||||
GL20.glUniform2(this.Location, IntBuffer.wrap( boolArrayToIntArray( (boolean[])Value ) ));
|
||||
break;
|
||||
case IntVec2:
|
||||
GL20.glUniform2(this.Location, IntBuffer.wrap( (int[])Value ));
|
||||
break;
|
||||
case FloatVec2:
|
||||
float[] value = (float[])Value;
|
||||
GL20.glUniform2f(this.Location, value[0], value[1]);
|
||||
break;
|
||||
case FloatMat2:
|
||||
GL20.glUniformMatrix2(this.Location, false, FloatBuffer.wrap( (float[])Value ));
|
||||
break;
|
||||
case BoolVec3:
|
||||
GL20.glUniform3(this.Location, IntBuffer.wrap( boolArrayToIntArray( (boolean[])Value ) ));
|
||||
break;
|
||||
case IntVec3:
|
||||
GL20.glUniform3(this.Location, IntBuffer.wrap( (int[])Value ));
|
||||
break;
|
||||
case FloatVec3:
|
||||
GL20.glUniform3(this.Location, FloatBuffer.wrap( (float[])Value ));
|
||||
break;
|
||||
case FloatMat3:
|
||||
GL20.glUniformMatrix3(this.Location, false, FloatBuffer.wrap( (float[])Value ));
|
||||
break;
|
||||
case BoolVec4:
|
||||
GL20.glUniform4(this.Location, IntBuffer.wrap( boolArrayToIntArray( (boolean[])Value ) ));
|
||||
break;
|
||||
case IntVec4:
|
||||
GL20.glUniform4(this.Location, IntBuffer.wrap( (int[])Value ));
|
||||
break;
|
||||
case FloatVec4:
|
||||
GL20.glUniform4(this.Location, FloatBuffer.wrap( (float[])Value ));
|
||||
break;
|
||||
case FloatMat4:
|
||||
GL20.glUniformMatrix4(this.Location, false, FloatBuffer.wrap( (float[])Value ));
|
||||
break;
|
||||
case Sampler2D:
|
||||
GL20.glUniform1i(this.Location, (int) Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static Class ConvertType(String type) {
|
||||
switch (type) {
|
||||
default:
|
||||
//throw new Exception(String.format("Uniform type %s is not supported.", type));
|
||||
System.out.println(String.format("Uniform type %s is not supported.", type));
|
||||
return null;
|
||||
case "Bool":
|
||||
return Boolean.class;
|
||||
case "Int":
|
||||
return Integer.class;
|
||||
case "Float":
|
||||
return Float.class;
|
||||
case "BoolVec2":
|
||||
return boolean[].class;
|
||||
case "IntVec2":
|
||||
return int[].class;
|
||||
case "FloatVec2":
|
||||
return float[].class;
|
||||
case "BoolVec3":
|
||||
return boolean[].class;
|
||||
case "IntVec3":
|
||||
return int[].class;
|
||||
case "FloatVec3":
|
||||
return float[].class;
|
||||
case "BoolVec4":
|
||||
return boolean[].class;
|
||||
case "IntVec4":
|
||||
return int[].class;
|
||||
case "FloatVec4":
|
||||
return float[].class;
|
||||
case "FloatMat2":
|
||||
return float[].class;
|
||||
case "FloatMat3":
|
||||
return float[].class;
|
||||
case "FloatMat4":
|
||||
return float[].class;
|
||||
case "Sampler2D":
|
||||
case "SamplerCube":
|
||||
return Integer.class;
|
||||
}
|
||||
}
|
||||
|
||||
/*public static Type ConvertType(int type) {
|
||||
(UniformType)24;
|
||||
}*/
|
||||
|
||||
public static int[] boolArrayToIntArray(boolean[] array) {
|
||||
int[] value = new int[array.length];
|
||||
for (int i = 0; i < array.length; i++)
|
||||
value[i] = array[i] ? 1 : 0;
|
||||
return value;
|
||||
}
|
||||
}
|
39
src/com/osufx/sunpy/UniformType.java
Normal file
39
src/com/osufx/sunpy/UniformType.java
Normal file
|
@ -0,0 +1,39 @@
|
|||
package com.osufx.sunpy;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public enum UniformType {
|
||||
Int(GL11.GL_INT),
|
||||
Float(GL11.GL_FLOAT),
|
||||
FloatVec2(GL20.GL_FLOAT_VEC2),
|
||||
FloatVec3(GL20.GL_FLOAT_VEC3),
|
||||
FloatVec4(GL20.GL_FLOAT_VEC4),
|
||||
IntVec2(GL20.GL_INT_VEC2),
|
||||
IntVec3(GL20.GL_INT_VEC3),
|
||||
IntVec4(GL20.GL_INT_VEC4),
|
||||
Bool(GL20.GL_BOOL),
|
||||
BoolVec2(GL20.GL_BOOL_VEC2),
|
||||
BoolVec3(GL20.GL_BOOL_VEC3),
|
||||
BoolVec4(GL20.GL_BOOL_VEC4),
|
||||
FloatMat2(GL20.GL_FLOAT_MAT2),
|
||||
FloatMat3(GL20.GL_FLOAT_MAT3),
|
||||
FloatMat4(GL20.GL_FLOAT_MAT4),
|
||||
Sampler2D(GL20.GL_SAMPLER_2D),
|
||||
SamplerCube(GL20.GL_SAMPLER_CUBE);
|
||||
|
||||
private final int glIndex;
|
||||
|
||||
public static HashMap<Integer, UniformType> map = new HashMap<>();
|
||||
static {
|
||||
for (UniformType v : UniformType.values()) {
|
||||
map.put(v.glIndex, v);
|
||||
}
|
||||
}
|
||||
|
||||
UniformType(int glIndex) {
|
||||
this.glIndex = glIndex;
|
||||
}
|
||||
}
|
|
@ -170,7 +170,7 @@ public class GameData {
|
|||
HIT_ANIMATION_RESULT = 12; // not a hit result
|
||||
|
||||
/** Hit result-related images (indexed by HIT_* constants to HIT_MAX). */
|
||||
private Image[] hitResults;
|
||||
public static Image[] hitResults;
|
||||
|
||||
/** Counts of each hit result so far (indexed by HIT_* constants to HIT_MAX). */
|
||||
private int[] hitResultCount;
|
||||
|
@ -1222,7 +1222,7 @@ public class GameData {
|
|||
/**
|
||||
* Increases the combo streak by one.
|
||||
*/
|
||||
private void incrementComboStreak() {
|
||||
protected void incrementComboStreak() {
|
||||
combo++;
|
||||
comboPopTime = 0;
|
||||
if (combo > comboMax)
|
||||
|
@ -1404,7 +1404,7 @@ public class GameData {
|
|||
* @param noIncrementCombo if the combo should not be incremented by this result
|
||||
* @return the actual hit result (HIT_* constants)
|
||||
*/
|
||||
private int handleHitResult(int time, int result, float x, float y, Color color, boolean end,
|
||||
protected int handleHitResult(int time, int result, float x, float y, Color color, boolean end,
|
||||
HitObject hitObject, HitObjectType hitResultType, int repeat, boolean noIncrementCombo) {
|
||||
// update health, score, and combo streak based on hit result
|
||||
int hitValue = 0;
|
||||
|
|
|
@ -83,6 +83,13 @@ public class Circle extends GameObject {
|
|||
super.updateStartEndPositions(time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameObject clone(GameData data) {
|
||||
Circle c = new Circle(hitObject, game, data, comboColorIndex, comboEnd);
|
||||
c.isreplay = true;
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics g, int trackPosition, boolean mirror) {
|
||||
Color orig = color;
|
||||
|
@ -165,12 +172,13 @@ public class Circle extends GameObject {
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean isreplay;
|
||||
@Override
|
||||
public boolean update(boolean overlap, int delta, int mouseX, int mouseY, boolean keyPressed, int trackPosition) {
|
||||
int time = hitObject.getTime();
|
||||
|
||||
int[] hitResultOffset = game.getHitResultOffsets();
|
||||
boolean isAutoMod = GameMod.AUTO.isActive();
|
||||
boolean isAutoMod = !isreplay && GameMod.AUTO.isActive();
|
||||
|
||||
if (trackPosition > time + hitResultOffset[GameData.HIT_50]) {
|
||||
if (isAutoMod) {// "auto" mod: catch any missed notes due to lag
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package itdelatrisu.opsu.objects;
|
||||
|
||||
import itdelatrisu.opsu.GameData;
|
||||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
import itdelatrisu.opsu.objects.curves.Vec2f;
|
||||
|
||||
|
@ -29,7 +30,7 @@ import org.newdawn.slick.Graphics;
|
|||
*/
|
||||
public class DummyObject extends GameObject {
|
||||
/** The associated HitObject. */
|
||||
private HitObject hitObject;
|
||||
public final HitObject hitObject;
|
||||
|
||||
/** The scaled starting x, y coordinates. */
|
||||
private float x, y;
|
||||
|
@ -49,6 +50,11 @@ public class DummyObject extends GameObject {
|
|||
updateStartEndPositions(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameObject clone(GameData a) {
|
||||
return new DummyObject(this.hitObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics g, int trackPosition, boolean mirror) {}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package itdelatrisu.opsu.objects;
|
||||
|
||||
import itdelatrisu.opsu.GameData;
|
||||
import itdelatrisu.opsu.objects.curves.Vec2f;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
|
@ -114,4 +115,6 @@ public abstract class GameObject {
|
|||
return hue;
|
||||
}
|
||||
|
||||
public abstract GameObject clone(GameData data);
|
||||
|
||||
}
|
||||
|
|
|
@ -189,6 +189,13 @@ public class Slider extends GameObject {
|
|||
repeats = hitObject.getRepeatCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameObject clone(GameData data) {
|
||||
Slider s = new Slider(hitObject, game, data, comboColorIndex, comboEnd);
|
||||
s.isreplay = true;
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics g, int trackPosition, boolean mirror) {
|
||||
if (trackPosition > getEndTime()) {
|
||||
|
@ -630,11 +637,12 @@ public class Slider extends GameObject {
|
|||
return false;
|
||||
}
|
||||
|
||||
boolean isreplay;
|
||||
@Override
|
||||
public boolean update(boolean overlap, int delta, int mouseX, int mouseY, boolean keyPressed, int trackPosition) {
|
||||
int repeatCount = hitObject.getRepeatCount();
|
||||
int[] hitResultOffset = game.getHitResultOffsets();
|
||||
boolean isAutoMod = GameMod.AUTO.isActive();
|
||||
boolean isAutoMod = !isreplay && GameMod.AUTO.isActive();
|
||||
|
||||
if (!sliderClickedInitial) {
|
||||
int time = hitObject.getTime();
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package itdelatrisu.opsu.objects;
|
||||
|
||||
import awlex.ospu.FakeGameObject;
|
||||
import itdelatrisu.opsu.GameData;
|
||||
import itdelatrisu.opsu.GameData.HitObjectType;
|
||||
import itdelatrisu.opsu.GameImage;
|
||||
|
@ -170,6 +171,12 @@ public class Spinner extends GameObject {
|
|||
rotationsNeeded = spinsPerMinute * (hitObject.getEndTime() - hitObject.getTime()) / 60000f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameObject clone(GameData data) {
|
||||
//return new DummyObject(hitObject);
|
||||
return new Spinner(hitObject, game, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics g, int trackPosition, boolean mirror) {
|
||||
if (mirror) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
package itdelatrisu.opsu.render;
|
||||
|
||||
import com.osufx.sunpy.Shader;
|
||||
import itdelatrisu.opsu.GameImage;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
|
@ -248,7 +249,7 @@ public class CurveRenderState {
|
|||
GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
|
||||
|
||||
GL20.glUseProgram(0);
|
||||
GL20.glUseProgram(Shader.currentShader);
|
||||
|
||||
GL11.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GL11.glPushMatrix();
|
||||
|
|
|
@ -45,6 +45,8 @@ import itdelatrisu.opsu.ui.animations.AnimatedValue;
|
|||
import itdelatrisu.opsu.ui.animations.AnimationEquation;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
@ -57,6 +59,7 @@ import org.newdawn.slick.Input;
|
|||
import org.newdawn.slick.SlickException;
|
||||
import org.newdawn.slick.util.Log;
|
||||
import yugecin.opsudance.*;
|
||||
import yugecin.opsudance.ReplayPlayback.HitData;
|
||||
import yugecin.opsudance.core.state.ComplexOpsuState;
|
||||
import yugecin.opsudance.objects.curves.FakeCombinedCurve;
|
||||
import yugecin.opsudance.options.OptionGroups;
|
||||
|
@ -304,7 +307,7 @@ public class Game extends ComplexOpsuState {
|
|||
MUSICBAR_HOVER = new Color(12, 9, 10, 0.35f),
|
||||
MUSICBAR_FILL = new Color(255, 255, 255, 0.75f);
|
||||
|
||||
private final Cursor mirrorCursor;
|
||||
private Cursor mirrorCursor;
|
||||
private final MoveStoryboard moveStoryboardOverlay;
|
||||
private final StoryboardOverlay storyboardOverlay;
|
||||
private final OptionsOverlay optionsOverlay;
|
||||
|
@ -315,7 +318,7 @@ public class Game extends ComplexOpsuState {
|
|||
|
||||
public Game() {
|
||||
super();
|
||||
mirrorCursor = new Cursor(true);
|
||||
//mirrorCursor = new Cursor(true);
|
||||
this.moveStoryboardOverlay = new MoveStoryboard();
|
||||
this.optionsOverlay = new OptionsOverlay(OptionGroups.storyboardOptions);
|
||||
this.storyboardOverlay = new StoryboardOverlay(moveStoryboardOverlay, optionsOverlay, this);
|
||||
|
@ -722,6 +725,28 @@ public class Game extends ComplexOpsuState {
|
|||
}
|
||||
|
||||
UI.draw(g);
|
||||
|
||||
if (replayCursors == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//g.setColor(new Color(0.2f, 0.2f, 0.2f));
|
||||
//g.fillRect(0, 0, ReplayPlayback.SQSIZE * 2, displayContainer.height);
|
||||
//g.setColor(Color.black);
|
||||
//g.fillRect(ReplayPlayback.SQSIZE * 2, 0, ReplayPlayback.SQSIZE * 2, displayContainer.height);
|
||||
float totalHeight = 0f;
|
||||
for (ReplayPlayback replayPlayback : replays) {
|
||||
totalHeight += replayPlayback.getHeight();
|
||||
}
|
||||
float ypos = (height - totalHeight) / 2f - ReplayPlayback.lineHeight;
|
||||
for (ReplayPlayback replayPlayback : replays) {
|
||||
float h = replayPlayback.getHeight();
|
||||
ypos += h;
|
||||
//if (h > 0f) {
|
||||
replayPlayback.render(renderDelta, g, ypos, trackPosition);
|
||||
//}
|
||||
}
|
||||
replayCursors.draw();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -910,6 +935,7 @@ public class Game extends ComplexOpsuState {
|
|||
|
||||
// set mouse coordinates
|
||||
autoMousePosition.set(autoPoint.x, autoPoint.y);
|
||||
autoMousePosition.set(-100, -100);
|
||||
}
|
||||
|
||||
if (isReplay) {
|
||||
|
@ -1454,6 +1480,16 @@ public class Game extends ComplexOpsuState {
|
|||
return true;
|
||||
}
|
||||
|
||||
static class ReplayData {
|
||||
final HitData hitdata;
|
||||
final Replay replay;
|
||||
ReplayData(HitData hitdata, Replay replay) {
|
||||
this.hitdata = hitdata;
|
||||
this.replay = replay;
|
||||
}
|
||||
}
|
||||
private ReplayPlayback[] replays;
|
||||
private ReplayCursors replayCursors;
|
||||
@Override
|
||||
public void enter() {
|
||||
overlays.clear();
|
||||
|
@ -1465,7 +1501,84 @@ public class Game extends ComplexOpsuState {
|
|||
|
||||
super.enter();
|
||||
|
||||
displayContainer.drawCursor = false;
|
||||
/*
|
||||
File replaydir = new File("d:/Users/Robin/games/osu/osr-stuff-master/xi/");
|
||||
if (!replaydir.exists()) {
|
||||
bubNotifs.sendf(Colors.BUB_RED, "replay folder '%s' does not exist", replaydir.getAbsolutePath());
|
||||
displayContainer.switchStateInstantly(songMenuState);
|
||||
return;
|
||||
}
|
||||
|
||||
File[] files = replaydir.listFiles(new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
return pathname.getName().endsWith(".osr");
|
||||
}
|
||||
});
|
||||
|
||||
if (replayCursors != null) {
|
||||
replayCursors.destroy();
|
||||
}
|
||||
final ArrayList<ReplayData> rdata = new ArrayList<>(50);
|
||||
for (File file : files) {
|
||||
final String datafilename = file.getName().substring(0, file.getName().length() - 3) + "ope";
|
||||
final File hitdatafile = new File(file.getParentFile(), datafilename);
|
||||
if (!hitdatafile.exists()) {
|
||||
bubNotifs.sendf(Colors.BUB_RED, "no hitdata file for %s", file.getName());
|
||||
continue;
|
||||
}
|
||||
final ReplayPlayback.HitData hitdata;
|
||||
try {
|
||||
hitdata = new ReplayPlayback.HitData(hitdatafile);
|
||||
} catch (Exception e) {
|
||||
bubNotifs.sendf(
|
||||
Colors.BUB_RED,
|
||||
"cannot parse hitdata for '%s': %s",
|
||||
hitdatafile.getName(),
|
||||
e.toString()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
final Replay r = new Replay(file);
|
||||
try {
|
||||
r.load();
|
||||
} catch (IOException e) {
|
||||
bubNotifs.sendf(Colors.BUB_RED, "could not load replay %s", file.getName());
|
||||
continue;
|
||||
}
|
||||
rdata.add(new ReplayData(hitdata, r));
|
||||
}
|
||||
|
||||
rdata.sort(new Comparator<ReplayData>() {
|
||||
@Override
|
||||
public int compare(ReplayData o1, ReplayData o2) {
|
||||
return Integer.compare(o2.replay.score, o1.replay.score);
|
||||
}
|
||||
});
|
||||
|
||||
replayCursors = new ReplayCursors(rdata.size());
|
||||
replays = new ReplayPlayback[rdata.size()];
|
||||
|
||||
float hueshift = 360f / rdata.size();
|
||||
float hue = 180;
|
||||
|
||||
int replayidx = 0;
|
||||
for (ReplayData d : rdata) {
|
||||
final Color c = new Color(java.awt.Color.HSBtoRGB((hue) / 360f, .7f, 1.0f));
|
||||
final ReplayCursor cursor = new ReplayCursor(c);
|
||||
replays[replayidx] = new ReplayPlayback(
|
||||
d.replay,
|
||||
d.hitdata,
|
||||
c,
|
||||
cursor
|
||||
);
|
||||
replayCursors.playbacks[replayidx] = replays[replayidx];
|
||||
|
||||
hue += hueshift;
|
||||
replayidx++;
|
||||
}
|
||||
|
||||
displayContainer.drawCursor = false;*/
|
||||
|
||||
isInGame = true;
|
||||
if (!skippedToCheckpoint) {
|
||||
|
@ -1749,6 +1862,12 @@ public class Game extends ComplexOpsuState {
|
|||
|
||||
knorkesliders = null;
|
||||
|
||||
if (replayCursors != null) {
|
||||
replayCursors.destroy();
|
||||
replayCursors = null;
|
||||
replays = null;
|
||||
}
|
||||
|
||||
Dancer.instance.setGameObjects(null);
|
||||
|
||||
Cursor.lastObjColor = Color.white;
|
||||
|
@ -1766,10 +1885,6 @@ public class Game extends ComplexOpsuState {
|
|||
GameMod.loadModState(previousMods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the beatmap's local music offset.
|
||||
* @param sign the sign (multiplier)
|
||||
*/
|
||||
public void adjustLocalMusicOffset(int amount) {
|
||||
int newOffset = beatmap.localMusicOffset + amount;
|
||||
barNotifs.send(String.format("Local beatmap offset set to %dms", newOffset));
|
||||
|
|
|
@ -18,18 +18,31 @@
|
|||
|
||||
package itdelatrisu.opsu.ui;
|
||||
|
||||
import com.osufx.sunpy.Shader;
|
||||
import itdelatrisu.opsu.GameImage;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.render.Rendertarget;
|
||||
import itdelatrisu.opsu.ui.animations.AnimationEquation;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.*;
|
||||
import org.newdawn.slick.*;
|
||||
import org.newdawn.slick.opengl.Texture;
|
||||
import org.newdawn.slick.opengl.TextureImpl;
|
||||
|
||||
import yugecin.opsudance.Dancer;
|
||||
import yugecin.opsudance.ReplayPlayback;
|
||||
import yugecin.opsudance.skinning.SkinService;
|
||||
|
||||
import static yugecin.opsudance.options.Options.*;
|
||||
import static itdelatrisu.opsu.GameImage.CURSOR;
|
||||
import static itdelatrisu.opsu.GameImage.CURSOR_MIDDLE;
|
||||
import static itdelatrisu.opsu.GameImage.CURSOR_TRAIL;
|
||||
import static yugecin.opsudance.core.InstanceContainer.*;
|
||||
|
||||
/**
|
||||
|
@ -55,8 +68,7 @@ public class Cursor {
|
|||
/** The time it takes for the cursor to scale, in milliseconds. */
|
||||
private static final float CURSOR_SCALE_TIME = 125;
|
||||
|
||||
/** Stores all previous cursor locations to display a trail. */
|
||||
private LinkedList<Point> trail = new LinkedList<>();
|
||||
private TrailList trail = new TrailList();
|
||||
|
||||
private boolean newStyle;
|
||||
|
||||
|
@ -68,6 +80,12 @@ public class Cursor {
|
|||
|
||||
private boolean isMirrored;
|
||||
|
||||
private Color filter;
|
||||
|
||||
private Rendertarget fbo;
|
||||
|
||||
private Shader cursorTrailShader;
|
||||
|
||||
public Cursor() {
|
||||
this(false);
|
||||
}
|
||||
|
@ -75,6 +93,14 @@ public class Cursor {
|
|||
public Cursor(boolean isMirrored) {
|
||||
resetLocations(0, 0);
|
||||
this.isMirrored = isMirrored;
|
||||
this.lastPosition = new Point(width2, 0);
|
||||
this.filter = new Color(255,255,255);
|
||||
this.cursorTrailShader = new Shader("sh_Cursor.vertex", "sh_Texture.frag");
|
||||
}
|
||||
|
||||
public Cursor(Color filter) {
|
||||
this(false);
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,6 +108,10 @@ public class Cursor {
|
|||
* @param mousePressed whether or not the mouse button is pressed
|
||||
*/
|
||||
public void draw(boolean mousePressed) {
|
||||
if (fbo == null) {
|
||||
this.fbo = Rendertarget.createRTTFramebuffer(width, height);
|
||||
}
|
||||
/*
|
||||
if (OPTION_DISABLE_CURSOR.state) {
|
||||
return;
|
||||
}
|
||||
|
@ -131,6 +161,10 @@ public class Cursor {
|
|||
lastCursorColor = filter = Dancer.cursorColorOverride.getColor();
|
||||
}
|
||||
|
||||
if (this.filter != null) {
|
||||
filter = this.filter;
|
||||
}
|
||||
|
||||
// draw a fading trail
|
||||
float alpha = 0f;
|
||||
float t = 2f / trail.size();
|
||||
|
@ -139,7 +173,7 @@ public class Cursor {
|
|||
cursorTrail.startUse();
|
||||
for (Point p : trail) {
|
||||
alpha += t;
|
||||
cursorTrail.setImageColor(filter.r, filter.g, filter.b, alpha);
|
||||
cursorTrail.setImageColor(filter.r, filter.g, filter.b, alpha * 0.25f);
|
||||
cursorTrail.drawEmbedded(
|
||||
p.x - (cursorTrailWidth / 2f), p.y - (cursorTrailHeight / 2f),
|
||||
cursorTrailWidth, cursorTrailHeight, cursorTrailRotation);
|
||||
|
@ -157,6 +191,110 @@ public class Cursor {
|
|||
if (hasMiddle) {
|
||||
cursorMiddle.drawCentered(lastPosition.x, lastPosition.y, OPTION_DANCE_CURSOR_ONLY_COLOR_TRAIL.state ? Color.white : filter);
|
||||
}
|
||||
*/
|
||||
final Image img = CURSOR_TRAIL.getImage();
|
||||
final Texture txt = img.getTexture();
|
||||
|
||||
final float trailw2 = img.getWidth() * OPTION_CURSOR_SIZE.val / 100f / 2f;
|
||||
final float trailh2 = img.getHeight() * OPTION_CURSOR_SIZE.val / 100f / 2f;
|
||||
float txtw = txt.getWidth();
|
||||
float txth = txt.getHeight();
|
||||
|
||||
//img.draw(0f,0f);
|
||||
|
||||
/*
|
||||
// stuff copied from CurveRenderState and stuff, I don't know what I'm doing
|
||||
int oldFb = GL11.glGetInteger(EXTFramebufferObject.GL_FRAMEBUFFER_BINDING_EXT);
|
||||
int oldTex = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
IntBuffer oldViewport = BufferUtils.createIntBuffer(16);
|
||||
GL11.glGetInteger(GL11.GL_VIEWPORT, oldViewport);
|
||||
EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, fbo.getID());
|
||||
GL11.glViewport(0, 0, fbo.width, fbo.height);
|
||||
GL11.glClearColor(0f, 0f, 0f, 0f);
|
||||
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
*/
|
||||
//Color.white.bind();
|
||||
|
||||
|
||||
//GL20.glVertexAttrib4f(cursorTrailShader.attribLoc.get("m_Position"), 1f, 1f, 1f, 1f);
|
||||
|
||||
|
||||
//GL11.glBindTexture(GL11.GL_TEXTURE_2D, );
|
||||
//cursorTrailShader.Properties.set("m_Sampler", txt.getTextureID());
|
||||
|
||||
//GL20.glUniform1i(cursorTrailShader.attribLoc.get("m_Sampler"), txt.getTextureID());
|
||||
//IntBuffer buf = BufferUtils.createIntBuffer(1).put(txt.getTextureID());
|
||||
|
||||
cursorTrailShader.Begin();
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE5);
|
||||
//GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
//GL11.glGenTextures();
|
||||
|
||||
|
||||
//txt.bind();
|
||||
//TextureImpl.unbind();
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, txt.getTextureID());
|
||||
//GL33.glBindSampler(GL20.GL_SAMPLER_2D, txt.getTextureID());
|
||||
//GL20.glUniform1i(cursorTrailShader.attribLoc.get("m_Sampler"), 0);
|
||||
|
||||
//cursorTrailShader.Properties.set("m_TexCoord", new float[] { txtw, txth });
|
||||
int idd = GL20.glGetAttribLocation(cursorTrailShader.programId, "m_TexCoord");
|
||||
|
||||
cursorTrailShader.Properties.set("m_Sampler", 5);
|
||||
//GL20.glUniform1i(cursorTrailShader.Properties.get("m_Sampler").Location, 5);
|
||||
|
||||
|
||||
|
||||
cursorTrailShader.Properties.set("g_FadeClock", 0.04f);
|
||||
GL11.glBegin(GL11.GL_QUADS);
|
||||
float alpha = 0f;
|
||||
float alphaIncrease = .4f / trail.size;
|
||||
for (Trailpart p : trail) {
|
||||
//alpha += alphaIncrease;
|
||||
//GL11.glColor4f(filter.r, filter.g, filter.b, 1f);
|
||||
GL11.glTexCoord2f(0f, 0f);
|
||||
GL20.glVertexAttrib2f(idd, 0f, 0f);
|
||||
GL11.glVertex3f(p.x - trailw2, p.y - trailh2, 0f);
|
||||
GL11.glTexCoord2f(txtw, 0);
|
||||
GL20.glVertexAttrib2f(idd, txtw, 0);
|
||||
GL11.glVertex3f(p.x + trailw2, p.y - trailh2, 0f);
|
||||
GL11.glTexCoord2f(txtw, txth);
|
||||
GL20.glVertexAttrib2f(idd, txtw, txth);
|
||||
GL11.glVertex3f(p.x + trailw2, p.y + trailh2, 0f);
|
||||
GL11.glTexCoord2f(0f, txth);
|
||||
GL20.glVertexAttrib2f(idd, 0f, txth);
|
||||
GL11.glVertex3f(p.x - trailw2, p.y + trailh2, 0f);
|
||||
//break;
|
||||
}
|
||||
GL11.glEnd();
|
||||
cursorTrailShader.End();
|
||||
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
|
||||
/*
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, oldTex);
|
||||
EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, oldFb);
|
||||
GL11.glViewport(oldViewport.get(0), oldViewport.get(1), oldViewport.get(2), oldViewport.get(3));
|
||||
|
||||
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
|
||||
//GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
//GL11.glDisable(GL11.GL_TEXTURE_1D);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo.getTextureID());
|
||||
GL11.glBegin(GL11.GL_QUADS);
|
||||
GL11.glColor4f(1f, 1f, 1f, 1f);
|
||||
GL11.glTexCoord2f(1f, 1f);
|
||||
GL11.glVertex2i(fbo.width, 0);
|
||||
GL11.glTexCoord2f(0f, 1f);
|
||||
GL11.glVertex2i(0, 0);
|
||||
GL11.glTexCoord2f(0f, 0f);
|
||||
GL11.glVertex2i(0, fbo.height);
|
||||
GL11.glTexCoord2f(1f, 0f);
|
||||
GL11.glVertex2i(fbo.width, fbo.height);
|
||||
GL11.glEnd();
|
||||
GL14.glBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
*/
|
||||
CURSOR.getScaledImage(OPTION_CURSOR_SIZE.val / 100f).drawCentered(lastPosition.x, lastPosition.y, filter);
|
||||
CURSOR_MIDDLE.getScaledImage(OPTION_CURSOR_SIZE.val / 100f).drawCentered(lastPosition.x, lastPosition.y, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -165,34 +303,22 @@ public class Cursor {
|
|||
* @param mouseY y coordinate to set position to
|
||||
*/
|
||||
public void setCursorPosition(int delta, int mouseX, int mouseY) {
|
||||
// TODO: use an image buffer
|
||||
int removeCount = 0;
|
||||
float FPSmod = Math.max(1000 / Math.max(delta, 1), 1) / 30f; // TODO
|
||||
if (newStyle) {
|
||||
// new style: add all points between cursor movements
|
||||
if ((lastPosition.x == 0 && lastPosition.y == 0) || !addCursorPoints(lastPosition.x, lastPosition.y, mouseX, mouseY)) {
|
||||
trail.add(new Point(mouseX, mouseY));
|
||||
}
|
||||
nowtime = System.currentTimeMillis();
|
||||
|
||||
addCursorPoints(lastPosition.x, lastPosition.y, mouseX, mouseY);
|
||||
lastPosition.move(mouseX, mouseY);
|
||||
|
||||
removeCount = (int) (trail.size() / (6 * FPSmod)) + 1;
|
||||
} else {
|
||||
// old style: sample one point at a time
|
||||
trail.add(new Point(mouseX, mouseY));
|
||||
|
||||
int max = (int) (10 * FPSmod);
|
||||
if (trail.size() > max)
|
||||
removeCount = trail.size() - max;
|
||||
int removecount = 0;
|
||||
TrailNode newfirst = trail.first;
|
||||
while (newfirst != null && newfirst.value.time < nowtime - 175) {
|
||||
newfirst = newfirst.next;
|
||||
removecount++;
|
||||
}
|
||||
|
||||
int cursortraillength = OPTION_DANCE_CURSOR_TRAIL_OVERRIDE.val;
|
||||
if (cursortraillength > 20) {
|
||||
removeCount = trail.size() - cursortraillength;
|
||||
trail.first = newfirst;
|
||||
if (newfirst == null) {
|
||||
trail.last = null;
|
||||
}
|
||||
|
||||
// remove points from the lists
|
||||
for (int i = 0; i < removeCount && !trail.isEmpty(); i++)
|
||||
trail.remove();
|
||||
trail.size -= removecount;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -200,8 +326,8 @@ public class Cursor {
|
|||
* @author http://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#Java
|
||||
*/
|
||||
private boolean addCursorPoints(int x1, int y1, int x2, int y2) {
|
||||
int size = trail.size;
|
||||
// delta of exact value and rounded value of the dependent variable
|
||||
boolean added = false;
|
||||
int d = 0;
|
||||
int dy = Math.abs(y2 - y1);
|
||||
int dx = Math.abs(x2 - x1);
|
||||
|
@ -211,16 +337,11 @@ public class Cursor {
|
|||
int ix = x1 < x2 ? 1 : -1; // increment direction
|
||||
int iy = y1 < y2 ? 1 : -1;
|
||||
|
||||
int k = 5; // sample size
|
||||
if (dy <= dx) {
|
||||
for (int i = 0; ; i++) {
|
||||
if (i == k) {
|
||||
trail.add(new Point(x1, y1));
|
||||
added = true;
|
||||
i = 0;
|
||||
}
|
||||
for (;;) {
|
||||
if (x1 == x2)
|
||||
break;
|
||||
trail.add(new Trailpart(x1, y1));
|
||||
x1 += ix;
|
||||
d += dy2;
|
||||
if (d > dx) {
|
||||
|
@ -229,14 +350,10 @@ public class Cursor {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; ; i++) {
|
||||
if (i == k) {
|
||||
trail.add(new Point(x1, y1));
|
||||
added = true;
|
||||
i = 0;
|
||||
}
|
||||
for (;;) {
|
||||
if (y1 == y2)
|
||||
break;
|
||||
trail.add(new Trailpart(x1, y1));
|
||||
y1 += iy;
|
||||
d += dx2;
|
||||
if (d > dy) {
|
||||
|
@ -245,7 +362,64 @@ public class Cursor {
|
|||
}
|
||||
}
|
||||
}
|
||||
return added;
|
||||
return trail.size != size;
|
||||
}
|
||||
|
||||
private static class TrailList implements Iterable<Trailpart>
|
||||
{
|
||||
TrailNode first;
|
||||
TrailNode last;
|
||||
int size;
|
||||
|
||||
public void add(Trailpart t)
|
||||
{
|
||||
if (last == null) {
|
||||
last = first = new TrailNode(t);
|
||||
} else {
|
||||
TrailNode n = new TrailNode(t);
|
||||
last.next = n;
|
||||
last = n;
|
||||
}
|
||||
size++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Trailpart> iterator()
|
||||
{
|
||||
return new Iterator<Trailpart>() {
|
||||
TrailNode node = first;
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return node != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trailpart next() {
|
||||
Trailpart v = node.value;
|
||||
node = node.next;
|
||||
return v;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static class TrailNode
|
||||
{
|
||||
TrailNode next;
|
||||
Trailpart value;
|
||||
TrailNode(Trailpart t) {
|
||||
value = t;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Trailpart {
|
||||
int x, y;
|
||||
long time;
|
||||
Trailpart(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.time = nowtime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -278,11 +452,7 @@ public class Cursor {
|
|||
* Resets all cursor location data.
|
||||
*/
|
||||
public void resetLocations(int mouseX, int mouseY) {
|
||||
trail.clear();
|
||||
lastPosition = new Point(mouseX, mouseY);
|
||||
for (int i = 0; i < 50; i++) {
|
||||
trail.add(new Point(lastPosition));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
191
src/yugecin/opsudance/ReplayCursor.java
Normal file
191
src/yugecin/opsudance/ReplayCursor.java
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 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 yugecin.opsudance;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.lwjgl.opengl.*;
|
||||
import org.newdawn.slick.*;
|
||||
|
||||
import static itdelatrisu.opsu.GameImage.*;
|
||||
import static yugecin.opsudance.core.InstanceContainer.*;
|
||||
import static yugecin.opsudance.options.Options.*;
|
||||
|
||||
public class ReplayCursor
|
||||
{
|
||||
private final Point lastPosition;
|
||||
private TrailList trail = new TrailList();
|
||||
|
||||
public Color filter;
|
||||
|
||||
public ReplayCursor(Color filter) {
|
||||
this.filter = filter;
|
||||
this.lastPosition = new Point(width2, 0);
|
||||
}
|
||||
|
||||
public void drawTrail(float trailw2, float trailh2, float txtw, float txth)
|
||||
{
|
||||
float alpha = 0f;
|
||||
float alphaIncrease = .4f / trail.size;
|
||||
for (Trailpart p : trail) {
|
||||
alpha += alphaIncrease;
|
||||
GL11.glColor4f(filter.r, filter.g, filter.b, alpha);
|
||||
GL11.glTexCoord2f(0f, 0f);
|
||||
GL11.glVertex3f(p.x - trailw2, p.y - trailh2, 0f);
|
||||
GL11.glTexCoord2f(txtw, 0);
|
||||
GL11.glVertex3f(p.x + trailw2, p.y - trailh2, 0f);
|
||||
GL11.glTexCoord2f(txtw, txth);
|
||||
GL11.glVertex3f(p.x + trailw2, p.y + trailh2, 0f);
|
||||
GL11.glTexCoord2f(0f, txth);
|
||||
GL11.glVertex3f(p.x - trailw2, p.y + trailh2, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void drawCursor()
|
||||
{
|
||||
CURSOR.getScaledImage(OPTION_CURSOR_SIZE.val / 100f).drawCentered(lastPosition.x, lastPosition.y, filter);
|
||||
CURSOR_MIDDLE.getScaledImage(OPTION_CURSOR_SIZE.val / 100f).drawCentered(lastPosition.x, lastPosition.y, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cursor position to given point and updates trail.
|
||||
* @param mouseX x coordinate to set position to
|
||||
* @param mouseY y coordinate to set position to
|
||||
*/
|
||||
public void setCursorPosition(int delta, int mouseX, int mouseY) {
|
||||
nowtime = System.currentTimeMillis();
|
||||
|
||||
addCursorPoints(lastPosition.x, lastPosition.y, mouseX, mouseY);
|
||||
lastPosition.move(mouseX, mouseY);
|
||||
|
||||
int removecount = 0;
|
||||
TrailNode newfirst = trail.first;
|
||||
while (newfirst != null && newfirst.value.time < nowtime - 175) {
|
||||
newfirst = newfirst.next;
|
||||
removecount++;
|
||||
}
|
||||
trail.first = newfirst;
|
||||
if (newfirst == null) {
|
||||
trail.last = null;
|
||||
}
|
||||
trail.size -= removecount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all points between (x1, y1) and (x2, y2) to the cursor point lists.
|
||||
* @author http://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#Java
|
||||
*/
|
||||
private boolean addCursorPoints(int x1, int y1, int x2, int y2) {
|
||||
int size = trail.size;
|
||||
// delta of exact value and rounded value of the dependent variable
|
||||
int d = 0;
|
||||
int dy = Math.abs(y2 - y1);
|
||||
int dx = Math.abs(x2 - x1);
|
||||
|
||||
int dy2 = (dy << 1); // slope scaling factors to avoid floating
|
||||
int dx2 = (dx << 1); // point
|
||||
int ix = x1 < x2 ? 1 : -1; // increment direction
|
||||
int iy = y1 < y2 ? 1 : -1;
|
||||
|
||||
if (dy <= dx) {
|
||||
for (;;) {
|
||||
if (x1 == x2)
|
||||
break;
|
||||
trail.add(new Trailpart(x1, y1));
|
||||
x1 += ix;
|
||||
d += dy2;
|
||||
if (d > dx) {
|
||||
y1 += iy;
|
||||
d -= dx2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (;;) {
|
||||
if (y1 == y2)
|
||||
break;
|
||||
trail.add(new Trailpart(x1, y1));
|
||||
y1 += iy;
|
||||
d += dx2;
|
||||
if (d > dy) {
|
||||
x1 += ix;
|
||||
d -= dy2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return trail.size != size;
|
||||
}
|
||||
|
||||
private static class TrailList implements Iterable<Trailpart>
|
||||
{
|
||||
TrailNode first;
|
||||
TrailNode last;
|
||||
int size;
|
||||
|
||||
public void add(Trailpart t)
|
||||
{
|
||||
if (last == null) {
|
||||
last = first = new TrailNode(t);
|
||||
} else {
|
||||
TrailNode n = new TrailNode(t);
|
||||
last.next = n;
|
||||
last = n;
|
||||
}
|
||||
size++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Trailpart> iterator()
|
||||
{
|
||||
return new Iterator<Trailpart>() {
|
||||
TrailNode node = first;
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return node != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trailpart next() {
|
||||
Trailpart v = node.value;
|
||||
node = node.next;
|
||||
return v;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static class TrailNode
|
||||
{
|
||||
TrailNode next;
|
||||
Trailpart value;
|
||||
TrailNode(Trailpart t) {
|
||||
value = t;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Trailpart {
|
||||
int x, y;
|
||||
long time;
|
||||
Trailpart(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.time = nowtime;
|
||||
}
|
||||
}
|
||||
}
|
111
src/yugecin/opsudance/ReplayCursors.java
Normal file
111
src/yugecin/opsudance/ReplayCursors.java
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* opsu!dance - fork of opsu! with cursordance auto
|
||||
* Copyright (C) 2018 yugecin
|
||||
*
|
||||
* opsu!dance is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* opsu!dance is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with opsu!dance. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package yugecin.opsudance;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.*;
|
||||
import org.newdawn.slick.*;
|
||||
import org.newdawn.slick.opengl.Texture;
|
||||
import org.newdawn.slick.opengl.TextureImpl;
|
||||
|
||||
import itdelatrisu.opsu.render.Rendertarget;
|
||||
|
||||
import static itdelatrisu.opsu.GameImage.*;
|
||||
import static yugecin.opsudance.core.InstanceContainer.*;
|
||||
import static yugecin.opsudance.options.Options.*;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public class ReplayCursors
|
||||
{
|
||||
public final ReplayPlayback[] playbacks;
|
||||
|
||||
private final Rendertarget fbo;
|
||||
|
||||
public ReplayCursors(int amount)
|
||||
{
|
||||
this.playbacks = new ReplayPlayback[amount];
|
||||
this.fbo = Rendertarget.createRTTFramebuffer(width, height);
|
||||
}
|
||||
|
||||
public void draw()
|
||||
{
|
||||
final Image img = CURSOR_TRAIL.getImage();
|
||||
final Texture txt = img.getTexture();
|
||||
|
||||
final float trailw2 = img.getWidth() * OPTION_CURSOR_SIZE.val / 100f / 2f;
|
||||
final float trailh2 = img.getHeight() * OPTION_CURSOR_SIZE.val / 100f / 2f;
|
||||
float txtw = txt.getWidth();
|
||||
float txth = txt.getHeight();
|
||||
|
||||
for (ReplayPlayback p : playbacks) {
|
||||
if (!p.shouldDrawCursor()) {
|
||||
continue;
|
||||
}
|
||||
// stuff copied from CurveRenderState and stuff, I don't know what I'm doing
|
||||
int oldFb = GL11.glGetInteger(EXTFramebufferObject.GL_FRAMEBUFFER_BINDING_EXT);
|
||||
int oldTex = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
IntBuffer oldViewport = BufferUtils.createIntBuffer(16);
|
||||
GL11.glGetInteger(GL11.GL_VIEWPORT, oldViewport);
|
||||
EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, fbo.getID());
|
||||
GL11.glViewport(0, 0, fbo.width, fbo.height);
|
||||
GL11.glClearColor(0f, 0f, 0f, 0f);
|
||||
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
txt.bind();
|
||||
TextureImpl.unbind();
|
||||
GL11.glBegin(GL11.GL_QUADS);
|
||||
p.cursor.drawTrail(trailw2, trailh2, txtw, txth);
|
||||
GL11.glEnd();
|
||||
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, oldTex);
|
||||
EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, oldFb);
|
||||
GL11.glViewport(oldViewport.get(0), oldViewport.get(1), oldViewport.get(2), oldViewport.get(3));
|
||||
|
||||
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
|
||||
//GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
//GL11.glDisable(GL11.GL_TEXTURE_1D);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo.getTextureID());
|
||||
GL11.glBegin(GL11.GL_QUADS);
|
||||
GL11.glColor4f(1f, 1f, 1f, 1f);
|
||||
GL11.glTexCoord2f(1f, 1f);
|
||||
GL11.glVertex2i(fbo.width, 0);
|
||||
GL11.glTexCoord2f(0f, 1f);
|
||||
GL11.glVertex2i(0, 0);
|
||||
GL11.glTexCoord2f(0f, 0f);
|
||||
GL11.glVertex2i(0, fbo.height);
|
||||
GL11.glTexCoord2f(1f, 0f);
|
||||
GL11.glVertex2i(fbo.width, fbo.height);
|
||||
GL11.glEnd();
|
||||
GL14.glBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
|
||||
for (ReplayPlayback p : playbacks) {
|
||||
if (!p.shouldDrawCursor()) {
|
||||
continue;
|
||||
}
|
||||
p.cursor.drawCursor();
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy()
|
||||
{
|
||||
this.fbo.destroyRTT();
|
||||
}
|
||||
}
|
490
src/yugecin/opsudance/ReplayPlayback.java
Normal file
490
src/yugecin/opsudance/ReplayPlayback.java
Normal file
|
@ -0,0 +1,490 @@
|
|||
/*
|
||||
* opsu!dance - fork of opsu! with cursordance auto
|
||||
* Copyright (C) 2017 yugecin
|
||||
*
|
||||
* opsu!dance is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* opsu!dance is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with opsu!dance. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package yugecin.opsudance;
|
||||
|
||||
import itdelatrisu.opsu.GameData;
|
||||
import itdelatrisu.opsu.replay.Replay;
|
||||
import itdelatrisu.opsu.replay.ReplayFrame;
|
||||
import itdelatrisu.opsu.ui.Fonts;
|
||||
import itdelatrisu.opsu.ui.animations.AnimationEquation;
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.Graphics;
|
||||
import org.newdawn.slick.Image;
|
||||
import yugecin.opsudance.core.Entrypoint;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import static itdelatrisu.opsu.GameData.*;
|
||||
import static itdelatrisu.opsu.Utils.*;
|
||||
import static itdelatrisu.opsu.ui.animations.AnimationEquation.*;
|
||||
import static yugecin.opsudance.core.InstanceContainer.*;
|
||||
import static yugecin.opsudance.options.Options.*;
|
||||
|
||||
public class ReplayPlayback
|
||||
{
|
||||
private final HitData hitdata;
|
||||
public final Replay replay;
|
||||
public ReplayFrame currentFrame;
|
||||
public ReplayFrame nextFrame;
|
||||
private int frameIndex;
|
||||
private Color color;
|
||||
private final Color originalcolor;
|
||||
public final ReplayCursor cursor;
|
||||
private int keydelay[];
|
||||
public final int PADDING = 3;
|
||||
public final int sqsize;
|
||||
public final int unitHeight;
|
||||
public static int lineHeight;
|
||||
private boolean hr;
|
||||
private String player;
|
||||
private String mods;
|
||||
private int playerwidth;
|
||||
private int modwidth;
|
||||
private String currentAcc;
|
||||
private int currentAccWidth;
|
||||
private final int ACCMAXWIDTH;
|
||||
|
||||
private int c300, c100, c50, fakecmiss;
|
||||
|
||||
private Image hitImage;
|
||||
private int hitImageTimer = 0;
|
||||
private boolean knockedout;
|
||||
private final LinkedList<MissIndicator> missIndicators;
|
||||
|
||||
private Image gradeImage;
|
||||
|
||||
private static final Color missedColor = new Color(0.4f, 0.4f, 0.4f, 1f);
|
||||
|
||||
public ReplayPlayback(Replay replay, HitData hitdata, Color color, ReplayCursor cursor) {
|
||||
this.missIndicators = new LinkedList<>();
|
||||
this.replay = replay;
|
||||
this.hitdata = hitdata;
|
||||
resetFrameIndex();
|
||||
this.color = new Color(color);
|
||||
this.originalcolor = color;
|
||||
this.cursor = cursor;
|
||||
keydelay = new int[4];
|
||||
this.player = replay.playerName;
|
||||
this.playerwidth = Fonts.SMALLBOLD.getWidth(this.player);
|
||||
this.mods = "";
|
||||
this.currentAcc = "100,00%";
|
||||
this.currentAccWidth = Fonts.SMALLBOLD.getWidth(currentAcc);
|
||||
this.ACCMAXWIDTH = currentAccWidth + 10;
|
||||
this.unitHeight = (int) (Fonts.SMALLBOLD.getLineHeight() * 0.9f);
|
||||
this.sqsize = unitHeight - PADDING;
|
||||
lineHeight = this.unitHeight;
|
||||
if ((replay.mods & 0x1) > 0) {
|
||||
this.mods += "NF";
|
||||
}
|
||||
if ((replay.mods & 0x2) > 0) {
|
||||
this.mods += "EZ";
|
||||
}
|
||||
if ((replay.mods & 0x8) > 0 && (replay.mods & 0x200) == 0) {
|
||||
this.mods += "HD";
|
||||
}
|
||||
if ((replay.mods & 0x10) > 0) {
|
||||
this.mods += "HR";
|
||||
hr = true;
|
||||
}
|
||||
if ((replay.mods & 0x20) > 0) {
|
||||
this.mods += "SD";
|
||||
}
|
||||
if ((replay.mods & 0x40) > 0) {
|
||||
this.mods += "DT";
|
||||
}
|
||||
if ((replay.mods & 0x80) > 0) {
|
||||
this.mods += "RL";
|
||||
}
|
||||
if ((replay.mods & 0x100) > 0) {
|
||||
this.mods += "HT";
|
||||
}
|
||||
if ((replay.mods & 0x200) > 0) {
|
||||
this.mods += "NC";
|
||||
}
|
||||
if ((replay.mods & 0x400) > 0) {
|
||||
this.mods += "FL";
|
||||
}
|
||||
if ((replay.mods & 0x4000) > 0) {
|
||||
this.mods += "PF";
|
||||
}
|
||||
if (this.mods.length() > 0) {
|
||||
this.mods = " +" + this.mods;
|
||||
this.modwidth = Fonts.SMALLBOLD.getWidth(this.mods);
|
||||
}
|
||||
updateGradeImage();
|
||||
}
|
||||
|
||||
public void resetFrameIndex() {
|
||||
frameIndex = 0;
|
||||
currentFrame = replay.frames[frameIndex++];
|
||||
nextFrame = replay.frames[frameIndex];
|
||||
}
|
||||
|
||||
private void updateGradeImage() {
|
||||
if (knockedout || !OPTION_RP_SHOW_GRADES.state) {
|
||||
gradeImage = null;
|
||||
return;
|
||||
}
|
||||
|
||||
boolean silver = (replay.mods & 0x408) > 0 && (replay.mods & 0x200) == 0;
|
||||
GameData.Grade grade = GameData.getGrade(c300, c100, c50, fakecmiss, silver);
|
||||
|
||||
if (grade == GameData.Grade.NULL) {
|
||||
if ((replay.mods & 0x8) > 0 && (replay.mods & 0x200) == 0) {
|
||||
grade = GameData.Grade.SSH;
|
||||
} else {
|
||||
grade = GameData.Grade.SS;
|
||||
}
|
||||
}
|
||||
gradeImage = grade.getSmallImage().getScaledCopy(unitHeight, unitHeight);
|
||||
}
|
||||
|
||||
private int HITIMAGETIMEREXPAND = 250;
|
||||
private int HITIMAGETIMERFADESTART = 500;
|
||||
private int HITIMAGETIMERFADEEND = 700;
|
||||
private float HITIMAGETIMERFADEDELTA = HITIMAGETIMERFADEEND - HITIMAGETIMERFADESTART;
|
||||
private int HITIMAGEDEADFADE = 4000;
|
||||
private float SHRINKTIME = 500f;
|
||||
private void showHitImage(int renderdelta, int xpos, float ypos) {
|
||||
if (hitImage == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
hitImageTimer += renderdelta;
|
||||
if (!knockedout && hitImageTimer > HITIMAGETIMERFADEEND) {
|
||||
hitImage = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Color color = new Color(1f, 1f, 1f, 1f);
|
||||
if (!knockedout && hitImageTimer > HITIMAGETIMERFADESTART) {
|
||||
color.a = (HITIMAGETIMERFADEEND - hitImageTimer) / HITIMAGETIMERFADEDELTA;
|
||||
}
|
||||
if (knockedout) {
|
||||
if (hitImageTimer > HITIMAGEDEADFADE) {
|
||||
this.color.a = color.a = 0f;
|
||||
} else {
|
||||
this.color.a = color.a = 1f - AnimationEquation.IN_CIRC.calc((float) hitImageTimer / HITIMAGEDEADFADE);
|
||||
}
|
||||
}
|
||||
float scale = 1f;
|
||||
float offset = 0f;
|
||||
if (hitImageTimer < HITIMAGETIMEREXPAND) {
|
||||
scale = AnimationEquation.OUT_EXPO.calc((float) hitImageTimer / HITIMAGETIMEREXPAND);
|
||||
offset = unitHeight / 2f * (1f - scale);
|
||||
}
|
||||
hitImage.draw(xpos, 2f + ypos + offset, scale, color);
|
||||
}
|
||||
|
||||
public float getHeight() {
|
||||
if (hitImageTimer < HITIMAGEDEADFADE) {
|
||||
return unitHeight;
|
||||
}
|
||||
if (hitImageTimer >= HITIMAGEDEADFADE + SHRINKTIME) {
|
||||
return 0f;
|
||||
}
|
||||
return unitHeight * (1f - AnimationEquation.OUT_QUART.calc((hitImageTimer - HITIMAGEDEADFADE) / SHRINKTIME));
|
||||
}
|
||||
|
||||
public void render(int renderdelta, Graphics g, float ypos, int time)
|
||||
{
|
||||
while (nextFrame != null && nextFrame.getTime() < time) {
|
||||
currentFrame = nextFrame;
|
||||
processKeys();
|
||||
frameIndex++;
|
||||
if (frameIndex >= replay.frames.length) {
|
||||
nextFrame = null;
|
||||
continue;
|
||||
}
|
||||
nextFrame = replay.frames[frameIndex];
|
||||
}
|
||||
processKeys();
|
||||
g.setColor(color);
|
||||
if (!knockedout) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (keydelay[i] > 0) {
|
||||
g.fillRect(sqsize * i, ypos + PADDING, sqsize, sqsize);
|
||||
}
|
||||
keydelay[i] -= renderdelta;
|
||||
}
|
||||
|
||||
boolean hitschanged = false;
|
||||
while (!hitdata.acc.isEmpty() && hitdata.acc.getFirst().time <= time) {
|
||||
currentAcc = String.format("%.2f%%", hitdata.acc.removeFirst().acc).replace('.', ',');
|
||||
currentAccWidth = Fonts.SMALLBOLD.getWidth(currentAcc);
|
||||
}
|
||||
|
||||
while (!hitdata.time300.isEmpty() && hitdata.time300.getFirst() <= time) {
|
||||
hitdata.time300.removeFirst();
|
||||
c300++;
|
||||
hitschanged = true;
|
||||
}
|
||||
|
||||
while (!hitdata.time100.isEmpty() && hitdata.time100.getFirst() <= time) {
|
||||
hitdata.time100.removeFirst();
|
||||
hitImageTimer = 0;
|
||||
hitImage = GameData.hitResults[GameData.HIT_100];
|
||||
c100++;
|
||||
hitschanged = true;
|
||||
}
|
||||
|
||||
while (!hitdata.time50.isEmpty() && hitdata.time50.getFirst() <= time) {
|
||||
hitdata.time50.removeFirst();
|
||||
hitImageTimer = 0;
|
||||
hitImage = GameData.hitResults[GameData.HIT_50];
|
||||
c50++;
|
||||
hitschanged = true;
|
||||
}
|
||||
|
||||
while (!hitdata.timeCombobreaks.isEmpty() && hitdata.timeCombobreaks.getFirst() <= time) {
|
||||
hitdata.timeCombobreaks.removeFirst();
|
||||
hitImageTimer = 0;
|
||||
hitImage = GameData.hitResults[GameData.HIT_MISS];
|
||||
fakecmiss++;
|
||||
hitschanged = true;
|
||||
if (OPTION_RP_SHOW_MISSES.state) {
|
||||
float posx = currentFrame.getScaledX();
|
||||
float posy = currentFrame.getScaledY();
|
||||
if (hr) {
|
||||
posy = height - posy;
|
||||
}
|
||||
this.missIndicators.add(new MissIndicator(posx, posy));
|
||||
}
|
||||
if (OPTION_RP_KNOCKOUT.state) {
|
||||
knockedout = true;
|
||||
color = new Color(missedColor);
|
||||
}
|
||||
}
|
||||
|
||||
if (hitImage != null) {
|
||||
final float h = hitImage.getHeight();
|
||||
if (h == 0) {
|
||||
hitImage = null;
|
||||
} else {
|
||||
hitImage = hitImage.getScaledCopy(unitHeight / h);
|
||||
}
|
||||
}
|
||||
|
||||
if (hitschanged) {
|
||||
updateGradeImage();
|
||||
}
|
||||
}
|
||||
int xpos = sqsize * (OPTION_RP_SHOW_MOUSECOLUMN.state ? 5 : 3);
|
||||
if (OPTION_RP_SHOW_ACC.state) {
|
||||
Fonts.SMALLBOLD.drawString(xpos + ACCMAXWIDTH - currentAccWidth - 10, ypos, currentAcc, new Color(.4f, .4f, .4f, color.a));
|
||||
xpos += ACCMAXWIDTH;
|
||||
}
|
||||
if (gradeImage != null) {
|
||||
gradeImage.draw(xpos, ypos);
|
||||
xpos += sqsize + 10;
|
||||
}
|
||||
Fonts.SMALLBOLD.drawString(xpos, ypos, this.player, color);
|
||||
xpos += playerwidth;
|
||||
if (!this.mods.isEmpty()) {
|
||||
Fonts.SMALLBOLD.drawString(xpos, ypos, this.mods, new Color(1f, 1f, 1f, color.a));
|
||||
xpos += modwidth;
|
||||
}
|
||||
xpos += 10;
|
||||
if (OPTION_RP_SHOW_HITS.state) {
|
||||
showHitImage(renderdelta, xpos, ypos);
|
||||
}
|
||||
if (OPTION_RP_SHOW_MISSES.state) {
|
||||
final Iterator<MissIndicator> iter = this.missIndicators.iterator();
|
||||
while (iter.hasNext()) {
|
||||
final MissIndicator mi = iter.next();
|
||||
if (mi.timer >= HITIMAGEDEADFADE) {
|
||||
iter.remove();
|
||||
continue;
|
||||
}
|
||||
float progress = (float) mi.timer / HITIMAGEDEADFADE;
|
||||
float failposy = mi.posy + 50f * OUT_QUART.calc(progress);
|
||||
Color col = new Color(originalcolor);
|
||||
col.a = 1f - IN_QUAD.calc(clamp(progress * 2f, 0f, 1f));
|
||||
Fonts.SMALLBOLD.drawString(mi.posx - playerwidth / 2, failposy, player, col);
|
||||
Color failimgcol = new Color(1f, 1f, 1f, col.a);
|
||||
Image failimg = hitResults[HIT_MISS].getScaledCopy(unitHeight, unitHeight);
|
||||
failimg.draw(mi.posx + playerwidth / 2 + 5, failposy + 2f, failimgcol);
|
||||
mi.timer += renderdelta;
|
||||
}
|
||||
}
|
||||
if (knockedout) {
|
||||
return;
|
||||
}
|
||||
int y = currentFrame.getScaledY();
|
||||
if (hr) {
|
||||
y = height - y;
|
||||
}
|
||||
cursor.setCursorPosition(renderdelta, currentFrame.getScaledX(), y);
|
||||
}
|
||||
|
||||
public boolean shouldDrawCursor()
|
||||
{
|
||||
return !knockedout;
|
||||
}
|
||||
|
||||
private void processKeys() {
|
||||
int keys = currentFrame.getKeys();
|
||||
if ((keys & 5) == 5) {
|
||||
keydelay[0] = OPTION_RP_KEYPRESS_DELAY.val;
|
||||
}
|
||||
if ((keys & 10) == 10) {
|
||||
keydelay[1] = OPTION_RP_KEYPRESS_DELAY.val;
|
||||
}
|
||||
if ((keys ^ 5) == 4) {
|
||||
keydelay[2] = OPTION_RP_KEYPRESS_DELAY.val;
|
||||
}
|
||||
if ((keys ^ 10) == 8) {
|
||||
keydelay[3] = OPTION_RP_KEYPRESS_DELAY.val;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MissIndicator
|
||||
{
|
||||
private float posx, posy;
|
||||
private int timer;
|
||||
|
||||
private MissIndicator(float posx, float posy)
|
||||
{
|
||||
this.posx = posx;
|
||||
this.posy = posy;
|
||||
this.timer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HitData
|
||||
{
|
||||
LinkedList<Integer> time300 = new LinkedList<>();
|
||||
LinkedList<Integer> time100 = new LinkedList<>();
|
||||
LinkedList<Integer> time50 = new LinkedList<>();
|
||||
LinkedList<Integer> timeCombobreaks = new LinkedList<>();
|
||||
LinkedList<AccData> acc = new LinkedList<>();
|
||||
LinkedList<ComboData> combo = new LinkedList<>();
|
||||
|
||||
public HitData(File file) {
|
||||
try (InputStream in = new FileInputStream(file)) {
|
||||
int lasttime = -1;
|
||||
int lastcombo = 0;
|
||||
int last300 = 0;
|
||||
int last100 = 0;
|
||||
int last50 = 0;
|
||||
while (true) {
|
||||
byte[] time = new byte[4];
|
||||
int rd = in.read(time);
|
||||
if (rd <= 0) {
|
||||
break;
|
||||
}
|
||||
if (rd != 4) {
|
||||
throw new RuntimeException("expected 4 bytes, got " + rd);
|
||||
}
|
||||
byte[] _time = { time[3], time[2], time[1], time[0] };
|
||||
lasttime = ByteBuffer.wrap(_time).getInt();
|
||||
lasttime += 200;
|
||||
int type = in.read();
|
||||
if (type == -1) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
if (in.read(time) != 4) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
_time = new byte[] { time[3], time[2], time[1], time[0] };
|
||||
switch (type) {
|
||||
case 1:
|
||||
int this100 = ByteBuffer.wrap(_time).getInt();
|
||||
spread(time100, lasttime, this100 - last100);
|
||||
last100 = this100;
|
||||
break;
|
||||
case 3:
|
||||
int this300 = ByteBuffer.wrap(_time).getInt();
|
||||
spread(time300, lasttime, this300 - last300);
|
||||
last300 = this300;
|
||||
break;
|
||||
case 5:
|
||||
int this50 = ByteBuffer.wrap(_time).getInt();
|
||||
spread(time50, lasttime, this50 - last50);
|
||||
last50 = this50;
|
||||
break;
|
||||
case 10:
|
||||
acc.add(new AccData(lasttime, ByteBuffer.wrap(_time).getFloat()));
|
||||
break;
|
||||
case 12:
|
||||
int c = ByteBuffer.wrap(_time).getInt();
|
||||
combo.add(new ComboData(lasttime, c));
|
||||
if (c < lastcombo) {
|
||||
timeCombobreaks.add(lasttime);
|
||||
}
|
||||
lastcombo = c;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("unexpected data");
|
||||
}
|
||||
}
|
||||
if (lasttime == -1) {
|
||||
throw new RuntimeException("nodata");
|
||||
}
|
||||
Entrypoint.sout(String.format(
|
||||
"%s lastcombo %d lasttime %d",
|
||||
file.getName(),
|
||||
lastcombo,
|
||||
lasttime
|
||||
));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void spread(LinkedList<Integer> list, int time, int d) {
|
||||
if (list.isEmpty() || d <= 1) {
|
||||
list.add(time);
|
||||
return;
|
||||
}
|
||||
|
||||
int dtime = time - list.getLast();
|
||||
int inc = dtime / d;
|
||||
int ttime = list.getLast();
|
||||
for (int i = 0; i < d; i++) {
|
||||
ttime += inc;
|
||||
if (i == d - 1) {
|
||||
ttime = time;
|
||||
}
|
||||
list.add(ttime);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class AccData {
|
||||
public int time;
|
||||
public float acc;
|
||||
public AccData(int time, float acc) {
|
||||
this.time = time;
|
||||
this.acc = acc;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ComboData {
|
||||
public int time;
|
||||
public int combo;
|
||||
public ComboData(int time, int combo) {
|
||||
this.time = time;
|
||||
this.combo = combo;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -92,7 +92,7 @@ public class DisplayContainer implements ErrorDumpable, SkinChangedListener {
|
|||
|
||||
private long exitconfirmation;
|
||||
|
||||
public final Cursor cursor;
|
||||
public Cursor cursor;
|
||||
public boolean drawCursor;
|
||||
|
||||
private final List<ResolutionChangedListener> resolutionChangedListeners;
|
||||
|
@ -106,8 +106,6 @@ public class DisplayContainer implements ErrorDumpable, SkinChangedListener {
|
|||
public DisplayContainer()
|
||||
{
|
||||
this.resolutionChangedListeners = new ArrayList<>();
|
||||
this.cursor = new Cursor();
|
||||
drawCursor = true;
|
||||
|
||||
skinservice.addSkinChangedListener(this);
|
||||
|
||||
|
@ -174,6 +172,7 @@ public class DisplayContainer implements ErrorDumpable, SkinChangedListener {
|
|||
|
||||
public void run() throws Exception {
|
||||
while(!exitRequested && !(Display.isCloseRequested() && state.onCloseRequest()) || !confirmExit()) {
|
||||
nowtime = System.currentTimeMillis();
|
||||
delta = getDelta();
|
||||
|
||||
timeSinceLastRender += delta;
|
||||
|
@ -270,6 +269,8 @@ public class DisplayContainer implements ErrorDumpable, SkinChangedListener {
|
|||
glVersion = GL11.glGetString(GL11.GL_VERSION);
|
||||
glVendor = GL11.glGetString(GL11.GL_VENDOR);
|
||||
GLHelper.hideNativeCursor();
|
||||
this.cursor = new Cursor();
|
||||
drawCursor = true;
|
||||
}
|
||||
|
||||
// TODO: move this elsewhere
|
||||
|
|
|
@ -35,6 +35,7 @@ public class Entrypoint {
|
|||
try {
|
||||
InstanceContainer.kickstart();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
JOptionPane.showMessageDialog(null, e.getMessage(), "Cannot start " + PROJECT_NAME, JOptionPane.ERROR_MESSAGE);
|
||||
// TODO replace with errorhandler
|
||||
}
|
||||
|
|
|
@ -82,6 +82,7 @@ public class InstanceContainer {
|
|||
public static boolean isWidescreen;
|
||||
public static int mouseX, mouseY;
|
||||
public static int renderDelta;
|
||||
public static long nowtime;
|
||||
|
||||
public static void kickstart() {
|
||||
updater = new Updater();
|
||||
|
|
|
@ -161,6 +161,15 @@ public class OptionGroups {
|
|||
OPTION_DANCE_RGB_CURSOR_INC,
|
||||
OPTION_DANCE_CURSOR_TRAIL_OVERRIDE,
|
||||
}),
|
||||
new OptionTab("REPLAYSTUFF", new Option[] {
|
||||
OPTION_RP_KNOCKOUT,
|
||||
OPTION_RP_SHOW_MISSES,
|
||||
OPTION_RP_SHOW_GRADES,
|
||||
OPTION_RP_SHOW_HITS,
|
||||
OPTION_RP_SHOW_ACC,
|
||||
OPTION_RP_SHOW_MOUSECOLUMN,
|
||||
OPTION_RP_KEYPRESS_DELAY,
|
||||
}),
|
||||
new OptionTab("MISC", new Option[] {
|
||||
OPTION_DANCE_HIDE_UI,
|
||||
OPTION_DANCE_REMOVE_BG,
|
||||
|
|
|
@ -373,7 +373,7 @@ public class Options {
|
|||
}
|
||||
};
|
||||
|
||||
public static final NumericOption OPTION_CURSOR_SIZE = new NumericOption("Size", "CursorSize", "Change the cursor scale.", 100, 50, 200) {
|
||||
public static final NumericOption OPTION_CURSOR_SIZE = new NumericOption("Size", "CursorSize", "Change the cursor scale.", 100, 10, 200) {
|
||||
@Override
|
||||
public String getValueString () {
|
||||
return String.format("%.2fx", val / 100f);
|
||||
|
@ -998,4 +998,19 @@ public class Options {
|
|||
public static final ToggleOption OPTION_PIPPI_SLIDER_FOLLOW_EXPAND = new ToggleOption("Followcircle expand", "PippiFollowExpand", "Increase radius in followcircles", false);
|
||||
public static final ToggleOption OPTION_PIPPI_PREVENT_WOBBLY_STREAMS = new ToggleOption("Prevent wobbly streams", "PippiPreventWobblyStreams", "Force linear mover while doing streams to prevent wobbly pippi", true);
|
||||
|
||||
public static final ToggleOption
|
||||
OPTION_RP_KNOCKOUT = new ToggleOption("Knockout", "ReplayKnockout", "Remove replays on combobreaks.", true),
|
||||
OPTION_RP_SHOW_MISSES = new ToggleOption("Show misses", "ReplayShowMiss", "Show falling miss indicators.", true),
|
||||
OPTION_RP_SHOW_GRADES = new ToggleOption("Show grades", "ReplayShowGrades", "Show grades next to players.", true),
|
||||
OPTION_RP_SHOW_HITS = new ToggleOption("Show hits", "ReplayShowHits", "Show miss, 50, 100 hits next to players.", true),
|
||||
OPTION_RP_SHOW_MOUSECOLUMN = new ToggleOption("Mouse buttons column", "ReplayShowMouseColumn", "Preserve space for mouse button columns.", true),
|
||||
OPTION_RP_SHOW_ACC = new ToggleOption("Show accuracy", "ReplayShowAcc", "Show accuracy next to players.", true);
|
||||
|
||||
public static final NumericOption
|
||||
OPTION_RP_KEYPRESS_DELAY = new NumericOption("Key indicator delay", "ReplayKeyDelay", "How long the key indicator should show after the key being released (ms).", 10, 1, 50) {
|
||||
@Override
|
||||
public String getValueString() {
|
||||
return String.valueOf(val);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user