diff --git a/scripts/Postbuild.hx b/scripts/Postbuild.hx new file mode 100644 index 000000000..179b48ac8 --- /dev/null +++ b/scripts/Postbuild.hx @@ -0,0 +1,49 @@ +package art; + +using DateTools; + +class Postbuild +{ + static function main() + { + /* trace('creating NG zip'); + var binFolder:String = 'export/release/html5/bin/'; + + var date = Date.now(); + var outputName:String = 'NG-' + date.format("%Y%m%d--%H%M") + '.zip'; + + var out = sys.io.File.write(outputName); + var zip = new haxe.zip.Writer(out); + zip.write(getEntries(binFolder)); + trace('Finished creating ZIP'); + */ + } + + static function getEntries(dir:String, entries:List = null, inDir:Null = null) + { + if (entries == null) entries = new List(); + if (inDir == null) inDir = dir; + + for (file in sys.FileSystem.readDirectory(dir)) + { + var path = haxe.io.Path.join([dir, file]); + if (sys.FileSystem.isDirectory(path)) getEntries(path, entries, inDir); + else + { + var bytes:haxe.io.Bytes = haxe.io.Bytes.ofData(sys.io.File.getBytes(path).getData()); + var entry:haxe.zip.Entry = + { + fileName: StringTools.replace(path, inDir, ""), + fileSize: bytes.length, + fileTime: Date.now(), + compressed: false, + dataSize: sys.FileSystem.stat(path).size, + data: bytes, + crc32: haxe.crypto.Crc32.make(bytes) + }; + entries.push(entry); + } + } + return entries; + } +} diff --git a/scripts/SongConverter.hx b/scripts/SongConverter.hx new file mode 100644 index 000000000..2d27ef4f4 --- /dev/null +++ b/scripts/SongConverter.hx @@ -0,0 +1,180 @@ +import haxe.Json; +import haxe.Log; +import haxe.macro.Compiler; +import sys.FileSystem; +import sys.io.File; + +using StringTools; + +#if !interp +package art; + +// needs this thing to be run by interp, just for me lol! +#end +// general use of this!! +// if run in INTERP MODE: +// - art>haxe --main SongConverter --interp +// - song to convert: ../assets/preload/data/ +// it will run through EVERY folder in there and convert the old songs to the new format! +// TODO make instructions for the C# script version! +class SongConverter +{ + public static var VERSION:String = '0.1.0'; + + // TODO + // Update engine shit, to accomodate single file + static function main() + { + // trace(Compiler.get); + // trace(Compiler.getDefine('sysfsd')); + + Sys.stdout().writeString('Please type directory you want to convert: '); + Sys.stdout().flush(); + final input = Sys.stdin().readLine(); + trace('Hello ${input}!'); + + /* Sys.stdout().writeString('What type of conversion do you need?\n\t0: Old charts to new chart\n\t1: new chart to .... new chart? lol\n>'); + Sys.stdout().flush(); + final deleteShitIDK = Std.parseInt(Sys.stdin().readLine()); + + trace(deleteShitIDK); */ + + var songCount:Int = 0; + + for (fileThing in FileSystem.readDirectory('${input}/.')) + { + // trace(fileThing); + // trace(FileSystem.isDirectory(${input} + "/" + fileThing)); + + if (FileSystem.isDirectory(${input} + "/" + fileThing) && fileThing.toLowerCase() != 'smash') + { + trace('Formatting $fileThing'); + formatSongs(fileThing, input); + + songCount++; + } + } + + trace('CONVERTED $songCount SONGS!!'); + + var timer:Int = 5; + + while (timer > 0) + { + Sys.stdout().writeString('CLOSING IN $timer SECONDS!'); + Sys.sleep(1); + Sys.print("\r"); + timer -= 1; + } + } + + public static function formatSongs(songName:String, root:String) + { + var existsArray:Array = []; + + var songMap:Map = new Map(); + var speedMap:Map = new Map(); + + for (songFile in FileSystem.readDirectory('$root/$songName/')) + { + if (!songFile.endsWith('json') || !songFile.startsWith(songName)) continue; // ACCOMODATE FOR PICO SPEAKER EVENTUALLY! + + var funnyFile:Dynamic = cast Json.parse(File.getContent('$root/$songName/$songFile')); + + var songSplit:Array = songFile.split('-'); + var songShit:String = 'normal'; + + if (songSplit.length > 1) + { + if (songSplit[1] == 'new.json') + { + FileSystem.deleteFile('$root/$songName/$songFile'); + trace('DELETEING $songFile'); + continue; + } + + songShit = songSplit[1].substr(0, songSplit[1].length - 5); + } + + if (songShit != 'normal') + { + songMap[songShit] = funnyFile.song.notes; + speedMap[songShit] = funnyFile.song.speed; + } + else + { + // + + if (funnyFile.song.notes is Array) + { + songMap[songShit] = funnyFile.song.notes[1]; + speedMap[songShit] = funnyFile.song.speed[1]; + } + else if (Type.typeof(funnyFile.song.notes) == TObject) + { + var dipshitMap:Map = cast funnyFile.song.notes; + + // songMap[songShit] = dipshitMap.get('normal'); + } + else + { + songMap[songShit] = funnyFile.song.notes; + speedMap[songShit] = funnyFile.song.speed; + } + } + } + + var fileNormal:Dynamic = cast Json.parse(File.getContent('$root/$songName/$songName.json')); + + fileNormal.song.notes = songMap; + fileNormal.song.speed = speedMap; + + fileNormal.song.hasDialogueFile = false; + fileNormal.song.stageDefault = getStage(songName); + + fileNormal.version = 'FNF SongConverter $VERSION'; + + var daJson = Json.stringify(fileNormal, null, "\t"); + // trace(daJson); + File.saveContent('$root/$songName/$songName.json', daJson); + } + + static function noteCleaner(notes:Array):Array + { + var swagArray:Array = []; + for (i in notes) + { + if (i.sectionNotes.length > 0) swagArray.push(i); + } + + return swagArray; + } + + /** + * Quicky lil function just for me formatting the old songs hehehe + */ + static function getStage(songName:String):String + { + switch (songName) + { + case 'spookeez' | 'monster' | 'south': + return "spooky"; + case 'pico' | 'blammed' | 'philly': + return 'philly'; + case "milf" | 'satin-panties' | 'high': + return 'limo'; + case "cocoa" | 'eggnog': + return 'mall'; + case 'winter-horrorland': + return 'mallEvil'; + case 'senpai' | 'roses': + return 'school'; + case 'thorns': + return 'schoolEvil'; + case 'guns' | 'stress' | 'ugh': + return 'tank'; + default: + return 'stage'; + } + } +} diff --git a/scripts/chartingSheets/sheetsToCharts.py b/scripts/chartingSheets/sheetsToCharts.py new file mode 100644 index 000000000..bfe0874ca --- /dev/null +++ b/scripts/chartingSheets/sheetsToCharts.py @@ -0,0 +1,99 @@ +import csv +import re +import sys +import json +import subprocess + +# This script imports a CSV file, and bulk updates the chart metadata files +# +# Requirements: +# python3 I believe +# and you need to have npm/npx, as we use that to run prettier to keep the same formatting +# +# Usage: +# In our Google Sheets, export as .csv file +# +# Then in here from within this directory, run: +# python3 sheetsToCharts.py path/to/csv/file.csv +# +# use from within this directory, as the paths to the chart metadata files are relative to this directory + + +# Get the command line argument, if one wasn't already provided +if len(sys.argv) < 2: + csv_file = input("Please provide the CSV file path: ") +else: + csv_file = sys.argv[1] + +# Open the CSV file +with open(csv_file, 'r') as file: + reader = csv.reader(file) + + # Which rows for each difficulty are we looking for + diff_rows = {} + + # Iterate over each row in the CSV file + for idx, row in enumerate(reader): + + # The first row has headers for the columns, stuff like "Easy - Difficulty", so we want + # to know which rows corresponde to which difficulty + if idx == 0: + pattern = r"\b(?:difficulty)\b" + + diff_cols = [] + diff_words = [] + + # Go through each column, and see if it includes the word "difficulty" + # then append the column index, and the first word of the column (usually what difficulty it is, "easy", "normal", "hard", etc.) + for colindex, column in enumerate(row): + txt = re.findall(pattern, column, flags=re.IGNORECASE) + if txt: + diff_cols.append(colindex) + diff_words.append(column.split()[0].lower()) + + # We then create a dictionary for use later, where the key is the difficulty, and the value is the column index + diff_rows = dict(zip(diff_words, diff_cols)) + else: + # Some song title parsing, to match the filenames we used in for the chart files + song_title = row[1].lower() # we use lowercase for filenames, SOUTH -> south + song_title = song_title.replace(" ", "-") # usually we change spaces to dashes, PHILLY NICE -> philly-nice + song_title = song_title.replace(".", "") # M.I.L.F. -> milf + song_title = song_title.replace("'", "") # blazin' -> blazin + + # we open the chart metadata json file for writing and reading ("r+") + with open(f"../../../assets/preload/data/songs/{song_title}/{song_title}-metadata.json", "r+") as chart_metadata_file: + + json_data = json.load(chart_metadata_file) # load the file as a python data structure/dict + play_data = json_data["playData"] # we want to modify the "playData" section, as that holds the difficulty + + # note to self, python equals(=) operator seems to create a reference for the variable, + # so modifying play_data will also modify json_data, so we can save json_data easily later + + # if the chart metadata file doesn't already have a "ratings" dict/section, we create one here with 0 for each + if "ratings" not in play_data: + play_data["ratings"] = {'easy': 0, 'normal': 0, 'hard': 0} + + ratings = play_data["ratings"] + + # Now we go through our data from the csv file, and the data we kept from the columns there + # will be put into our new ratings var, if it exists + for diff, col in diff_rows.items(): + if row[col] == "": + continue + if diff in ratings: + ratings[diff] = round(float(row[col])) # convert the string to a float, and then round it to nearest int + + # Convert the python json_data dict back to a json string + json_output = json.dumps(json_data) + + # Write the json string back to the file, and truncate the rest of the file + chart_metadata_file.seek(0) + chart_metadata_file.write(json_output) + chart_metadata_file.truncate() + chart_metadata_file.close() + +# Bit hacky, but we simply run `npx prettier` using the same rules we use for FNF here +# essnetially running it via cli and passing in the songs folder and our prettier config file +# This should make the spacing and formatting consistent +command = "npx prettier ../../../assets/preload/data/songs --write --config ../../../.prettierrc.js" +subprocess.run(command, shell=True) diff --git a/scripts/convertOGGToMP3.sh b/scripts/convertOGGToMP3.sh new file mode 100644 index 000000000..0f2601cfd --- /dev/null +++ b/scripts/convertOGGToMP3.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# Find all files in OGG format and execute ffmpeg to convert them to MP3 +# Skips files that are already in MP3 format +find "../assets/" -name "*.ogg" -exec sh -c 'ffmpeg -n -i {} "${0%.*}.mp3"' {} \; diff --git a/scripts/genNGEvents.py b/scripts/genNGEvents.py new file mode 100644 index 000000000..cf2a64f00 --- /dev/null +++ b/scripts/genNGEvents.py @@ -0,0 +1,218 @@ +import requests +import sys + +PROJECT_ID = 5582053 + +TEMPLATE = 'jQuery("#new-event-name").val("{event}"); jQuery("#save-new-event").click();' + +EVENT_TEMPLATES = [ + "start-game", + "watch-cartoon", + "open-credits", + + "start-song_{songId}-{variation}", + "blueballs_{songId}-{variation}", + "complete-song_{songId}-{variation}", + "start-level_{levelId}", + "complete-level_{levelId}", + "earn-rank_{rankName}", +] + +SONG_IDS = [ + { "songId": "tutorial", "variation": "default" }, + { "songId": "tutorial", "variation": "erect" }, + { "songId": "tutorial", "variation": "pico" }, + + { "songId": "bopeebo", "variation": "default" }, + { "songId": "bopeebo", "variation": "erect" }, + { "songId": "bopeebo", "variation": "pico" }, + + { "songId": "fresh", "variation": "default" }, + { "songId": "fresh", "variation": "erect" }, + { "songId": "fresh", "variation": "pico" }, + + { "songId": "dadbattle", "variation": "default" }, + { "songId": "dadbattle", "variation": "erect" }, + { "songId": "dadbattle", "variation": "pico" }, + + { "songId": "spookeez", "variation": "default" }, + { "songId": "spookeez", "variation": "erect" }, + { "songId": "spookeez", "variation": "pico" }, + + { "songId": "south", "variation": "default" }, + { "songId": "south", "variation": "erect" }, + { "songId": "south", "variation": "pico" }, + + { "songId": "monster", "variation": "default" }, + { "songId": "monster", "variation": "erect" }, + { "songId": "monster", "variation": "pico" }, + + { "songId": "pico", "variation": "default" }, + { "songId": "pico", "variation": "erect" }, + { "songId": "pico", "variation": "pico" }, + + { "songId": "philly-nice", "variation": "default" }, + { "songId": "philly-nice", "variation": "erect" }, + { "songId": "philly-nice", "variation": "pico" }, + + { "songId": "blammed", "variation": "default" }, + { "songId": "blammed", "variation": "erect" }, + { "songId": "blammed", "variation": "pico" }, + + { "songId": "satin-panties", "variation": "default" }, + { "songId": "satin-panties", "variation": "erect" }, + { "songId": "satin-panties", "variation": "pico" }, + + { "songId": "high", "variation": "default" }, + { "songId": "high", "variation": "erect" }, + { "songId": "high", "variation": "pico" }, + + { "songId": "milf", "variation": "default" }, + { "songId": "milf", "variation": "erect" }, + { "songId": "milf", "variation": "pico" }, + + { "songId": "cocoa", "variation": "default" }, + { "songId": "cocoa", "variation": "erect" }, + { "songId": "cocoa", "variation": "pico" }, + + { "songId": "eggnog", "variation": "default" }, + { "songId": "eggnog", "variation": "erect" }, + { "songId": "eggnog", "variation": "pico" }, + + { "songId": "winter-horrorland", "variation": "default" }, + { "songId": "winter-horrorland", "variation": "erect" }, + { "songId": "winter-horrorland", "variation": "pico" }, + + { "songId": "senpai", "variation": "default" }, + { "songId": "senpai", "variation": "erect" }, + { "songId": "senpai", "variation": "pico" }, + + { "songId": "roses", "variation": "default" }, + { "songId": "roses", "variation": "erect" }, + { "songId": "roses", "variation": "pico" }, + + { "songId": "thorns", "variation": "default" }, + { "songId": "thorns", "variation": "erect" }, + { "songId": "thorns", "variation": "pico" }, + + { "songId": "ugh", "variation": "default" }, + { "songId": "ugh", "variation": "erect" }, + { "songId": "ugh", "variation": "pico" }, + + { "songId": "guns", "variation": "default" }, + { "songId": "guns", "variation": "erect" }, + { "songId": "guns", "variation": "pico" }, + + { "songId": "stress", "variation": "default" }, + { "songId": "stress", "variation": "erect" }, + { "songId": "stress", "variation": "pico" }, + + { "songId": "darnell", "variation": "default" }, + { "songId": "darnell", "variation": "erect" }, + { "songId": "darnell", "variation": "bf" }, + + { "songId": "lit-up", "variation": "default" }, + { "songId": "lit-up", "variation": "erect" }, + { "songId": "lit-up", "variation": "bf" }, + + { "songId": "2hot", "variation": "default" }, + { "songId": "2hot", "variation": "erect" }, + { "songId": "2hot", "variation": "bf" }, + + { "songId": "blazin", "variation": "default" }, + { "songId": "blazin", "variation": "erect" }, + { "songId": "blazin", "variation": "bf" }, +] + +LEVEL_IDS = [ + "tutorial", + "week1", + "week2", + "week3", + "week4", + "week5", + "week6", + "week7", + "weekend1", + "week8", +] + +RANKS = [ + "PERFECT_GOLD", + "PERFECT", + "EXCELLENT", + "GREAT", + "GOOD", + "SHIT", +] + + +def formatEventSong(event): + result = [] + for song in SONG_IDS: + result.append(event.format( + songId = song["songId"], + variation = song["variation"] + )) + return result + +def formatEventLevel(event): + result = [] + for level in LEVEL_IDS: + result.append(event.format( + levelId = level + )) + return result + +def formatEventRank(event): + result = [] + for rank in RANKS: + result.append(event.format( + rankName = rank + )) + return result + +def formatEvent(event): + if "{songId}" in event or "{variation}" in event: + return formatEventSong(event) + + if "{levelId}" in event: + return formatEventLevel(event) + + if "{rankName}" in event: + return formatEventRank(event) + + return [event] + +def createEvent(event): + print(TEMPLATE.format(event=event)) + +def main(): + event_list = [] + + for event in EVENT_TEMPLATES: + event_list.extend(formatEvent(event)) + + # Filter out duplicates then sort alphabetically + event_list = list(set(event_list)) + event_list.sort() + + for event in event_list: + createEvent(event) + +if __name__ == "__main__": + main() + + + +#function sleep(ms) { +# console.log("sleeping for " + ms + "ms"); +# return new Promise(resolve => setTimeout(resolve, ms)); +#} +#async function example() { +# jQuery("#new-event-name").val("blueballs_blazin-erect"); jQuery("#save-new-event").click(); +# await sleep(1000); +# jQuery("#new-event-name").val("blueballs_blazin-erect"); jQuery("#save-new-event").click(); +# await sleep(1000); +#} +#example(); diff --git a/scripts/itch-building/build-Itch-HTML-RELEASE.bat b/scripts/itch-building/build-Itch-HTML-RELEASE.bat new file mode 100644 index 000000000..2183bcb46 --- /dev/null +++ b/scripts/itch-building/build-Itch-HTML-RELEASE.bat @@ -0,0 +1,11 @@ +@echo off +color 0a +cd .. +@echo on +echo BUILDING GAME +lime build html5 -final +echo UPLOADING TO ITCH +butler push ./export/release/html5/bin ninja-muffin24/funkin-secret:html5 +butler status ninja-muffin24/funkin-secret:html5 +echo ITCH SHIT UPDATED LMAOOOOO +pause \ No newline at end of file diff --git a/scripts/itch-building/build-Itch-HTML.bat b/scripts/itch-building/build-Itch-HTML.bat new file mode 100644 index 000000000..d1c7e0aee --- /dev/null +++ b/scripts/itch-building/build-Itch-HTML.bat @@ -0,0 +1,11 @@ +@echo off +color 0a +cd .. +@echo on +echo BUILDING GAME +lime build html5 -debug +echo UPLOADING TO ITCH +butler push ./export/debug/html5/bin ninja-muffin24/funkin-secret:html5 +butler status ninja-muffin24/funkin-secret:html5 +echo ITCH SHIT UPDATED LMAOOOOO +pause \ No newline at end of file diff --git a/scripts/itch-building/build-Itch-WINDOWS-CLEAN.bat b/scripts/itch-building/build-Itch-WINDOWS-CLEAN.bat new file mode 100644 index 000000000..6598bef20 --- /dev/null +++ b/scripts/itch-building/build-Itch-WINDOWS-CLEAN.bat @@ -0,0 +1,15 @@ +@echo off +color 0a +cd .. +@echo on +echo BUILDING GAME +lime build windows -final -clean +echo UPLOADING 64 BIT VERSION TO ITCH +butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-64bit +lime build windows -final -32 +echo UPLOADING 32 BIT VERSION TO ITCH +butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-32bit +butler status ninja-muffin24/funkin:windows-32bit +butler status ninja-muffin24/funkin:windows-64bit +echo ITCH SHIT UPDATED LMAOOOOO +pause \ No newline at end of file diff --git a/scripts/itch-building/build-Itch-WINDOWS-SECRET.bat b/scripts/itch-building/build-Itch-WINDOWS-SECRET.bat new file mode 100644 index 000000000..c75a4e6c0 --- /dev/null +++ b/scripts/itch-building/build-Itch-WINDOWS-SECRET.bat @@ -0,0 +1,11 @@ +@echo off +color 0a +cd .. +@echo on +echo BUILDING GAME +lime build windows -debug +echo UPLOADING 64 BIT VERSION TO ITCH +butler push ./export/debug/windows/bin ninja-muffin24/funkin:windows-secretBeta +butler status ninja-muffin24/funkin:windows-secretBeta +echo ITCH SHIT UPDATED LMAOOOOO +pause \ No newline at end of file diff --git a/scripts/itch-building/build-Itch-WINDOWS.bat b/scripts/itch-building/build-Itch-WINDOWS.bat new file mode 100644 index 000000000..b2a074484 --- /dev/null +++ b/scripts/itch-building/build-Itch-WINDOWS.bat @@ -0,0 +1,15 @@ +@echo off +color 0a +cd .. +@echo on +echo BUILDING GAME +lime build windows -release +echo UPLOADING 64 BIT VERSION TO ITCH +butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-64bit +lime build windows -release -32 +echo UPLOADING 32 BIT VERSION TO ITCH +butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-32bit +butler status ninja-muffin24/funkin:windows-32bit +butler status ninja-muffin24/funkin:windows-64bit +echo ITCH SHIT UPDATED LMAOOOOO +pause \ No newline at end of file diff --git a/scripts/itch-building/build-NG-HTML-RELEASE.bat b/scripts/itch-building/build-NG-HTML-RELEASE.bat new file mode 100644 index 000000000..d58b30a6c --- /dev/null +++ b/scripts/itch-building/build-NG-HTML-RELEASE.bat @@ -0,0 +1,12 @@ +@echo off +color 0a +cd .. +@echo on +echo BUILDING GAME +lime build html5 -final -Dng +echo ZIPPING +cd export +7z a -tzip -r swag release\html5\bin +start . +start https://www.newgrounds.com/projects/games/1528775 +pause \ No newline at end of file diff --git a/scripts/itch-building/build-lime-SWITCH.bat b/scripts/itch-building/build-lime-SWITCH.bat new file mode 100644 index 000000000..33da144b6 --- /dev/null +++ b/scripts/itch-building/build-lime-SWITCH.bat @@ -0,0 +1,27 @@ +@echo off +cd .. +@echo on +echo REBUILDING LIME FOR EXPORT (VERBOSE) +@echo off +color 0b +@echo on +lime rebuild windows -clean -v +@echo off +color 0a +@echo on +lime rebuild switch -clean -v +@echo off +color 0e +@echo on +lime rebuild tools -clean -v +echo HOPE AND PRAY... +@echo off +color 0a +@echo on +echo BUILDING GAME +lime build switch -final -clean -v +@echo off +color 0b +@echo on +echo NSP FILE CREATED +pause \ No newline at end of file diff --git a/scripts/jsfl/frames.jsfl b/scripts/jsfl/frames.jsfl new file mode 100644 index 000000000..da5a04dad --- /dev/null +++ b/scripts/jsfl/frames.jsfl @@ -0,0 +1,110 @@ +// Highlight keyframes in flash +// drag .jsfl script in +// will export the layer info of the first(?) element it finds? +// maybe someone can extend this to add more depth / functionality! +// see FramesJSFLParser.hx and CharSelectGF.hx for sample implementation + +function splitArrayIntoChunks(array, chunkSize) { + var result = []; + for (var i = 0; i < array.length; i += chunkSize) { + var chunk = array.slice(i, i + chunkSize); + result.push(chunk); + } + return result; +} + +var isJson = false; + +function addField(name, variable, end) +{ + return (isJson) ? addFJson(name, variable, end) : addF(variable, end); +} +function addFJson(name, variable, end) { + return "\"" + name + "\": " + variable + ((!end) ? "," : ""); +} +function addF(variable, end) { + return variable + ((end)? "" : " "); +} + + +fl.outputPanel.clear(); + +var timeline = fl.getDocumentDOM().getTimeline(); +var daSelection = timeline.getSelectedFrames(); +var splitArrays = splitArrayIntoChunks(daSelection, 3); +var uri = undefined; + + +if (fl.version.substring(4).split(",")[0] < 13) +{ + var macFormat = "Folder|TEXT[*.||"; + var winFormat = "Folder|*.||"; + var previewArea = {}; + uri = fl.browseForFileURL("save", "where to save files pick folder...", previewArea, macFormat, winFormat); +} +else + uri = fl.browseForFileURL("save", "where to save files pick folder..", "Folder(*.)", "*."); + + +for (var i = 0; i < splitArrays.length; i+=1) +{ + var curLayerInfo = splitArrays[i]; + var curLayerInd = curLayerInfo[0]; + var curLayer = fl.getDocumentDOM().getTimeline().layers[curLayerInd]; + + var layerURI = uri; + var fileName = ""; + var uris = uri.split("/"); + + fileName = uris[uris.length - 1]; + + + FLfile.createFolder(layerURI); + + + var fileData = "{\n"; + if (!isJson) + fileData = ""; + + var selectedFrames = curLayer.frames; + var sliceLength = curLayerInfo[2] - curLayerInfo[1]; + selectedFrames =selectedFrames.slice(curLayerInfo[1],curLayerInfo[2]); + + if (isJson) + fileData += "\"frames\": ["; + + for (var frameInd = curLayerInfo[1]; frameInd < curLayerInfo[2]; frameInd+=1) + { + if (isJson) + fileData += "\n{" + var curFrame = curLayer.frames[frameInd]; + if (curFrame.isEmpty) + { + continue; + } + + for (var elementInd = 0; elementInd < curFrame.elements.length; elementInd+=1) + { + + var curElement = curFrame.elements[elementInd]; + + fileData += addField("x", curElement.left, false); + fileData += addField("y", curElement.top, false); + fileData += addField("alpha", (curElement.colorAlphaPercent == undefined) ? 100 : curElement.colorAlphaPercent, false); + fileData += addField("scaleX", curElement.scaleX, false); + fileData += addField("scaleY", curElement.scaleY, true); + if (frameInd != curLayerInfo[2] - 1) + fileData += "\n"; + } + + if (isJson) + fileData += "},"; + } + if (isJson) + fileData += "]"; + + if (isJson) + fileData += "\n}"; + + FLfile.write(layerURI + "/" + fileName + ".txt", fileData); +} diff --git a/scripts/jsfl/readme.md b/scripts/jsfl/readme.md new file mode 100644 index 000000000..1d55d2c0a --- /dev/null +++ b/scripts/jsfl/readme.md @@ -0,0 +1,6 @@ +# README + +jsfl is a scripting format with javascript syntax, I believe it's old js (no `let`, idk the spec!) + +Official Adobe JSFL docuementation +[https://github.com/AdobeDocs/developers-animatesdk-docs/tree/master](https://github.com/AdobeDocs/developers-animatesdk-docs/tree/master) diff --git a/scripts/macos-codesign.sh b/scripts/macos-codesign.sh new file mode 100755 index 000000000..b0196681c --- /dev/null +++ b/scripts/macos-codesign.sh @@ -0,0 +1,120 @@ +#!/bin/bash +set -e # Exit immediately if a command exits with a non-zero status + +echo "Note! This should be run from the root of the main Funkin repo, after creating a universal macOS build!" +echo "This script will assist in codesigning the Funkin.app for macOS" +echo "" + +TEAM_ID="Z7G7AVNGSH" +ISSUER_ID="5205caca-2a9b-4b5e-9390-1db124cf51f4" +DEVELOPER_CERT_NAME="Developer ID Application: The Funkin' Crew Incorporated ($TEAM_ID)" +APP_PATH="export/release/macosUniversal/Funkin.app" + +# Check if app exists +if [ ! -d "$APP_PATH" ]; then + echo "Error: $APP_PATH does not exist! Please build the app first." + exit 1 +fi + +echo "Preparing/Checking the AuthKey" +team_key_path=~/.appstoreconnect/private_keys/ +# Check if directory exists, if not, create it +if [ ! -d "$team_key_path" ]; then + echo "The directory $team_key_path does not exist! Creating it now." + mkdir -p "$team_key_path" + echo "" + echo "$team_key_path created, put the private key file in there (should be called 'AuthKey_XXXXXXX.p8')" + echo "Ask Cameron for the private key file!" + echo "" +fi + +# wait for the user to put the file in the directory, if it's not there +while [ ! -f "${team_key_path}"AuthKey_*.p8 ]; do + echo "Waiting for the private key file to be placed in $team_key_path... (press Ctrl+C to exit)" + sleep 5 +done + +key_id=$(ls "$team_key_path" | grep -oE '[A-Z0-9]{10}' | head -n 1) +if [ -z "$key_id" ]; then + echo "Error: Could not extract key ID from the AuthKey file name" + exit 1 +fi + +echo "Found AuthKey with key id: $key_id" +key_path="${team_key_path}AuthKey_${key_id}.p8" + +cd export/release/macosUniversal || { echo "Failed to change directory"; exit 1; } + +echo "Ensuring properly formatted Entitlements.plist" +if [ ! -f "Funkin.app/Contents/Entitlements.plist" ]; then + echo "Error: Entitlements.plist not found" + exit 1 +fi + +plutil -convert xml1 Funkin.app/Contents/Entitlements.plist +plutil -convert xml1 Funkin.app/Contents/Info.plist +plutil -lint Funkin.app/Contents/Entitlements.plist +plutil -lint Funkin.app/Contents/Info.plist +echo "Entitlements.plist and Info.plist should be properly formatted..." +echo "Codesigning - Funkin.app" +# Check the keychain for the funkin crew certificate +echo "Checking for The Funkin' Crew certificate in the keychain..." +if ! security find-identity -p codesigning | grep -q "The Funkin' Crew Incorporated"; then + echo "The Funkin' Crew Developer ID Application certificate was not found in the keychain!" + echo "Download the certificate from:" + echo "https://developer.apple.com/account/resources/certificates/download/Z62SXF29U7" + exit 1 +fi + +echo "Codesigning - Funkin.app" +codesign --force --deep --timestamp --sign "$DEVELOPER_CERT_NAME" --options runtime Funkin.app || { + echo "Code signing failed" + exit 1 +} + +echo "Codesigning - Funkin.app done!" +# Debug info to see if the app is properly signed +codesign -vv --deep --strict Funkin.app || { + echo "Code signing verification failed" + exit 1 +} +echo "" +echo "Zipping Funkin.app for notarization (using ditto)..." +# Check if Funkin.app.zip already exists, if so, delete it +if [ -f Funkin.app.zip ]; then + echo "Funkin.app.zip already exists, deleting it..." + rm -rf Funkin.app.zip +fi + +ditto -c -k --keepParent Funkin.app Funkin.app.zip || { + echo "Failed to create zip file" + exit 1 +} + +echo "Funkin.app zipped, now sending to Apple for notarization..." +xcrun notarytool submit Funkin.app.zip --issuer "$ISSUER_ID" --key-id "$key_id" --key "$key_path" --wait || { + echo "Notarization submission failed" + exit 1 +} + +# Clean up zip file +rm -rf Funkin.app.zip + +echo "Stapling Funkin.app" +xcrun stapler staple Funkin.app || { + echo "Stapling failed" + exit 1 +} + +echo "Code sign information!" +codesign -dv --verbose=4 Funkin.app + +echo "Notarization information (stapler):" +xcrun stapler validate Funkin.app || { + echo "Stapler validation failed" + exit 1 +} + +cd ../../.. || { echo "Failed to return to original directory"; exit 1; } +echo "Notarization complete! Funkin.app is now codesigned and notarized!" + diff --git a/scripts/macos-universal.sh b/scripts/macos-universal.sh new file mode 100755 index 000000000..520a87aba --- /dev/null +++ b/scripts/macos-universal.sh @@ -0,0 +1,17 @@ +#!/bin/sh +lime build mac -64 -release +cd export/release +rm -rf macosUniversal +mkdir macosUniversal +cp -r macos/bin/Funkin.app macosUniversal +cd ../.. +lime build mac -arm64 -release +cd export/release +cp -r macos/bin/Funkin.app/Contents/MacOS/Funkin macosUniversal/Funkin.app/Contents/MacOS/FunkinArm +cp -r macos/bin/Funkin.app/Contents/MacOS/lime.ndll macosUniversal/Funkin.app/Contents/MacOS/limeArm.ndll +cd macosUniversal/Funkin.app/Contents/MacOS +mv Funkin Funkin64 +mv lime.ndll lime64.ndll +lipo -create -output lime.ndll lime64.ndll limeArm.ndll +lipo -create -output Funkin Funkin64 FunkinArm +rm FunkinArm Funkin64 lime64.ndll limeArm.ndll diff --git a/scripts/optimizePNG.sh b/scripts/optimizePNG.sh new file mode 100644 index 000000000..ccc6d272b --- /dev/null +++ b/scripts/optimizePNG.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Find all files in PNG format and execute oxipng to optimize file sizes +cd ../assets/ && oxipng -o 6 --strip safe --alpha {} **/*.png diff --git a/scripts/resizeHealthIcons.sh b/scripts/resizeHealthIcons.sh new file mode 100644 index 000000000..fdeda55ab --- /dev/null +++ b/scripts/resizeHealthIcons.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Resize each health icon to 512x512 +find "./discord/" -name "icon-*.png" -exec sh -c 'ffmpeg -i {} -vf scale=512:512 "${0%.*}2.png"' {} \;