54 Commits
Author SHA1 Message Date
FuroandGitHub 9757b002bd 1.0.1 (no more rc) 2025-08-10 14:28:33 +03:00
Ralty 3140ee8512 Account variation to last freeplay play too 2025-08-10 17:26:51 +07:00
FuroandGitHub c96ee6c465 Use FunkinModchart Static Commit 2025-08-10 13:05:43 +03:00
TheZoroForce240andGitHub 1c4c962f4d Add Funkin Modchart classes to hscript config to allow custom classes (#727)
* add funkin modchart to hscript config for custom classes

* change to only classes that are needed
2025-08-10 12:59:20 +03:00
Ralty 49d4708265 Fix Freeplay Last Saved Thingies 2025-08-10 13:47:45 +07:00
Ralty ffa5e66bc8 These should be in debug too... 2025-08-10 13:03:36 +07:00
Ralty 18fcacc2b5 I forgot 2025-08-10 12:51:26 +07:00
Ralty a2ff5c112e Band-aid Fix for psych charts 2025-08-10 12:50:05 +07:00
Ralty 3862995213 fix auto needsVoices 2025-08-10 09:06:37 +07:00
Ralty f7e4875097 h 2025-08-10 08:56:43 +07:00
Ralty cabdffd058 color too 2025-08-10 08:55:52 +07:00
Ralty c28e056a8a oh my god 2025-08-10 08:36:54 +07:00
Ralty 3fd87c8d52 Dont force set chart name 2025-08-10 08:34:57 +07:00
Ralty eaacc5dfc2 Variation face should be parent face 2025-08-10 08:30:38 +07:00
Ralty 4573fef74d Remove this trace 2025-08-10 08:28:31 +07:00
Ralty 978022dc0b Fix VSliceParser crash 2025-08-10 08:25:52 +07:00
Ralty 970d294ea1 Didnt i fixed this before 2025-08-10 07:52:57 +07:00
Ralty 7568cbfd99 songVariations -> variants 2025-08-10 07:42:45 +07:00
Ralty c5686b09db Fix Chart Saving in assets/assets if no path found 2025-08-10 07:41:12 +07:00
Ralty ce4d1a0d61 V-Slice should have needsVoices false if no voices 2025-08-10 07:40:57 +07:00
Ralty 2eac58aaa5 fpsLerpTo should use getFPSRatio 2025-08-10 07:03:43 +07:00
Ralty ce98ad0645 aaghh 2025-08-10 05:41:32 +07:00
Ralty 037f564676 forgor 2025-08-10 05:17:10 +07:00
Ralty 5227ed3253 Fix Freeplay Autoplay 2025-08-10 05:16:27 +07:00
Ralty 9e4b6f31c0 Bring back difficulty metas for now 2025-08-10 05:16:16 +07:00
Ralty fe02ba7b4b Hold Subdivisions should be in control by modders 2025-08-09 20:15:11 +07:00
Ralty ec63be9e77 Fix backward comp issues and freeplay 2025-08-09 20:00:49 +07:00
Ralty 88ea51064b This one too 2025-08-09 19:56:10 +07:00
Ralty 647c70839d Furo requested 2025-08-09 18:57:44 +07:00
Ralty 39237dd01d Fix creating variation & VSliceParser 2025-08-09 17:52:18 +07:00
Ralty d3bd40baf3 Seperate global events for variations 2025-08-09 16:40:57 +07:00
Ralty 7d7db50c19 Fix Variation Editor Crash 2025-08-09 15:54:03 +07:00
Ralty 8aa2023057 cancellable onSongEnd 2025-08-09 15:43:04 +07:00
d979b5c33d (attempt to) make icons more constantly sized between menus (#644)
Co-authored-by: Ralty <78720179+Raltyro@users.noreply.github.com>
2025-08-09 15:21:14 +07:00
theoandGitHub ba50edfc99 Add modcharting features (using FunkinModchart) (#526)
Technically already in the codes but yeah
2025-08-09 15:13:19 +07:00
Ralty 5acabccd9a Fix pixel icons in CharterStrumline 2025-08-09 14:55:11 +07:00
Ralty 66c1cd9dde Fix compiling error 2025-08-09 14:42:07 +07:00
Ralty ec65cae8fc Fix Charter Strumlines length not updating 2025-08-09 14:42:01 +07:00
Ralty 3de652c73e Merge Options XML crash fix PR 2025-08-09 14:31:18 +07:00
Ralty 8303e7ecfd Whoops 2025-08-09 14:24:56 +07:00
Ralty e17b5d6b86 Fix audio calling complete callbacks twice 2025-08-09 13:41:05 +07:00
Ralty 0ff829e1c6 Replace meta difficulties with variations. 2025-08-09 13:32:49 +07:00
rufiusandGitHub 769a33f138 Fix incorrect translation identifiers during stage creation (#721) 2025-08-09 09:27:48 +03:00
Ralty 91abb37281 Fix 2025-08-09 13:23:02 +07:00
Ralty 32bca0420e Change storyPlaylist, backward comp in loadSong 2025-08-09 13:22:53 +07:00
Ralty faa0dbb324 CharterSelection Song Variations 2025-08-09 12:16:58 +07:00
Ralty 14c81ef26d Merge branch 'main' into repurpose-variants 2025-08-09 05:53:36 +07:00
Ralty 62f4819493 Use previous difficulty if possible 2025-08-09 05:53:29 +07:00
Ralty 8ba41990e6 UpdateUtil hopefully doesnt show null this time 2025-08-09 05:47:40 +07:00
Ralty 6c92fcff8b Fix Window Title if no MOD_NAME presents 2025-08-09 05:47:27 +07:00
Ralty 6a802c2281 Song Variations Ordering 2025-08-09 05:40:03 +07:00
Ralty 9c7bb8f073 Aghhh variant, variation, whatever 2025-08-09 00:31:45 +07:00
Ralty 6112e2f759 Changed FunkinSave Highscores Implementation 2025-08-08 22:20:54 +07:00
Ralty 3939439086 Chart Variations Backend Implementation 2025-08-08 20:50:12 +07:00
38 changed files with 592 additions and 352 deletions
+2 -1
View File
@@ -79,4 +79,5 @@ _**QOL = Quality of Life**_
- Features not found in other editors!
- Every single state & substate can be modified via HScript (`data/states/StateName.hx`)
- **Instances launched via `lime test windows` will automatically use assets from source.**
</details>
- Modcharting features powered by [FunkinModchart](https://lib.haxe.org/p/funkin-modchart/).
</details>
+1
View File
@@ -87,4 +87,5 @@ If you don't have a github account please go onto https://codename-engine.com/ a
- Credits to the [FlxAnimate](https://github.com/Dot-Stuff/flxanimate) team for the Animate Atlas support
- Credits to Smokey555 for the backup Animate Atlas to spritesheet code
- Credits to MAJigsaw77 for [hxvlc](https://github.com/MAJigsaw77/hxvlc) (video cutscene/mp4 support) and [hxdiscord_rpc](https://github.com/MAJigsaw77/hxdiscord_rpc) (discord rpc integration)
- Credits to [TheoDev](https://github.com/TheoDevelops) for [FunkinModchart](https://lib.haxe.org/p/funkin-modchart/). ***(library used for modcharting features)***
</details>
+4
View File
@@ -45,12 +45,15 @@
<group name="CharterSelection" prefix="charterSelection.">
<str id="desc">Select a song to modify the charts from.</str>
<str id="acceptSong">Press ACCEPT to choose a difficulty to edit.</str>
<str id="acceptVariation">Press ACCEPT to choose a song variation to edit.</str>
<str id="acceptDifficulty">Press ACCEPT to edit the chart for the selected difficulty.</str>
<str id="selectDifficulty">Select a difficulty to continue.</str>
<str id="newDifficulty">New Difficulty</str>
<str id="newDifficultyDesc">Press ACCEPT to create a new chart difficulty.</str>
<str id="newSong">New Song</str>
<str id="newSongDesc">Press ACCEPT to create a new song.</str>
<str id="newVariation">New Variation</str>
<str id="newVariationDesc">Press ACCEPT to create a new song variation.</str>
</group>
<group name="ChartCreationScreen" prefix="chartCreation.">
@@ -104,6 +107,7 @@
<str id="songData">Song Data</str>
<str id="songName">Song Name</str>
<str id="variation">Song Variation</str>
<str id="bpm">BPM</str>
<str id="timeSignature">Time Signature</str>
<str id="menusData">Menus Data (Freeplay/Story)</str>
+1 -1
View File
@@ -16,7 +16,7 @@
<git name="hscript-improved" url="https://github.com/CodenameCrew/hscript-improved" ref="codename-dev" />
<git name="flxanimate" url="https://github.com/CodenameCrew/cne-flxanimate" />
<git name="hxdiscord_rpc" url="https://github.com/CodenameCrew/cne-hxdiscord_rpc" skipDeps="true" />
<git name="funkin-modchart" url="https://github.com/CodenameCrew/FunkinModchart" skipDeps="true" />
<git name="funkin-modchart" url="https://github.com/TheoDevelops/FunkinModchart" ref="f30676579d8de7ccac7d12e13b664e8b2c42c6bf" skipDeps="true" />
<lib name="hxvlc" version="1.9.3" skipDeps="true" />
<!-- Documentation and other features -->
+3 -3
View File
@@ -5,7 +5,7 @@
title="Friday Night Funkin' - Codename Engine" main="funkin.backend.system.Main"
file="CodenameEngine" packageName="com.yoshman29.codenameengine"
package="com.yoshman29.codenameengine"
version="1.0.1-rc2" company="Yoshman29" />
version="1.0.1" company="Yoshman29" />
<!--
CHANGE THE SAVE PATH & NAME FOR YOUR MOD HERE!
@@ -227,9 +227,9 @@
<setenv name="HAXEPATH" value="./"/>
<haxedef name="HXCPP_M32" if="32bit" />
<haxedef name="HXCPP_CHECK_POINTER" if="release" />
<haxedef name="HXCPP_STACK_LINE" if="release" />
<haxedef name="HXCPP_DEBUG_LINK" if="debug" />
<haxedef name="HXCPP_CHECK_POINTER" />
<haxedef name="HXCPP_STACK_LINE" />
<haxedef name="HXCPP_CATCH_SEGV" />
<haxedef name="hscriptPos" /> <!-- for logging -->
+2 -2
View File
@@ -128,11 +128,11 @@ class Paths
return scriptPath;
}
static public function chart(song:String, ?difficulty:String):String
static public function chart(song:String, ?difficulty:String, ?variant:String):String
{
difficulty = (difficulty != null ? difficulty : Flags.DEFAULT_DIFFICULTY);
return getPath('songs/$song/charts/$difficulty.json', null);
return getPath('songs/$song/charts/${variant != null ? variant + "/" : ""}$difficulty.json', null);
}
public static function character(character:String):String {
+142 -103
View File
@@ -71,8 +71,9 @@ class Chart {
return LEGACY;
}
public static function loadEventsJson(songName:String) {
var path = Paths.file('songs/${songName}/events.json');
public static function loadEventsJson(songName:String, ?variant:String) {
var variantSuffix = variant != null && variant != "" ? '-$variant' : "";
var path = Paths.file('songs/${songName}/events$variantSuffix.json');
var data:Array<ChartEvent> = null;
if (Assets.exists(path)) {
try {
@@ -83,77 +84,91 @@ class Chart {
return data;
}
public static function loadChartMeta(songName:String, difficulty:String = '', fromMods:Bool = true, includeMetaDifficulties:Bool = true):ChartMetaData {
var metaPath = Paths.file('songs/${songName}/meta.json');
var metaDiffPath = Paths.file('songs/${songName}/meta-${difficulty}.json');
var data:ChartMetaData = null, fromDifficulty = false;
var fromMods:Bool = fromMods;
for (path in [metaDiffPath, metaPath]) if (Assets.exists(path)) {
fromMods = Paths.assetsTree.existsSpecific(path, "TEXT", MODS);
try {
var tempData = Json.parse(Assets.getText(path));
tempData.color = CoolUtil.getColorFromDynamic(tempData.color).getDefault(Flags.DEFAULT_COLOR);
data = tempData;
} catch(e) Logs.trace('Failed to load song metadata for ${songName} ($path): ${Std.string(e)}', ERROR);
if (data != null) {
fromDifficulty = path == metaDiffPath;
break;
}
}
if (data == null) {
data = {
name: songName,
bpm: Flags.DEFAULT_BPM
};
}
else {
data.name = songName;
data.setFieldDefault("bpm", Flags.DEFAULT_BPM);
}
inline public static function defaultChartMetaFields(data:ChartMetaData):ChartMetaData {
data.setFieldDefault("displayName", data.name);
data.setFieldDefault("bpm", Flags.DEFAULT_BPM);
data.setFieldDefault("beatsPerMeasure", Flags.DEFAULT_BEATS_PER_MEASURE);
data.setFieldDefault("stepsPerBeat", Flags.DEFAULT_STEPS_PER_BEAT);
data.setFieldDefault("icon", Flags.DEFAULT_HEALTH_ICON);
data.setFieldDefault("difficulties", []);
data.setFieldDefault("coopAllowed", Flags.DEFAULT_COOP_ALLOWED);
data.setFieldDefault("opponentModeAllowed", Flags.DEFAULT_OPPONENT_MODE_ALLOWED);
data.setFieldDefault("instSuffix", "");
data.setFieldDefault("vocalsSuffix", "");
data.setFieldDefault("needsVoices", true);
data.setFieldDefault("difficulties", []);
data.setFieldDefault("variants", []);
data.setFieldDefault("metas", []);
if (data.difficulties.length <= 0) {
data.difficulties = [for(f in Paths.getFolderContent('songs/${songName}/charts/', false, fromMods ? MODS : SOURCE)) if (Path.extension(f.toUpperCase()) == "JSON") Path.withoutExtension(f)];
var tempDiffs = [];
if (data.difficulties.length == 3) {
for(d in data.difficulties) {
switch(d.toLowerCase()) {
case "easy": tempDiffs.insert(0, d);
case "normal": tempDiffs.insert(1, d);
case "hard": tempDiffs.insert(2, d);
}
}
data.difficulties = tempDiffs;
return data;
}
public static function loadChartMeta(songName:String, ?variant:String, ?difficulty:String, fromMods:Bool = true, includeMetaVariations = true):ChartMetaData {
var folder = 'songs/$songName', isVariant = false, data:ChartMetaData = null;
var defaultPaths = [Paths.file('$folder/meta-$difficulty.json'), Paths.file('$folder/meta.json')], variantPaths = [];
if (difficulty != null) defaultPaths.unshift(Paths.file('$folder/meta-$difficulty.json'));
if (variant != null && variant != '') {
variantPaths.push(Paths.file('$folder/meta-$variant.json'));
if (difficulty != null) variantPaths.unshift(Paths.file('$folder/meta-$variant-$difficulty.json'));
}
for (path in variantPaths.concat(defaultPaths)) if (Assets.exists(path)) {
fromMods = Paths.assetsTree.existsSpecific(path, "TEXT", MODS);
try {
var tempData = Json.parse(Assets.getText(path));
tempData.color = CoolUtil.getColorFromDynamic(tempData.color).getDefault(Flags.DEFAULT_COLOR);
data = tempData;
} catch(e) Logs.trace('Failed to load song metadata for $songName ($path): ${Std.string(e)}', ERROR);
if (data != null) {
isVariant = variantPaths.contains(path);
break;
}
}
if (includeMetaDifficulties && !fromDifficulty) {
data.metas = [];
for (difficulty in data.difficulties) {
if (!data.metas.exists(difficulty) && Assets.exists(Paths.file('songs/${songName}/meta-${difficulty}.json')))
data.metas.set(difficulty, loadChartMeta(songName, difficulty, fromMods, false));
if (data != null) data.name = songName;
else data = {
name: songName,
color: Flags.DEFAULT_COLOR
};
if (isVariant) data.variant = variant;
else data.variant = null;
defaultChartMetaFields(data);
if (data.difficulties.length <= 0) {
var path = 'songs/$songName/charts/';
if (isVariant) path += '$variant/';
data.difficulties = [for(f in Paths.getFolderContent(path, false, fromMods ? MODS : SOURCE)) if (Path.extension(f.toUpperCase()) == "JSON") Path.withoutExtension(f)];
if (data.difficulties.length == 3) {
var tempDiffs = [];
for (d in data.difficulties) switch(d.toLowerCase()) {
case "easy": tempDiffs.insert(0, d);
case "normal": tempDiffs.insert(1, d);
case "hard": tempDiffs.insert(2, d);
}
if (tempDiffs.length == 3) data.difficulties = tempDiffs;
}
}
data.metas = [];
if (includeMetaVariations && data.variants.length > 0) for (variant in data.variants) {
if (!data.metas.exists(variant) && Assets.exists(Paths.file('songs/$songName/meta-$variant.json'))) {
var meta = loadChartMeta(songName, variant, fromMods);
if (meta.variant != null) data.metas.set(variant, meta);
}
}
return data;
}
public static function parse(songName:String, ?difficulty:String):ChartData {
public static function parse(songName:String, ?difficulty:String, ?variant:String):ChartData {
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
var chartPath = Paths.chart(songName, difficulty);
var chartPath = Paths.chart(songName, difficulty, variant);
var base:ChartData = {
strumLines: [],
noteTypes: [],
@@ -167,15 +182,15 @@ class Chart {
fromMods: Paths.assetsTree.existsSpecific(chartPath, "TEXT", MODS)
};
var valid:Bool = true;
var valid:Bool = true, namePrint = '$songName $difficulty' + ((variant != null && variant != '') ? ' ($variant)' : '');
if (!Assets.exists(chartPath)) {
Logs.error('Chart for song ${songName} ($difficulty) at "$chartPath" was not found.');
Logs.error('Chart for song $namePrint at "$chartPath" was not found.');
valid = false;
}
var data:Dynamic = null;
if (valid) {
try data = Json.parse(Assets.getText(chartPath))
catch(e) Logs.trace('Could not parse chart for song ${songName} ($difficulty): ${Std.string(e)}', ERROR, RED);
catch(e) Logs.trace('Could not parse chart for song $namePrint: ${Std.string(e)}', ERROR, RED);
}
/**
@@ -207,13 +222,12 @@ class Chart {
}
#end
var loadedMeta = loadChartMeta(songName, difficulty, base.fromMods);
var loadedMeta = loadChartMeta(songName, variant, base.fromMods, false);
if (base.meta == null) base.meta = loadedMeta;
else {
for (field in Reflect.fields(base.meta)) {
var f = Reflect.field(base.meta, field);
if (f != null)
Reflect.setField(loadedMeta, field, f);
if (f != null) Reflect.setField(loadedMeta, field, f);
}
base.meta = loadedMeta;
}
@@ -222,9 +236,8 @@ class Chart {
* events.json LOADING
*/
#if REGION
var extraEvents:Array<ChartEvent> = loadEventsJson(songName);
if (extraEvents != null)
base.events = base.events.concat(extraEvents);
var extraEvents:Array<ChartEvent> = loadEventsJson(songName, variant);
if (extraEvents != null) base.events = base.events.concat(extraEvents);
#end
/**
@@ -252,61 +265,59 @@ class Chart {
/**
* Saves the chart to the specific song folder path.
* @param songFolderPath Path to the song folder (ex: `mods/your mod/songs/song/`)
* @param chart Chart to save
* @param difficulty Name of the difficulty
* @param saveSettings
* @param chart Chart to save.
* @param difficulty Name of the difficulty (Optional).
* @param variant Name of the Variant (Optional).
* @param saveSettings (Optional).
* @return Filtered chart used for saving.
*/
public static function save(songFolderPath:String, chart:ChartData, ?difficulty:String, ?saveSettings:ChartSaveSettings):ChartData {
public static function save(chart:ChartData, ?difficulty:String, ?variant:String, ?saveSettings:ChartSaveSettings):ChartData {
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
if (saveSettings == null) saveSettings = {};
if (saveSettings.saveMetaInChart == null) saveSettings.saveMetaInChart = true;
if (saveSettings.saveLocalEvents == null) saveSettings.saveLocalEvents = true;
if (saveSettings.saveGlobalEvents == null) saveSettings.saveGlobalEvents = false;
var filteredChart = filterChartForSaving(chart, saveSettings.saveMetaInChart, saveSettings.saveLocalEvents, saveSettings.saveGlobalEvents);
var meta = filteredChart.meta;
var filteredChart = filterChartForSaving(chart, saveSettings.saveMetaInChart, saveSettings.saveLocalEvents, saveSettings.saveGlobalEvents && saveSettings.seperateGlobalEvents != true);
#if sys
var saveFolder:String = saveSettings.folder == null ? "charts" : saveSettings.folder;
var songPath = saveSettings.songFolder == null ? 'songs/${chart.meta.name}' : saveSettings.songFolder, variantSuffix = variant != null && variant != "" ? '-$variant' : "";
var metaPath = 'meta$variantSuffix.json', prettyPrint = saveSettings.prettyPrint == true ? Flags.JSON_PRETTY_PRINT : null, temp:String;
if ((temp = Paths.assetsTree.getPath('assets/$songPath/$metaPath')) != null) {
songPath = temp.substr(0, temp.length - metaPath.length - 1);
metaPath = temp;
}
else if (saveSettings.songFolder == null)
metaPath = (songPath = '${Paths.getAssetsRoot()}/$songPath') + '/$metaPath';
if (!FileSystem.exists('${songFolderPath}/$saveFolder/'))
FileSystem.createDirectory('${songFolderPath}/$saveFolder/');
var chartFolder = saveSettings.folder == null ? ((variant == null || variant == '') ? 'charts' : 'charts/$variant') : saveSettings.folder;
var chartPath = '$songPath/$chartFolder/${difficulty.trim()}.json';
var chartPath = '${songFolderPath}/$saveFolder/${difficulty.trim()}.json';
var metaPath = '${songFolderPath}/meta.json';
if (saveSettings.saveChart == null || saveSettings.saveChart == true)
CoolUtil.safeSaveFile(chartPath, Json.stringify(filteredChart, null, prettyPrint));
CoolUtil.safeSaveFile(chartPath, Json.stringify(filteredChart, null, saveSettings.prettyPrint == true ? Flags.JSON_PRETTY_PRINT : null));
if (saveSettings.overrideExistingMeta || !FileSystem.exists(metaPath))
CoolUtil.safeSaveFile(metaPath, Json.stringify(filterMetaForSaving(chart.meta), null, prettyPrint));
if (meta != null && (saveSettings.overrideExistingMeta || !FileSystem.exists(metaPath)))
CoolUtil.safeSaveFile(metaPath, makeMetaSaveable(meta));
if (saveSettings.seperateGlobalEvents == true) {
var eventsPath = '$songPath/events$variantSuffix.json', events = filterEventsForSaving(chart.events, false, true);
if (events.length != 0) CoolUtil.safeSaveFile(eventsPath, Json.stringify({events: events}, null, prettyPrint));
}
#end
return filteredChart;
}
public static function filterChartForSaving(chart:ChartData, ?saveMetaInChart:Bool, ?saveLocalEvents:Bool, ?saveGlobalEvents:Bool):ChartData {
public static function filterChartForSaving(chart:ChartData, saveMetaInChart = true, ?saveLocalEvents:Bool, ?saveGlobalEvents:Bool):ChartData {
var meta = chart.meta, events = chart.events;
chart.meta = null;
chart.events = null;
var data = Reflect.copy(chart); // make a copy of the chart to leave the OG intact
if (saveMetaInChart != true) {
data.meta = null;
} else {
data.meta = Reflect.copy(chart.meta); // also make a copy of the metadata to leave the OG intact.
if (data.meta != null && Reflect.hasField(data.meta, "parsedColor")) Reflect.deleteField(data.meta, "parsedColor");
}
// in this part abt the events, i gotta account that these booleans can be null - Nex
if (saveLocalEvents != true && saveGlobalEvents != true) data.events = null;
else {
data.events = [];
for (event in chart.events) if ((saveLocalEvents == true && event.global != true) || (saveGlobalEvents == true && event.global == true)) {
var copy = Reflect.copy(event);
if (saveLocalEvents == true ? event.global != true : event.global == true) Reflect.deleteField(copy, "global"); // should NOT delete the field when saving with the local events and the event should have been global - Nex
data.events.push(copy);
}
if (data.events.length == 0) data.events = null;
}
chart.meta = meta;
chart.events = events;
data.meta = saveMetaInChart ? filterMetaForSaving(meta) : null;
data.events = filterEventsForSaving(events, saveLocalEvents, saveGlobalEvents);
data.fromMods = null;
var sortedData:Dynamic = {};
@@ -318,19 +329,47 @@ class Chart {
return sortedData;
}
public static inline function makeMetaSaveable(meta:ChartMetaData, prettyPrint:Bool = true):String {
public static function filterEventsForSaving(events:Array<ChartEvent>, saveLocalEvents = true, saveGlobalEvents = false):Array<ChartEvent> {
var data = [];
if (!saveLocalEvents && !saveGlobalEvents) return data;
for (event in events) if ((saveLocalEvents && event.global != true) || (saveGlobalEvents && event.global == true)) {
var copy = Reflect.copy(event);
if (saveLocalEvents ? event.global != true : event.global == true) Reflect.deleteField(copy, "global"); // should NOT delete the field when saving with the local events and the event should have been global - Nex
data.push(copy);
}
return data;
}
public static inline function makeMetaSaveable(meta:ChartMetaData, prettyPrint:Bool = true):String
return Json.stringify(filterMetaForSaving(meta), null, prettyPrint ? Flags.JSON_PRETTY_PRINT : null);
public static inline function filterMetaForSaving(meta:ChartMetaData):ChartMetaData {
var data:Dynamic = Reflect.copy(meta);
if (data.color != null) data.color = FlxColor.fromInt(data.color).toWebString(); // dont even ask me - Nex
if (data.color != null) data.color = FlxColor.fromInt(data.color).toWebString(); // dont even ask me - Nex
Reflect.deleteField(data, "name");
Reflect.deleteField(data, 'parsedColor');
Reflect.deleteField(data, 'metas');
return Json.stringify(data, null, prettyPrint ? Flags.JSON_PRETTY_PRINT : null);
Reflect.deleteField(data, "variant");
if (data.instSuffix != null && data.instSuffix == "") Reflect.deleteField(data, "instSuffix");
if (data.vocalsSuffix != null && data.vocalsSuffix == "") Reflect.deleteField(data, "vocalsSuffix");
if (data.variants != null && data.variants.length == 0) Reflect.deleteField(data, "variants");
return data;
}
}
typedef ChartSaveSettings = {
var ?overrideExistingMeta:Bool;
var ?prettyPrint:Bool;
var ?saveMetaInChart:Bool;
var ?saveLocalEvents:Bool;
var ?saveGlobalEvents:Bool;
var ?prettyPrint:Bool;
var ?saveChart:Bool;
var ?overrideExistingMeta:Bool;
var ?seperateGlobalEvents:Bool;
var ?folder:String;
var ?songFolder:String;
}
+10 -3
View File
@@ -18,16 +18,23 @@ typedef ChartData = {
typedef ChartMetaData = {
public var name:String;
public var ?bpm:Float;
public var ?variant:String;
public var ?displayName:String;
public var ?bpm:Float;
public var ?beatsPerMeasure:Float;
public var ?stepsPerBeat:Int;
public var ?difficulties:Array<String>;
public var ?variants:Array<String>;
public var ?customValues:Dynamic;
public var ?icon:String;
public var ?color:FlxColor;
public var ?difficulties:Array<String>;
public var ?coopAllowed:Bool;
public var ?opponentModeAllowed:Bool;
public var ?customValues:Dynamic;
public var ?metas:Map<String, ChartMetaData>;
public var ?instSuffix:String;
public var ?vocalsSuffix:String;
+7 -11
View File
@@ -26,12 +26,12 @@ class VSliceParser {
stage: Flags.DEFAULT_STAGE,
codenameChart: true
};
parseChart(Reflect.field(chartData.notes, diff), metaData, chartData.events, base);
parseChart(Reflect.field(chartData.notes, diff), metaData, chartData.events, base, resultMeta);
resultCharts.push({diffName: diff, chart: base});
}
}
public static function parseChart(data:Dynamic, metaData:Dynamic, events:Dynamic, result:ChartData) {
public static function parseChart(data:Dynamic, metaData:Dynamic, events:Dynamic, result:ChartData, resultMeta:ChartMetaData) {
// vslice chart parsing
var data:Array<SwagNote> = data;
var metadata:SwagMetadata = metaData;
@@ -45,14 +45,14 @@ class VSliceParser {
type: 0,
position: (p2isGF = metadata.playData.characters.opponent.startsWith("gf")) ? "girlfriend" : "dad",
notes: [],
vocalsSuffix: "-Opponent"
vocalsSuffix: '-${metadata.playData.characters.opponent}${resultMeta.vocalsSuffix != null ? resultMeta.vocalsSuffix : ""}'
});
result.strumLines.push({
characters: [metadata.playData.characters.player],
type: 1,
position: "boyfriend",
notes: [],
vocalsSuffix: "-Player"
vocalsSuffix: '-${metadata.playData.characters.player}${resultMeta.vocalsSuffix != null ? resultMeta.vocalsSuffix : ""}'
});
var gfName = metadata.playData.characters.girlfriend;
if (!p2isGF && gfName != "none") {
@@ -108,7 +108,7 @@ class VSliceParser {
{
case "FocusCamera":
var isPosOnly = false;
var arr:Array<Dynamic> = [switch(values.char) {
var arr:Array<Dynamic> = [switch(values is Int ? values : values.char) {
// more cases here in the future if they add em? (i hope not) - Nex
case 0: 1;
case 1: 0;
@@ -177,16 +177,12 @@ class VSliceParser {
var songID:String = songID == null ? songName.toLowerCase().replace(" ", "-") : songID;
var firstTimeChange:SwagTimeChange = data.timeChanges[0];
result.name = songID;
result.bpm = firstTimeChange.bpm.getDefault(Flags.DEFAULT_BPM);
result.beatsPerMeasure = firstTimeChange.n.getDefault(Flags.DEFAULT_BEATS_PER_MEASURE);
result.stepsPerBeat = firstTimeChange.d.getDefault(Flags.DEFAULT_STEPS_PER_BEAT);
result.displayName = songName;
result.icon = Flags.DEFAULT_HEALTH_ICON;
result.color = 0xFFFFFF;
result.opponentModeAllowed = true;
result.coopAllowed = true;
result.difficulties = data.playData.difficulties.concat(data.playData.songVariations.getDefault([]));
result.difficulties = data.playData.difficulties.copy();
if (data.playData.songVariations != null) result.variants = data.playData.songVariations.copy();
if (result.customValues == null) result.customValues = {};
result.customValues.artist = data.artist;
@@ -9,6 +9,10 @@ final class FreeplaySongSelectEvent extends CancellableEvent {
* Difficulty name
*/
public var difficulty:String;
/**
* Variation Name
*/
public var variant:String;
/**
* Whenever opponent mode is enabled or not.
*/
+8 -1
View File
@@ -127,6 +127,9 @@ class Flags {
public static var DEFAULT_NOTE_MS_LIMIT:Float = 1500;
public static var DEFAULT_NOTE_SCALE:Float = 0.7;
#if MODCHARTING_FEATURES
public static var DEFAULT_MODCHART_HOLD_SUBDIVISIONS:Int = 4;
#end
@:also(funkin.game.Character.FALLBACK_DEAD_CHARACTER)
public static var DEFAULT_GAMEOVER_CHARACTER:String = "bf-dead";
@@ -269,6 +272,8 @@ class Flags {
@:bypass public static var customFlags:Map<String, String> = [];
public static function loadFromData(flags:Map<String, String>, data:String) {
WINDOW_TITLE_USE_MOD_NAME = false;
if (!(data.length > 0)) return;
var res = IniUtil.parseString(data);
@@ -286,7 +291,7 @@ class Flags {
}
}
if (!flags.exists("WINDOW_TITLE_USE_MOD_NAME")) WINDOW_TITLE_USE_MOD_NAME = !flags.exists('TITLE');
if (!flags.exists("WINDOW_TITLE_USE_MOD_NAME")) WINDOW_TITLE_USE_MOD_NAME = !flags.exists('TITLE') && flags.exists('MOD_NAME');
else WINDOW_TITLE_USE_MOD_NAME = parseBool(flags.get("WINDOW_TITLE_USE_MOD_NAME"));
flags.remove("WINDOW_TITLE_USE_MOD_NAME");
@@ -305,6 +310,8 @@ class Flags {
for(name=>value in flags)
if(!parse(name, value))
customFlags.set(name, value);
Options.modchartingHoldSubdivisions = DEFAULT_MODCHART_HOLD_SUBDIVISIONS;
}
/**
@@ -43,7 +43,6 @@ class FunkinSaveMacro {
* SAVE FUNCTION
*/
var saveFuncBlocks:Array<Expr> = [for(f in fieldNames) macro $i{saveFieldName}.data.$f = $i{f}];
saveFuncBlocks.push(macro $i{saveFieldName}.flush());
fields.push({
@@ -59,6 +58,8 @@ class FunkinSaveMacro {
access: [APublic, AStatic]
});
var loadFuncBlocks:Array<Expr> = [for(f in fieldNames) macro if ($i{saveFieldName}.data.$f != null) $i{f} = $i{saveFieldName}.data.$f];
/**
* LOAD FUNCTION
*/
@@ -69,10 +70,7 @@ class FunkinSaveMacro {
args: [],
expr: {
pos: Context.currentPos(),
expr: EBlock([for(f in fieldNames)
macro if ($i{saveFieldName}.data.$f != null)
$i{f} = $i{saveFieldName}.data.$f
])
expr: EBlock(loadFuncBlocks)
}
}),
access: [APublic, AStatic]
@@ -63,7 +63,7 @@ class UpdateUtil {
}
static function __checkForUpdates():UpdateCheckCallback {
var curTag = 'v${Flags.VERSION}', error = false;
var curTag = 'v' + (Flags.VERSION == null ? Application.current.meta.get('version') : Flags.VERSION), error = false;
var newUpdates = __doReleaseFiltering(GitHub.getReleases(Flags.REPO_OWNER, Flags.REPO_NAME, (e) -> {
error = true;
}), curTag);
@@ -38,6 +38,6 @@ class FlxInterpolateColor {
alpha = (color.alphaFloat - alpha) * ratio + alpha;
}
public inline function fpsLerpTo(color:FlxColor, ratio:Float)
lerpTo(color, ratio * 60 * FlxG.elapsed);
public inline function fpsLerpTo(color:FlxColor, ratio:Float, ?delta:Float)
lerpTo(color, CoolUtil.getFPSRatio(ratio, delta));
}
+2 -1
View File
@@ -26,6 +26,7 @@ typedef WeekCharacter = {
typedef WeekSong = {
var name:String;
var hide:Bool;
var ?hide:Bool;
var ?variation:String;
var ?displayName:String;
}
+39 -43
View File
@@ -28,6 +28,7 @@ import flixel.util.FlxColor;
class Charter extends UIState {
public static var __song:String;
static var __diff:String;
static var __variant:String;
static var __reload:Bool;
var chart(get, never):ChartData;
@@ -114,11 +115,12 @@ class Charter extends UIState {
private var SONGPOSINFO_BPM = TU.getRaw("songPosInfo.bpm");
private var SONGPOSINFO_TIMESIGNATURE = TU.getRaw("songPosInfo.timeSignature");
public function new(song:String, diff:String, reload:Bool = true) {
public function new(song:String, diff:String, variant:String, reload:Bool = true) {
super();
if (song != null) {
__song = song;
__diff = diff;
__variant = variant;
__reload = reload;
}
}
@@ -578,7 +580,7 @@ class Charter extends UIState {
dataDisplay.screenCenter(Y);
dataDisplay.cameras = [charterCamera]; dataDisplay.x = -dataDisplay.width; add(dataDisplay);*/
DiscordUtil.call("onEditorLoaded", ["Chart Editor", __song + " (" + __diff + ")"]);
DiscordUtil.call("onEditorLoaded", ["Chart Editor", __song + " (" + __diff + ")" + (__variant != null && __variant != "" ? " (" + __variant + ")" : "")]);
}
override function destroy() {
@@ -595,9 +597,10 @@ class Charter extends UIState {
public function loadSong() {
if (__reload) {
EventsData.reloadEvents();
PlayState.loadSong(__song, __diff, false, false);
PlayState.loadSong(__song, __diff, __variant, false, false);
__resetStatics();
}
Conductor.setupSong(PlayState.SONG);
noteTypes = PlayState.SONG.noteTypes;
@@ -612,14 +615,14 @@ class Charter extends UIState {
gridBackdrops.createGrids(PlayState.SONG.strumLines.length);
for(strL in PlayState.SONG.strumLines)
var noteCount:Int = 0;
for (strL in PlayState.SONG.strumLines) {
createStrumline(strumLines.members.length, strL, false, false);
noteCount += strL.notes.length;
}
// create notes
notesGroup.autoSort = false;
var noteCount:Int = 0;
for (strL in PlayState.SONG.strumLines)
noteCount += strL.notes.length;
notesGroup.preallocate(noteCount);
var notesCreated:Int = 0;
@@ -683,10 +686,10 @@ class Charter extends UIState {
var wavesToGenerate:Array<{name:String, sound:FlxSound}> = [];
if (FlxG.sound.music.loaded)
wavesToGenerate.push({name: "Inst.ogg", sound: FlxG.sound.music});
wavesToGenerate.push({name: 'Inst${PlayState.SONG.meta.instSuffix}.ogg', sound: FlxG.sound.music});
if (vocals.loaded)
wavesToGenerate.push({name: "Voices.ogg", sound: vocals});
wavesToGenerate.push({name: 'Voices${PlayState.SONG.meta.vocalsSuffix}.ogg', sound: vocals});
for (strumLine in strumLines)
if (strumLine.vocals != null && strumLine.strumLine.vocalsSuffix != null && strumLine.strumLine.vocalsSuffix != "" && strumLine.vocals.loaded)
@@ -788,12 +791,13 @@ class Charter extends UIState {
autoSaveTimer = Options.charterAutoSaveTime;
if (!autoSaveNotif.cancelled) {
buildChart();
var songPath:String = '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}';
if (Options.charterAutoSavesSeparateFolder)
Chart.save(songPath, PlayState.SONG, __autoSaveLocation, {saveMetaInChart: true, saveLocalEvents: true, saveGlobalEvents: true, folder: "autosaves", prettyPrint: Options.editorCharterPrettyPrint});
else // These two chart saves are particular, to avoid any kind of loss, stuff like meta, global and local events will be save all together - Nex
Chart.save(songPath, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: true, saveLocalEvents: true, saveGlobalEvents: true, prettyPrint: Options.editorCharterPrettyPrint});
Chart.save(PlayState.SONG, __diff.toLowerCase(), __autoSaveLocation, {saveMetaInChart: true, saveLocalEvents: true, seperateGlobalEvents: true, folder: 'autosaves', prettyPrint: Options.editorCharterPrettyPrint});
else
Chart.save(PlayState.SONG, __diff.toLowerCase(), __variant, {saveMetaInChart: true, saveLocalEvents: true, seperateGlobalEvents: true, prettyPrint: Options.editorCharterPrettyPrint});
FlxG.sound.play(Paths.sound('editors/save'));
undos.save();
}
autoSaveNotif.cancelled = false;
@@ -1206,7 +1210,9 @@ class Charter extends UIState {
var strL = strumLines.members[strumLineID].strumLine;
strumLines.members[strumLineID].destroy();
strumLines.members.remove(strumLines.members[strumLineID]);
strumLines.members[strumLineID] = strumLines.members[strumLines.members.length - 1];
strumLines.members.pop();
@:privateAccess strumLines.length--;
strumLines.refreshStrumlineIDs();
strumLines.snapStrums();
@@ -1476,8 +1482,8 @@ class Charter extends UIState {
public static function saveChart(shouldBuild:Bool = true, withEvents:Bool = true) {
#if sys
FlxG.sound.play(Paths.sound('editors/save'));
saveTo('${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}', !withEvents, shouldBuild);
if (shouldBuild && instance != null) instance.buildChart();
Chart.save(PlayState.SONG, __diff.toLowerCase(), __variant, {saveMetaInChart: false, saveLocalEvents: withEvents, prettyPrint: Options.editorCharterPrettyPrint});
if (undos != null) undos.save();
#else
saveChartAs(shouldBuild, withEvents);
@@ -1493,13 +1499,9 @@ class Charter extends UIState {
public static function saveEvents(shouldBuild:Bool = true) {
#if sys
FlxG.sound.play(Paths.sound('editors/save'));
if (shouldBuild && instance != null) instance.buildChart();
var data = {events: Chart.filterChartForSaving(PlayState.SONG, false, false, true).events};
var path = '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/events.json';
if (data.events != null && data.events.length > 0) CoolUtil.safeSaveFile(path, Json.stringify(data, null, Options.editorCharterPrettyPrint ? Flags.JSON_PRETTY_PRINT : null));
else if (FileSystem.exists(path)) FileSystem.deleteFile(path); // Instead of replacing with a useless empty file, deletes the file directly - Nex
Chart.save(PlayState.SONG, __diff.toLowerCase(), __variant, {saveChart: false, seperateGlobalEvents: true, prettyPrint: Options.editorCharterPrettyPrint});
if (undos != null) undos.save();
#else
saveEventsAs(shouldBuild);
#end
@@ -1507,7 +1509,7 @@ class Charter extends UIState {
public static function saveEventsAs(shouldBuild:Bool = true) {
if (shouldBuild && instance != null) instance.buildChart();
var data = {events: Chart.filterChartForSaving(PlayState.SONG, false, false, true).events};
var data = {events: Chart.filterEventsForSaving(PlayState.SONG.events, false, true)};
saveAs(data, null, Options.editorCharterPrettyPrint ? Flags.JSON_PRETTY_PRINT : null, {
defaultSaveFile: 'events.json'
@@ -1516,12 +1518,8 @@ class Charter extends UIState {
public static function saveMeta(shouldBuild:Bool = true) {
#if sys
FlxG.sound.play(Paths.sound('editors/save'));
if (shouldBuild && instance != null) instance.buildChart();
CoolUtil.safeSaveFile(
'${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/meta.json',
Json.stringify(PlayState.SONG.meta == null ? {} : Chart.filterChartForSaving(PlayState.SONG, true, false, false).meta, null, Flags.JSON_PRETTY_PRINT)
);
Chart.save(PlayState.SONG, __diff.toLowerCase(), __variant, {saveChart: false, overrideExistingMeta: true, prettyPrint: true});
#else
saveMetaAs(shouldBuild);
#end
@@ -1545,9 +1543,7 @@ class Charter extends UIState {
}, null, shouldBuild);
}
public static function saveAs(data:Dynamic, ?replacer:(key:Dynamic, value:Dynamic) -> Dynamic, ?space:String, ?options:SaveSubstate.SaveSubstateData, ?saveOptions:Map<String, Bool>, shouldBuild:Bool = true) {
FlxG.sound.play(Paths.sound('editors/save'));
if (shouldBuild && instance != null) instance.buildChart();
public static function saveAs(data:Dynamic, ?replacer:(key:Dynamic, value:Dynamic) -> Dynamic, ?space:String, ?options:SaveSubstate.SaveSubstateData, ?saveOptions:Map<String, Bool>, shouldBuild:Bool = true) { if (shouldBuild && instance != null) instance.buildChart();
var cur = FlxG.state;
while(true) {
if (instance != null || cur.subState == null) return cur.openSubState(new SaveSubstate(Json.stringify(data, replacer, space), options, saveOptions));
@@ -1558,7 +1554,7 @@ class Charter extends UIState {
#if sys
public static function saveTo(path:String, separateEvents:Bool = false, shouldBuild:Bool = true) {
if (shouldBuild && instance != null) instance.buildChart();
Chart.save(path, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: false, saveLocalEvents: !separateEvents, prettyPrint: Options.editorCharterPrettyPrint});
Chart.save(PlayState.SONG, __diff.toLowerCase(), __variant, {saveMetaInChart: false, saveLocalEvents: !separateEvents, songFolder: path, prettyPrint: Options.editorCharterPrettyPrint});
}
#end
#end
@@ -1570,17 +1566,17 @@ class Charter extends UIState {
else {undos = null; FlxG.switchState(new CharterSelection()); Charter.instance.__clearStatics();}
}
function _file_save_all(_) saveEverything();
function _file_save(_) saveChart();
function _file_saveas(_) saveChartAs();
function _file_events_save(_) saveEvents();
function _file_events_saveas(_) saveEventsAs();
function _file_save_no_events(_) saveChart(true, false);
function _file_saveas_no_events(_) saveChartAs(true, false);
function _file_meta_save(_) saveMeta();
function _file_meta_saveas(_) saveMetaAs();
function _file_saveas_fnflegacy(_) saveLegacyChartAs();
function _file_saveas_psych(_) savePsychChartAs();
function _file_save_all(_) {saveEverything(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_save(_) {saveChart(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_saveas(_) {saveChartAs(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_events_save(_) {saveEvents(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_events_saveas(_) {saveEventsAs(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_save_no_events(_) {saveChart(true, false); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_saveas_no_events(_) {saveChartAs(true, false); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_meta_save(_) {saveMeta(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_meta_saveas(_) {saveMetaAs(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_saveas_fnflegacy(_) {saveLegacyChartAs(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_saveas_psych(_) {savePsychChartAs(); FlxG.sound.play(Paths.sound('editors/save'));}
function _edit_copy(_, playSFX=true) {
if (playSFX) FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_COPY_SOUND));
@@ -150,20 +150,21 @@ class CharterMetaDataScreen extends UISubstateWindow {
Reflect.setProperty(customVals, vals.propertyText.label.text, vals.valueText.label.text);
}
PlayState.SONG.meta = {
name: songNameTextBox.label.text,
bpm: bpmStepper.value,
needsVoices: needsVoicesCheckbox.checked,
beatsPerMeasure: Std.int(beatsPerMeasureStepper.value),
stepsPerBeat: Std.int(16 / denominatorStepper.value),
displayName: displayNameTextBox.label.text,
icon: iconTextBox.label.text,
color: colorWheel.curColor,
opponentModeAllowed: opponentModeCheckbox.checked,
coopAllowed: coopAllowedCheckbox.checked,
difficulties: [for (diff in difficultiesTextBox.label.text.split(",")) diff.trim()],
customValues: customVals,
};
var meta = PlayState.SONG.meta;
if (meta == null) meta = {name: songNameTextBox.label.text};
else meta.name = songNameTextBox.label.text;
meta.bpm = bpmStepper.value;
meta.needsVoices = needsVoicesCheckbox.checked;
meta.beatsPerMeasure = Std.int(beatsPerMeasureStepper.value);
meta.stepsPerBeat = Std.int(16 / denominatorStepper.value);
meta.displayName = displayNameTextBox.label.text;
meta.icon = iconTextBox.label.text;
meta.color = colorWheel.curColor;
meta.opponentModeAllowed = opponentModeCheckbox.checked;
meta.coopAllowed = coopAllowedCheckbox.checked;
meta.difficulties = [for (diff in difficultiesTextBox.label.text.split(",")) diff.trim()];
meta.customValues = customVals;
Charter.instance.updateBPMEvents();
Charter.instance.vocals.muted = !needsVoicesCheckbox.checked;
@@ -25,55 +25,72 @@ class CharterSelectionScreen extends EditorTreeMenuScreen {
public var songList:Array<String> = [];
public var curSong:ChartMetaData;
inline public function makeChartOption(d:String, name:String):TextOption {
return new TextOption(d, getID('acceptDifficulty'), () -> FlxG.switchState(new Charter(name, d)));
inline public function makeChartOption(d:String, v:String, name:String):TextOption {
return new TextOption(d, getID('acceptDifficulty'), () -> FlxG.switchState(new Charter(name, d, v)));
}
inline public function makeVariationOption(s:ChartMetaData):TextOption {
return new TextOption(s.variant, getID('acceptVariation'), " >", () -> openSongOption(s, false));
}
public function openSongOption(s:ChartMetaData, first = true) {
curSong = s;
var isVariant = s.variant != null && s.variant != '';
var screen = new EditorTreeMenuScreen((first || !isVariant) ? (s.name + (isVariant ? ' (${s.variant})' : '')) : s.variant, getID('selectDifficulty'));
for (d in s.difficulties) if (d != '') screen.add(makeChartOption(d, isVariant ? s.variant : null, s.name));
screen.add(new Separator());
for (v in s.variants) if (s.metas.get(v) != null) screen.add(makeVariationOption(s.metas.get(v)));
#if sys
screen.insert(0, new NewOption(getID('newDifficulty'), getID('newDifficultyDesc'), () -> {
parent.openSubState(new ChartCreationScreen(saveChart));
}));
if (!first) screen.curSelected = 1;
else {
cast(screen.members[0], NewOption).itemHeight = 120;
screen.insert(1, new NewOption(getID('newVariation'), getID('newVariationDesc'), () -> {
parent.openSubState(new VariationCreationScreen(s, saveSong));
}));
screen.curSelected = 2;
}
#end
parent.addMenu(screen);
}
public function makeSongOption(s:ChartMetaData):IconOption {
songList.push(s.name.toLowerCase());
var opt = new IconOption(s.name, getID('acceptSong'), s.icon, () -> {
curSong = s;
var screen = new EditorTreeMenuScreen(s.name, getID('selectDifficulty'), [
for (d in s.difficulties) if (d != '') makeChartOption(d, s.name)
]);
#if sys
screen.insert(0, new NewOption(getID('newDifficulty'), getID('newDifficultyDesc'), () -> {
parent.openSubState(new ChartCreationScreen(saveChart));
}));
screen.curSelected = 1;
#end
parent.addMenu(screen);
});
opt.suffix = ' >';
var opt = new IconOption(s.name, getID('acceptSong'), s.icon, () -> openSongOption(s, true));
opt.suffix = " >";
opt.editorFlashColor = s.color.getDefault(FlxColor.WHITE);
return opt;
}
public function new() {
super('editor.chart.name', 'charterSelection.desc', 'charterSelection.', 'newSong', 'newSongDesc', () -> {
super('editor.chart.name', 'charterSelection.desc', 'charterSelection.', 'newSong', 'newSongDesc', #if sys () -> {
parent.openSubState(new SongCreationScreen(saveSong));
});
} #end);
freeplayList = FreeplaySonglist.get(false);
for (i => s in freeplayList.songs) add(makeSongOption(s));
}
#if sys
public function saveSong(creation:SongCreationData, ?callback:String -> SongCreationData -> Void) {
var songAlreadyExists:Bool = songList.contains(creation.meta.name.toLowerCase());
if (songAlreadyExists) {
public function saveSong(creation:SongCreationData, ?callback:String -> Void) {
var variant = creation.meta.variant != null && creation.meta.variant != "" ? creation.meta.variant : null;
if (variant != null && curSong != null ? curSong.metas.exists(variant) : songList.contains(creation.meta.name.toLowerCase())) {
parent.openSubState(new UIWarningSubstate(TU.translate("chartCreation.warnings.song-exists-title"), TU.translate("chartCreation.warnings.song-exists-body"), [
{label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: (t) -> {}},
]));
return;
}
var songFolder:String = '${Paths.getAssetsRoot()}/songs/${creation.meta.name}';
var songFolder:String = '${Paths.getAssetsRoot()}/songs/${variant != null && curSong != null ? curSong.name : creation.meta.name}';
#if sys
// Make Directories
@@ -82,24 +99,37 @@ class CharterSelectionScreen extends EditorTreeMenuScreen {
sys.FileSystem.createDirectory('$songFolder/charts');
// Save Files
CoolUtil.safeSaveFile('$songFolder/meta.json', Chart.makeMetaSaveable(creation.meta));
if (creation.instBytes != null) sys.io.File.saveBytes('$songFolder/song/Inst.${Flags.SOUND_EXT}', creation.instBytes);
if (creation.voicesBytes != null) sys.io.File.saveBytes('$songFolder/song/Voices.${Flags.SOUND_EXT}', creation.voicesBytes);
var instSuffix = creation.meta.instSuffix != null ? creation.meta.instSuffix : '', vocalsSuffix = creation.meta.vocalsSuffix != null ? creation.meta.vocalsSuffix : '';
CoolUtil.safeSaveFile('$songFolder/meta${variant != null ? "-" + variant : ""}.json', Json.stringify(Chart.filterMetaForSaving(creation.meta), null, Flags.JSON_PRETTY_PRINT));
if (creation.instBytes != null) sys.io.File.saveBytes('$songFolder/song/Inst$instSuffix.${Flags.SOUND_EXT}', creation.instBytes);
if (creation.voicesBytes != null) sys.io.File.saveBytes('$songFolder/song/Voices$vocalsSuffix.${Flags.SOUND_EXT}', creation.voicesBytes);
if (creation.playerVocals != null) sys.io.File.saveBytes('$songFolder/song/Voices-Player.${Flags.SOUND_EXT}', creation.playerVocals);
if (creation.oppVocals != null) sys.io.File.saveBytes('$songFolder/song/Voices-Opponent.${Flags.SOUND_EXT}', creation.oppVocals);
if (creation.playerVocals != null) sys.io.File.saveBytes('$songFolder/song/Voices-Player$vocalsSuffix.${Flags.SOUND_EXT}', creation.playerVocals);
if (creation.oppVocals != null) sys.io.File.saveBytes('$songFolder/song/Voices-Opponent$vocalsSuffix.${Flags.SOUND_EXT}', creation.oppVocals);
#end
if (callback != null) callback(songFolder, creation);
if (callback != null) callback(songFolder);
// Add to List
freeplayList.songs.insert(0, creation.meta);
insert(1, makeSongOption(creation.meta));
if (variant != null && curSong != null) {
if (curSong.variants == null) curSong.variants = [];
if (!curSong.variants.contains(variant)) curSong.variants.push(variant);
curSong.metas.set(variant, creation.meta);
parent.tree.last().add(makeVariationOption(creation.meta));
var metaPath = '$songFolder/meta${curSong.variant != null && curSong.variant == "" ? "-" + curSong.variant : ""}.json';
CoolUtil.safeSaveFile(metaPath, Chart.makeMetaSaveable(curSong));
}
else {
freeplayList.songs.insert(0, creation.meta);
insert(1, makeSongOption(creation.meta));
}
}
public function saveChart(name:String, data:ChartData) {
var difficultyAlreadyExists:Bool = curSong.difficulties.contains(name);
if (difficultyAlreadyExists) {
if (curSong.difficulties.contains(name)) {
parent.openSubState(new UIWarningSubstate(TU.translate("chartCreation.warnings.chart-exists-title"), TU.translate("chartCreation.warnings.chart-exists-body"), [
{label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: (t) -> {}},
]));
@@ -113,14 +143,15 @@ class CharterSelectionScreen extends EditorTreeMenuScreen {
// Add to List
curSong.difficulties.push(name);
parent.tree.last().insert(parent.tree.last().length - 1, makeChartOption(name, curSong.name));
var screen = parent.tree.last();
var idx = 0;
while (!(screen.members[idx] is Separator)) idx++;
screen.insert(idx, makeChartOption(name, curSong.variant != null && curSong.variant != "" ? curSong.variant : null, curSong.name));
// Add to Meta
var meta = Json.parse(sys.io.File.getContent('$songFolder/meta.json'));
if (meta.difficulties != null && !meta.difficulties.contains(name)) {
meta.difficulties.push(name);
CoolUtil.safeSaveFile('$songFolder/meta.json', Chart.makeMetaSaveable(meta));
}
var metaPath = '$songFolder/meta${curSong.variant != null && curSong.variant == "" ? "-" + curSong.variant : ""}.json';
CoolUtil.safeSaveFile(metaPath, Chart.makeMetaSaveable(curSong));
}
#end
}
@@ -127,7 +127,7 @@ class CharterStrumline extends UISprite {
var maxRow = Math.floor((icons.length-1) / 4) + 1;
for (i=>icon in icons) {
var healthIcon = new HealthIcon(Character.getIconFromCharName(icon));
healthIcon.scale.x = healthIcon.scale.y = Math.max((0.6 - (icons.length / 20)), 0.35);
healthIcon.scale.x = healthIcon.scale.y = Math.max((0.6 - (icons.length / 20)), 0.35) * (150 / Math.max(healthIcon.frameWidth, healthIcon.frameHeight));
healthIcon.updateHitbox();
healthIcon.x = FlxMath.lerp(0, Math.min(icons.length * 20, 120), (maxCol-1 != 0 ? (i % 4) / (maxCol-1) : 0));
healthIcon.y = (draggable ? 14 : 7) + FlxMath.lerp(0, Math.min(maxRow * 15, 60), (maxRow-1 != 0 ? Math.floor(i / 4) / (maxRow-1) : 0));
@@ -3,6 +3,7 @@ package funkin.editors.charter;
import flixel.group.FlxGroup;
import flixel.text.FlxText.FlxTextFormat;
import flixel.text.FlxText.FlxTextFormatMarkerPair;
import funkin.backend.chart.Chart;
import funkin.backend.chart.ChartData;
import funkin.backend.chart.FNFLegacyParser.SwagSong;
import funkin.backend.chart.PsychParser;
@@ -23,7 +24,7 @@ typedef SongCreationData = {
}
class SongCreationScreen extends UISubstateWindow {
private var onSave:Null<SongCreationData> -> Null<String -> SongCreationData -> Void> -> Void = null;
private var onSave:Null<SongCreationData> -> Null<String -> Void> -> Void = null;
public var songNameTextBox:UITextBox;
public var bpmStepper:UINumericStepper;
@@ -72,7 +73,7 @@ class SongCreationScreen extends UISubstateWindow {
public var curPage:Int = 0;
public function new(?onSave:SongCreationData -> Null<String -> SongCreationData -> Void> -> Void) {
public function new(?onSave:SongCreationData -> Null<String -> Void> -> Void) {
super();
if (onSave != null) this.onSave = onSave;
}
@@ -245,7 +246,7 @@ class SongCreationScreen extends UISubstateWindow {
var pages = isImporting ? importPages : pages;
if (curPage == pages.length-1) {
saveSongInfo();
close();
if (subState == null) close();
} else {
curPage++;
refreshPages();
@@ -296,7 +297,7 @@ class SongCreationScreen extends UISubstateWindow {
if (curPage == 1) {
importInstExplorer.selectable = importVoicesExplorer.selectable = !project;
saveButton.selectable = project ? true : (importInstExplorer.file != null);
saveButton.selectable = #if TEST_BUILD true #else project ? true : (importInstExplorer.file != null) #end;
} else if (curPage == 2) {
importIdTextBox.selectable = !project;
importChartFile.fileType = project ? "fnfc" : "json";
@@ -356,6 +357,13 @@ class SongCreationScreen extends UISubstateWindow {
iconSprite.scrollFactor.set(1, 1);
}
// for variations
function formatMeta(meta:ChartMetaData):ChartMetaData
return Chart.defaultChartMetaFields(meta);
function getChartSavePath(meta:ChartMetaData, diff:String):String
return 'charts/${meta.variant != null && meta.variant != "" ? meta.variant + "/" : ""}$diff.json';
function saveSongInfo() {
if (isImporting)
{
@@ -379,34 +387,36 @@ class SongCreationScreen extends UISubstateWindow {
saveFromVSlice(files, songId);
default /*"Psych/Legacy FNF"*/:
var songId = importIdTextBox.label.text;
var oldChart:SwagSong = Json.parse(cast importChartFile.file);
var oldChart:SwagSong = Chart.cleanSongData(Json.parse(cast importChartFile.file));
var base:ChartData = {
strumLines: [],
noteTypes: [],
events: [],
meta: {name: null},
meta: {name: songId},
scrollSpeed: Flags.DEFAULT_SCROLL_SPEED,
stage: Flags.DEFAULT_STAGE,
codenameChart: true
};
PsychParser.parse(oldChart, base);
var meta = formatMeta({
name: songId,
difficulties: ['normal'],
bpm: oldChart.bpm,
beatsPerMeasure: oldChart.beatsPerMeasure,
stepsPerBeat: oldChart.stepsPerBeat,
displayName: oldChart.song
});
if (onSave != null) onSave({
meta: {
name: songId,
difficulties: [songId],
bpm: oldChart.bpm,
beatsPerMeasure: oldChart.beatsPerMeasure,
stepsPerBeat: oldChart.stepsPerBeat,
displayName: oldChart.song
},
meta: meta,
instBytes: importInstExplorer.file,
voicesBytes: importVoicesExplorer.file,
}, (songFolder:String, creation:SongCreationData) -> {
}, (songFolder:String) -> {
#if sys
CoolUtil.safeSaveFile('$songFolder/charts/${songId}.json', Json.stringify(base, Flags.JSON_PRETTY_PRINT));
CoolUtil.safeSaveFile('$songFolder/${getChartSavePath(meta, "normal")}', Json.stringify(base, Flags.JSON_PRETTY_PRINT));
#end
});
} catch (e:haxe.Exception) {
trace(e.stack, e.message);
openSubState(new UIWarningSubstate("Importing Song/Charts: Error!", e.details(), [
{label: "Ok", color: 0xFFFF0000, onClick: function(t) {}}
]));
@@ -415,7 +425,7 @@ class SongCreationScreen extends UISubstateWindow {
for (stepper in [bpmStepper, beatsPerMeasureStepper, denominatorStepper])
@:privateAccess stepper.__onChange(stepper.label.text);
var meta:ChartMetaData = {
var meta:ChartMetaData = formatMeta({
name: songNameTextBox.label.text,
bpm: bpmStepper.value,
beatsPerMeasure: Std.int(beatsPerMeasureStepper.value),
@@ -425,8 +435,8 @@ class SongCreationScreen extends UISubstateWindow {
color: colorWheel.curColor,
opponentModeAllowed: opponentModeCheckbox.checked,
coopAllowed: coopAllowedCheckbox.checked,
difficulties: [for (diff in difficultiesTextBox.label.text.split(",")) diff.trim()],
};
difficulties: [for (diff in difficultiesTextBox.label.text.split(",")) diff.trim()]
});
if (onSave != null) onSave({
meta: meta,
@@ -442,19 +452,23 @@ class SongCreationScreen extends UISubstateWindow {
var vslicechart:NewSwagSong = Json.parse(files.get('${songId}-chart.json'));
var playData = vslicemeta.playData;
var meta:ChartMetaData = {name: songId};
var diffCharts:Array<ChartDataWithInfo> = [];
var meta:ChartMetaData = formatMeta({name: songId, needsVoices: files.get('Voices.${Flags.SOUND_EXT}') != null});
var diffCharts:Array<ChartDataWithInfo> = [], events:Array<ChartEvent> = null;
VSliceParser.parse(vslicemeta, vslicechart, meta, diffCharts, songId);
if (diffCharts.length != 0) events = diffCharts[0].chart.events;
for (diff in diffCharts) diff.chart.events = [];
if (onSave != null) onSave({
meta: meta,
instBytes: files.get('Inst.${Flags.SOUND_EXT}'),
voicesBytes: files.get('Voices.${Flags.SOUND_EXT}'), //it may exist
playerVocals: files.get('Voices-${playData.characters.playerVocals != null ? playData.characters.playerVocals[0] : playData.characters.player}.${Flags.SOUND_EXT}'),
oppVocals: files.get('Voices-${playData.characters.opponentVocals != null ? playData.characters.opponentVocals[0] : playData.characters.opponent}.${Flags.SOUND_EXT}'),
}, (songFolder:String, creation:SongCreationData) -> {
}, (songFolder:String) -> {
#if sys
for (diff in diffCharts) CoolUtil.safeSaveFile('$songFolder/charts/${diff.diffName}.json', Json.stringify(diff.chart, Flags.JSON_PRETTY_PRINT));
for (diff in diffCharts) CoolUtil.safeSaveFile('$songFolder/${getChartSavePath(meta, diff.diffName)}', Json.stringify(diff.chart, Flags.JSON_PRETTY_PRINT));
if (events != null) CoolUtil.safeSaveFile('$songFolder/events${meta.variant != null && meta.variant != "" ? "-" + meta.variant : ""}.json', Json.stringify({events: events}, Flags.JSON_PRETTY_PRINT));
#end
});
}
@@ -0,0 +1,46 @@
package funkin.editors.charter;
import flixel.text.FlxText.FlxTextFormat;
import flixel.text.FlxText.FlxTextFormatMarkerPair;
import funkin.backend.chart.ChartData;
import funkin.editors.charter.SongCreationScreen;
class VariationCreationScreen extends SongCreationScreen {
public var parentMeta:ChartMetaData;
public function new(parent:ChartMetaData, ?onSave:SongCreationData -> Null<String -> Void> -> Void) {
super(onSave);
this.parentMeta = parent;
}
override function create() {
super.create();
songNameTextBox.label.text = translateMeta("variation");
cast(songNameTextBox.members[songNameTextBox.members.length - 1], UIText).text = translateMeta("variation");
cast(importIdTextBox.members[importIdTextBox.members.length - 1], UIText).applyMarkup(
translateMeta("variation"),
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]
);
bpmStepper.value = parentMeta.bpm;
beatsPerMeasureStepper.value = parentMeta.beatsPerMeasure;
denominatorStepper.value = Std.int(16 / parentMeta.stepsPerBeat);
displayNameTextBox.label.text = parentMeta.displayName;
iconTextBox.label.text = parentMeta.icon;
opponentModeCheckbox.checked = parentMeta.opponentModeAllowed;
coopAllowedCheckbox.checked = parentMeta.coopAllowed;
colorWheel.setColor(parentMeta.color);
difficultiesTextBox.label.text = parentMeta.difficulties.join(', ');
}
override function formatMeta(meta:ChartMetaData):ChartMetaData {
meta.variant = meta.name;
meta.vocalsSuffix = meta.instSuffix = '-${meta.variant}';
meta.name = parentMeta.name;
meta.icon = parentMeta.icon;
meta.color = parentMeta.color;
return super.formatMeta(meta);
}
}
@@ -40,11 +40,11 @@ class StageCreationScreen extends UISubstateWindow {
var stageInfo:UIText;
add(stageInfo = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("title"), 28));
add(stageNameTextBox = new UITextBox(stageInfo.x, stageInfo.y + stageInfo.height + 36, translate("stage-name")));
addLabelOn(stageNameTextBox, translate("stage-name"));
add(stageNameTextBox = new UITextBox(stageInfo.x, stageInfo.y + stageInfo.height + 36, translate("stageName")));
addLabelOn(stageNameTextBox, translate("stageName"));
add(stagePathTextBox = new UITextBox(stageNameTextBox.x + 320 + 26, stageNameTextBox.y, translate("stage-path")));
addLabelOn(stagePathTextBox, translate("stage-path"));
add(stagePathTextBox = new UITextBox(stageNameTextBox.x + 320 + 26, stageNameTextBox.y, translate("stagePath")));
addLabelOn(stagePathTextBox, translate("stagePath"));
add(saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() {
saveStageInfo();
+5 -1
View File
@@ -31,7 +31,6 @@ class UIColorwheel extends UISliceSprite {
hue = curColor.hue; saturation = curColor.saturation; brightness = curColor.brightness;
colorPickerShader = new CustomShader("engine/colorPicker");
colorPickerShader.hset("hue", hue);
colorPicker = new FlxSprite(x + 12.5, (y + 125/2) - (100/2)).makeGraphic(100, 100, FlxColor.TRANSPARENT);
colorPicker.drawRoundRect(0, 0, 100, 100, 10, 10, FlxColor.WHITE);
@@ -91,6 +90,11 @@ class UIColorwheel extends UISliceSprite {
updateWheel(false);
}
public function setColor(color:FlxColor) {
hue = curColor.hue; saturation = curColor.saturation; brightness = curColor.brightness;
updateWheel(false);
}
inline function updateColorPickerSelector()
colorPickerSelector.selector.setPosition(colorPicker.x + (colorPicker.width *saturation) - 8, colorPicker.y + (colorPicker.height + (colorPicker.height * -brightness))- 8);
-3
View File
@@ -178,9 +178,6 @@ class HealthIcon extends FunkinSprite
loadGraphic(iconAsset, true, Std.int(Math.min(iconSize, assetW)), Std.int(Math.min(iconSize, assetH)));
setGraphicSize(150);
updateHitbox();
animation.add(char, [for(i in 0...iconAmt) i], 0, false, isPlayer != iconIsPlayer);
animation.play(char);
}
+11
View File
@@ -53,6 +53,13 @@ class Note extends FlxSprite
*/
public var nextSustain:Note;
/**
* The parent of the sustain.
*
* If this note is not sustain, this will be null.
*/
public var sustainParent:Null<Note>;
/**
* Name of the splash.
*/
@@ -125,6 +132,10 @@ class Note extends FlxSprite
for(field in Reflect.fields(noteData)) if(!DEFAULT_FIELDS.contains(field))
this.extra.set(field, Reflect.field(noteData, field));
// work around to set the `sustainParent`
if (isSustainNote)
sustainParent = prevNote.isSustainNote ? prevNote.sustainParent : prevNote;
x += 50;
// MAKE SURE ITS DEFINITELY OFF SCREEN?
y -= 2000;
+34 -17
View File
@@ -61,10 +61,18 @@ class PlayState extends MusicBeatState
* The remaining songs in the Story Mode playlist.
*/
public static var storyPlaylist:Array<String> = [];
/**
* The remaining variations to play with the Story Mode
*/
public static var storyVariations:Array<String> = [];
/**
* The selected difficulty name.
*/
public static var difficulty:String = Flags.DEFAULT_DIFFICULTY;
/**
* The selected variation name. (It can be null)
*/
public static var variation:Null<String>;
/**
* Whenever the week is coming from the mods folder or not.
*/
@@ -657,7 +665,7 @@ class PlayState extends MusicBeatState
persistentDraw = true;
if (SONG == null)
SONG = Chart.parse('tutorial', 'normal');
SONG = Chart.parse('tutorial', difficulty = 'normal', variation = null);
scrollSpeed = SONG.scrollSpeed;
@@ -1352,7 +1360,7 @@ class PlayState extends MusicBeatState
updateRatingStuff();
if (canAccessDebugMenus && chartingMode && controls.DEV_ACCESS)
FlxG.switchState(new funkin.editors.charter.Charter(SONG.meta.name, difficulty, false));
FlxG.switchState(new funkin.editors.charter.Charter(SONG.meta.name, difficulty, variation, false));
if (Options.camZoomOnBeat && camZooming) {
var beat = Conductor.getBeats(camZoomingEvery, camZoomingInterval, camZoomingOffset);
@@ -1687,8 +1695,8 @@ class PlayState extends MusicBeatState
*/
public function endSong():Void
{
if (gameAndCharsEvent("onSongEnd", new CancellableEvent()).cancelled) return;
endingSong = true;
gameAndCharsCall("onSongEnd");
canPause = false;
for (strumLine in strumLines.members) strumLine.vocals.stop();
@@ -1697,7 +1705,7 @@ class PlayState extends MusicBeatState
if (validScore) {
#if !switch
FunkinSave.setSongHighscore(SONG.meta.name, difficulty, {
FunkinSave.setSongHighscore(SONG.meta.name, difficulty, variation, {
score: songScore,
misses: misses,
accuracy: accuracy,
@@ -1729,6 +1737,7 @@ class PlayState extends MusicBeatState
campaignAccuracyTotal += accuracy;
campaignAccuracyCount++;
storyPlaylist.shift();
storyVariations.shift();
if (storyPlaylist.length <= 0) {
FlxG.switchState(new StoryMenuState());
@@ -1748,16 +1757,16 @@ class PlayState extends MusicBeatState
FlxG.save.flush();
}
else {
Logs.infos('Loading next song (${storyPlaylist[0].toLowerCase()}/$difficulty)', "PlayState");
Logs.infos('Loading next song (${storyPlaylist[0].toLowerCase()}/$difficulty/${storyVariations[0]})', "PlayState");
registerSmoothTransition();
__loadSong(storyPlaylist[0], difficulty);
__loadSong(storyPlaylist[0], difficulty, storyVariations[0]);
FlxG.switchState(new PlayState());
}
}
else if (chartingMode)
FlxG.switchState(new funkin.editors.charter.Charter(SONG.meta.name, difficulty, false));
FlxG.switchState(new funkin.editors.charter.Charter(SONG.meta.name, difficulty, variation, false));
else
FlxG.switchState(new FreeplayState());
}
@@ -2132,43 +2141,51 @@ class PlayState extends MusicBeatState
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
storyWeek = weekData;
storyPlaylist = [for (e in weekData.songs) e.name];
storyVariations = [for (e in weekData.songs) e.variation];
isStoryMode = true;
campaignScore = 0;
campaignMisses = 0;
campaignAccuracyTotal = 0;
campaignAccuracyCount = 0;
chartingMode = false;
opponentMode = coopMode = false;
__loadSong(storyPlaylist[0], difficulty);
chartingMode = coopMode = opponentMode = false;
__loadSong(storyPlaylist[0], difficulty, storyVariations[0]);
}
/**
* Loads a song into PlayState
* @param name Song name
* @param difficulty Chart difficulty (if invalid, will load an empty chart)
* @param variation Song Variation
* @param opponentMode Whenever opponent mode is on
* @param coopMode Whenever co-op mode is on.
*/
public static function loadSong(_name:String, ?_difficulty:String, _opponentMode:Bool = false, _coopMode:Bool = false) {
public static function loadSong(_name:String, ?_difficulty:String, ?_variation:Dynamic, _opponentMode:Bool = false, _coopMode:Bool = false) {
if (_difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
isStoryMode = false;
if (_variation is Bool) { // vackward cumpatibshit
_coopMode = _opponentMode;
_opponentMode = cast _variation;
_variation = null;
}
else if (!(_variation is String)) _variation = null;
opponentMode = _opponentMode;
chartingMode = false;
coopMode = _coopMode;
__loadSong(_name, _difficulty);
isStoryMode = chartingMode = false;
__loadSong(_name, _difficulty, cast _variation);
}
/**
* (INTERNAL) Loads a song without resetting story mode/opponent mode/coop mode values.
* @param name Song name
* @param difficulty Song difficulty
* @param difficulty Chart difficulty
* @param variation Song Variation
*/
public static function __loadSong(_name:String, _difficulty:String) {
public static function __loadSong(_name:String, _difficulty:String, _variation:String) {
difficulty = _difficulty;
variation = _variation;
seenCutscene = false;
deathCounter = 0;
SONG = Chart.parse(_name, _difficulty);
SONG = Chart.parse(_name, _difficulty, _variation);
fromMods = SONG.fromMods;
}
}
+60 -43
View File
@@ -25,9 +25,14 @@ class FreeplayState extends MusicBeatState
public var curSong:Null<ChartMetaData>;
/**
* Current song metadata difficulty
* Current song difficulties
*/
public var curSongDifficulty:Null<String>;
public var curDifficulties:Array<String>;
/**
* songs[curSelected].metas.get(curDiffMetaIndices[curDifficulty])
*/
public var curDiffMetaKeys:Array<String> = [];
/**
* Currently selected song
@@ -117,16 +122,15 @@ class FreeplayState extends MusicBeatState
curSelected = k;
}
}
updateCurSongMeta();
if (curSong != null) {
for(k=>diff in songs[curSelected].difficulties) {
if (diff == Options.freeplayLastDifficulty) {
curDifficulty = k;
}
}
updateCurDifficulties();
for(i=>diff in curDifficulties) {
if (curDiffMetaKeys[i] == Options.freeplayLastVariation && diff == Options.freeplayLastDifficulty)
curDifficulty = i;
}
updateCurSong();
DiscordUtil.call("onMenuLoaded", ["Freeplay"]);
super.create();
@@ -151,8 +155,7 @@ class FreeplayState extends MusicBeatState
var icon:HealthIcon = new HealthIcon(songs[i].icon);
icon.sprTracker = songText;
icon.setUnstretchedGraphicSize(150, 150, true);
icon.updateHitbox();
if (Math.max(icon.width, icon.height) > 150) icon.setUnstretchedGraphicSize(150, 150);
// using a FlxGroup is too much fuss!
iconArray.push(icon);
@@ -181,7 +184,6 @@ class FreeplayState extends MusicBeatState
add(scoreText);
changeSelection(0, true);
changeDiff(0, true);
changeCoopMode(0, true);
interpColor = new FlxInterpolateColor(bg.color);
@@ -257,7 +259,7 @@ class FreeplayState extends MusicBeatState
var dontPlaySongThisFrame = false;
autoplayElapsed += elapsed;
if (!disableAutoPlay && !songInstPlaying && (autoplayElapsed > timeUntilAutoplay)) {
if (curPlayingInst != (curPlayingInst = Paths.inst(curSong.name, curSongDifficulty, curSong.instSuffix))) {
if (curPlayingInst != (curPlayingInst = Paths.inst(curSong.name, curDifficulties[curDifficulty], curSong.instSuffix))) {
var streamed = false;
if (Options.streamedMusic) {
var sound = Assets.getMusic(curPlayingInst, true, false);
@@ -330,9 +332,9 @@ class FreeplayState extends MusicBeatState
public function select() {
updateCoopModes();
if (songs[curSelected].difficulties.length <= 0) return;
if (curDifficulties.length == 0) return;
var event = event("onSelect", EventManager.get(FreeplaySongSelectEvent).recycle(curSong.name, curSongDifficulty, __opponentMode, __coopMode));
var event = event("onSelect", EventManager.get(FreeplaySongSelectEvent).recycle(curSong.name, curDifficulties[curDifficulty], curSong.variant, __opponentMode, __coopMode));
if (event.cancelled) return;
@@ -341,16 +343,17 @@ class FreeplayState extends MusicBeatState
#end
Options.freeplayLastSong = curSong.name;
Options.freeplayLastDifficulty = curSongDifficulty;
Options.freeplayLastDifficulty = curDifficulties[curDifficulty];
Options.freeplayLastVariation = curSong.variant;
PlayState.loadSong(event.song, event.difficulty, event.opponentMode, event.coopMode);
PlayState.loadSong(event.song, event.difficulty, event.variant, event.opponentMode, event.coopMode);
FlxG.switchState(new PlayState());
}
public function convertChart() {
trace('Converting ${curSong.name} (${curSongDifficulty}) to Codename format...');
var chart = Chart.parse(curSong.name, curSongDifficulty);
Chart.save('${Main.pathBack}assets/songs/${curSong.name}', chart, curSongDifficulty);
trace('Converting ${curSong.name} ${curDifficulties[curDifficulty]} ${curSong.variant} to Codename format...');
var chart = Chart.parse(curSong.name, curDifficulties[curDifficulty], curSong.variant);
Chart.save(chart, curDifficulties[curDifficulty], curSong.variant);
}
/**
@@ -358,39 +361,35 @@ class FreeplayState extends MusicBeatState
* @param change How much to change.
* @param force Force the change if `change` is equal to 0
*/
public function changeDiff(change:Int = 0, force:Bool = false)
{
public function changeDiff(change:Int = 0, force:Bool = false) {
if (change == 0 && !force) return;
var song = songs[curSelected];
var validDifficulties = song.difficulties.length > 0;
var event = event("onChangeDiff", EventManager.get(MenuChangeEvent).recycle(curDifficulty, validDifficulties ? FlxMath.wrap(curDifficulty + change, 0, song.difficulties.length-1) : 0, change));
var validDifficulties = curDifficulties.length > 0;
var event = event("onChangeDiff", EventManager.get(MenuChangeEvent).recycle(curDifficulty, validDifficulties ? FlxMath.wrap(curDifficulty + change, 0, curDifficulties.length-1) : 0, change));
if (event.cancelled) {
if (force) updateCurSongMeta();
if (force) updateCurSong();
return;
}
var prevInstSuffix = curSong.instSuffix;
var prevSong = curSong;
curDifficulty = event.value;
updateCurSongMeta();
updateCurSong();
updateScore();
#if PRELOAD_ALL
if (curSong.instSuffix != prevInstSuffix) {
if (curSong != prevSong) {
autoplayElapsed = 0;
songInstPlaying = false;
}
#end
if (song.difficulties.length > 1)
diffText.text = '< ${song.difficulties[curDifficulty].toUpperCase()} >';
else
diffText.text = validDifficulties ? song.difficulties[curDifficulty].toUpperCase() : "-";
var text = validDifficulties ? curDifficulties[curDifficulty].toUpperCase() + (curSong != songs[curSelected] ? ' (${curSong.variant.toUpperCase()})' : '') : '-';
diffText.text = curDifficulties.length > 1 ? '< $text >' : text;
}
function updateScore() {
if (songs[curSelected].difficulties.length <= 0) {
if (curDifficulties.length == 0) {
intendedScore = 0;
return;
}
@@ -398,7 +397,7 @@ class FreeplayState extends MusicBeatState
var changes:Array<HighscoreChange> = [];
if (__coopMode) changes.push(CCoopMode);
if (__opponentMode) changes.push(COpponentMode);
var saveData = FunkinSave.getSongHighscore(curSong.name, curSongDifficulty, changes);
var saveData = FunkinSave.getSongHighscore(curSong.name, curDifficulties[curDifficulty], curSong.variant, changes);
intendedScore = saveData.score;
}
@@ -448,8 +447,7 @@ class FreeplayState extends MusicBeatState
* @param change How much to change
* @param force Force the change, even if `change` is equal to 0.
*/
public function changeSelection(change:Int = 0, force:Bool = false)
{
public function changeSelection(change:Int = 0, force:Bool = false) {
if (change == 0 && !force) return;
var event = event("onChangeSelection", EventManager.get(MenuChangeEvent).recycle(curSelected, FlxMath.wrap(curSelected + change, 0, songs.length-1), change));
@@ -458,6 +456,14 @@ class FreeplayState extends MusicBeatState
curSelected = event.value;
if (event.playMenuSFX) CoolUtil.playMenuSFX(SCROLL, 0.7);
var prevDiff = curDifficulties[curDifficulty], prevVariant = curDiffMetaKeys[curDifficulty];
updateCurDifficulties();
for (i => diff in curDifficulties) if (diff == prevDiff && curDiffMetaKeys[i] == prevVariant) {
curDifficulty = i;
break;
}
changeDiff(0, true);
#if PRELOAD_ALL
@@ -491,13 +497,24 @@ class FreeplayState extends MusicBeatState
}
}
function updateCurSongMeta() {
var song = songs[curSelected];
if (song == null) {
curSong = null;
curSongDifficulty = null;
function updateCurDifficulties() {
curDiffMetaKeys.resize(0);
curDifficulties = songs[curSelected].difficulties.copy();
for (i in 0...curDifficulties.length) curDiffMetaKeys.push(null);
if (songs[curSelected].variants != null) {
var meta:ChartMetaData;
for (variant in songs[curSelected].variants) if ((meta = songs[curSelected].metas.get(variant)) != null) {
curDifficulties = curDifficulties.concat(meta.difficulties);
for (i in 0...meta.difficulties.length) curDiffMetaKeys.push(variant);
}
}
else if ((curSong = song.metas.get(curSongDifficulty = song.difficulties[curDifficulty])) == null)
}
function updateCurSong() {
var song = songs[curSelected];
if (song == null) curSong = null;
else if ((curSong = song.metas.get(curDiffMetaKeys[curDifficulty])) == null)
curSong = song;
}
}
+1 -1
View File
@@ -167,7 +167,7 @@ class PauseSubState extends MusicBeatSubstate
case "Change Options":
FlxG.switchState(new OptionsMenu((_) -> FlxG.switchState(new PlayState())));
case "Exit to charter":
FlxG.switchState(new Charter(PlayState.SONG.meta.name, PlayState.difficulty, false));
FlxG.switchState(new Charter(PlayState.SONG.meta.name, PlayState.difficulty, PlayState.variation, false));
case "Exit to menu":
if (PlayState.chartingMode && Charter.undos.unsaved)
game.saveWarn(false);
+1
View File
@@ -73,6 +73,7 @@ class Options
*/
public static var freeplayLastSong:String = null;
public static var freeplayLastDifficulty:String = "normal";
public static var freeplayLastVariation:String = null;
public static var contributors:Array<funkin.backend.system.github.GitHubContributor.CreditsGitHubContributor> = [];
public static var mainDevs:Array<Int> = []; // IDs
public static var lastUpdated:Null<Float>;
+6 -6
View File
@@ -151,6 +151,11 @@ class OptionsMenu extends TreeMenu {
var options:Array<FlxSprite> = [];
for(node in xml.elements) {
switch(node.name) {
case "separator":
options.push(new Separator(node.has.height ? Std.parseFloat(node.att.height) : 67));
}
if (!node.has.name) {
Logs.warn("An option node requires a name attribute.");
continue;
@@ -165,7 +170,6 @@ class OptionsMenu extends TreeMenu {
continue;
}
options.push(new Checkbox(name, desc, node.att.id, null, FlxG.save.data));
case "number":
if (!node.has.id) {
Logs.warn("A number option requires an \"id\" for option saving.");
@@ -195,7 +199,7 @@ class OptionsMenu extends TreeMenu {
continue;
}
var v:Dynamic = Std.parseFloat(node.att.value);
options.push(new RadioButton(screen, name, desc, node.att.id, v != null ? v : node.att.value, null, FlxG.save.data, node.att.forId));
options.push(new RadioButton(screen, name, desc, node.att.id, v != null ? v : node.att.value, null, FlxG.save.data, node.has.forId ? node.att.forId : null));
case 'slider':
if (!node.has.id) {
Logs.warn("A slider option requires an \"id\" for option saving.");
@@ -204,10 +208,6 @@ class OptionsMenu extends TreeMenu {
var step = node.has.change ? Std.parseFloat(node.att.change) : (node.has.step ? Std.parseFloat(node.att.step) : null);
var segments = node.has.segments ? Std.parseInt(node.att.segments) : 5;
options.push(new SliderOption(name, desc, Std.parseFloat(node.att.min), Std.parseFloat(node.att.max), step, segments, node.att.id, Std.parseInt(node.att.barWidth), null, FlxG.save.data));
case 'separator':
options.push(new Separator(Std.parseInt(node.att.height)));
case "menu":
options.push(new TextOption(name, desc, ' >', () -> {
var screen = new TreeMenuScreen(name, desc);
+3 -2
View File
@@ -128,11 +128,12 @@ class TreeMenuScreen extends FlxSpriteGroup {
var i = curSelected, y = initY, object:FlxSprite = null, itemHeight:Float = 0;
inline function updateItem() {
object.y = CoolUtil.fpsLerp(object.y, y - (itemHeight = object.height) * 0.5, r);
object.y = CoolUtil.fpsLerp(object.y, y - itemHeight * 0.5, r);
object.x = x + 100 - Math.pow(Math.abs((object.y - (FlxG.height - itemHeight) * 0.5) / itemHeight / FlxG.height * FlxG.initialHeight), 1.6) * 15;
}
while (i < length) if ((object = members[i++]) != null) {
itemHeight = object.height;
updateItem();
y += itemHeight;
}
@@ -140,7 +141,7 @@ class TreeMenuScreen extends FlxSpriteGroup {
y = initY;
i = curSelected;
while (i-- > 0) if ((object = members[i]) != null) {
y -= itemHeight;
y -= (itemHeight = object.height);
updateItem();
}
}
@@ -60,4 +60,4 @@ class AdvancedAppearanceOptions extends TreeMenuScreen {
private function __changeAntialiasing() {
FlxG.game.stage.quality = (FlxG.enableAntialiasing = Options.antialiasing) ? BEST : LOW;
}
}
}
+1 -2
View File
@@ -16,10 +16,9 @@ class IconOption extends TextOption {
iconSpr = new HealthIcon(icon, false);
iconSpr.setPosition(90 - iconSpr.width, (__text.height - iconSpr.height) / 2);
iconSpr.setUnstretchedGraphicSize(150, 150, true);
iconSpr.updateHitbox();
iconSpr.sprTracker = __text;
iconSpr.sprTrackerAlignment = LEFT;
if (Math.max(iconSpr.width, iconSpr.height) > 150) iconSpr.setUnstretchedGraphicSize(150, 150);
add(iconSpr);
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ class NewOption extends TextOption {
public function new(name:String, desc:String, callback:Void->Void) {
super(name, desc, callback);
itemHeight = 160;
itemHeight = 150;
__text.color = FlxColor.LIME;
__text.x = 100;
+1 -1
View File
@@ -6,7 +6,7 @@ import flixel.FlxBasic;
class Separator extends FlxSprite {
var separatorHeight:Float;
public function new(height = 67) {
public function new(height = 67.0) {
super();
separatorHeight = height;
}
+55 -12
View File
@@ -3,25 +3,24 @@ package funkin.savedata;
import flixel.util.FlxSave;
import lime.app.Application;
import openfl.Lib;
import haxe.Serializer;
import haxe.Unserializer;
/**
* Class used for saves WITHOUT going through the struggle of type checks
* Just add your save variables the way you would do in the Options.hx file.
* The macro will automatically generate the `flush` and `load` functions.
*/
@:build(funkin.backend.system.macros.FunkinSaveMacro.build("save", "flush", "load"))
@:build(funkin.backend.system.macros.FunkinSaveMacro.build("save", "__flush", "__load"))
class FunkinSave {
public static var highscores:Map<HighscoreEntry, SongScore> = [];
@:doNotSave public static var highscores:Map<HighscoreEntry, SongScore> = [];
/**
* ONLY OPEN IF YOU WANT TO EDIT FUNCTIONS RELATED TO SAVING, LOADING OR HIGHSCORES.
*/
#if REGION
@:dox(hide) @:doNotSave
private static var __eventAdded = false;
@:doNotSave
public static var save:FlxSave;
@:dox(hide) @:doNotSave private static var __eventAdded = false;
@:doNotSave public static var save:FlxSave;
public static function init() {
var path = Flags.SAVE_PATH, name = Flags.SAVE_NAME;
@@ -46,20 +45,64 @@ class FunkinSave {
}
}
public static function load() {
__load();
if (save.data.highscores != null) {
var temp;
for (entryData in Reflect.fields(save.data.highscores))
if ((temp = __getHighscoreEntry(entryData)) != null && Reflect.field(save.data.highscores, entryData) != null)
highscores.set(temp, Reflect.field(save.data.highscores, entryData));
}
}
public static function flush() {
if (save.data.highscores == null) save.data.highscores = {};
for (entry => score in highscores) Reflect.setField(save.data.highscores, __formatHighscoreEntry(entry), score);
__flush();
}
static function __getHighscoreEntry(data:String):HighscoreEntry {
try {
var d = Unserializer.run(data);
if (d.song is String)
return HSongEntry(d.song, d.diff, d.variation, d.changes);
else if (d.week is String)
return HWeekEntry(d.week, d.diff);
}
catch (e) {}
return null;
}
static function __formatHighscoreEntry(entry:HighscoreEntry):String {
switch (entry) {
case HWeekEntry(weekName, difficulty):
return Serializer.run({week: weekName, diff: difficulty});
case HSongEntry(songName, difficulty, variation, changes):
var d:Dynamic = {
song: songName,
diff: difficulty,
changes: changes
};
if (variation != null && variation != '') d.variation = variation;
return Serializer.run(d);
}
return '';
}
/**
* Returns the high-score for a song.
* @param name Song name
* @param diff Song difficulty
* @param changes Changes made to that song in freeplay.
*/
public static inline function getSongHighscore(name:String, diff:String, ?changes:Array<HighscoreChange>) {
public static inline function getSongHighscore(name:String, diff:String, ?variation:String, ?changes:Array<HighscoreChange>) {
if (changes == null) changes = [];
return safeGetHighscore(HSongEntry(name.toLowerCase(), diff.toLowerCase(), changes));
return safeGetHighscore(HSongEntry(name.toLowerCase(), diff.toLowerCase(), variation, changes));
}
public static inline function setSongHighscore(name:String, diff:String, highscore:SongScore, ?changes:Array<HighscoreChange>) {
public static inline function setSongHighscore(name:String, diff:String, ?variation:String, highscore:SongScore, ?changes:Array<HighscoreChange>) {
if (changes == null) changes = [];
if (safeRegisterHighscore(HSongEntry(name.toLowerCase(), diff.toLowerCase(), changes), highscore)) {
if (safeRegisterHighscore(HSongEntry(name.toLowerCase(), diff.toLowerCase(), variation, changes), highscore)) {
flush();
return true;
}
@@ -103,7 +146,7 @@ class FunkinSave {
enum HighscoreEntry {
HWeekEntry(weekName:String, difficulty:String);
HSongEntry(songName:String, difficulty:String, changes:Array<HighscoreChange>);
HSongEntry(songName:String, difficulty:String, variation:Null<String>, changes:Array<HighscoreChange>);
}
enum HighscoreChange {
+4
View File
@@ -7,6 +7,10 @@ class Config {
"flixel",
"funkin",
#if MODCHARTING_FEATURES
"modchart.engine",
"modchart.backend.standalone",
#end
#end
];
@@ -161,9 +161,9 @@ class NativeAudioSource {
}
private function complete() {
completed = true;
if (!completed) parent.onComplete.dispatch();
stop();
parent.onComplete.dispatch();
completed = true;
}
// Streaming, atleast for vorbis for now.