diff --git a/buildnumber.txt b/buildnumber.txt index bd19a3c6..1c8a8a85 100644 --- a/buildnumber.txt +++ b/buildnumber.txt @@ -1 +1 @@ -921 \ No newline at end of file +932 \ No newline at end of file diff --git a/source/funkin/game/Note.hx b/source/funkin/game/Note.hx index f02b94b1..3e52d87f 100644 --- a/source/funkin/game/Note.hx +++ b/source/funkin/game/Note.hx @@ -23,7 +23,19 @@ class Note extends FlxSprite public var canBeHit:Bool = false; public var tooLate:Bool = false; public var wasGoodHit:Bool = false; + + /** + * The note that comes before this one (sustain and not) + */ public var prevNote:Note; + /** + * The note that comes after this one (sustain and not) + */ + public var nextNote:Note; + /** + * The next sustain after this one + */ + public var nextSustain:Note; public var strumID(get, never):Int; private function get_strumID() { @@ -115,13 +127,13 @@ class Note extends FlxSprite if (prevNote.isSustainNote) { + prevNote.nextSustain = this; prevNote.animation.play('hold'); - prevNote.scale.y *= Conductor.stepCrochet / 100 * 1.5 * PlayState.instance.scrollSpeed; - prevNote.updateHitbox(); } } } + public var lastScrollSpeed:Null = null; public var angleOffsets:Bool = true; override function draw() { @@ -176,8 +188,19 @@ class Note extends FlxSprite super.update(elapsed); } - public function updateClipRect(strum:Strum) { - var t = FlxMath.bound((Conductor.songPosition - strumTime) / (height / (0.45 * FlxMath.roundDecimal(strum.getScrollSpeed(), 2))), 0, 1); + public function updateSustain(strum:Strum) { + var scrollSpeed = strum.getScrollSpeed(this); + + if (nextSustain != null && lastScrollSpeed != scrollSpeed) { + // is long sustain + lastScrollSpeed = scrollSpeed; + + scale.y = Conductor.stepCrochet / 100 * 1.5 * scrollSpeed * 0.7; + updateHitbox(); + } + + if (!wasGoodHit) return; + var t = FlxMath.bound((Conductor.songPosition - strumTime) / (height / (0.45 * FlxMath.roundDecimal(scrollSpeed, 2))), 0, 1); var swagRect = new FlxRect(0, t * frameHeight, frameWidth, frameHeight); setClipRect(swagRect); diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 59367445..0f8a53ef 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -470,7 +470,7 @@ class PlayState extends MusicBeatState else swagNote = null; - swagNote = new Note(daStrumTime, daNoteData % 4, daNoteType, gottaHitNote, swagNote); + swagNote.nextNote = (swagNote = new Note(daStrumTime, daNoteData % 4, daNoteType, gottaHitNote, swagNote)); swagNote.sustainLength = songNotes[2]; swagNote.scrollFactor.set(0, 0); notes.add(swagNote); diff --git a/source/funkin/game/Strum.hx b/source/funkin/game/Strum.hx index ccbe58bd..b8b468df 100644 --- a/source/funkin/game/Strum.hx +++ b/source/funkin/game/Strum.hx @@ -72,8 +72,7 @@ class Strum extends FlxSprite { public function updateSustain(daNote:Note) { if (!daNote.isSustainNote) return; daNote.flipY = PlayState.instance.downscroll != (getScrollSpeed(daNote) < 0); - if (!daNote.wasGoodHit) return; - daNote.updateClipRect(this); + daNote.updateSustain(this); } public function updatePlayerInput(pressed:Bool, justPressed:Bool, justReleased:Bool) { diff --git a/source/funkin/scripting/Script.hx b/source/funkin/scripting/Script.hx index 08843ff1..5a60855b 100644 --- a/source/funkin/scripting/Script.hx +++ b/source/funkin/scripting/Script.hx @@ -60,6 +60,7 @@ class Script extends FlxBasic implements IFlxDestroyable { "Paths" => funkin.system.Paths, "Conductor" => funkin.system.Conductor, "CoolUtil" => funkin.system.CoolUtil, + "FunkinShader" => funkin.shaders.FunkinShader, ]; } /** diff --git a/source/funkin/shaders/BlendModeEffect.hx b/source/funkin/shaders/BlendModeEffect.hx index 1c9038be..b28f20b0 100644 --- a/source/funkin/shaders/BlendModeEffect.hx +++ b/source/funkin/shaders/BlendModeEffect.hx @@ -1,4 +1,4 @@ -package; +package funkin.shaders; import flixel.util.FlxColor; import openfl.display.ShaderParameter; diff --git a/source/funkin/shaders/CustomShader.hx b/source/funkin/shaders/CustomShader.hx new file mode 100644 index 00000000..831ec468 --- /dev/null +++ b/source/funkin/shaders/CustomShader.hx @@ -0,0 +1,5 @@ +package funkin.shaders; + +class CustomShader extends FunkinShader { + +} \ No newline at end of file diff --git a/source/funkin/shaders/FunkinShader.hx b/source/funkin/shaders/FunkinShader.hx new file mode 100644 index 00000000..833713ef --- /dev/null +++ b/source/funkin/shaders/FunkinShader.hx @@ -0,0 +1,482 @@ +package funkin.shaders; + +import flixel.graphics.tile.FlxGraphicsShader; +import openfl.display3D.Program3D; +import flixel.system.FlxAssets.FlxShader; + +import openfl.display.BitmapData; +import openfl.display.ShaderParameter; +import openfl.display.ShaderParameterType; +import openfl.display.ShaderInput; +import lime.utils.Float32Array; + +using StringTools; + +class FunkinShader extends FlxShader { + public var glslVer:String = "120"; + + /** + * Creates a new shader from the specified fragment and vertex source. + * Accepts `#pragma header`. + * @param frag Fragment source (pass `null` to use default) + * @param vert Vertex source (pass `null` to use default) + * @param glslVer Version of GLSL to use (defaults to 120) + */ + public override function new(frag:String, vert:String, glslVer:String = "120") { + if (frag == null) frag = ShaderTemplates.defaultFragmentSource; + if (vert == null) vert = ShaderTemplates.defaultVertexSource; + this.glFragmentSource = frag.replace("#pragma header", ShaderTemplates.fragHeader).replace("#pragma body", ShaderTemplates.fragBody); + this.glVertexSource = vert.replace("#pragma header", ShaderTemplates.vertHeader).replace("#pragma body", ShaderTemplates.vertBody); + + trace(glFragmentSource); + trace(glVertexSource); + + this.glslVer = glslVer; + super(); + } + + @:noCompletion private override function __initGL():Void + { + if (__glSourceDirty || __paramBool == null) + { + __glSourceDirty = false; + program = null; + + __inputBitmapData = new Array(); + __paramBool = new Array(); + __paramFloat = new Array(); + __paramInt = new Array(); + + __processGLData(glVertexSource, "attribute"); + __processGLData(glVertexSource, "uniform"); + __processGLData(glFragmentSource, "uniform"); + } + + if (__context != null && program == null) + { + var prefix = '#version ${glslVer}\n'; + + @:privateAccess var gl = __context.gl; + + prefix += "#ifdef GL_ES + " + + (precisionHint == FULL ? "#ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; + #else + precision mediump float; + #endif" : "precision lowp float;") + + " + #endif + "; + + var vertex = prefix + glVertexSource; + var fragment = prefix + glFragmentSource; + + var id = vertex + fragment; + @:privateAccess + if (__context.__programs.exists(id)) + { + @:privateAccess program = __context.__programs.get(id); + } + else + { + program = __context.createProgram(GLSL); + + // TODO + // program.uploadSources (vertex, fragment); + @:privateAccess program.__glProgram = __createGLProgram(vertex, fragment); + + @:privateAccess __context.__programs.set(id, program); + } + + if (program != null) + { + @:privateAccess glProgram = program.__glProgram; + + for (input in __inputBitmapData) + { + @:privateAccess + if (input.__isUniform) + { + @:privateAccess input.index = gl.getUniformLocation(glProgram, input.name); + } + else + { + @:privateAccess input.index = gl.getAttribLocation(glProgram, input.name); + } + } + + for (parameter in __paramBool) + { + @:privateAccess + if (parameter.__isUniform) + { + @:privateAccess parameter.index = gl.getUniformLocation(glProgram, parameter.name); + } + else + { + @:privateAccess parameter.index = gl.getAttribLocation(glProgram, parameter.name); + } + } + + for (parameter in __paramFloat) + { + @:privateAccess + if (parameter.__isUniform) + { + @:privateAccess parameter.index = gl.getUniformLocation(glProgram, parameter.name); + } + else + { + @:privateAccess parameter.index = gl.getAttribLocation(glProgram, parameter.name); + } + } + + for (parameter in __paramInt) + { + @:privateAccess + if (parameter.__isUniform) + { + @:privateAccess + parameter.index = gl.getUniformLocation(glProgram, parameter.name); + } + else + { + @:privateAccess + parameter.index = gl.getAttribLocation(glProgram, parameter.name); + } + } + } + } + } + + + @:noCompletion private override function __processGLData(source:String, storageType:String):Void + { + var lastMatch = 0, position, regex, name, type; + + if (storageType == "uniform") + { + regex = ~/uniform ([A-Za-z0-9]+) ([A-Za-z0-9_]+)/; + } + else + { + regex = ~/attribute ([A-Za-z0-9]+) ([A-Za-z0-9_]+)/; + } + + while (regex.matchSub(source, lastMatch)) + { + type = regex.matched(1); + name = regex.matched(2); + + if (StringTools.startsWith(name, "gl_")) + { + continue; + } + + var isUniform = (storageType == "uniform"); + + if (StringTools.startsWith(type, "sampler")) + { + var input = new ShaderInput(); + input.name = name; + @:privateAccess + input.__isUniform = isUniform; + __inputBitmapData.push(input); + + switch (name) + { + case "openfl_Texture": + __texture = input; + case "bitmap": + __bitmap = input; + default: + } + + Reflect.setField(__data, name, input); + try{Reflect.setField(this, name, input);} catch(e) {} + } + else if (!Reflect.hasField(__data, name) || Reflect.field(__data, name) == null) + { + var parameterType:ShaderParameterType = switch (type) + { + case "bool": BOOL; + case "double", "float": FLOAT; + case "int", "uint": INT; + case "bvec2": BOOL2; + case "bvec3": BOOL3; + case "bvec4": BOOL4; + case "ivec2", "uvec2": INT2; + case "ivec3", "uvec3": INT3; + case "ivec4", "uvec4": INT4; + case "vec2", "dvec2": FLOAT2; + case "vec3", "dvec3": FLOAT3; + case "vec4", "dvec4": FLOAT4; + case "mat2", "mat2x2": MATRIX2X2; + case "mat2x3": MATRIX2X3; + case "mat2x4": MATRIX2X4; + case "mat3x2": MATRIX3X2; + case "mat3", "mat3x3": MATRIX3X3; + case "mat3x4": MATRIX3X4; + case "mat4x2": MATRIX4X2; + case "mat4x3": MATRIX4X3; + case "mat4", "mat4x4": MATRIX4X4; + default: null; + } + + var length = switch (parameterType) + { + case BOOL2, INT2, FLOAT2: 2; + case BOOL3, INT3, FLOAT3: 3; + case BOOL4, INT4, FLOAT4, MATRIX2X2: 4; + case MATRIX3X3: 9; + case MATRIX4X4: 16; + default: 1; + } + + var arrayLength = switch (parameterType) + { + case MATRIX2X2: 2; + case MATRIX3X3: 3; + case MATRIX4X4: 4; + default: 1; + } + + switch (parameterType) + { + case BOOL, BOOL2, BOOL3, BOOL4: + var parameter = new ShaderParameter(); + parameter.name = name; + @:privateAccess + parameter.type = parameterType; + @:privateAccess + parameter.__arrayLength = arrayLength; + @:privateAccess + parameter.__isBool = true; + @:privateAccess + parameter.__isUniform = isUniform; + @:privateAccess + parameter.__length = length; + __paramBool.push(parameter); + + if (name == "openfl_HasColorTransform") + { + __hasColorTransform = parameter; + } + + Reflect.setField(__data, name, parameter); + try{Reflect.setField(this, name, parameter);} catch(e) {} + + case INT, INT2, INT3, INT4: + var parameter = new ShaderParameter(); + parameter.name = name; + @:privateAccess + parameter.type = parameterType; + @:privateAccess + parameter.__arrayLength = arrayLength; + @:privateAccess + parameter.__isInt = true; + @:privateAccess + parameter.__isUniform = isUniform; + @:privateAccess + parameter.__length = length; + @:privateAccess + __paramInt.push(parameter); + Reflect.setField(__data, name, parameter); + try{Reflect.setField(this, name, parameter);} catch(e) {} + + default: + var parameter = new ShaderParameter(); + parameter.name = name; + @:privateAccess + parameter.type = parameterType; + @:privateAccess + parameter.__arrayLength = arrayLength; + #if lime + @:privateAccess + if (arrayLength > 0) parameter.__uniformMatrix = new Float32Array(arrayLength * arrayLength); + #end + @:privateAccess + parameter.__isFloat = true; + @:privateAccess + parameter.__isUniform = isUniform; + @:privateAccess + parameter.__length = length; + __paramFloat.push(parameter); + + if (StringTools.startsWith(name, "openfl_")) + { + switch (name) + { + case "openfl_Alpha": __alpha = parameter; + case "openfl_ColorMultiplier": __colorMultiplier = parameter; + case "openfl_ColorOffset": __colorOffset = parameter; + case "openfl_Matrix": __matrix = parameter; + case "openfl_Position": __position = parameter; + case "openfl_TextureCoord": __textureCoord = parameter; + case "openfl_TextureSize": __textureSize = parameter; + default: + } + } + + Reflect.setField(__data, name, parameter); + try{Reflect.setField(this, name, parameter);} catch(e) {} + } + } + + position = regex.matchedPos(); + lastMatch = position.pos + position.len; + } + } +} + +class ShaderTemplates { + public static final fragHeader:String = "varying float openfl_Alphav; + varying vec4 openfl_ColorMultiplierv; + varying vec4 openfl_ColorOffsetv; + varying vec2 openfl_TextureCoordv; + + uniform bool openfl_HasColorTransform; + uniform vec2 openfl_TextureSize; + uniform sampler2D bitmap; + + uniform bool hasTransform; + uniform bool hasColorTransform; + + vec4 flixel_texture2D(sampler2D bitmap, vec2 coord) + { + vec4 color = texture2D(bitmap, coord); + if (!hasTransform) + { + return color; + } + + if (color.a == 0.0) + { + return vec4(0.0, 0.0, 0.0, 0.0); + } + + if (!hasColorTransform) + { + return color * openfl_Alphav; + } + + color = vec4(color.rgb / color.a, color.a); + + mat4 colorMultiplier = mat4(0); + colorMultiplier[0][0] = openfl_ColorMultiplierv.x; + colorMultiplier[1][1] = openfl_ColorMultiplierv.y; + colorMultiplier[2][2] = openfl_ColorMultiplierv.z; + colorMultiplier[3][3] = openfl_ColorMultiplierv.w; + + color = clamp(openfl_ColorOffsetv + (color * colorMultiplier), 0.0, 1.0); + + if (color.a > 0.0) + { + return vec4(color.rgb * color.a * openfl_Alphav, color.a * openfl_Alphav); + } + return vec4(0.0, 0.0, 0.0, 0.0); + } + + uniform vec4 _camSize; + + vec2 getCamPos(vec2 pos) { + return (pos * openfl_TextureSize / vec2(_camSize.z, _camSize.w)) + vec2(_camSize.x / _camSize.z, _camSize.y / _camSize.z); + } + vec2 camToOg(vec2 pos) { + return ((pos - vec2(_camSize.x / _camSize.z, _camSize.y / _camSize.z)) * vec2(_camSize.z, _camSize.w) / openfl_TextureSize); + } + vec4 textureCam(sampler2D bitmap, vec2 pos) { + return texture2D(bitmap, camToOg(pos)); + }"; + + public static final fragBody:String = "vec4 color = texture2D (bitmap, openfl_TextureCoordv); + + if (color.a == 0.0) { + + gl_FragColor = vec4 (0.0, 0.0, 0.0, 0.0); + + } else if (openfl_HasColorTransform) { + + color = vec4 (color.rgb / color.a, color.a); + + mat4 colorMultiplier = mat4 (0); + colorMultiplier[0][0] = openfl_ColorMultiplierv.x; + colorMultiplier[1][1] = openfl_ColorMultiplierv.y; + colorMultiplier[2][2] = openfl_ColorMultiplierv.z; + colorMultiplier[3][3] = 1.0; // openfl_ColorMultiplierv.w; + + color = clamp (openfl_ColorOffsetv + (color * colorMultiplier), 0.0, 1.0); + + if (color.a > 0.0) { + + gl_FragColor = vec4 (color.rgb * color.a * openfl_Alphav, color.a * openfl_Alphav); + + } else { + + gl_FragColor = vec4 (0.0, 0.0, 0.0, 0.0); + + } + + } else { + + gl_FragColor = color * openfl_Alphav; + + }"; + public static final vertHeader:String = "attribute float openfl_Alpha; + attribute vec4 openfl_ColorMultiplier; + attribute vec4 openfl_ColorOffset; + attribute vec4 openfl_Position; + attribute vec2 openfl_TextureCoord; + + varying float openfl_Alphav; + varying vec4 openfl_ColorMultiplierv; + varying vec4 openfl_ColorOffsetv; + varying vec2 openfl_TextureCoordv; + + uniform mat4 openfl_Matrix; + uniform bool openfl_HasColorTransform; + uniform vec2 openfl_TextureSize;"; + + public static final vertBody:String = "openfl_Alphav = openfl_Alpha; + openfl_TextureCoordv = openfl_TextureCoord; + + if (openfl_HasColorTransform) { + + openfl_ColorMultiplierv = openfl_ColorMultiplier; + openfl_ColorOffsetv = openfl_ColorOffset / 255.0; + + } + + gl_Position = openfl_Matrix * openfl_Position;"; + + + public static final defaultVertexSource:String = "#pragma header + + attribute float alpha; + attribute vec4 colorMultiplier; + attribute vec4 colorOffset; + uniform bool hasColorTransform; + + void main(void) + { + #pragma body + + openfl_Alphav = openfl_Alpha * alpha; + + if (hasColorTransform) + { + openfl_ColorOffsetv = colorOffset / 255.0; + openfl_ColorMultiplierv = colorMultiplier; + } + }"; + + + // TODO: camera stuff + public static final defaultFragmentSource:String = "#pragma header + + void main(void) + { + gl_FragColor = flixel_texture2D(bitmap, openfl_TextureCoordv); + }"; +} \ No newline at end of file