diff --git a/source/HealthIcon.hx b/source/HealthIcon.hx index b0d6554..c72ed47 100644 --- a/source/HealthIcon.hx +++ b/source/HealthIcon.hx @@ -43,15 +43,26 @@ class HealthIcon extends FlxSprite var file:Dynamic = Paths.image(name); loadGraphic(file); //Load stupidly first for getting the file size - loadGraphic(file, true, Math.floor(width / 3), Math.floor(height)); //Then load it fr - iconOffsets[0] = (width - 150) / 3; - iconOffsets[1] = (width - 150) / 3; - iconOffsets[2] = (width - 150) / 3; - updateHitbox(); - - animation.add(char, [0, 1, 2], 0, false, isPlayer); - animation.play(char); - this.char = char; + if(width == 450){ // goofy ahh codesing + loadGraphic(file, true, Math.floor(width / 3), Math.floor(height)); //Then load it fr + iconOffsets[0] = (width - 150) / 3; + iconOffsets[1] = (width - 150) / 3; + updateHitbox(); + animation.add(char, [0, 1, 2], 0, false, isPlayer); + animation.play(char); + this.char = char; + + } + else if(width == 300){ + loadGraphic(file, true, Math.floor(width / 2), Math.floor(height)); //Then load it fr + iconOffsets[0] = (width - 150) / 2; + iconOffsets[1] = (width - 150) / 2; + updateHitbox(); + animation.add(char, [0, 1], 0, false, isPlayer); + animation.play(char); + this.char = char; + + } antialiasing = ClientPrefs.globalAntialiasing; if(char.endsWith('-pixel')) { diff --git a/source/Main.hx b/source/Main.hx index 01128d3..12e7118 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -11,6 +11,19 @@ import openfl.display.Sprite; import openfl.events.Event; import openfl.display.StageScaleMode; + +import lime.app.Application; +import openfl.events.UncaughtErrorEvent; +import haxe.CallStack; +import haxe.io.Path; +import sys.FileSystem; +import sys.io.File; +import sys.io.Process; +import Discord.DiscordClient; + +using StringTools; + + class Main extends Sprite { var gameWidth:Int = 1280; // Width of the game in pixels (might be less / more in actual pixels depending on your zoom). @@ -76,7 +89,7 @@ class Main extends Sprite // fuck you, persistent caching stays ON during sex FlxGraphic.defaultPersist = true; // the reason for this is we're going to be handling our own cache smartly - addChild(new FlxGame(gameWidth, gameHeight, initialState, zoom, framerate, framerate, skipSplash, startFullscreen)); + addChild(new FlxGame(gameWidth, gameHeight, initialState #if(flixel < "5.0.0"), zoom #end, framerate, framerate, skipSplash, startFullscreen)); // lol #if !mobile fpsVar = new FPS(10, 3, 0xFFFFFF); @@ -92,5 +105,45 @@ class Main extends Sprite FlxG.autoPause = false; FlxG.mouse.visible = false; #end + + Lib.current.loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, onCrash); } + + function onCrash(e:UncaughtErrorEvent):Void // putting shiz in, smh + { + var errMsg:String = ""; + var path:String; + var callStack:Array = CallStack.exceptionStack(true); + var dateNow:String = Date.now().toString(); + + dateNow = dateNow.replace(" ", "_"); + dateNow = dateNow.replace(":", "'"); + + path = "./crash/" + "PsychEngine_" + dateNow + ".txt"; + + for (stackItem in callStack) + { + switch (stackItem) + { + case FilePos(s, file, line, column): + errMsg += file + " (line " + line + ")\n"; + default: + Sys.println(stackItem); + } + } + + errMsg += "\nUncaught Error: " + e.error + "\nPlease report this error to the GitHub page: https://github.com/ShadowMario/FNF-PsychEngine\n\n> Crash Handler written by: sqirra-rng"; + + if (!FileSystem.exists("./crash/")) + FileSystem.createDirectory("./crash/"); + + File.saveContent(path, errMsg + "\n"); + + Sys.println(errMsg); + Sys.println("Crash dump saved in " + Path.normalize(path)); + + Application.current.window.alert(errMsg, "Error!"); + DiscordClient.shutdown(); + Sys.exit(1); + } } diff --git a/source/MainMenuState.hx b/source/MainMenuState.hx index c0d1976..f80375e 100644 --- a/source/MainMenuState.hx +++ b/source/MainMenuState.hx @@ -110,7 +110,7 @@ class MainMenuState extends MusicBeatState add(leGradientBar); leGradientBar.scrollFactor.set(0, 0); - var bgScroll:FlxBackdrop = new FlxBackdrop(Paths.image('cubicbg'), 5, 5, true, true); + var bgScroll:FlxBackdrop = new FlxBackdrop(Paths.image('cubicbg'), #if (flixel < "5.0.0") 5, 5, true, true,#else XY,#end 1,1); // flixel compatibility sucks bgScroll.scrollFactor.set(); bgScroll.screenCenter(); bgScroll.velocity.set(50, 50); diff --git a/source/PlayState.hx b/source/PlayState.hx index c05c34d..fe64b51 100644 --- a/source/PlayState.hx +++ b/source/PlayState.hx @@ -1,6 +1,7 @@ // coded by Aadiyan2 and AverageDaveLover69 // mfs i had to change it -redstoneSC // fixed wavy modchart by Aadiyan2 +// Gunna add new icon bounce n shit -JSCD package; import flixel.graphics.FlxGraphic; @@ -89,16 +90,16 @@ class PlayState extends MusicBeatState public static var ratingStuff:Array = [ - ['You Suck!', 0.2], //From 0% to 19% - ['Shit', 0.4], //From 20% to 39% - ['Bad', 0.5], //From 40% to 49% - ['Bruh', 0.6], //From 50% to 59% - ['Meh', 0.69], //From 60% to 68% - ['Nice', 0.7], //69% - ['Good', 0.8], //From 70% to 79% - ['Great', 0.9], //From 80% to 89% - ['Sick!', 1], //From 90% to 99% - ['Perfect!!', 1] //The value on this one isn't used actually, since Perfect is always "1" + ['D', 0.2], //From 0% to 19% + ['C', 0.4], //From 20% to 39% + ['B', 0.5], //From 40% to 49% + ['A', 0.6], //From 50% to 59% + ['A.', 0.69], //From 60% to 68% + ['A:', 0.7], //69% + ['AA.', 0.8], //From 70% to 79% + ['AA:', 0.9], //From 80% to 89% + ['AAA', 1], //From 90% to 99% + ['S', 1] //The value on this one isn't used actually, since Perfect is always "1" ]; public var modchartTweens:Map = new Map(); public var modchartSprites:Map = new Map(); @@ -129,6 +130,8 @@ class PlayState extends MusicBeatState public var songSpeed(default, set):Float = 1; public var songSpeedType:String = "multiplicative"; public var noteKillOffset:Float = 350; + + var offsettest:Float = 0; public var boyfriendGroup:FlxSpriteGroup; public var dadGroup:FlxSpriteGroup; @@ -293,7 +296,12 @@ class PlayState extends MusicBeatState public static var seenCutscene:Bool = false; public static var deathCounter:Int = 0; - public var defaultCamZoom:Float = 1.05; + var notePressArray:Array = []; + var nps:Int; + var maxnps:Int; + + + public var defaultCamZoom:Float = 1.25; // how big to stretch the pixel art assets public static var daPixelZoom:Float = 6; @@ -302,7 +310,6 @@ class PlayState extends MusicBeatState public var inCutscene:Bool = false; public var skipCountdown:Bool = false; var songLength:Float = 0; - public var boyfriendCameraOffset:Array = null; public var opponentCameraOffset:Array = null; public var girlfriendCameraOffset:Array = null; @@ -1291,7 +1298,7 @@ class PlayState extends MusicBeatState infoThingy.screenCenter(Y); infoThingy.y += 100;*/ - moarAdvancedUIIII = new FlxText(-10 * 2, healthBarBG.y - (20 * 4), FlxG.width, "", 20); + moarAdvancedUIIII = new FlxText(-10 * 2, healthBarBG.y - (30 * 4), FlxG.width, "", 20); //if(!ClientPrefs.DISABLETHEFUCKINGFONTAAAAAAAAAA) { moarAdvancedUIIII.setFormat(Paths.font("vcr.ttf"), 26, FlxColor.WHITE, RIGHT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); if(PlayState.isPixelStage) { @@ -2536,6 +2543,23 @@ class PlayState extends MusicBeatState override public function update(elapsed:Float) { + var goof = notePressArray.length-1; + while (goof >= 0){ + var shit:Date = notePressArray[goof]; + if(shit != null && shit.getTime() + 1000 < Date.now().getTime()){// uh oh, this will cause to crash + notePressArray.remove(shit); + } + else{ + goof = 0; + } + goof--; + } + nps = notePressArray.length; + + if(nps > maxnps){ + maxnps = nps; + } + elapsedtime += elapsed; if (FlxG.keys.justPressed.NINE) { @@ -2765,11 +2789,8 @@ class PlayState extends MusicBeatState super.update(elapsed); - if(ratingName == '?') { - scoreTxt.text = 'Score: ' + songScore + ' | Misses: ' + songMisses + ' | Rating: ' + ratingName; - } else { - scoreTxt.text = 'Score: ' + songScore + ' | Misses: ' + songMisses + ' | Rating: ' + ratingName + ' (' + Highscore.floorDecimal(ratingPercent * 100, 2) + '%)' + ' - ' + ratingFC;//peeps wanted no integer rating - } + scoreTxt.text = 'NPS: ${nps}(Max ${maxnps}) |' + ' Score: ' + songScore + ' | Combo Breaks: ' + songMisses + " | Accuracy: " + Highscore.floorDecimal(ratingPercent * 100, 2) + '% | ' + nameOfShit;//peeps wanted no integer rating + /*if (cpuControlled) { scoreTxt.text = ' ' + scoreTxtCPURandomizer; }*/ //nope nope nope nope @@ -2778,7 +2799,7 @@ class PlayState extends MusicBeatState } if (ClientPrefs.advancedUI) { - moarAdvancedUIIII.text = "Rating: " + ratingName + "\nGained Score: " + songScore + ' (' + Highscore.floorDecimal(ratingPercent * 100, 2) + '%)' + ' ' + ratingFC + "\nMisses: " + songMisses + "\nTotal Notes Hit: " + totalNotesHit; + moarAdvancedUIIII.text ='NPS: ${nps}(Max ${maxnps})' + "\nAccuracy: " + Highscore.floorDecimal(ratingPercent * 100, 2) + ' | ' + nameOfShit + "\nCombo Breaks: " + songMisses + "\nScore: " + songScore + "\nTotal Notes Hit: " + totalNotesHit; //songInfoText.text = "Info: " + PlayState.SONG.infothingg; } @@ -2838,8 +2859,8 @@ class PlayState extends MusicBeatState var iconOffset:Int = 26; - iconP1.x = (opponentChart ? -593 : 0) + healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.percent, 0, (opponentChart ? -100 : 100), 100, 0) * 0.01) - iconOffset); - iconP2.x = (opponentChart ? -593 : 0) + healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.percent, 0, (opponentChart ? -100 : 100), 100, 0) * 0.01)) - (iconP2.width - iconOffset); + iconP1.x =(opponentChart ? -593 : 0) + healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.percent, 0, (opponentChart ? -100 : 100), 100, 0) * 0.01)) + (150 * iconP1.scale.x - 150) / 2 - iconOffset; + iconP2.x = (opponentChart ? -593 : 0) + healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.percent, 0, (opponentChart ? -100 : 100), 100, 0) * 0.01)) - (150 * iconP2.scale.x) / 2 - iconOffset * 2; // bro what the f if (health > 2) health = 2; @@ -2898,13 +2919,13 @@ class PlayState extends MusicBeatState songPercent = (curTime / songLength); var songCalc:Float = (songLength - curTime); - if(ClientPrefs.timeBarType == 'Time Elapsed') songCalc = curTime; + songCalc = curTime; // bruh var secondsTotal:Int = Math.floor(songCalc / 1000); if(secondsTotal < 0) secondsTotal = 0; if(ClientPrefs.timeBarType != 'Song Name') - timeTxt.text = FlxStringUtil.formatTime(secondsTotal, false); + timeTxt.text = FlxStringUtil.formatTime(secondsTotal, false) + '/' + FlxStringUtil.formatTime(Math.floor(songLength/1000), false); } } @@ -3854,6 +3875,9 @@ class PlayState extends MusicBeatState public var timeShown = 0; public var currentTimingShown:FlxText = null; + public static var lastRating:FlxSprite; + var offsettesting:Bool = false; + private function popUpScore(?note:Note, ?optionalRating:Float):Void { var noteDiff:Float = Math.abs(note.strumTime - Conductor.songPosition + ClientPrefs.ratingOffset); @@ -3927,8 +3951,8 @@ class PlayState extends MusicBeatState if(scoreTxtTween != null) { scoreTxtTween.cancel(); } - scoreTxt.scale.x = 1.075; - scoreTxt.scale.y = 1.075; + scoreTxt.scale.x = 1.025; + scoreTxt.scale.y = 1.025; scoreTxtTween = FlxTween.tween(scoreTxt.scale, {x: 1, y: 1}, 0.2, { onComplete: function(twn:FlxTween) { scoreTxtTween = null; @@ -3958,15 +3982,11 @@ class PlayState extends MusicBeatState rating.cameras = [camHUD]; rating.screenCenter(); rating.x = coolText.x - 40; - rating.y -= 60; - rating.acceleration.y = 550; - rating.velocity.y -= FlxG.random.int(140, 175); - rating.velocity.x -= FlxG.random.int(0, 10); rating.visible = (!ClientPrefs.hideHud && showRating); rating.x += ClientPrefs.comboOffset[0]; rating.y -= ClientPrefs.comboOffset[1]; - var msTiming = HelperFunctions.truncateFloat(noteDiff, 3); + var msTiming = HelperFunctions.truncateFloat(noteDiff, 2); if(cpuControlled) msTiming = 0; if (currentTimingShown != null) @@ -3977,12 +3997,13 @@ class PlayState extends MusicBeatState currentTimingShown.borderStyle = OUTLINE; currentTimingShown.borderSize = 2; + currentTimingShown.font = Paths.font('funkfridafont.ttf'); // what to say about it currentTimingShown.borderColor = FlxColor.BLACK; currentTimingShown.text = msTiming + "ms"; currentTimingShown.size = 20; currentTimingShown.visible = !ClientPrefs.hideHud; - if (msTiming >= 0.03) + if (msTiming >= 0.03 && offsettesting) { //Remove Outliers hits.shift(); @@ -3997,35 +4018,49 @@ class PlayState extends MusicBeatState for(i in hits) total += i; + offsettest = HelperFunctions.truncateFloat(total/hits.length,2); // sorry red + } + + switch(daRating){ + case 'sick': + currentTimingShown.color = FlxColor.CYAN; + case 'good': + currentTimingShown.color = FlxColor.LIME; + case 'bad' | 'shit': + currentTimingShown.color = FlxColor.RED; } if (currentTimingShown.alpha != 1) currentTimingShown.alpha = 1; - insert(members.indexOf(strumLineNotes), currentTimingShown); + // insert(members.indexOf(strumLineNotes), currentTimingShown); // no. var comboSpr:FlxSprite = new FlxSprite().loadGraphic(Paths.image(pixelShitPart1 + 'combo' + pixelShitPart2)); comboSpr.cameras = [camHUD]; comboSpr.screenCenter(); comboSpr.x = coolText.x; - comboSpr.acceleration.y = 600; - comboSpr.velocity.y -= 150; comboSpr.visible = (!ClientPrefs.hideHud && showCombo); comboSpr.x += ClientPrefs.comboOffset[0]; comboSpr.y -= ClientPrefs.comboOffset[1]; + currentTimingShown.screenCenter(); - currentTimingShown.acceleration.y = 600; - currentTimingShown.velocity.y -= 150; currentTimingShown.cameras = [camHUD]; - currentTimingShown.velocity.x += comboSpr.velocity.x; currentTimingShown.updateHitbox(); - currentTimingShown.x += ClientPrefs.comboOffset[0] + 560; - currentTimingShown.y += ClientPrefs.comboOffset[1] + 180; + currentTimingShown.acceleration.y = 600; + currentTimingShown.velocity.x -= 150; + currentTimingShown.x = comboSpr.x + 100; + currentTimingShown.y = rating.y + 180; // m + add(currentTimingShown); // :) comboSpr.velocity.x += FlxG.random.int(1, 10); insert(members.indexOf(strumLineNotes), rating); + if(lastRating != null){ + lastRating.kill(); + } + lastRating = rating; + if (!PlayState.isPixelStage) { rating.setGraphicSize(Std.int(rating.width * 0.7)); @@ -4100,15 +4135,26 @@ class PlayState extends MusicBeatState coolText.text = Std.string(seperatedScore); // add(coolText); - FlxTween.tween(rating, {alpha: 0}, 0.2, { - startDelay: Conductor.crochet * 0.001 - }); + FlxTween.tween(rating, {y: rating.y - 20}, 0.2, {type:BACKWARD, ease:FlxEase.circOut}); // removed stacking :hi: + FlxTween.tween(rating, {'scale.x': 0, 'scale.y': 0}, 0.2, { + startDelay: Conductor.crochet * 0.00125 + , onUpdate:function(tween:FlxTween){ + if(currentTimingShown != null){ + FlxTween.tween(currentTimingShown, {alpha:0}, 0.2); + } + timeShown++; + }}); // im sorry red FlxTween.tween(comboSpr, {alpha: 0}, 0.2, { onComplete: function(tween:FlxTween) { coolText.destroy(); comboSpr.destroy(); + if(currentTimingShown != null && timeShown >= 20){ + remove(currentTimingShown); + currentTimingShown = null; + } + timeShown++; rating.destroy(); }, @@ -4470,6 +4516,10 @@ class PlayState extends MusicBeatState camZooming = true; } + if(!note.isSustainNote){ + notePressArray.unshift(Date.now()); // bruj + } + if (!note.wasGoodHit) { if (ClientPrefs.hitsoundVolume > 0 && !note.hitsoundDisabled) @@ -4884,6 +4934,29 @@ class PlayState extends MusicBeatState iconP1.setGraphicSize(Std.int(iconP1.width + (50 * (2 - funny))),Std.int(iconP1.height - (25 * (2 - funny)))); iconP2.setGraphicSize(Std.int(iconP2.width + (50 * (2 - funny))),Std.int(iconP2.height - (25 * (2 - funny)))); + if(curBeat % gfSpeed == 0){ // goofy code for the bounce + if(curBeat % (gfSpeed * 4) == 0){ + if(curBeat % (gfSpeed * 2) == 0){ + FlxTween.angle(iconP1, -20, 0, Conductor.crochet / 1300 * gfSpeed, {ease: FlxEase.quadOut}); + FlxTween.angle(iconP2, 20, 0, Conductor.crochet / 1300 * gfSpeed, {ease: FlxEase.quadOut}); + } + else{ + iconP1.angle = 0; + iconP1.angle = 0; + } + } + else{ + if(curBeat % (gfSpeed * 2) == 0){ + FlxTween.angle(iconP1, 20, 0, Conductor.crochet / 1300 * gfSpeed, {ease: FlxEase.quadOut}); + FlxTween.angle(iconP2, -20, 0, Conductor.crochet / 1300 * gfSpeed, {ease: FlxEase.quadOut}); + } + else{ + iconP1.angle = 0; + iconP1.angle = 0; + } + } + } + iconP1.updateHitbox(); iconP2.updateHitbox(); @@ -5013,11 +5086,14 @@ class PlayState extends MusicBeatState public var ratingName:String = '?'; public var ratingPercent:Float; public var ratingFC:String; + public var nameOfShit:String = 'N/A'; public function RecalculateRating() { setOnLuas('score', songScore); setOnLuas('misses', songMisses); setOnLuas('hits', songHits); + + var ret:Dynamic = callOnLuas('onRecalculateRating', []); if(ret != FunkinLua.Function_Stop) { @@ -5046,14 +5122,20 @@ class PlayState extends MusicBeatState } } } - // Rating FC ratingFC = ""; - if (sicks > 0) ratingFC = "SFC"; + if (sicks > 0) ratingFC = "MFC"; if (goods > 0) ratingFC = "GFC"; if (bads > 0 || shits > 0) ratingFC = "FC"; if (songMisses > 0 && songMisses < 10) ratingFC = "SDCB"; else if (songMisses >= 10) ratingFC = "Clear"; + + if(ratingPercent == 0){ + nameOfShit = 'N/A'; + } + else{ + nameOfShit = "(" + ratingFC + ") " + ratingName; + } } setOnLuas('rating', ratingPercent); setOnLuas('ratingName', ratingName); diff --git a/source/flixel/system/FlxRuntimeShader.hx b/source/flixel/system/FlxRuntimeShader.hx new file mode 100644 index 0000000..87032fc --- /dev/null +++ b/source/flixel/system/FlxRuntimeShader.hx @@ -0,0 +1,728 @@ +package flixel.system; + +import flixel.system.FlxAssets.FlxShader; +import lime.utils.Float32Array; +import openfl.display.BitmapData; +import openfl.display.ShaderInput; +import openfl.display.ShaderParameter; +import openfl.display.ShaderParameterType; + +/** + * An wrapper for Flixel/OpenFL's shaders, which takes fragment and vertex source + * in the constructor instead of using macros, so it can be provided data + * at runtime (for example, when using mods). + * + * HOW TO USE: + * 1. Create an instance of this class, passing the text of the `.frag` and `.vert` files. + * Note that you can set either of these to null (making them both null would make the shader do nothing???). + * 2. Use `flxSprite.shader = runtimeShader` to apply the shader to the sprite. + * 3. Use `runtimeShader.setFloat()`, `setBool()`, etc. to modify any uniforms. + * + * @author MasterEric + * @see https://github.com/openfl/openfl/blob/develop/src/openfl/utils/_internal/ShaderMacro.hx + * @see https://dixonary.co.uk/blog/shadertoy + */ +class FlxRuntimeShader extends FlxShader +{ + #if FLX_DRAW_QUADS + // We need to add stuff from FlxGraphicsShader too! + #else + // Only stuff from openfl.display.Shader is needed + #end + // These variables got copied from openfl.display.GraphicsShader + // and from flixel.graphics.tile.FlxGraphicsShader, + // and probably won't change ever. + static final BASE_VERTEX_HEADER:String = " + #pragma version + + #pragma precision + + 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; + "; + static final BASE_VERTEX_BODY: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; + "; + + static final BASE_FRAGMENT_HEADER:String = " + #pragma version + + #pragma precision + + 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; + " + + #if FLX_DRAW_QUADS + // Add on more stuff! + + " + 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); + } + "; + #else + // No additional data. + ; + #end + static final BASE_FRAGMENT_BODY: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; + } + "; + + #if FLX_DRAW_QUADS + static final DEFAULT_FRAGMENT_SOURCE:String = " + #pragma header + + void main(void) + { + gl_FragColor = flixel_texture2D(bitmap, openfl_TextureCoordv); + } + "; + #else + static final DEFAULT_FRAGMENT_SOURCE:String = " + #pragma header + void main(void) { + #pragma body + } + "; + #end + + #if FLX_DRAW_QUADS + static final DEFAULT_VERTEX_SOURCE: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; + } + } + "; + #else + static final DEFAULT_VERTEX_SOURCE:String = " + #pragma header + void main(void) { + #pragma body + } + "; + #end + + static final PRAGMA_HEADER:String = "#pragma header"; + static final PRAGMA_BODY:String = "#pragma body"; + static final PRAGMA_PRECISION:String = "#pragma precision"; + static final PRAGMA_VERSION:String = "#pragma version"; + + private var _glslVersion:Int; + + /** + * Constructs a GLSL shader. + * @param fragmentSource The fragment shader source. + * @param vertexSource The vertex shader source. + * Note you also need to `initialize()` the shader MANUALLY! It can't be done automatically. + */ + public function new(fragmentSource:String = null, vertexSource:String = null, glslVersion:Int = 120):Void + { + _glslVersion = glslVersion; + + if (fragmentSource == null) + { + trace('Loading default fragment source...'); + glFragmentSource = processFragmentSource(DEFAULT_FRAGMENT_SOURCE); + } + else + { + trace('Loading fragment source from argument...'); + glFragmentSource = processFragmentSource(fragmentSource); + } + + if (vertexSource == null) + { + var s = processVertexSource(DEFAULT_VERTEX_SOURCE); + glVertexSource = s; + } + else + { + var s = processVertexSource(vertexSource); + glVertexSource = s; + } + + @:privateAccess { + // This tells the shader that the glVertexSource/glFragmentSource have been updated. + __glSourceDirty = true; + // This tells the shader that the shader properties are NOT reflected on this class automatically. + __isGenerated = false; + } + + super(); + } + + /** + * Replace the `#pragma header` and `#pragma body` with the fragment shader header and body. + */ + function processFragmentSource(input:String):String + { + var result = StringTools.replace(input, PRAGMA_HEADER, BASE_FRAGMENT_HEADER); + result = StringTools.replace(result, PRAGMA_BODY, BASE_FRAGMENT_BODY); + return result; + } + + /** + * Replace the `#pragma header` and `#pragma body` with the vertex shader header and body. + */ + function processVertexSource(input:String):String + { + var result = StringTools.replace(input, PRAGMA_HEADER, BASE_VERTEX_HEADER); + result = StringTools.replace(result, PRAGMA_BODY, BASE_VERTEX_BODY); + return result; + } + + function buildPrecisionHeaders():String { + return "#ifdef GL_ES + " + (precisionHint == FULL ? "#ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; + #else + precision mediump float; + #endif" : "precision lowp float;") + + " + #endif + "; + } + + /** + * The parent function that initializes the shader. + * This is done to add the `#version` shader directive. + */ + 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"); + } + + @:privateAccess + if (__context != null && program == null) + { + var gl = __context.gl; + + var precisionHeaders = buildPrecisionHeaders(); + var versionHeader = '#version ${_glslVersion}\n'; + + var vertex = StringTools.replace(glVertexSource, PRAGMA_PRECISION, precisionHeaders); + vertex = StringTools.replace(vertex, PRAGMA_VERSION, versionHeader); + var fragment = StringTools.replace(glFragmentSource, PRAGMA_PRECISION, precisionHeaders); + fragment = StringTools.replace(fragment, PRAGMA_VERSION, versionHeader); + + var id = vertex + fragment; + + if (__context.__programs.exists(id)) { + // Use the existing program if it has been compiled. + program = __context.__programs.get(id); + } else { + // Build the program. + program = __context.createProgram(GLSL); + program.__glProgram = __createGLProgram(vertex, fragment); + __context.__programs.set(id, program); + } + + if (program != null) { + glProgram = program.__glProgram; + + // Map attributes for each type. + + for (input in __inputBitmapData) { + if (input.__isUniform) { + input.index = gl.getUniformLocation(glProgram, input.name); + } else { + input.index = gl.getAttribLocation(glProgram, input.name); + } + } + + for (parameter in __paramBool) { + if (parameter.__isUniform) { + parameter.index = gl.getUniformLocation(glProgram, parameter.name); + } else { + parameter.index = gl.getAttribLocation(glProgram, parameter.name); + } + } + + for (parameter in __paramFloat) { + if (parameter.__isUniform) { + parameter.index = gl.getUniformLocation(glProgram, parameter.name); + } else { + parameter.index = gl.getAttribLocation(glProgram, parameter.name); + } + } + + for (parameter in __paramInt) { + if (parameter.__isUniform) { + parameter.index = gl.getUniformLocation(glProgram, parameter.name); + } else { + parameter.index = gl.getAttribLocation(glProgram, parameter.name); + } + } + } + } + } + + private var __fieldList:Array = null; + private function thisHasField(name:String) { + // Reflect.hasField(this, name) is REALLY expensive so we use a cache. + if (__fieldList == null) { + __fieldList = Reflect.fields(this) + .concat(Type.getInstanceFields(Type.getClass(this))); + } + return __fieldList.indexOf(name) != -1; + } + + /** + * The parent function that initializes the shader. + * This is done because some shader fields (such as `bitmap`) have to automatically propagate from the shader, + * but others may not exist or be properties rather than fields. + */ + private override function __processGLData(source:String, storageType:String):Void + { + var position; + var name; + var type; + + var regex = (storageType == "uniform") + ? ~/uniform ([A-Za-z0-9]+) ([A-Za-z0-9_]+)/ + : ~/attribute ([A-Za-z0-9]+) ([A-Za-z0-9_]+)/; + + var lastMatch = 0; + + @:privateAccess + 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; + input.__isUniform = isUniform; + __inputBitmapData.push(input); + + switch (name) { + case "openfl_Texture": + __texture = input; + case "bitmap": + __bitmap = input; + default: + } + + Reflect.setField(__data, name, input); + if (__isGenerated && thisHasField(name)) Reflect.setProperty(this, name, input); + } 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; + parameter.type = parameterType; + parameter.__arrayLength = arrayLength; + parameter.__isBool = true; + parameter.__isUniform = isUniform; + parameter.__length = length; + __paramBool.push(parameter); + + if (name == "openfl_HasColorTransform") + { + __hasColorTransform = parameter; + } + + Reflect.setField(__data, name, parameter); + if (__isGenerated && thisHasField(name)) Reflect.setProperty(this, name, parameter); + + case INT, INT2, INT3, INT4: + var parameter = new ShaderParameter(); + parameter.name = name; + parameter.type = parameterType; + parameter.__arrayLength = arrayLength; + parameter.__isInt = true; + parameter.__isUniform = isUniform; + parameter.__length = length; + __paramInt.push(parameter); + Reflect.setField(__data, name, parameter); + if (__isGenerated && thisHasField(name)) Reflect.setProperty(this, name, parameter); + + default: + var parameter = new ShaderParameter(); + parameter.name = name; + parameter.type = parameterType; + parameter.__arrayLength = arrayLength; + #if lime + if (arrayLength > 0) parameter.__uniformMatrix = new Float32Array(arrayLength * arrayLength); + #end + parameter.__isFloat = true; + parameter.__isUniform = isUniform; + 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); + if (__isGenerated && thisHasField(name)) Reflect.setProperty(this, name, parameter); + } + } + + position = regex.matchedPos(); + lastMatch = position.pos + position.len; + } + } + /** + * Modify a float parameter of the shader. + * @param name The name of the parameter to modify. + * @param value The new value to use. + */ + public function setFloat(name:String, value:Float):Void + { + var prop:ShaderParameter = Reflect.field(this.data, name); + @:privateAccess + if (prop == null) + { + trace('[WARN] Shader float property ${name} not found.'); + return; + } + prop.value = [value]; + } + + /** + * Modify a float array parameter of the shader. + * @param name The name of the parameter to modify. + * @param value The new value to use. + */ + public function setFloatArray(name:String, value:Array):Void + { + var prop:ShaderParameter = Reflect.field(this.data, name); + if (prop == null) + { + trace('[WARN] Shader float[] property ${name} not found.'); + return; + } + prop.value = value; + } + + /** + * Modify an integer parameter of the shader. + * @param name The name of the parameter to modify. + * @param value The new value to use. + */ + public function setInt(name:String, value:Int):Void + { + var prop:ShaderParameter = Reflect.field(this.data, name); + if (prop == null) + { + trace('[WARN] Shader int property ${name} not found.'); + return; + } + prop.value = [value]; + } + + /** + * Modify an integer array parameter of the shader. + * @param name The name of the parameter to modify. + * @param value The new value to use. + */ + public function setIntArray(name:String, value:Array):Void + { + var prop:ShaderParameter = Reflect.field(this.data, name); + if (prop == null) + { + trace('[WARN] Shader int[] property ${name} not found.'); + return; + } + prop.value = value; + } + + /** + * Modify a boolean parameter of the shader. + * @param name The name of the parameter to modify. + * @param value The new value to use. + */ + public function setBool(name:String, value:Bool):Void + { + var prop:ShaderParameter = Reflect.field(this.data, name); + if (prop == null) + { + trace('[WARN] Shader bool property ${name} not found.'); + return; + } + prop.value = [value]; + } + + /** + * Modify a boolean array parameter of the shader. + * @param name The name of the parameter to modify. + * @param value The new value to use. + */ + public function setBoolArray(name:String, value:Array):Void + { + var prop:ShaderParameter = Reflect.field(this.data, name); + if (prop == null) + { + trace('[WARN] Shader bool[] property ${name} not found.'); + return; + } + prop.value = value; + } + + /** + * Set or modify a sampler2D input of the shader. + * @param name The name of the shader input to modify. + * @param value The texture to use as the sampler2D input. + */ + public function setSampler2D(name:String, value:BitmapData) + { + var prop:ShaderInput = Reflect.field(this.data, name); + if(prop == null) + { + trace('[WARNING] Shader sampler2D property ${name} not found.'); + return; + } + prop.input = value; + } + + /** + * Retrieve a float parameter of the shader. + * @param name The name of the parameter to retrieve. + */ + public function getFloat(name:String):Null + { + var prop:ShaderParameter = Reflect.field(this.data, name); + if (prop == null || prop.value.length == 0) + { + trace('[WARN] Shader float property ${name} not found.'); + return null; + } + return prop.value[0]; + } + + /** + * Retrieve a float array parameter of the shader. + * @param name The name of the parameter to retrieve. + */ + public function getFloatArray(name:String):Null> + { + var prop:ShaderParameter = Reflect.field(this.data, name); + if (prop == null) + { + trace('[WARN] Shader float[] property ${name} not found.'); + return null; + } + return prop.value; + } + + /** + * Retrieve an integer parameter of the shader. + * @param name The name of the parameter to retrieve. + */ + public function getInt(name:String):Null + { + var prop:ShaderParameter = Reflect.field(this.data, name); + if (prop == null || prop.value.length == 0) + { + trace('[WARN] Shader int property ${name} not found.'); + return null; + } + return prop.value[0]; + } + + /** + * Retrieve an integer array parameter of the shader. + * @param name The name of the parameter to retrieve. + */ + public function getIntArray(name:String):Null> + { + var prop:ShaderParameter = Reflect.field(this.data, name); + if (prop == null) + { + trace('[WARN] Shader int[] property ${name} not found.'); + return null; + } + return prop.value; + } + + /** + * Retrieve a boolean parameter of the shader. + * @param name The name of the parameter to retrieve. + */ + public function getBool(name:String):Null + { + var prop:ShaderParameter = Reflect.field(this.data, name); + if (prop == null || prop.value.length == 0) + { + trace('[WARN] Shader bool property ${name} not found.'); + return null; + } + return prop.value[0]; + } + + /** + * Retrieve a boolean array parameter of the shader. + * @param name The name of the parameter to retrieve. + */ + public function getBoolArray(name:String):Null> + { + var prop:ShaderParameter = Reflect.field(this.data, name); + if (prop == null) + { + trace('[WARN] Shader bool[] property ${name} not found.'); + return null; + } + return prop.value; + } + + public function toString():String + { + return 'FlxRuntimeShader'; + } +} \ No newline at end of file