diff --git a/assets/images/burns.png b/assets/images/burns.png new file mode 100644 index 00000000..bb20b8e6 Binary files /dev/null and b/assets/images/burns.png differ diff --git a/buildnumber.txt b/buildnumber.txt index 397787a8..972fd778 100644 --- a/buildnumber.txt +++ b/buildnumber.txt @@ -1 +1 @@ -949 \ No newline at end of file +958 \ No newline at end of file diff --git a/source/funkin/scripting/Script.hx b/source/funkin/scripting/Script.hx index 5a60855b..4d9c4323 100644 --- a/source/funkin/scripting/Script.hx +++ b/source/funkin/scripting/Script.hx @@ -61,6 +61,7 @@ class Script extends FlxBasic implements IFlxDestroyable { "Conductor" => funkin.system.Conductor, "CoolUtil" => funkin.system.CoolUtil, "FunkinShader" => funkin.shaders.FunkinShader, + "CustomShader" => funkin.shaders.CustomShader, ]; } /** diff --git a/source/funkin/shaders/CustomShader.hx b/source/funkin/shaders/CustomShader.hx index 831ec468..514ad281 100644 --- a/source/funkin/shaders/CustomShader.hx +++ b/source/funkin/shaders/CustomShader.hx @@ -1,5 +1,126 @@ package funkin.shaders; -class CustomShader extends FunkinShader { - +import haxe.Exception; +import openfl.display.ShaderParameter; +import openfl.display.BitmapData; +import openfl.display.ShaderInput; +import openfl.Assets; +import hscript.IHScriptCustomBehaviour; + +/** + * Class for custom shaders. + * + * To create one, create a `shaders` folder in your assets/mod folder, then add a file named `my-shader.frag` or/and `my-shader.vert`. + * + * Non-existent shaders will only load the default one, and throw a warning in the console. + * + * To access the shader's uniform variables, use `shader.variable` + */ +class CustomShader extends FunkinShader implements IHScriptCustomBehaviour { + private static var __instanceFields = Type.getInstanceFields(CustomShader); + /** + * Creates a new custom shader + * @param name Name of the frag and vert files. + * @param glslVersion GLSL version to use. Defaults to `120`. + */ + public function new(name:String, glslVersion:String = "120") { + var fragShaderPath = Paths.fragShader(name); + var vertShaderPath = Paths.vertShader(name); + var fragCode = Assets.exists(fragShaderPath) ? Assets.getText(fragShaderPath) : null; + var vertCode = Assets.exists(vertShaderPath) ? Assets.getText(vertShaderPath) : null; + + if (fragCode != null && vertCode != null) { + // TODO: logging!! + } + + super(fragCode, vertCode, glslVersion); + } + + public function hget(v:String):Dynamic { + if (__instanceFields.contains(v) || __instanceFields.contains('get_${v}')) { + return Reflect.getProperty(this, v); + } + if (!Reflect.hasField(data, v)) return null; + var field = Reflect.field(data, v); + if (field is ShaderInput) { + var si = cast(field, ShaderInput); + return si.input; + } else if (field is ShaderParameter) { + var sp = cast(field, ShaderParameter); + switch(sp.type) { + case BOOL, FLOAT, INT: + return sp.value[0]; + default: + return sp.value; + } + } + return field; + } + + public function hset(v:String, val:Dynamic):Dynamic { + if (__instanceFields.contains(v) || __instanceFields.contains('set_${v}')) { + Reflect.setProperty(this, v, val); + return val; + } + + if (!Reflect.hasField(data, v)) { + Reflect.setField(data, v, val); + return val; + } else { + var field = Reflect.field(data, v); + var cl = Std.string(Type.getClass(field)); + + // cant do "field is ShaderInput" for some reason + if (cl.startsWith("openfl.display.ShaderInput")) { + // shader input!! + if (!(val is BitmapData)) { + throw new ShaderTypeException(v, Type.getClass(val), BitmapData); + return null; + } + field.input = cast val; + } else if (cl.startsWith("openfl.display.ShaderParameter")) { + @:privateAccess + if (field.__arrayLength <= 1) { + // that means we wait for a single number, instead of an array + @:privateAccess + if (field.__isInt && !(val is Int)) { + throw new ShaderTypeException(v, Type.getClass(val), 'Int'); + return null; + } else + @:privateAccess + if (field.__isBool && !(val is Bool)) { + throw new ShaderTypeException(v, Type.getClass(val), 'Bool'); + return null; + } else + @:privateAccess + if (field.__isFloat && !(val is Float)) { + throw new ShaderTypeException(v, Type.getClass(val), 'Float'); + return null; + } + return field.value = [val]; + } else { + if (!(val is Array)) { + throw new ShaderTypeException(v, Type.getClass(val), Array); + return null; + } + return field.value = val; + } + } + } + + return val; + } +} + +class ShaderTypeException extends Exception { + var has:Class; + var want:Class; + var name:String; + + public function new(name:String, has:Class, want:Dynamic) { + this.has = has; + this.want = want; + this.name = name; + super('ShaderTypeException - Tried to set the shader uniform "${name}" as a ${Type.getClassName(has)}, but the shader uniform is a ${Std.string(want)}.'); + } } \ No newline at end of file diff --git a/source/funkin/system/Paths.hx b/source/funkin/system/Paths.hx index 52747922..082f4e35 100644 --- a/source/funkin/system/Paths.hx +++ b/source/funkin/system/Paths.hx @@ -60,6 +60,16 @@ class Paths return getPath('data/$key.txt', TEXT, library); } + inline static public function fragShader(key:String, ?library:String) + { + return getPath('shaders/$key.frag', TEXT, library); + } + + inline static public function vertShader(key:String, ?library:String) + { + return getPath('shaders/$key.vert', TEXT, library); + } + inline static public function xml(key:String, ?library:String) { return getPath('data/$key.xml', TEXT, library);