Merge remote-tracking branch 'upstream/main' into new-editors-main
# Conflicts: # commandline/commands/Compiler.hx # source/funkin/backend/FunkinSprite.hx # source/funkin/backend/MusicBeatState.hx # source/funkin/backend/MusicBeatSubstate.hx # source/funkin/backend/chart/Chart.hx # source/funkin/backend/chart/EventsData.hx # source/funkin/backend/scripting/GlobalScript.hx # source/funkin/backend/system/Controls.hx # source/funkin/backend/system/MainState.hx # source/funkin/backend/utils/AudioAnalyzer.hx # source/funkin/backend/utils/CoolUtil.hx # source/funkin/editors/character/CharacterInfoScreen.hx # source/funkin/editors/charter/Charter.hx # source/funkin/editors/charter/CharterSelection.hx # source/funkin/editors/charter/CharterStrumline.hx # source/funkin/editors/charter/CharterStrumlineButton.hx # source/funkin/editors/charter/SongCreationScreen.hx # source/funkin/editors/ui/UIDropDown.hx # source/funkin/editors/ui/UIFileExplorer.hx # source/funkin/game/Note.hx # source/funkin/game/PlayState.hx # source/funkin/game/SplashGroup.hx # source/funkin/game/Stage.hx # source/funkin/game/Strum.hx # source/funkin/menus/FreeplayState.hx # source/funkin/options/Options.hx # source/funkin/options/keybinds/KeybindsOptions.hx
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
package funkin;
|
||||
|
||||
@:dox(hide)
|
||||
typedef SingleOrFloat = #if (java || hl || cpp) Single #else Float #end;
|
||||
@@ -85,25 +85,34 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I
|
||||
moves = false;
|
||||
}
|
||||
|
||||
public static function copyFrom(source:FunkinSprite)
|
||||
/**
|
||||
* Gets the graphics and copies other properties from another sprite (Works both for `FlxSprite` and `FunkinSprite`!).
|
||||
*/
|
||||
public static function copyFrom(source:FlxSprite):FunkinSprite
|
||||
{
|
||||
var spr = new FunkinSprite();
|
||||
var casted:FunkinSprite = null;
|
||||
if (source is FunkinSprite)
|
||||
casted = cast source;
|
||||
|
||||
@:privateAccess {
|
||||
spr.setPosition(source.x, source.y);
|
||||
spr.frames = source.frames;
|
||||
if (source.animateAtlas != null && source.atlasPath != null)
|
||||
spr.loadSprite(source.atlasPath);
|
||||
if (casted != null && casted.animateAtlas != null && casted.atlasPath != null)
|
||||
spr.loadSprite(casted.atlasPath);
|
||||
spr.animation.copyFrom(source.animation);
|
||||
spr.visible = source.visible;
|
||||
spr.alpha = source.alpha;
|
||||
spr.antialiasing = source.antialiasing;
|
||||
spr.scale.set(source.scale.x, source.scale.y);
|
||||
spr.scrollFactor.set(source.scrollFactor.x, source.scrollFactor.y);
|
||||
spr.skew.set(source.skew.x, source.skew.y);
|
||||
spr.transformMatrix = source.transformMatrix;
|
||||
spr.matrixExposed = source.matrixExposed;
|
||||
spr.zoomFactor = source.zoomFactor;
|
||||
spr.animOffsets = source.animOffsets.copy();
|
||||
|
||||
if (casted != null) {
|
||||
spr.skew.set(casted.skew.x, casted.skew.y);
|
||||
spr.transformMatrix = casted.transformMatrix;
|
||||
spr.matrixExposed = casted.matrixExposed;
|
||||
spr.animOffsets = casted.animOffsets.copy();
|
||||
}
|
||||
}
|
||||
return spr;
|
||||
}
|
||||
|
||||
@@ -105,6 +105,8 @@ class MusicBeatState extends FlxState implements IBeatReceiver
|
||||
public static var skipTransOut:Bool = false;
|
||||
public static var skipTransIn:Bool = false;
|
||||
|
||||
public static var ALLOW_DEV_RELOAD:Bool = true;
|
||||
|
||||
inline function get_controls():Controls
|
||||
return PlayerSettings.solo.controls;
|
||||
inline function get_controlsP1():Controls
|
||||
@@ -153,16 +155,20 @@ class MusicBeatState extends FlxState implements IBeatReceiver
|
||||
call("postUpdate", [elapsed]);
|
||||
}
|
||||
|
||||
if (_requestSubStateReset)
|
||||
{
|
||||
if (_requestSubStateReset) {
|
||||
_requestSubStateReset = false;
|
||||
resetSubState();
|
||||
}
|
||||
if (subState != null)
|
||||
{
|
||||
subState.tryUpdate(elapsed);
|
||||
|
||||
if (/*subState == null && */(ALLOW_DEV_RELOAD && controls.DEV_RELOAD)) {
|
||||
Logs.trace("Reloading Current State...", INFO, YELLOW);
|
||||
FlxG.resetState();
|
||||
}
|
||||
|
||||
if (subState != null)
|
||||
subState.tryUpdate(elapsed);
|
||||
}
|
||||
|
||||
override function create()
|
||||
{
|
||||
loadScript();
|
||||
@@ -223,12 +229,6 @@ class MusicBeatState extends FlxState implements IBeatReceiver
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
// TODO: DEBUG MODE!!
|
||||
#if EXPERMENTAL_SCRIPT_RELOADING
|
||||
if (FlxG.keys.justPressed.F5) {
|
||||
loadScript();
|
||||
}
|
||||
#end
|
||||
call("update", [elapsed]);
|
||||
|
||||
super.update(elapsed);
|
||||
|
||||
@@ -136,15 +136,19 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver
|
||||
call("postUpdate", [elapsed]);
|
||||
}
|
||||
|
||||
if (_requestSubStateReset)
|
||||
{
|
||||
// if (subState == null && (MusicBeatState.ALLOW_DEV_RELOAD && controls.DEV_RELOAD)) {
|
||||
// Logs.trace("Reloading Current SubState...", INFO, YELLOW);
|
||||
// var test = Type.createInstance(Type.getClass(this), [this.scriptsAllowed, this.scriptName]);
|
||||
// parent.openSubState(test);
|
||||
// }
|
||||
|
||||
if (_requestSubStateReset) {
|
||||
_requestSubStateReset = false;
|
||||
resetSubState();
|
||||
}
|
||||
|
||||
if (subState != null)
|
||||
{
|
||||
subState.tryUpdate(elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
override function close() {
|
||||
@@ -181,12 +185,6 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
#if EXPERMENTAL_SCRIPT_RELOADING
|
||||
// TODO: DEBUG MODE!!
|
||||
if (FlxG.keys.justPressed.F5) {
|
||||
loadScript();
|
||||
}
|
||||
#end
|
||||
call("update", [elapsed]);
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class AssetsLibraryList extends AssetLibrary {
|
||||
if (l is IModsAssetLibrary) {
|
||||
var lib = cast(l, IModsAssetLibrary);
|
||||
for(e in lib.getFiles(folder))
|
||||
content.push(e);
|
||||
content.pushOnce(e);
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
@@ -86,6 +86,11 @@ class ModsFolder {
|
||||
public static function getModsList():Array<String> {
|
||||
var mods:Array<String> = [];
|
||||
#if MOD_SUPPORT
|
||||
if (!FileSystem.exists(modsPath)) {
|
||||
// Mods directory does not exist yet, create it
|
||||
FileSystem.createDirectory(modsPath);
|
||||
}
|
||||
|
||||
final modsList:Array<String> = FileSystem.readDirectory(modsPath);
|
||||
|
||||
if (modsList == null || modsList.length <= 0)
|
||||
|
||||
@@ -25,10 +25,7 @@ class ModsFolderLibrary extends AssetLibrary implements IModsAssetLibrary {
|
||||
this.folderPath = folderPath;
|
||||
this.libName = libName;
|
||||
this.prefix = 'assets/$libName/';
|
||||
if(modName == null)
|
||||
this.modName = libName;
|
||||
else
|
||||
this.modName = modName;
|
||||
this.modName = modName == null ? libName : modName;
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
@@ -71,16 +71,14 @@ class Paths
|
||||
inline static public function voices(song:String, ?difficulty:String, ?prefix:String = "", ?ext:String) {
|
||||
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
|
||||
if (ext == null) ext = Flags.SOUND_EXT;
|
||||
song = song.toLowerCase();
|
||||
var diff = getPath('songs/$song/song/Voices$prefix-${difficulty.toLowerCase()}.${ext}', null);
|
||||
var diff = getPath('songs/$song/song/Voices$prefix-${difficulty}.${ext}', null);
|
||||
return OpenFlAssets.exists(diff) ? diff : getPath('songs/$song/song/Voices$prefix.${ext}', null);
|
||||
}
|
||||
|
||||
inline static public function inst(song:String, ?difficulty:String, ?prefix:String = "", ?ext:String) {
|
||||
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
|
||||
if (ext == null) ext = Flags.SOUND_EXT;
|
||||
song = song.toLowerCase();
|
||||
var diff = getPath('songs/$song/song/Inst$prefix-${difficulty.toLowerCase()}.${ext}', null);
|
||||
var diff = getPath('songs/$song/song/Inst$prefix-${difficulty}.${ext}', null);
|
||||
return OpenFlAssets.exists(diff) ? diff : getPath('songs/$song/song/Inst$prefix.${ext}', null);
|
||||
}
|
||||
|
||||
@@ -111,8 +109,7 @@ class Paths
|
||||
|
||||
static public function chart(song:String, ?difficulty:String):String
|
||||
{
|
||||
difficulty = (difficulty != null ? difficulty : Flags.DEFAULT_DIFFICULTY).toLowerCase();
|
||||
song = song.toLowerCase();
|
||||
difficulty = (difficulty != null ? difficulty : Flags.DEFAULT_DIFFICULTY);
|
||||
|
||||
return getPath('songs/$song/charts/$difficulty.json', null);
|
||||
}
|
||||
@@ -193,6 +190,29 @@ class Paths
|
||||
return tempFramesCache[key] = loadFrames(assetsPath ? key : Paths.image(key, library, true, ext), false, null, false, ext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the images needed for using getFrames() exist.
|
||||
* @param key Path to the image
|
||||
* @param checkAtlas Whenever to check for the Animation.json file (used in FlxAnimate)
|
||||
* @param assetsPath Whenever to use the raw path or to pass it through Paths.image()
|
||||
* @param library (Additional) library to load the frames from.
|
||||
* @return True if the images exist, false otherwise.
|
||||
**/
|
||||
public static function framesExists(key:String, checkAtlas:Bool = false, checkMulti:Bool = true, assetsPath:Bool = false, ?library:String) {
|
||||
var path = assetsPath ? key : Paths.image(key, library, true);
|
||||
var noExt = Path.withoutExtension(path);
|
||||
if(checkAtlas && Assets.exists('$noExt/Animation.json'))
|
||||
return true;
|
||||
if(checkMulti && Assets.exists('$noExt/1.png'))
|
||||
return true;
|
||||
if(Assets.exists('$noExt.xml'))
|
||||
return true;
|
||||
if(Assets.exists('$noExt.txt'))
|
||||
return true;
|
||||
if(Assets.exists('$noExt.json'))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads frames from a specific image path. Supports Sparrow Atlases, Packer Atlases, and multiple spritesheets.
|
||||
@@ -247,13 +267,13 @@ class Paths
|
||||
}
|
||||
return content;
|
||||
}
|
||||
static public function getFolderContent(key:String, addPath:Bool = false, source:AssetsLibraryList.AssetSource = BOTH):Array<String> {
|
||||
static public function getFolderContent(key:String, addPath:Bool = false, source:AssetsLibraryList.AssetSource = BOTH, noExtension:Bool = false):Array<String> {
|
||||
// designed to work both on windows and web
|
||||
if (!key.endsWith("/")) key += "/";
|
||||
var content = assetsTree.getFiles('assets/$key', source);
|
||||
if (addPath) {
|
||||
for(k=>e in content)
|
||||
content[k] = '$key$e';
|
||||
for (k => e in content) {
|
||||
if (noExtension) e = Path.withoutExtension(e);
|
||||
content[k] = addPath ? '$key$e' : e;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
package funkin.backend.chart;
|
||||
|
||||
import funkin.backend.chart.ChartData.ChartMetaData;
|
||||
|
||||
// These new structures are kinda a mess to port, i love and hate them at the same time; why the hell are every difficulty in the same file??? - Nex
|
||||
class BaseGameParser {
|
||||
public static function parseChart(data:Dynamic, metaData:Dynamic, result:ChartData) {
|
||||
// TO DO
|
||||
}
|
||||
|
||||
public static function parseMeta(data:Dynamic, result:ChartMetaData) {
|
||||
var data:SwagMetadata = data;
|
||||
result.name = data.songName;
|
||||
result.artist = data.artist;
|
||||
|
||||
result.customValues = {};
|
||||
result.customValues.timeChanges = data.timeChanges;
|
||||
var firstTimeChange:SwagTimeChange = data.timeChanges[0];
|
||||
result.bpm = firstTimeChange.bpm;
|
||||
result.beatsPerMeasure = firstTimeChange.b != null ? firstTimeChange.b : Flags.DEFAULT_BEATS_PER_MEASURE;
|
||||
|
||||
result.difficulties = data.playData.difficulties.concat(data.playData.songVariations);
|
||||
Reflect.deleteField(data.playData, "difficulties");
|
||||
|
||||
for (field in Reflect.fields(data.playData))
|
||||
Reflect.setProperty(result.customValues, field, Reflect.getProperty(data.playData, field));
|
||||
}
|
||||
|
||||
public static function encodeMeta(meta:ChartMetaData, ?chart:ChartData):SwagMetadata {
|
||||
var addVars:Dynamic = meta.customValues;
|
||||
var defStage:String = addVars.stage != null ? addVars.stage : Flags.DEFAULT_STAGE;
|
||||
var defChars:SwagCharactersList = addVars.characters != null ? addVars.characters : {player: Flags.DEFAULT_CHARACTER, girlfriend: Flags.DEFAULT_GIRLFRIEND, opponent: Flags.DEFAULT_OPPONENT};
|
||||
var defTimeCh:Array<SwagTimeChange>;
|
||||
|
||||
if (addVars.timeChanges != null && addVars.timeChanges.length > 0) {
|
||||
defTimeCh = addVars.timeChanges;
|
||||
defTimeCh[0].bpm = meta.bpm;
|
||||
}
|
||||
else defTimeCh = [{bpm: meta.bpm, t: -1}];
|
||||
|
||||
if (chart != null) {
|
||||
defStage = chart.stage;
|
||||
|
||||
var done:Array<Bool> = [false, false, false];
|
||||
for (strumLine in chart.strumLines) switch (strumLine.type) {
|
||||
case OPPONENT:
|
||||
if (!done[0]) {
|
||||
done[0] = true;
|
||||
defChars.opponent = strumLine.characters.getDefault([defChars.opponent])[0];
|
||||
}
|
||||
case PLAYER:
|
||||
if (!done[1]) {
|
||||
done[1] = true;
|
||||
defChars.player = strumLine.characters.getDefault([defChars.player])[0];
|
||||
}
|
||||
case ADDITIONAL:
|
||||
if (!done[3]) {
|
||||
done[3] = true;
|
||||
defChars.girlfriend = strumLine.characters.getDefault([defChars.girlfriend])[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result:SwagMetadata = {
|
||||
songName: meta.name,
|
||||
timeFormat: MILLISECONDS,
|
||||
artist: meta.artist,
|
||||
timeChanges: defTimeCh,
|
||||
looped: false,
|
||||
generatedBy: 'Basegame Chart Importer (Codename Engine)',
|
||||
version: Flags.BASEGAME_SONG_METADATA_VERSION,
|
||||
playData: {
|
||||
stage: defStage,
|
||||
characters: defChars,
|
||||
songVariations: addVars.songVariations != null ? [for (i in 0...addVars.songVariations.length) {meta.difficulties.remove(addVars.songVariations[i]); addVars.songVariations[i];}] : [],
|
||||
difficulties: meta.difficulties,
|
||||
noteStyle: addVars.noteStyle != null ? addVars.noteStyle : Flags.BASEGAME_DEFAULT_NOTE_STYLE,
|
||||
album: addVars.album != null ? addVars.album : Flags.BASEGAME_DEFAULT_ALBUM_ID,
|
||||
previewStart: addVars.previewStart != null ? addVars.previewStart : Flags.BASEGAME_DEFAULT_PREVIEW_START,
|
||||
previewEnd: addVars.previewEnd != null ? addVars.previewEnd : Flags.BASEGAME_DEFAULT_PREVIEW_END
|
||||
},
|
||||
};
|
||||
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function encodeChart(chart:ChartData):NewSwagSong {
|
||||
// TO DO
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// METADATA STRUCTURES
|
||||
typedef SwagMetadata =
|
||||
{
|
||||
var timeFormat:SwagTimeFormat;
|
||||
var artist:String;
|
||||
var songName:String;
|
||||
var playData:SwagPlayData;
|
||||
var timeChanges:Array<SwagTimeChange>;
|
||||
var generatedBy:String;
|
||||
var looped:Bool;
|
||||
var version:String;
|
||||
|
||||
var ?divisions:Int;
|
||||
var ?offsets:SwagSongOffsets;
|
||||
}
|
||||
|
||||
enum abstract SwagTimeFormat(String) from String to String
|
||||
{
|
||||
var TICKS = 'ticks';
|
||||
var FLOAT = 'float';
|
||||
var MILLISECONDS = 'ms';
|
||||
}
|
||||
|
||||
typedef SwagTimeChange =
|
||||
{
|
||||
var ?d:Int; // Time Signature Den
|
||||
var ?n:Int; // Time Signature Num
|
||||
var t:Int; // Time Stamp
|
||||
var ?b:Int; // Beat Time
|
||||
var ?bt:Array<Int>; // Beat Tuplets
|
||||
var bpm:Float;
|
||||
}
|
||||
|
||||
typedef SwagPlayData =
|
||||
{
|
||||
var album:String;
|
||||
var previewStart:Float;
|
||||
var previewEnd:Float;
|
||||
var stage:String;
|
||||
var characters:SwagCharactersList;
|
||||
var songVariations:Array<String>;
|
||||
var difficulties:Array<String>;
|
||||
var noteStyle:String;
|
||||
}
|
||||
|
||||
typedef SwagCharactersList =
|
||||
{
|
||||
var player:String;
|
||||
var girlfriend:String;
|
||||
var opponent:String;
|
||||
}
|
||||
|
||||
typedef SwagSongOffsets =
|
||||
{
|
||||
var ?instrumental:Float;
|
||||
var ?altInstrumentals:Dynamic;
|
||||
var ?vocals:Dynamic;
|
||||
}
|
||||
|
||||
// CHART STRUCTURE
|
||||
typedef NewSwagSong =
|
||||
{
|
||||
var version:String;
|
||||
var scrollSpeed:Dynamic; // Map<String, Float>
|
||||
var events:Array<SwagEvent>;
|
||||
var notes:Dynamic; // Map<String, Array<SwagNote>>
|
||||
var generatedBy:String;
|
||||
}
|
||||
|
||||
typedef SwagEvent =
|
||||
{
|
||||
var t:Float; // Time
|
||||
var e:String; // Event Kind
|
||||
var v:Dynamic; // Value (Map<String, Dynamic>)
|
||||
}
|
||||
|
||||
typedef SwagNote =
|
||||
{
|
||||
var t:Float; // Time
|
||||
var d:Int; // Data
|
||||
var l:Float; // Length
|
||||
var k:String; // Kind
|
||||
}
|
||||
@@ -72,7 +72,7 @@ class Chart {
|
||||
}
|
||||
|
||||
public static function loadEventsJson(songName:String) {
|
||||
var path = Paths.file('songs/${songName.toLowerCase()}/events.json');
|
||||
var path = Paths.file('songs/${songName}/events.json');
|
||||
var data:Array<ChartEvent> = null;
|
||||
if (Assets.exists(path)) {
|
||||
try {
|
||||
@@ -83,31 +83,29 @@ class Chart {
|
||||
return data;
|
||||
}
|
||||
|
||||
public static function loadChartMeta(songName:String, ?difficulty:String, fromMods:Bool = true) {
|
||||
public static function loadChartMeta(songName:String, ?difficulty:String, fromMods:Bool = true):ChartMetaData {
|
||||
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
|
||||
|
||||
var songNameLower = songName.toLowerCase();
|
||||
var metaPath = Paths.file('songs/${songNameLower}/meta.json');
|
||||
var metaDiffPath = Paths.file('songs/${songNameLower}/meta-${difficulty.toLowerCase()}.json');
|
||||
var metaPath = Paths.file('songs/${songName}/meta.json');
|
||||
var metaDiffPath = Paths.file('songs/${songName}/meta-${difficulty}.json');
|
||||
|
||||
var data:ChartMetaData = null;
|
||||
var fromMods:Bool = fromMods;
|
||||
for(path in [metaDiffPath, metaPath]) {
|
||||
if (Assets.exists(path)) {
|
||||
fromMods = Paths.assetsTree.existsSpecific(path, "TEXT", MODS);
|
||||
try {
|
||||
data = Json.parse(Assets.getText(path));
|
||||
} catch(e) {
|
||||
Logs.trace('Failed to load song metadata for ${songName} ($path): ${Std.string(e)}', ERROR);
|
||||
}
|
||||
if (data != null) break;
|
||||
}
|
||||
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) break;
|
||||
}
|
||||
|
||||
if (data == null)
|
||||
data = {
|
||||
name: songName
|
||||
};
|
||||
if (data == null) data = {
|
||||
name: songName,
|
||||
bpm: 100
|
||||
};
|
||||
|
||||
data.setFieldDefault("name", songName);
|
||||
data.setFieldDefault("bpm", Flags.DEFAULT_BPM);
|
||||
data.setFieldDefault("beatsPerMeasure", Flags.DEFAULT_BEATS_PER_MEASURE);
|
||||
@@ -118,24 +116,19 @@ class Chart {
|
||||
data.setFieldDefault("coopAllowed", Flags.DEFAULT_COOP_ALLOWED);
|
||||
data.setFieldDefault("opponentModeAllowed", Flags.DEFAULT_OPPONENT_MODE_ALLOWED);
|
||||
data.setFieldDefault("displayName", data.name);
|
||||
data.setFieldDefault("parsedColor", data.color.getColorFromDynamic().getDefault(Flags.DEFAULT_COLOR));
|
||||
|
||||
if (data.difficulties.length <= 0) {
|
||||
data.difficulties = [for(f in Paths.getFolderContent('songs/${songNameLower}/charts/', false, fromMods ? MODS : SOURCE)) if (Path.extension(f = f.toUpperCase()) == "JSON") Path.withoutExtension(f)];
|
||||
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) {
|
||||
var hasHard = false, hasNormal = false, hasEasy = false;
|
||||
for(d in data.difficulties) {
|
||||
switch(d) {
|
||||
case "EASY": hasEasy = true;
|
||||
case "NORMAL": hasNormal = true;
|
||||
case "HARD": hasHard = true;
|
||||
switch(d.toLowerCase()) {
|
||||
case "easy": tempDiffs.insert(0, d);
|
||||
case "normal": tempDiffs.insert(1, d);
|
||||
case "hard": tempDiffs.insert(2, d);
|
||||
}
|
||||
}
|
||||
if (hasHard && hasNormal && hasEasy) {
|
||||
data.difficulties[0] = "EASY";
|
||||
data.difficulties[1] = "NORMAL";
|
||||
data.difficulties[2] = "HARD";
|
||||
}
|
||||
data.difficulties = tempDiffs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,11 +158,9 @@ class Chart {
|
||||
valid = false;
|
||||
}
|
||||
var data:Dynamic = null;
|
||||
try {
|
||||
if (valid)
|
||||
data = Json.parse(Assets.getText(chartPath));
|
||||
} catch(e) {
|
||||
Logs.trace('Could not parse chart for song ${songName} ($difficulty): ${Std.string(e)}', ERROR, RED);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +169,7 @@ class Chart {
|
||||
#if REGION
|
||||
if (data != null) switch (detectChartFormat(data)) {
|
||||
case CODENAME:
|
||||
// backward compat on events since its caused problems
|
||||
// backward compat on events since it caused problems
|
||||
var eventTypesToString:Map<Int, String> = [
|
||||
-1 => "HScript Call",
|
||||
0 => "Unknown",
|
||||
@@ -196,7 +187,7 @@ class Chart {
|
||||
|
||||
base = data;
|
||||
case PSYCH_NEW: PsychParser.parse(data, base);
|
||||
case VSLICE: // TODO
|
||||
case VSLICE: Logs.trace("Couldn't parse V-Slice chart because it's not supported on runtime, it MUST be imported and converted in the new song editor window.", ERROR, RED);
|
||||
case LEGACY: FNFLegacyParser.parse(data, base);
|
||||
}
|
||||
#end
|
||||
@@ -238,8 +229,7 @@ class Chart {
|
||||
return 0;
|
||||
default:
|
||||
var index = chart.noteTypes.indexOf(noteTypeName);
|
||||
if (index > -1)
|
||||
return index+1;
|
||||
if (index > -1) return index + 1;
|
||||
chart.noteTypes.push(noteTypeName);
|
||||
return chart.noteTypes.length;
|
||||
}
|
||||
@@ -275,9 +265,8 @@ class Chart {
|
||||
|
||||
CoolUtil.safeSaveFile(chartPath, Json.stringify(filteredChart, null, saveSettings.prettyPrint == true ? Flags.JSON_PRETTY_PRINT : null));
|
||||
|
||||
// idk how null reacts to it so better be sure
|
||||
if (saveSettings.overrideExistingMeta == true || !FileSystem.exists(metaPath))
|
||||
CoolUtil.safeSaveFile(metaPath, Json.stringify(meta, null, saveSettings.prettyPrint == true ? Flags.JSON_PRETTY_PRINT : null));
|
||||
CoolUtil.safeSaveFile(metaPath, makeMetaSaveable(meta));
|
||||
#end
|
||||
return filteredChart;
|
||||
}
|
||||
@@ -306,13 +295,19 @@ class Chart {
|
||||
data.fromMods = null;
|
||||
|
||||
var sortedData:Dynamic = {};
|
||||
for(f in Reflect.fields(data)) {
|
||||
for (f in Reflect.fields(data)) {
|
||||
var v = Reflect.field(data, f);
|
||||
if (v != null)
|
||||
Reflect.setField(sortedData, f, v);
|
||||
}
|
||||
return sortedData;
|
||||
}
|
||||
|
||||
public static inline function makeMetaSaveable(meta:ChartMetaData, prettyPrint:Bool = true):String {
|
||||
var data:Dynamic = Reflect.copy(meta);
|
||||
if (data.color != null) data.color = new FlxColor(data.color).toWebString(); // dont even ask me - Nex
|
||||
return Json.stringify(data, null, prettyPrint ? Flags.JSON_PRETTY_PRINT : null);
|
||||
}
|
||||
}
|
||||
|
||||
typedef ChartSaveSettings = {
|
||||
|
||||
@@ -22,16 +22,12 @@ typedef ChartMetaData = {
|
||||
public var ?beatsPerMeasure:Float;
|
||||
public var ?stepsPerBeat:Int;
|
||||
public var ?icon:String;
|
||||
public var ?color:Dynamic;
|
||||
public var ?color:FlxColor;
|
||||
public var ?difficulties:Array<String>;
|
||||
public var ?coopAllowed:Bool;
|
||||
public var ?opponentModeAllowed:Bool;
|
||||
public var ?customValues:Dynamic;
|
||||
public var ?needsVoices:Bool;
|
||||
public var ?artist:String;
|
||||
|
||||
// NOT TO BE EXPORTED
|
||||
public var ?parsedColor:FlxColor;
|
||||
}
|
||||
|
||||
typedef ChartStrumLine = {
|
||||
|
||||
@@ -9,18 +9,71 @@ import openfl.Assets;
|
||||
|
||||
using StringTools;
|
||||
|
||||
final class EventsData {
|
||||
public static var defaultEventsList:Array<String> = ["HScript Call", "Camera Movement", "Add Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation"];
|
||||
class EventsData {
|
||||
public static var defaultEventsList:Array<String> = ["HScript Call", "Camera Movement", "Camera Position", "Add Camera Zoom", "Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation"];
|
||||
public static var defaultEventsParams:Map<String, Array<EventParamInfo>> = [
|
||||
"HScript Call" => [
|
||||
{name: "Function Name", type: TString, defValue: "myFunc"},
|
||||
{name: "Function Parameters (String split with commas)", type: TString, defValue: ""}
|
||||
],
|
||||
"Camera Movement" => [{name: "Camera Target", type: TStrumLine, defValue: 0}],
|
||||
"Camera Movement" => [
|
||||
{name: "Camera Target", type: TStrumLine, defValue: 0},
|
||||
{name: "Tween Movement?", type: TBool, defValue: true, saveIfDefault: false},
|
||||
{name: "Tween Time (Steps, IF NOT CLASSIC)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4, saveIfDefault: false},
|
||||
{ // since its the most used event even by default, we'll set saveIfDefault false to avoid filling up with unnecessary parameters the files - Nex
|
||||
name: "Tween Ease (ex: circ, quad, cube)",
|
||||
type: TDropDown(['CLASSIC', 'linear', 'back', 'bounce', 'circ', 'cube', 'elastic', 'expo', 'quad', 'quart', 'quint', 'sine', 'smoothStep', 'smootherStep']),
|
||||
defValue: "CLASSIC",
|
||||
saveIfDefault: false
|
||||
},
|
||||
{
|
||||
name: "Tween Type (excluded if CLASSIC or linear, ex: InOut)",
|
||||
type: TDropDown(['In', 'Out', 'InOut']),
|
||||
defValue: "In",
|
||||
saveIfDefault: false
|
||||
}
|
||||
],
|
||||
"Camera Position" => [
|
||||
{name: "X", type: TFloat(null, null, 10, 3), defValue: 0},
|
||||
{name: "Y", type: TFloat(null, null, 10, 3), defValue: 0},
|
||||
{name: "Tween Movement?", type: TBool, defValue: true, saveIfDefault: false},
|
||||
{name: "Tween Time (Steps, IF NOT CLASSIC)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4, saveIfDefault: false},
|
||||
{
|
||||
name: "Tween Ease (ex: circ, quad, cube)",
|
||||
type: TDropDown(['CLASSIC', 'linear', 'back', 'bounce', 'circ', 'cube', 'elastic', 'expo', 'quad', 'quart', 'quint', 'sine', 'smoothStep', 'smootherStep']),
|
||||
defValue: "CLASSIC",
|
||||
saveIfDefault: false
|
||||
},
|
||||
{
|
||||
name: "Tween Type (excluded if CLASSIC or linear, ex: InOut)",
|
||||
type: TDropDown(['In', 'Out', 'InOut']),
|
||||
defValue: "In",
|
||||
saveIfDefault: false
|
||||
},
|
||||
{name: "Is Offset?", type: TBool, defValue: false, saveIfDefault: false}
|
||||
],
|
||||
"Add Camera Zoom" => [
|
||||
{name: "Amount", type: TFloat(-10, 10, 0.01, 2), defValue: 0.05},
|
||||
{name: "Camera", type: TDropDown(['camGame', 'camHUD']), defValue: "camGame"}
|
||||
],
|
||||
"Camera Zoom" => [
|
||||
{name: "Tween Zoom?", type: TBool, defValue: true},
|
||||
{name: "New Zoom", type: TFloat(-10, 10, 0.01, 2), defValue: 1},
|
||||
{name: "Camera", type: TDropDown(['camGame', 'camHUD']), defValue: "camGame"},
|
||||
{name: "Tween Time (Steps)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4},
|
||||
{
|
||||
name: "Tween Ease (ex: circ, quad, cube)",
|
||||
type: TDropDown(['linear', 'back', 'bounce', 'circ', 'cube', 'elastic', 'expo', 'quad', 'quart', 'quint', 'sine', 'smoothStep', 'smootherStep']),
|
||||
defValue: "linear"
|
||||
},
|
||||
{
|
||||
name: "Tween Type (excluded if linear, ex: InOut)",
|
||||
type: TDropDown(['In', 'Out', 'InOut']),
|
||||
defValue: "In"
|
||||
},
|
||||
{name: "Mode", type: TDropDown(['direct', 'stage']), defValue: "direct"},
|
||||
{name: "Multiplicative?", type: TBool, defValue: true}
|
||||
],
|
||||
"Camera Modulo Change" => [
|
||||
{name: "Modulo Interval (Beats)", type: TInt(1, 9999999, 1), defValue: 4},
|
||||
{name: "Bump Strength", type: TFloat(0.1, 10, 0.01, 2), defValue: 1}
|
||||
@@ -42,13 +95,23 @@ final class EventsData {
|
||||
defValue: "linear"
|
||||
},
|
||||
{
|
||||
name: "Tween Type (ex: InOut)",
|
||||
name: "Tween Type (excluded if linear, ex: InOut)",
|
||||
type: TDropDown(['In', 'Out', 'InOut']),
|
||||
defValue: "In"
|
||||
}
|
||||
},
|
||||
{name: "Multiplicative?", type: TBool, defValue: false}
|
||||
],
|
||||
"Alt Animation Toggle" => [{name: "Enable On Sing Poses", type: TBool, defValue: true}, {name: "Enable On Idle", type: TBool, defValue: true}, {name: "Strumline", type: TStrumLine, defValue: 0}],
|
||||
"Play Animation" => [{name: "Character", type: TStrumLine, defValue: 0}, {name: "Animation", type: TString, defValue: "animation"}, {name: "Is forced?", type: TBool, defValue: true}],
|
||||
"Play Animation" => [
|
||||
{name: "Character", type: TStrumLine, defValue: 0},
|
||||
{name: "Animation", type: TString, defValue: "animation"},
|
||||
{name: "Is forced?", type: TBool, defValue: true},
|
||||
{
|
||||
name: "Animation Context",
|
||||
type: TDropDown(["NONE", "SING", "DANCE", "MISS", "LOCK"]),
|
||||
defValue: "NONE"
|
||||
}
|
||||
],
|
||||
];
|
||||
|
||||
public static var eventsList:Array<String> = defaultEventsList.copy();
|
||||
@@ -136,6 +199,7 @@ typedef EventParamInfo = {
|
||||
var name:String;
|
||||
var type:EventParamType;
|
||||
var defValue:Dynamic;
|
||||
var ?saveIfDefault:Bool;
|
||||
}
|
||||
|
||||
enum EventParamType {
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
package funkin.backend.chart;
|
||||
|
||||
import funkin.backend.chart.ChartData.ChartMetaData;
|
||||
|
||||
typedef ChartDataWithInfo = {
|
||||
var diffName:String;
|
||||
var chart:ChartData;
|
||||
}
|
||||
|
||||
// These new structures are kinda a mess to port, i love and hate them at the same time; why the hell are every difficulty in the same file??? - Nex
|
||||
class VSliceParser {
|
||||
public static function parse(metaData:Dynamic, chartData:Dynamic, resultMeta:ChartMetaData, resultCharts:Array<ChartDataWithInfo>, ?songID:String) {
|
||||
var metaData:SwagMetadata = metaData;
|
||||
var chartData:NewSwagSong = chartData;
|
||||
|
||||
parseMeta(metaData, resultMeta, songID);
|
||||
|
||||
for (diff in Reflect.fields(chartData.notes))
|
||||
{
|
||||
var base:ChartData = {
|
||||
strumLines: [],
|
||||
noteTypes: [],
|
||||
events: [],
|
||||
meta: {name: null},
|
||||
scrollSpeed: Reflect.field(chartData.scrollSpeed, diff),
|
||||
stage: Flags.DEFAULT_STAGE,
|
||||
codenameChart: true
|
||||
};
|
||||
parseChart(Reflect.field(chartData.notes, diff), metaData, chartData.events, base);
|
||||
resultCharts.push({diffName: diff, chart: base});
|
||||
}
|
||||
}
|
||||
|
||||
public static function parseChart(data:Dynamic, metaData:Dynamic, events:Dynamic, result:ChartData) {
|
||||
// vslice chart parsing
|
||||
var data:Array<SwagNote> = data;
|
||||
var metadata:SwagMetadata = metaData;
|
||||
var events:Array<SwagEvent> = events;
|
||||
|
||||
result.stage = metadata.playData.stage;
|
||||
|
||||
var p2isGF:Bool = false;
|
||||
result.strumLines.push({
|
||||
characters: [metadata.playData.characters.opponent],
|
||||
type: 0,
|
||||
position: (p2isGF = metadata.playData.characters.opponent.startsWith("gf")) ? "girlfriend" : "dad",
|
||||
notes: [],
|
||||
vocalsSuffix: "-Opponent"
|
||||
});
|
||||
result.strumLines.push({
|
||||
characters: [metadata.playData.characters.player],
|
||||
type: 1,
|
||||
position: "boyfriend",
|
||||
notes: [],
|
||||
vocalsSuffix: "-Player"
|
||||
});
|
||||
var gfName = metadata.playData.characters.girlfriend;
|
||||
if (!p2isGF && gfName != "none") {
|
||||
result.strumLines.push({
|
||||
characters: [gfName],
|
||||
type: 2,
|
||||
position: "girlfriend",
|
||||
notes: [],
|
||||
visible: false,
|
||||
});
|
||||
}
|
||||
|
||||
var timeChanges = metadata.timeChanges;
|
||||
result.meta.bpm = timeChanges[0].bpm;
|
||||
result.meta.needsVoices = true;
|
||||
|
||||
for (note in data)
|
||||
{
|
||||
var daNoteType:Null<Int> = null;
|
||||
if (note.k != null)
|
||||
daNoteType = Chart.addNoteType(result, note.k == "alt" ? "Alt Anim Note" : note.k); // they hardcoded "alt" for converting old charts BUT THEN WHY THE HELL WOULD YOU CALL "MOM" THE NOTE KIND IN WEEK5 GRAHHH - Nex
|
||||
|
||||
var daNoteData:Int = Std.int(note.d % 8);
|
||||
var isMustHit:Bool = Math.floor(daNoteData / 4) == 0;
|
||||
|
||||
result.strumLines[isMustHit ? 1 : 0].notes.push({
|
||||
time: note.t,
|
||||
id: daNoteData % 4,
|
||||
type: daNoteType,
|
||||
sLen: note.l
|
||||
});
|
||||
}
|
||||
|
||||
var curBPM = result.meta.bpm;
|
||||
for (i in 1...timeChanges.length) // starting from 1 on purpose - Nex
|
||||
{
|
||||
var curChange = timeChanges[i];
|
||||
if (curBPM != curChange.bpm)
|
||||
{
|
||||
curBPM = curChange.bpm;
|
||||
result.events.push({
|
||||
time: curChange.t,
|
||||
name: "BPM Change",
|
||||
params: [curBPM]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (event in events)
|
||||
{
|
||||
var values = event.v;
|
||||
switch (event.e)
|
||||
{
|
||||
case "FocusCamera":
|
||||
var isPosOnly = false;
|
||||
var arr:Array<Dynamic> = [switch(values.char) {
|
||||
// more cases here in the future if they add em? (i hope not) - Nex
|
||||
case 0: 1;
|
||||
case 1: 0;
|
||||
case -1:
|
||||
isPosOnly = true;
|
||||
-1;
|
||||
default: 2;
|
||||
}];
|
||||
if (values.ease != "CLASSIC" && values.ease != null) {
|
||||
if (values.ease == "INSTANT") arr = arr.concat([false]);
|
||||
else {
|
||||
var cneEase = parseEase(values.ease);
|
||||
arr = arr.concat([true, values.duration == null ? 4 : values.duration, cneEase[0], cneEase[1]]);
|
||||
}
|
||||
}
|
||||
if (isPosOnly || ((values.x != null && values.x != 0) || (values.y != null && values.y != 0))) {
|
||||
var useNull = arr.length <= 2;
|
||||
result.events.push({
|
||||
time: event.t,
|
||||
name: "Camera Position",
|
||||
params: [values.x, values.y, useNull ? null : arr[1], useNull ? null : arr[2], useNull ? null : arr[3], useNull ? null : arr[4], true]
|
||||
});
|
||||
}
|
||||
if (!isPosOnly) result.events.push({
|
||||
time: event.t,
|
||||
name: "Camera Movement",
|
||||
params: arr
|
||||
});
|
||||
case "PlayAnimation":
|
||||
result.events.push({
|
||||
time: event.t,
|
||||
name: "Play Animation",
|
||||
params: [switch(values.target) {
|
||||
case 'boyfriend' | 'bf' | 'player': 1;
|
||||
case 'dad' | 'opponent': 0;
|
||||
default /*case 'girlfriend' | 'gf'*/: 2; // usually the default should be the stage prop but we dont have that sooo - Nex
|
||||
}, values.anim, values.force == null ? false : values.force]
|
||||
});
|
||||
case "ScrollSpeed":
|
||||
var cneEase = values.ease == null || values.ease == "INSTANT" ? ["linear", null] : parseEase(values.ease);
|
||||
result.events.push({
|
||||
time: event.t,
|
||||
name: "Scroll Speed Change", // we dont support the strumline value and also i will put the whole ease name into a single parameter since it works anyways - Nex
|
||||
params: [values.ease != "INSTANT", values.scroll == null ? 1 : values.scroll, values.duration == null ? 4 : values.duration, cneEase[0], cneEase[1], values.absolute != true]
|
||||
});
|
||||
case "SetCameraBop":
|
||||
result.events.push({
|
||||
time: event.t,
|
||||
name: "Camera Modulo Change",
|
||||
params: [values.rate == null ? 4 : values.rate, values.intensity == null ? 1 : values.intensity]
|
||||
});
|
||||
case "ZoomCamera":
|
||||
var cneEase = values.ease == null || values.ease == "INSTANT" ? ["linear", null] : parseEase(values.ease);
|
||||
result.events.push({
|
||||
time: event.t,
|
||||
name: "Camera Zoom", // we dont support the direct mode since welp, its kind of useless here - Nex
|
||||
params: [values.ease != "INSTANT", values.zoom == null ? 1 : values.zoom, "camGame", values.duration == null ? 4 : values.duration, cneEase[0], cneEase[1], values.mode, false]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function parseMeta(data:Dynamic, result:ChartMetaData, ?songID:String) {
|
||||
var data:SwagMetadata = data;
|
||||
var songName = data.songName;
|
||||
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([]));
|
||||
|
||||
if (result.customValues == null) result.customValues = {};
|
||||
result.customValues.artist = data.artist;
|
||||
if (data.charter != null) result.customValues.charter = data.charter;
|
||||
for (field in Reflect.fields(data.playData)) if (field != "difficulties" && field != "songVariations" && field != "characters" && field != "stage")
|
||||
Reflect.setProperty(result.customValues, field, Reflect.getProperty(data.playData, field));
|
||||
}
|
||||
|
||||
public static function encodeMeta(meta:ChartMetaData, ?chart:ChartData):SwagMetadata {
|
||||
var addVars:Dynamic = meta.customValues;
|
||||
var defStage:String = addVars.stage != null ? addVars.stage : Flags.DEFAULT_STAGE;
|
||||
var defChars:SwagCharactersList = addVars.characters != null ? addVars.characters : {player: Flags.DEFAULT_CHARACTER, girlfriend: Flags.DEFAULT_GIRLFRIEND, opponent: Flags.DEFAULT_OPPONENT, playerVocals: [], opponentVocals: [], instrumental: '', altInstrumentals: []};
|
||||
var defTimeCh:Array<SwagTimeChange>;
|
||||
|
||||
if (addVars.timeChanges != null && addVars.timeChanges.length > 0) {
|
||||
defTimeCh = addVars.timeChanges;
|
||||
defTimeCh[0].bpm = meta.bpm;
|
||||
}
|
||||
else defTimeCh = [{bpm: meta.bpm, t: -1}];
|
||||
|
||||
if (chart != null) {
|
||||
defStage = chart.stage;
|
||||
|
||||
var done:Array<Bool> = [false, false, false];
|
||||
for (strumLine in chart.strumLines) switch (strumLine.type) {
|
||||
case OPPONENT:
|
||||
if (!done[0]) {
|
||||
done[0] = true;
|
||||
defChars.opponent = strumLine.characters.getDefault([defChars.opponent])[0];
|
||||
}
|
||||
case PLAYER:
|
||||
if (!done[1]) {
|
||||
done[1] = true;
|
||||
defChars.player = strumLine.characters.getDefault([defChars.player])[0];
|
||||
}
|
||||
case ADDITIONAL:
|
||||
if (!done[3]) {
|
||||
done[3] = true;
|
||||
defChars.girlfriend = strumLine.characters.getDefault([defChars.girlfriend])[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result:SwagMetadata = {
|
||||
songName: meta.name,
|
||||
timeFormat: MILLISECONDS,
|
||||
artist: "",
|
||||
timeChanges: defTimeCh,
|
||||
looped: false,
|
||||
generatedBy: 'V-Slice Chart Importer (Codename Engine)',
|
||||
version: Flags.VSLICE_SONG_METADATA_VERSION,
|
||||
playData: {
|
||||
stage: defStage,
|
||||
characters: defChars,
|
||||
songVariations: addVars.songVariations != null ? [for (i in 0...addVars.songVariations.length) {meta.difficulties.remove(addVars.songVariations[i]); addVars.songVariations[i];}] : [],
|
||||
difficulties: meta.difficulties,
|
||||
noteStyle: addVars.noteStyle != null ? addVars.noteStyle : Flags.VSLICE_DEFAULT_NOTE_STYLE,
|
||||
album: addVars.album != null ? addVars.album : Flags.VSLICE_DEFAULT_ALBUM_ID,
|
||||
previewStart: addVars.previewStart != null ? addVars.previewStart : Flags.VSLICE_DEFAULT_PREVIEW_START,
|
||||
previewEnd: addVars.previewEnd != null ? addVars.previewEnd : Flags.VSLICE_DEFAULT_PREVIEW_END
|
||||
},
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static function encodeChart(chart:ChartData):NewSwagSong {
|
||||
// TO DO
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function parseEase(vsliceEase:String):Array<String> {
|
||||
for (key in ['InOut', 'In', 'Out']) if (vsliceEase.endsWith(key)) return [vsliceEase.substr(0, vsliceEase.length - key.length), key];
|
||||
return [vsliceEase];
|
||||
}
|
||||
}
|
||||
|
||||
// METADATA STRUCTURES
|
||||
typedef SwagMetadata =
|
||||
{
|
||||
var version:String;
|
||||
var songName:String;
|
||||
var artist:String;
|
||||
var ?charter:String;
|
||||
var ?divisions:Int;
|
||||
var ?looped:Bool;
|
||||
var ?offsets:SwagSongOffsets;
|
||||
var playData:SwagPlayData;
|
||||
var generatedBy:String;
|
||||
var ?timeFormat:SwagTimeFormat;
|
||||
var timeChanges:Array<SwagTimeChange>;
|
||||
}
|
||||
|
||||
enum abstract SwagTimeFormat(String) from String to String
|
||||
{
|
||||
var TICKS = 'ticks';
|
||||
var FLOAT = 'float';
|
||||
var MILLISECONDS = 'ms';
|
||||
}
|
||||
|
||||
typedef SwagTimeChange =
|
||||
{
|
||||
var t:Int; // Time Stamp
|
||||
var ?b:Int; // Beat Time
|
||||
var bpm:Float;
|
||||
var ?n:Int; // Time Signature Num
|
||||
var ?d:Int; // Time Signature Den
|
||||
var ?bt:Array<Int>; // Beat Tuplets
|
||||
}
|
||||
|
||||
typedef SwagSongOffsets =
|
||||
{
|
||||
var ?instrumental:Float;
|
||||
var ?altInstrumentals:Dynamic;
|
||||
var ?vocals:Dynamic;
|
||||
var ?altVocals:Dynamic;
|
||||
}
|
||||
|
||||
typedef SwagPlayData =
|
||||
{
|
||||
var ?songVariations:Array<String>;
|
||||
var difficulties:Array<String>;
|
||||
var characters:SwagCharactersList;
|
||||
var stage:String;
|
||||
var noteStyle:String;
|
||||
var ?ratings:Dynamic;
|
||||
var ?album:String;
|
||||
var ?previewStart:Int;
|
||||
var ?previewEnd:Int;
|
||||
}
|
||||
|
||||
typedef SwagCharactersList =
|
||||
{
|
||||
var ?player:String;
|
||||
var ?girlfriend:String;
|
||||
var ?opponent:String;
|
||||
var ?instrumental:String;
|
||||
var ?altInstrumentals:Array<String>;
|
||||
var ?opponentVocals:Array<String>;
|
||||
var ?playerVocals:Array<String>;
|
||||
}
|
||||
|
||||
// CHART STRUCTURE
|
||||
typedef NewSwagSong =
|
||||
{
|
||||
var version:String;
|
||||
var scrollSpeed:Dynamic; // Map<String, Float>
|
||||
var events:Array<SwagEvent>;
|
||||
var notes:Dynamic; // Map<String, Array<SwagNote>>
|
||||
var generatedBy:String;
|
||||
}
|
||||
|
||||
typedef SwagEvent =
|
||||
{
|
||||
var t:Float; // Time
|
||||
var e:String; // Event Kind
|
||||
var ?v:Dynamic; // Value (Map<String, Dynamic>)
|
||||
}
|
||||
|
||||
typedef SwagNote =
|
||||
{
|
||||
var t:Float; // Time
|
||||
var d:Int; // Data
|
||||
var ?l:Float; // Length
|
||||
var ?k:String; // Kind
|
||||
var ?p:Array<SwagNoteParamsData>; // Params
|
||||
}
|
||||
|
||||
typedef SwagNoteParamsData =
|
||||
{
|
||||
var n:String; // Name
|
||||
var v:Dynamic; // Value
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import flixel.FlxState;
|
||||
import funkin.backend.assets.ModsFolder;
|
||||
import funkin.backend.scripting.events.CancellableEvent;
|
||||
import funkin.backend.system.Conductor;
|
||||
import funkin.options.PlayerSettings;
|
||||
|
||||
#if GLOBAL_SCRIPT
|
||||
/**
|
||||
* Class for THE Global Script, aka script that runs in the background at all times.
|
||||
@@ -12,6 +14,8 @@ class GlobalScript {
|
||||
public static var scripts:ScriptPack;
|
||||
|
||||
private static var initialized:Bool = false;
|
||||
private static var reloading:Bool = false;
|
||||
private static var _lastAllow_Reload:Bool = false;
|
||||
|
||||
public static function init() {
|
||||
if(initialized) return;
|
||||
@@ -46,19 +50,13 @@ class GlobalScript {
|
||||
});
|
||||
FlxG.signals.postUpdate.add(function() {
|
||||
call("postUpdate", [FlxG.elapsed]);
|
||||
#if EXPERMENTAL_SCRIPT_RELOADING
|
||||
if (FlxG.keys.justPressed.F5) {
|
||||
if (scripts.scripts.length > 0) {
|
||||
Logs.trace('Reloading global script...', WARNING, YELLOW);
|
||||
scripts.reload();
|
||||
Logs.trace('Global script successfully reloaded.', WARNING, GREEN);
|
||||
} else {
|
||||
Logs.trace('Loading global script...', WARNING, YELLOW);
|
||||
onModSwitch(#if MOD_SUPPORT ModsFolder.currentModFolder #else null #end);
|
||||
}
|
||||
|
||||
if (reloading) {
|
||||
reloading = false;
|
||||
MusicBeatState.ALLOW_DEV_RELOAD = _lastAllow_Reload;
|
||||
}
|
||||
#end
|
||||
if (FlxG.keys.justPressed.F2)
|
||||
|
||||
if (PlayerSettings.solo.controls.DEV_CONSOLE)
|
||||
NativeAPI.allocConsole();
|
||||
});
|
||||
FlxG.signals.preDraw.add(function() {
|
||||
@@ -76,13 +74,26 @@ class GlobalScript {
|
||||
FlxG.signals.preStateSwitch.add(function() {
|
||||
call("preStateSwitch", []);
|
||||
});
|
||||
|
||||
FlxG.signals.preUpdate.add(function() {
|
||||
call("preUpdate", [FlxG.elapsed]);
|
||||
call("update", [FlxG.elapsed]);
|
||||
});
|
||||
|
||||
// Commented this out, because it might be the cause why global runs twice - Neo
|
||||
//onModSwitch(#if MOD_SUPPORT ModsFolder.currentModFolder #else null #end);
|
||||
if (FlxG.keys.pressed.SHIFT) {
|
||||
// If we want, we could just make reseting GlobalScript it's own keybind, but for now this works.
|
||||
if (PlayerSettings.solo.controls.DEV_RELOAD) {
|
||||
reloading = true;
|
||||
Logs.trace("Reloading Global Scripts...", INFO, YELLOW);
|
||||
|
||||
// yeah its a bit messy, sorry. This just prevents actually reloading the actual state.
|
||||
_lastAllow_Reload = MusicBeatState.ALLOW_DEV_RELOAD;
|
||||
MusicBeatState.ALLOW_DEV_RELOAD = false;
|
||||
|
||||
// Would be better to just re-initalize GlobalScript so there aren't any lose ends.
|
||||
onModSwitch(#if MOD_SUPPORT ModsFolder.currentModFolder #else null #end);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static function event<T:CancellableEvent>(name:String, event:T):T {
|
||||
@@ -95,6 +106,7 @@ class GlobalScript {
|
||||
if (scripts != null)
|
||||
scripts.call(name, args);
|
||||
}
|
||||
|
||||
public static function onModSwitch(newMod:String) {
|
||||
call("destroy");
|
||||
scripts = FlxDestroyUtil.destroy(scripts);
|
||||
|
||||
@@ -128,6 +128,10 @@ class HScript extends Script {
|
||||
interp.execute(expr);
|
||||
call("new", []);
|
||||
}
|
||||
|
||||
#if GLOBAL_SCRIPT
|
||||
funkin.backend.scripting.GlobalScript.call("onScriptSetup", [this, "hscript"]);
|
||||
#end
|
||||
}
|
||||
|
||||
public override function reload() {
|
||||
|
||||
@@ -83,7 +83,7 @@ class ScriptPack extends Script {
|
||||
**/
|
||||
public override function call(func:String, ?parameters:Array<Dynamic>):Dynamic {
|
||||
for(e in scripts)
|
||||
if(e.active)
|
||||
if (e.active)
|
||||
e.call(func, parameters);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package funkin.backend.scripting.events.gameplay;
|
||||
|
||||
final class RatingUpdateEvent extends CancellableEvent {
|
||||
/**
|
||||
New combo
|
||||
New combo (may be null if no ratings were found)
|
||||
**/
|
||||
public var rating:ComboRating;
|
||||
public var rating:Null<ComboRating>;
|
||||
/**
|
||||
Old combo (may be null)
|
||||
**/
|
||||
public var oldRating:ComboRating;
|
||||
public var oldRating:Null<ComboRating>;
|
||||
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package funkin.backend.scripting.events.healthicon;
|
||||
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
|
||||
final class HealthIconChangeEvent extends CancellableEvent {
|
||||
/**
|
||||
* Amount
|
||||
* Animation State
|
||||
*/
|
||||
public var amount:Int;
|
||||
public var anim:OneOfTwo<String, Int>;
|
||||
|
||||
/**
|
||||
* The health icon
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package funkin.backend.scripting.events.menu.storymenu;
|
||||
|
||||
import funkin.menus.StoryMenuState.WeekData;
|
||||
import funkin.backend.week.WeekData;
|
||||
|
||||
final class WeekSelectEvent extends CancellableEvent {
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package funkin.backend.scripting.events.note;
|
||||
|
||||
import funkin.game.Character;
|
||||
import funkin.game.HealthIcon;
|
||||
import funkin.game.Note;
|
||||
|
||||
final class NoteHitEvent extends CancellableEvent {
|
||||
@@ -113,6 +114,10 @@ final class NoteHitEvent extends CancellableEvent {
|
||||
* Whenever the animation should be forced to play (if it's null it will be forced based on the sprite's data xml, if it has one).
|
||||
*/
|
||||
public var forceAnim:Null<Bool> = true;
|
||||
/**
|
||||
* The attached healthIcon used distinction for icons amongst others
|
||||
*/
|
||||
public var healthIcon:HealthIcon;
|
||||
|
||||
/**
|
||||
* Prevents the default sing animation from being played.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package funkin.backend.scripting.events.splash;
|
||||
|
||||
import funkin.game.Splash;
|
||||
import funkin.game.SplashGroup;
|
||||
import funkin.game.Strum;
|
||||
|
||||
@@ -11,7 +12,7 @@ final class SplashShowEvent extends CancellableEvent {
|
||||
/**
|
||||
Splash that is shown
|
||||
**/
|
||||
public var splash:FunkinSprite;
|
||||
public var splash:Splash;
|
||||
/**
|
||||
Strum that the splash is shown on
|
||||
**/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package funkin.backend.shaders;
|
||||
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import haxe.Exception;
|
||||
@@ -555,34 +556,44 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
|
||||
return val;
|
||||
}
|
||||
|
||||
var field:Dynamic = Reflect.field(data, name);
|
||||
var cl:String = Type.getClassName(Type.getClass(field));
|
||||
var field = Reflect.field(data, name);
|
||||
var cl = Type.getClassName(Type.getClass(field));
|
||||
var isNotNull = val != null;
|
||||
// cant do "field is ShaderInput" for some reason
|
||||
if (cl.startsWith("openfl.display.ShaderParameter")) {
|
||||
if (field.__length <= 1) {
|
||||
if ((val is Array))
|
||||
return field.value = val;
|
||||
// that means we wait for a single number, instead of an array
|
||||
if (field.__isInt && !(val is Int))
|
||||
if (field.__isInt && isNotNull && !(val is Int)) {
|
||||
throw new ShaderTypeException(name, Type.getClass(val), 'Int');
|
||||
else if (field.__isBool && !(val is Bool))
|
||||
return null;
|
||||
} else
|
||||
if (field.__isBool && isNotNull && !(val is Bool)) {
|
||||
throw new ShaderTypeException(name, Type.getClass(val), 'Bool');
|
||||
else if (field.__isFloat && !(val is Float))
|
||||
return null;
|
||||
} else
|
||||
if (field.__isFloat && isNotNull && !(val is Float)) {
|
||||
throw new ShaderTypeException(name, Type.getClass(val), 'Float');
|
||||
|
||||
return field.value = [val];
|
||||
return null;
|
||||
}
|
||||
return field.value = isNotNull ? [val] : null;
|
||||
} else {
|
||||
if (!(val is Array))
|
||||
if (isNotNull && !(val is Array)) {
|
||||
throw new ShaderTypeException(name, Type.getClass(val), Array);
|
||||
|
||||
return null;
|
||||
}
|
||||
return field.value = val;
|
||||
}
|
||||
} else if (cl.startsWith("openfl.display.ShaderInput")) {
|
||||
// shader input!!
|
||||
if (!(val is BitmapData)) {
|
||||
var bitmap:BitmapData;
|
||||
if (!isNotNull) bitmap = null;
|
||||
else if (val is BitmapData) bitmap = val;
|
||||
else if (val is FlxGraphic) bitmap = val.bitmap;
|
||||
else {
|
||||
throw new ShaderTypeException(name, Type.getClass(val), BitmapData);
|
||||
return null;
|
||||
}
|
||||
field.input = cast val;
|
||||
field.input = bitmap;
|
||||
}
|
||||
|
||||
return val;
|
||||
|
||||
@@ -24,6 +24,11 @@ enum Control
|
||||
PAUSE;
|
||||
//CHEAT;
|
||||
SWITCHMOD;
|
||||
|
||||
// Debugs
|
||||
DEV_ACCESS;
|
||||
DEV_CONSOLE;
|
||||
DEV_RELOAD;
|
||||
}
|
||||
|
||||
enum KeyboardScheme
|
||||
@@ -150,6 +155,24 @@ class Controls extends FlxActionSet
|
||||
@:pressed("switchmod") public var SWITCHMOD_HOLD(get, set): Bool;
|
||||
@:justReleased("switchmod") public var SWITCHMOD_R(get, set): Bool;
|
||||
|
||||
@:devModeOnly
|
||||
@:gamepad([])
|
||||
@:justPressed("dev-access") public var DEV_ACCESS(get, set): Bool;
|
||||
@:pressed("dev-access") public var DEV_ACCESS_HOLD(get, set): Bool;
|
||||
@:justReleased("dev-access") public var DEV_ACCESS_R(get, set): Bool;
|
||||
|
||||
@:devModeOnly
|
||||
@:gamepad([])
|
||||
@:justPressed("dev-console") public var DEV_CONSOLE(get, set): Bool;
|
||||
@:pressed("dev-console") public var DEV_CONSOLE_HOLD(get, set): Bool;
|
||||
@:justReleased("dev-console") public var DEV_CONSOLE_R(get, set): Bool;
|
||||
|
||||
@:devModeOnly
|
||||
@:gamepad([])
|
||||
@:justPressed("dev-reload") public var DEV_RELOAD(get, set): Bool;
|
||||
@:pressed("dev-reload") public var DEV_RELOAD_HOLD(get, set): Bool;
|
||||
@:justReleased("dev-reload") public var DEV_RELOAD_R(get, set): Bool;
|
||||
|
||||
@:allow(funkin.backend.utils.ControlsUtil)
|
||||
var byName:Map<String, FlxActionDigital> = [];
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package funkin.backend.system;
|
||||
|
||||
import lime.utils.AssetLibrary as LimeAssetLibrary;
|
||||
import lime.utils.AssetType;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
import lime.app.Application;
|
||||
import funkin.backend.system.macros.GitCommitMacro;
|
||||
import funkin.backend.assets.IModsAssetLibrary;
|
||||
import funkin.backend.assets.ScriptedAssetLibrary;
|
||||
import funkin.backend.system.macros.GitCommitMacro;
|
||||
import lime.app.Application;
|
||||
import lime.utils.AssetLibrary as LimeAssetLibrary;
|
||||
import lime.utils.AssetType;
|
||||
|
||||
/**
|
||||
* A class that reads the `flags.ini` file, allowing to read settable Flags (customs too).
|
||||
@@ -28,10 +27,6 @@ class Flags {
|
||||
public static var REPO_OWNER:String = "CodenameCrew";
|
||||
public static var REPO_URL:String = 'https://github.com/$REPO_OWNER/$REPO_NAME';
|
||||
|
||||
// make this empty once you guys are done with the project.
|
||||
// good luck /gen <3 @crowplexus
|
||||
public static var RELEASE_CYCLE:String = "Beta";
|
||||
|
||||
/**
|
||||
* Preferred sound extension for the game's audio files.
|
||||
* Currently is set to `mp3` for web targets, and `ogg` for other targets.
|
||||
@@ -41,7 +36,7 @@ class Flags {
|
||||
public static var IMAGE_EXT:String = "png"; // we also support jpg
|
||||
|
||||
public static var DEFAULT_DISCORD_LOGO_KEY:String = "icon";
|
||||
public static var DEFAULT_DISCORD_CLIENT_ID:String = "1027994136193810442";
|
||||
public static var DEFAULT_DISCORD_CLIENT_ID:String = "1383853614589673472";
|
||||
public static var DEFAULT_DISCORD_LOGO_TEXT:String = "Codename Engine";
|
||||
|
||||
@:also(funkin.game.Character.FALLBACK_CHARACTER)
|
||||
@@ -59,23 +54,25 @@ class Flags {
|
||||
public static var WEEKS_LIST_MOD_MODE:Allow<"prepend", "override", "append"> = "override";
|
||||
|
||||
public static var DEFAULT_BPM:Float = 100.0;
|
||||
public static var DEFAULT_BEATS_PER_MEASURE:Float = 4;
|
||||
public static var DEFAULT_BEATS_PER_MEASURE:Int = 4;
|
||||
public static var DEFAULT_STEPS_PER_BEAT:Int = 4;
|
||||
public static var DEFAULT_LOOP_TIME:Float = 0.0;
|
||||
|
||||
public static var SUPPORTED_CHART_RUNTIME_FORMATS:Array<String> = ["Legacy", "Psych Engine"];
|
||||
public static var SUPPORTED_CHART_FORMATS:Array<String> = ["BaseGame"];
|
||||
|
||||
public static var BASEGAME_SONG_METADATA_VERSION:String = "2.2.2";
|
||||
public static var BASEGAME_SONG_CHART_DATA_VERSION:String = "2.0.0";
|
||||
public static var BASEGAME_DEFAULT_NOTE_STYLE:String = 'funkin';
|
||||
public static var BASEGAME_DEFAULT_ALBUM_ID:String = 'volume1';
|
||||
public static var BASEGAME_DEFAULT_PREVIEW_START:Float = 0;
|
||||
public static var BASEGAME_DEFAULT_PREVIEW_END:Float = 15000;
|
||||
public static var VSLICE_SONG_METADATA_VERSION:String = "2.2.2";
|
||||
public static var VSLICE_SONG_CHART_DATA_VERSION:String = "2.0.0";
|
||||
public static var VSLICE_DEFAULT_NOTE_STYLE:String = 'funkin';
|
||||
public static var VSLICE_DEFAULT_ALBUM_ID:String = 'volume1';
|
||||
public static var VSLICE_DEFAULT_PREVIEW_START:Int = 0;
|
||||
public static var VSLICE_DEFAULT_PREVIEW_END:Int = 15000;
|
||||
|
||||
/**
|
||||
* Default background colors for songs without bg color
|
||||
* Default background colors for songs or more without bg color
|
||||
*/
|
||||
public static var DEFAULT_COLOR:FlxColor = 0xFF9271FD;
|
||||
public static var DEFAULT_WEEK_COLOR:FlxColor = 0xFFF9CF51;
|
||||
public static var DEFAULT_COOP_ALLOWED:Bool = false;
|
||||
public static var DEFAULT_OPPONENT_MODE_ALLOWED:Bool = false;
|
||||
|
||||
@@ -97,7 +94,7 @@ class Flags {
|
||||
public static var DEFAULT_CAM_ZOOM_STRENGTH:Int = 1;
|
||||
public static var DEFAULT_CAM_ZOOM:Float = 1.05; // what zoom level it defaults to
|
||||
public static var DEFAULT_HUD_ZOOM:Float = 1.0;
|
||||
public static var MAX_CAMERA_ZOOM:Float = 1.35;
|
||||
public static var MAX_CAMERA_ZOOM_MULT:Float = 1.35;
|
||||
|
||||
public static var DEFAULT_PAUSE_ITEMS:Array<String> = ['Resume', 'Restart Song', 'Change Controls', 'Change Options', 'Exit to menu', "Exit to charter"];
|
||||
public static var DEFAULT_CUTSCENE_PAUSE_ITEMS:Array<String> = ['Resume Cutscene', 'Skip Cutscene', 'Restart Cutscene', 'Exit to menu'];
|
||||
@@ -157,7 +154,7 @@ class Flags {
|
||||
// -- End of Codename's Default Flags --
|
||||
|
||||
/**
|
||||
* Flags that Codename couldn't recognize as it's own defaults (they can only be `string`!).
|
||||
* Flags that Codename couldn't recognize as it's own defaults (they can only be `string`! due to them being unparsed).
|
||||
*/
|
||||
@:bypass public static var customFlags:Map<String, String> = [];
|
||||
|
||||
@@ -187,10 +184,9 @@ class Flags {
|
||||
}
|
||||
}
|
||||
|
||||
public static function loadFromDatas(files:Array<String>) {
|
||||
public static function loadFromDatas(datas:Array<String>) {
|
||||
var flags:Map<String, String> = [];
|
||||
for(file in files) {
|
||||
var data:String = Assets.getText(file);
|
||||
for(data in datas) {
|
||||
if(data != null)
|
||||
loadFromData(flags, data);
|
||||
}
|
||||
|
||||
@@ -81,12 +81,12 @@ class Main extends Sprite
|
||||
public static var audioDisconnected:Bool = false;
|
||||
|
||||
public static var changeID:Int = 0;
|
||||
public static var pathBack = #if windows
|
||||
public static var pathBack = #if (windows || linux)
|
||||
"../../../../"
|
||||
#elseif mac
|
||||
"../../../../../../../"
|
||||
#else
|
||||
""
|
||||
"../../../../"
|
||||
#end;
|
||||
public static var startedFromSource:Bool = #if TEST_BUILD true #else false #end;
|
||||
|
||||
@@ -180,6 +180,8 @@ class Main extends Sprite
|
||||
}
|
||||
|
||||
public static function refreshAssets() @:privateAccess {
|
||||
FunkinCache.instance.clearSecondLayer();
|
||||
|
||||
var game = FlxG.game;
|
||||
var daSndTray = Type.createInstance(game._customSoundTray = funkin.menus.ui.FunkinSoundTray, []);
|
||||
var index:Int = game.numChildren - 1;
|
||||
@@ -238,9 +240,9 @@ class Main extends Sprite
|
||||
Sys.setCwd(haxe.io.Path.directory(Sys.programPath()));
|
||||
}
|
||||
#elseif android
|
||||
Sys.setCwd(Path.addTrailingSlash(VERSION.SDK_INT > 30 ? Context.getObbDir() : Context.getExternalFilesDir()));
|
||||
Sys.setCwd(haxe.io.Path.addTrailingSlash(VERSION.SDK_INT > 30 ? Context.getObbDir() : Context.getExternalFilesDir()));
|
||||
#elseif (ios || switch)
|
||||
Sys.setCwd(Path.addTrailingSlash(openfl.filesystem.File.applicationStorageDirectory.nativePath));
|
||||
Sys.setCwd(haxe.io.Path.addTrailingSlash(openfl.filesystem.File.applicationStorageDirectory.nativePath));
|
||||
#end
|
||||
}
|
||||
|
||||
|
||||
@@ -6,16 +6,21 @@ import sys.FileSystem;
|
||||
import flixel.FlxState;
|
||||
import funkin.backend.assets.ModsFolder;
|
||||
import funkin.backend.chart.EventsData;
|
||||
import funkin.menus.BetaWarningState;
|
||||
import funkin.menus.TitleState;
|
||||
import funkin.backend.system.framerate.Framerate;
|
||||
import funkin.menus.TitleState;
|
||||
import haxe.io.Path;
|
||||
|
||||
@dox(hide)
|
||||
typedef AddonInfo = {
|
||||
var name:String;
|
||||
var path:String;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple state used for loading the game
|
||||
*/
|
||||
class MainState extends FlxState {
|
||||
public static var initiated:Bool = false;
|
||||
public static var betaWarningShown:Bool = false;
|
||||
public override function create() {
|
||||
super.create();
|
||||
if (!initiated) {
|
||||
@@ -36,25 +41,64 @@ class MainState extends FlxState {
|
||||
Paths.assetsTree.reset();
|
||||
|
||||
#if MOD_SUPPORT
|
||||
var _lowPriorityAddons:Array<String> = [];
|
||||
var _highPriorityAddons:Array<String> = [];
|
||||
var _noPriorityAddons:Array<String> = [];
|
||||
if (FileSystem.exists(ModsFolder.addonsPath) && FileSystem.isDirectory(ModsFolder.addonsPath)) {
|
||||
for(i=>addon in [for(dir in FileSystem.readDirectory(ModsFolder.addonsPath)) if (FileSystem.isDirectory(ModsFolder.addonsPath + dir)) dir]) {
|
||||
if (addon.startsWith("[LOW]")) _lowPriorityAddons.insert(0, addon);
|
||||
else if (addon.startsWith("[HIGH]")) _highPriorityAddons.insert(0, addon);
|
||||
else _noPriorityAddons.insert(0, addon);
|
||||
}
|
||||
for (addon in _lowPriorityAddons)
|
||||
Paths.assetsTree.addLibrary(ModsFolder.loadModLib(ModsFolder.addonsPath + addon, StringTools.ltrim(addon.substr("[LOW]".length))));
|
||||
}
|
||||
if (ModsFolder.currentModFolder != null)
|
||||
Paths.assetsTree.addLibrary(ModsFolder.loadModLib(ModsFolder.modsPath + ModsFolder.currentModFolder, ModsFolder.currentModFolder));
|
||||
inline function isDirectory(path:String):Bool
|
||||
return FileSystem.exists(path) && FileSystem.isDirectory(path);
|
||||
|
||||
if (FileSystem.exists(ModsFolder.addonsPath) && FileSystem.isDirectory(ModsFolder.addonsPath)){
|
||||
for (addon in _noPriorityAddons) Paths.assetsTree.addLibrary(ModsFolder.loadModLib(ModsFolder.addonsPath + addon, addon));
|
||||
for (addon in _highPriorityAddons) Paths.assetsTree.addLibrary(ModsFolder.loadModLib(ModsFolder.addonsPath + addon, StringTools.ltrim(addon.substr("[HIGH]".length))));
|
||||
inline function ltrim(str:String, prefix:String):String
|
||||
return str.substr(prefix.length).ltrim();
|
||||
|
||||
inline function loadLib(path:String, name:String)
|
||||
Paths.assetsTree.addLibrary(ModsFolder.loadModLib(path, name));
|
||||
|
||||
var _lowPriorityAddons:Array<AddonInfo> = [];
|
||||
var _highPriorityAddons:Array<AddonInfo> = [];
|
||||
var _noPriorityAddons:Array<AddonInfo> = [];
|
||||
|
||||
var addonPaths = [
|
||||
ModsFolder.addonsPath,
|
||||
(
|
||||
ModsFolder.currentModFolder != null ?
|
||||
ModsFolder.modsPath + ModsFolder.currentModFolder + "/addons/" :
|
||||
null
|
||||
)
|
||||
];
|
||||
|
||||
for(path in addonPaths) {
|
||||
if (path == null) continue;
|
||||
if (!isDirectory(path)) continue;
|
||||
|
||||
for(addon in FileSystem.readDirectory(path)) {
|
||||
if(!FileSystem.isDirectory(path + addon)) {
|
||||
switch(Path.extension(addon).toLowerCase()) {
|
||||
case 'zip':
|
||||
addon = Path.withoutExtension(addon);
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var data:AddonInfo = {
|
||||
name: addon,
|
||||
path: path + addon
|
||||
};
|
||||
|
||||
if (addon.startsWith("[LOW]")) _lowPriorityAddons.insert(0, data);
|
||||
else if (addon.startsWith("[HIGH]")) _highPriorityAddons.insert(0, data);
|
||||
else _noPriorityAddons.insert(0, data);
|
||||
}
|
||||
}
|
||||
|
||||
for (addon in _lowPriorityAddons)
|
||||
loadLib(addon.path, ltrim(addon.name, "[LOW]"));
|
||||
|
||||
if (ModsFolder.currentModFolder != null)
|
||||
loadLib(ModsFolder.modsPath + ModsFolder.currentModFolder, ModsFolder.currentModFolder);
|
||||
|
||||
for (addon in _noPriorityAddons)
|
||||
loadLib(addon.path, addon.name);
|
||||
|
||||
for (addon in _highPriorityAddons)
|
||||
loadLib(addon.path, ltrim(addon.name, "[HIGH]"));
|
||||
#end
|
||||
|
||||
Flags.load();
|
||||
@@ -67,14 +111,10 @@ class MainState extends FlxState {
|
||||
ControlsUtil.loadCustomControls();
|
||||
TitleState.initialized = false;
|
||||
|
||||
if(Framerate.isLoaded)
|
||||
if (Framerate.isLoaded)
|
||||
Framerate.instance.reload();
|
||||
|
||||
if (Flags.DISABLE_BETA_WARNING_SCREEN || betaWarningShown)
|
||||
FlxG.switchState(new TitleState());
|
||||
else
|
||||
FlxG.switchState(new BetaWarningState());
|
||||
betaWarningShown = true;
|
||||
FlxG.switchState(new TitleState());
|
||||
|
||||
#if sys
|
||||
CoolUtil.safeAddAttributes('./.temp/', NativeAPI.FileAttribute.HIDDEN);
|
||||
|
||||
@@ -13,5 +13,5 @@ class CodenameBuildField extends TextField {
|
||||
}
|
||||
|
||||
public function reload()
|
||||
text = 'Codename Engine ${Flags.RELEASE_CYCLE}\n${Flags.COMMIT_MESSAGE}';
|
||||
text = '${Flags.VERSION_MESSAGE}\n${Flags.COMMIT_MESSAGE}';
|
||||
}
|
||||
|
||||
@@ -12,9 +12,7 @@ class FlixelInfo extends FramerateCategory {
|
||||
|
||||
|
||||
@:privateAccess {
|
||||
var c:Int = 0;
|
||||
for(_ in FlxG.bitmap._cache.keys())
|
||||
c++;
|
||||
var c:Int = Lambda.count(FlxG.bitmap._cache);
|
||||
|
||||
if((FlxG.state is ModState)) {
|
||||
var state:ModState = cast FlxG.state;
|
||||
@@ -27,6 +25,7 @@ class FlixelInfo extends FramerateCategory {
|
||||
_text += '\nBitmaps Count: ${c}';
|
||||
_text += '\nSounds Count: ${FlxG.sound.list.length}';
|
||||
_text += '\nFlxG.game Childs Count: ${FlxG.game.numChildren}';
|
||||
if(FlxG.renderBlit) _text += '\nBlitting Render: true';
|
||||
// _text += '\nCached objects count: ${cachedObjects}';
|
||||
#if FLX_POINT_POOL
|
||||
//var points = flixel.math.FlxPoint.FlxBasePoint.pool;
|
||||
|
||||
@@ -20,7 +20,7 @@ class Framerate extends Sprite {
|
||||
public static var codenameBuildField:CodenameBuildField;
|
||||
#end
|
||||
|
||||
public static var fontName:String = #if windows '${Sys.getEnv("windir")}\\Fonts\\consola.ttf' #else "_sans" #end;
|
||||
public static var fontName:String = #if windows '${Sys.getEnv("windir")}\\Fonts\\consola.ttf' #else "_typewriter" #end;
|
||||
|
||||
/**
|
||||
* 0: FPS INVISIBLE
|
||||
@@ -46,7 +46,7 @@ class Framerate extends Sprite {
|
||||
super();
|
||||
if (instance != null) throw "Cannot create another instance";
|
||||
instance = this;
|
||||
textFormat = new TextFormat("Consolas", 12, -1);
|
||||
textFormat = new TextFormat(fontName, 12, -1);
|
||||
|
||||
isLoaded = true;
|
||||
|
||||
|
||||
@@ -53,6 +53,25 @@ class SystemInfo extends FramerateCategory {
|
||||
if (osName != "")
|
||||
osInfo = '${osName} ${osVersion}'.trim();
|
||||
}
|
||||
#elseif windows
|
||||
var windowsCurrentVersionPath = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
|
||||
var buildNumber = Std.parseInt(RegistryUtil.get(HKEY_LOCAL_MACHINE, windowsCurrentVersionPath, "CurrentBuildNumber"));
|
||||
var edition = RegistryUtil.get(HKEY_LOCAL_MACHINE, windowsCurrentVersionPath, "ProductName");
|
||||
|
||||
var lcuKey = "WinREVersion"; // Last Cumulative Update Key On Older Windows Versions
|
||||
if (buildNumber >= 22000) { // Windows 11 Initial Release Build Number
|
||||
edition = edition.replace("Windows 10", "Windows 11");
|
||||
lcuKey = "LCUVer"; // Last Cumulative Update Key On Windows 11
|
||||
}
|
||||
|
||||
var lcuVersion = RegistryUtil.get(HKEY_LOCAL_MACHINE, windowsCurrentVersionPath, lcuKey);
|
||||
|
||||
osInfo = edition;
|
||||
|
||||
if (lcuVersion != null && lcuVersion != "")
|
||||
osInfo += ' ${lcuVersion}';
|
||||
else if (lime.system.System.platformVersion != null && lime.system.System.platformVersion != "")
|
||||
osInfo += ' ${lime.system.System.platformVersion}';
|
||||
#else
|
||||
if (lime.system.System.platformLabel != null && lime.system.System.platformLabel != "" && lime.system.System.platformVersion != null && lime.system.System.platformVersion != "")
|
||||
osInfo = '${lime.system.System.platformLabel.replace(lime.system.System.platformVersion, "").trim()} ${lime.system.System.platformVersion}';
|
||||
@@ -62,10 +81,7 @@ class SystemInfo extends FramerateCategory {
|
||||
|
||||
try {
|
||||
#if windows
|
||||
var process = new HiddenProcess("wmic", ["cpu", "get", "name"]);
|
||||
if (process.exitCode() != 0) throw 'Could not fetch CPU information';
|
||||
|
||||
cpuName = process.stdout.readAll().toString().trim().split("\n")[1].trim();
|
||||
cpuName = RegistryUtil.get(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "ProcessorNameString");
|
||||
#elseif mac
|
||||
var process = new HiddenProcess("sysctl -a | grep brand_string"); // Somehow this isn't able to use the args but it still works
|
||||
if (process.exitCode() != 0) throw 'Could not fetch CPU information';
|
||||
@@ -86,7 +102,7 @@ class SystemInfo extends FramerateCategory {
|
||||
Logs.trace('Unable to grab CPU Name: $e', ERROR, RED);
|
||||
}
|
||||
|
||||
@:privateAccess {
|
||||
@:privateAccess if(FlxG.renderTile) { // Blit doesn't enable the gpu. Idk if we should fix this
|
||||
if (flixel.FlxG.stage.context3D != null && flixel.FlxG.stage.context3D.gl != null) {
|
||||
gpuName = Std.string(flixel.FlxG.stage.context3D.gl.getParameter(flixel.FlxG.stage.context3D.gl.RENDERER)).split("/")[0].trim();
|
||||
#if !flash
|
||||
|
||||
@@ -7,6 +7,8 @@ class BuildInfo {
|
||||
public static function printBuildInfo() {
|
||||
if(haxe.macro.Context.defined("display")) return;
|
||||
var haxeVersion = haxe.macro.Context.definedValue("haxe").trim();
|
||||
var sleepChangeVersion = false;
|
||||
|
||||
Sys.println('[ BUILD INFO ]');
|
||||
Sys.println('Haxe Version: ${haxeVersion}');
|
||||
try {
|
||||
@@ -33,8 +35,10 @@ class BuildInfo {
|
||||
}
|
||||
}
|
||||
|
||||
if(lastBuiltWith != null && lastBuiltWith.length > 0 && lastBuiltWith != haxeVersion)
|
||||
if(lastBuiltWith != null && lastBuiltWith.length > 0 && lastBuiltWith != haxeVersion) {
|
||||
Sys.println('Last Built With Haxe: ${lastBuiltWith} [!!!! MAKE SURE IF YOU SWITCHED VERSIONS YOU DELETE EXPORT FOLDERS !!!!]');
|
||||
sleepChangeVersion = true;
|
||||
}
|
||||
} catch(e) {}
|
||||
var targetPlatform = #if html5 "Web (HTML5)"
|
||||
#elseif ios "iOS"
|
||||
@@ -51,6 +55,14 @@ class BuildInfo {
|
||||
Sys.println('Target Platform: ' + targetPlatform);
|
||||
Sys.println('Build Date: ${Date.now().toString()}');
|
||||
Sys.println('');
|
||||
|
||||
if(sleepChangeVersion) {
|
||||
Sys.println('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
|
||||
Sys.println('!!!! MAKE SURE IF YOU SWITCHED VERSIONS YOU DELETE EXPORT FOLDERS !!!!');
|
||||
Sys.println('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
|
||||
Sys.println('');
|
||||
Sys.sleep(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
@@ -14,6 +14,7 @@ class ControlsMacro
|
||||
{
|
||||
static var _allControls: Array<String> = null;
|
||||
static var _allInternalControls: Array<String> = null;
|
||||
static var _allDevModeOnlyControls: Array<String> = null;
|
||||
static var _currentControls: Map<String, Array<Expr>> = null;
|
||||
static var _currentGamepadControls: Map<String, Expr> = null;
|
||||
static var _keySet: Map<String, String> = null;
|
||||
@@ -29,6 +30,7 @@ class ControlsMacro
|
||||
|
||||
_allControls = [];
|
||||
_allInternalControls = [];
|
||||
_allDevModeOnlyControls = [];
|
||||
_currentControls = [];
|
||||
_currentGamepadControls = [];
|
||||
_keySet = [];
|
||||
@@ -154,6 +156,7 @@ class ControlsMacro
|
||||
|
||||
_allControls = null;
|
||||
_allInternalControls = null;
|
||||
_allDevModeOnlyControls = null;
|
||||
_currentControls = null;
|
||||
_currentGamepadControls = null;
|
||||
_keySet = null;
|
||||
@@ -289,6 +292,9 @@ class ControlsMacro
|
||||
case ":justReleased":
|
||||
keyset = extractString(meta.params[0]);
|
||||
expr = macro func($i{internalName}, JUST_RELEASED);
|
||||
case ":devModeOnly":
|
||||
if (!_allDevModeOnlyControls.contains(shortName))
|
||||
_allDevModeOnlyControls.push(shortName);
|
||||
default:
|
||||
shouldRemove = false;
|
||||
}
|
||||
@@ -326,17 +332,16 @@ class ControlsMacro
|
||||
|
||||
// Generated Code:
|
||||
// inline function get_UI_UP(): Bool
|
||||
// return _uiUp.check();
|
||||
// return _uiUp.check(); or return Options.devMode && _uiUp.check(); depending if its dev mode
|
||||
var getField: Field = {
|
||||
name: "get_" + name,
|
||||
access: [APrivate, AInline],
|
||||
kind: FFun({
|
||||
ret: macro : Bool,
|
||||
params: [],
|
||||
expr: macro
|
||||
{
|
||||
return $i{internalName}.check();
|
||||
},
|
||||
expr: _allDevModeOnlyControls.contains(shortName) ?
|
||||
(macro return Options.devMode && $i{internalName}.check()) :
|
||||
(macro return $i{internalName}.check()),
|
||||
args: []
|
||||
}),
|
||||
pos: field.pos,
|
||||
|
||||
@@ -18,7 +18,7 @@ class Socket implements IFlxDestroyable {
|
||||
|
||||
public function read():String {
|
||||
try {
|
||||
return this.socket.input.readUntil(('\n').charCodeAt(0)).replace("\\n", "\n");
|
||||
return this.socket.input.readUntil('\n'.code).replace("\\n", "\n");
|
||||
} catch(e) {
|
||||
|
||||
}
|
||||
|
||||
@@ -21,21 +21,21 @@ class AudioAnalyzer {
|
||||
}
|
||||
|
||||
public function analyze(startPos:Float, endPos:Float):Float {
|
||||
var bitsPerSample = buffer.bitsPerSample;
|
||||
var multi = __timeMulti / 4000 / bitsPerSample;
|
||||
var bytesStartPos:Int = Math.floor(startPos * multi) * bitsPerSample;
|
||||
var bytesEndPos:Int = Math.floor(endPos * multi) * bitsPerSample;
|
||||
var bytesStartPos:Int = Math.floor(startPos * __timeMulti / 4000 / buffer.bitsPerSample) * buffer.bitsPerSample;
|
||||
var bytesEndPos:Int = Math.floor(endPos * __timeMulti / 4000 / buffer.bitsPerSample) * buffer.bitsPerSample;
|
||||
|
||||
bytesStartPos -= bytesStartPos % bitsPerSample;
|
||||
bytesEndPos -= bytesEndPos % bitsPerSample;
|
||||
|
||||
var dataBuffer = buffer.data.buffer;
|
||||
bytesStartPos -= bytesStartPos % buffer.bitsPerSample;
|
||||
bytesEndPos -= bytesEndPos % buffer.bitsPerSample;
|
||||
|
||||
var maxByte:Int = 0;
|
||||
for(i in 0...Math.floor((bytesEndPos - bytesStartPos) / bitsPerSample)) {
|
||||
var bytePos = bytesStartPos + (i * bitsPerSample);
|
||||
// What if bitsPerSample is 8? or 32? or 64? -Ne_Eo
|
||||
var byte:Int = dataBuffer.get(bytePos) | (dataBuffer.get(bytePos + 1) << 8);
|
||||
for(i in 0...Math.floor((bytesEndPos - bytesStartPos) / buffer.bitsPerSample)) {
|
||||
#if js
|
||||
var byte:Int = buffer.data[bytesStartPos + (i * buffer.bitsPerSample)]
|
||||
| (buffer.data[bytesStartPos + (i * buffer.bitsPerSample) + 1] << 8);
|
||||
#else
|
||||
var byte:Int = buffer.data.buffer.get(bytesStartPos + (i * buffer.bitsPerSample))
|
||||
| (buffer.data.buffer.get(bytesStartPos + (i * buffer.bitsPerSample) + 1) << 8);
|
||||
#end
|
||||
if (byte > 256 * 128) byte -= 256 * 256;
|
||||
if (maxByte < byte) maxByte = byte;
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ final class CoolUtil
|
||||
if(result.isNothing) Logs.trace('The file where it has been tried to get the attributes from, might be corrupted or inexistent (code: ${result.getValue()})', WARNING);
|
||||
return result;
|
||||
#else
|
||||
return 0;
|
||||
return new FileAttributeWrapper(0);
|
||||
#end
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ final class CoolUtil
|
||||
addMissingFolders(Path.directory(path));
|
||||
|
||||
var result = NativeAPI.addFileAttributes(path, attrib, useAbsolute);
|
||||
if(result == 0) Logs.trace('Failed to add attributes to $path with a code of: $result', WARNING);
|
||||
if (result == 0) Logs.trace('Failed to add attributes to $path with a code of: $result', WARNING);
|
||||
return result;
|
||||
#else
|
||||
return 0;
|
||||
@@ -453,9 +453,13 @@ final class CoolUtil
|
||||
if (Assets.exists(infoPath)) {
|
||||
var musicInfo = IniUtil.parseAsset(infoPath, [
|
||||
"BPM" => null,
|
||||
"TimeSignature" => Flags.DEFAULT_BEATS_PER_MEASURE + "/" + Flags.DEFAULT_STEPS_PER_BEAT
|
||||
"TimeSignature" => Flags.DEFAULT_BEATS_PER_MEASURE + "/" + Flags.DEFAULT_STEPS_PER_BEAT,
|
||||
"LoopTime" => '${Flags.DEFAULT_LOOP_TIME}'
|
||||
]);
|
||||
|
||||
if (FlxG.sound.music != null)
|
||||
FlxG.sound.music.loopTime = Std.parseFloat(musicInfo["LoopTime"]) * 1000;
|
||||
|
||||
var timeSignParsed:Array<Null<Float>> = musicInfo["TimeSignature"] == null ? [] : [for(s in musicInfo["TimeSignature"].split("/")) Std.parseFloat(s)];
|
||||
var beatsPerMeasure:Float = Flags.DEFAULT_BEATS_PER_MEASURE;
|
||||
var stepsPerBeat:Int = Flags.DEFAULT_STEPS_PER_BEAT;
|
||||
@@ -712,11 +716,11 @@ final class CoolUtil
|
||||
* @param spr Sprite to load the graphic for
|
||||
* @param path Path to the graphic
|
||||
*/
|
||||
public static function loadAnimatedGraphic(spr:FlxSprite, path:String) {
|
||||
public static function loadAnimatedGraphic(spr:FlxSprite, path:String, fps:Float = 24.0) {
|
||||
spr.frames = Paths.getFrames(path, true);
|
||||
|
||||
if (spr.frames != null && spr.frames.frames != null) {
|
||||
spr.animation.add("idle", [for(i in 0...spr.frames.frames.length) i], 24, true);
|
||||
spr.animation.add("idle", [for(i in 0...spr.frames.frames.length) i], fps, true);
|
||||
spr.animation.play("idle");
|
||||
}
|
||||
|
||||
@@ -915,8 +919,8 @@ final class CoolUtil
|
||||
* @param mainEase Main ease
|
||||
* @param suffix Suffix (Ignored if `mainEase` is `linear`)
|
||||
*/
|
||||
@:noUsing public static inline function flxeaseFromString(mainEase:String, suffix:String)
|
||||
return Reflect.field(FlxEase, mainEase + (mainEase == "linear" ? "" : suffix));
|
||||
@:noUsing public static inline function flxeaseFromString(mainEase:String, ?suffix:String)
|
||||
return Reflect.field(FlxEase, mainEase + (mainEase == "linear" || suffix == null ? "" : suffix));
|
||||
|
||||
/*
|
||||
* Returns the filename of a path, without the extension.
|
||||
@@ -1323,6 +1327,124 @@ final class CoolUtil
|
||||
public static inline function isMapEmpty<K, V>(map: Map<K, V>): Bool {
|
||||
return !map.keys().hasNext();
|
||||
}
|
||||
|
||||
public inline static function parsePropertyString(fieldPath:String):Array<OneOfTwo<String, Int>> {
|
||||
return FlxTween.parseFieldString(fieldPath);
|
||||
}
|
||||
|
||||
public static function stringifyFieldsPath(fields:Array<OneOfTwo<String, Int>>):String {
|
||||
var str = new StringBuf();
|
||||
var first = true;
|
||||
for (field in fields) {
|
||||
if (Type.typeof(field) == TInt) {
|
||||
str.add('[${field}]');
|
||||
} else {
|
||||
if (!first)
|
||||
str.add('.');
|
||||
str.add(field);
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
public static function parseProperty(target:Dynamic, fields:OneOfTwo<String, Array<OneOfTwo<String, Int>>>):Dynamic {
|
||||
var fields:Array<OneOfTwo<String, Int>> = {
|
||||
if((fields is String)) CoolUtil.parsePropertyString(fields);
|
||||
else fields;
|
||||
}
|
||||
|
||||
var field = CoolUtil.last(fields);
|
||||
for (i in 0...fields.length - 1) {
|
||||
var component = fields[i];
|
||||
if (Type.typeof(component) == TInt) {
|
||||
if ((target is Array)) {
|
||||
var index:Int = cast component;
|
||||
var arr:Array<Dynamic> = cast target;
|
||||
target = arr[index];
|
||||
}
|
||||
} else { // TClass(String)
|
||||
target = Reflect.getProperty(target, component);
|
||||
}
|
||||
if (!Reflect.isObject(target) && !(target is Array))
|
||||
throw 'The object does not have the property "$component" in "${stringifyFieldsPath(fields)}"';
|
||||
}
|
||||
return new PropertyInfo(target, field);
|
||||
}
|
||||
|
||||
public static function cloneProperty(toTarget:Dynamic, fields:OneOfTwo<String, Array<OneOfTwo<String, Int>>>, fromTarget:Dynamic):Dynamic {
|
||||
var fields:Array<OneOfTwo<String, Int>> = {
|
||||
if((fields is String)) CoolUtil.parsePropertyString(fields);
|
||||
else fields;
|
||||
}
|
||||
|
||||
var toProperty = CoolUtil.parseProperty(toTarget, fields);
|
||||
var fromProperty = CoolUtil.parseProperty(fromTarget, fields);
|
||||
|
||||
return toProperty.setValue(fromProperty.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
class PropertyInfo {
|
||||
public var object:Dynamic;
|
||||
public var field:OneOfTwo<String, Int>;
|
||||
public var typeOfField:Type.ValueType;
|
||||
#if hscript_improved
|
||||
public var isCustom:Bool = false;
|
||||
public var custom:hscript.IHScriptCustomBehaviour;
|
||||
#end
|
||||
|
||||
public function new(object:Dynamic, field:OneOfTwo<String, Int>) {
|
||||
this.object = object;
|
||||
this.field = field;
|
||||
#if hscript_improved
|
||||
if (object is hscript.IHScriptCustomBehaviour)
|
||||
{
|
||||
isCustom = true;
|
||||
custom = cast object;
|
||||
}
|
||||
#end
|
||||
|
||||
typeOfField = Type.typeof(field);
|
||||
}
|
||||
|
||||
public function getValue():Dynamic
|
||||
{
|
||||
if (typeOfField == TInt)
|
||||
{
|
||||
var index:Int = cast field;
|
||||
var arr:Array<Dynamic> = cast object;
|
||||
return arr[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
#if hscript_improved
|
||||
if (isCustom)
|
||||
return custom.hget(field);
|
||||
else
|
||||
#end
|
||||
return Reflect.getProperty(object, field);
|
||||
}
|
||||
}
|
||||
|
||||
public function setValue(value:Dynamic):Void
|
||||
{
|
||||
if (typeOfField == TInt)
|
||||
{
|
||||
var index:Int = cast field;
|
||||
var arr:Array<Dynamic> = cast object;
|
||||
arr[index] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if hscript_improved
|
||||
if (isCustom)
|
||||
custom.hset(field, value);
|
||||
else
|
||||
#end
|
||||
Reflect.setProperty(object, field, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -127,7 +127,7 @@ final class MemoryUtil {
|
||||
public static function getMemType():String {
|
||||
#if windows
|
||||
var memoryMap:Map<Int, String> = [
|
||||
0 => "Unknown",
|
||||
0 => null,
|
||||
1 => "Other",
|
||||
2 => "DRAM",
|
||||
3 => "Synchronous DRAM",
|
||||
@@ -166,9 +166,9 @@ final class MemoryUtil {
|
||||
];
|
||||
var memoryOutput:Int = -1;
|
||||
|
||||
var process = new HiddenProcess("wmic", ["memorychip", "get", "SMBIOSMemoryType"]);
|
||||
var process = new HiddenProcess("powershell", ["-Command", "Get-CimInstance Win32_PhysicalMemory | Select-Object -ExpandProperty SMBIOSMemoryType" ]);
|
||||
if (process.exitCode() == 0) memoryOutput = Std.int(Std.parseFloat(process.stdout.readAll().toString().trim().split("\n")[1]));
|
||||
if (memoryOutput != -1) return memoryMap[memoryOutput];
|
||||
if (memoryOutput != -1) return memoryMap[memoryOutput] == null ? 'Unknown ($memoryOutput)' : memoryMap[memoryOutput];
|
||||
#elseif mac
|
||||
var process = new HiddenProcess("system_profiler", ["SPMemoryDataType"]);
|
||||
var reg = ~/Type: (.+)/;
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
enum abstract RegistryHive(Int) {
|
||||
var HKEY_CLASSES_ROOT = 0x80000000;
|
||||
var HKEY_CURRENT_USER = 0x80000001;
|
||||
var HKEY_LOCAL_MACHINE = 0x80000002;
|
||||
var HKEY_USERS = 0x80000003;
|
||||
var HKEY_CURRENT_CONFIG = 0x80000005;
|
||||
}
|
||||
#if windows
|
||||
@:cppFileCode('
|
||||
#include <windows.h>
|
||||
#include <tchar.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
')
|
||||
#end
|
||||
class RegistryUtil {
|
||||
#if windows
|
||||
@:functionCode('
|
||||
HKEY hKey;
|
||||
LONG result;
|
||||
DWORD dataSize = 0;
|
||||
DWORD dataType = 0;
|
||||
|
||||
std::wstring subkey = std::wstring(key.wchar_str());
|
||||
std::wstring valname = std::wstring(string.wchar_str());
|
||||
|
||||
result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_READ, &hKey);
|
||||
if (result != ERROR_SUCCESS) return null();
|
||||
|
||||
result = RegQueryValueExW(hKey, valname.c_str(), NULL, &dataType, NULL, &dataSize);
|
||||
if (result != ERROR_SUCCESS || dataSize == 0) {
|
||||
RegCloseKey(hKey);
|
||||
return null();
|
||||
}
|
||||
|
||||
std::vector<wchar_t> buffer(dataSize / sizeof(wchar_t));
|
||||
result = RegQueryValueExW(hKey, valname.c_str(), NULL, NULL, (LPBYTE)buffer.data(), &dataSize);
|
||||
RegCloseKey(hKey);
|
||||
|
||||
if (result == ERROR_SUCCESS) {
|
||||
return ::String(buffer.data());
|
||||
}
|
||||
return null();
|
||||
')
|
||||
#end
|
||||
public static function get(hive:RegistryHive, key:String, string:String):Null<String>
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
#if windows
|
||||
@:functionCode('
|
||||
HKEY hKey;
|
||||
LONG result;
|
||||
|
||||
std::wstring subkey = std::wstring(key.wchar_str());
|
||||
std::wstring valname = std::wstring(string.wchar_str());
|
||||
std::wstring data = std::wstring(value.wchar_str());
|
||||
|
||||
result = RegCreateKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL);
|
||||
if (result != ERROR_SUCCESS) return false;
|
||||
|
||||
result = RegSetValueExW(hKey, valname.c_str(), 0, REG_SZ, (const BYTE*)data.c_str(), (DWORD)((data.length() + 1) * sizeof(wchar_t)));
|
||||
RegCloseKey(hKey);
|
||||
|
||||
return result == ERROR_SUCCESS;
|
||||
')
|
||||
#end
|
||||
public static function set(hive:RegistryHive, key:String, string:String, value:String):Bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if windows
|
||||
@:functionCode('
|
||||
HKEY hKey;
|
||||
LONG result;
|
||||
|
||||
std::wstring subkey = std::wstring(key.wchar_str());
|
||||
std::wstring valname = std::wstring(string.wchar_str());
|
||||
|
||||
result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_READ, &hKey);
|
||||
if (result != ERROR_SUCCESS) return false;
|
||||
|
||||
DWORD dataType = 0;
|
||||
result = RegQueryValueExW(hKey, valname.c_str(), NULL, &dataType, NULL, NULL);
|
||||
|
||||
RegCloseKey(hKey);
|
||||
|
||||
return result == ERROR_SUCCESS;
|
||||
')
|
||||
#end
|
||||
public static function exists(hive:RegistryHive, key:String, string:String):Bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if windows
|
||||
@:functionCode('
|
||||
HKEY hKey;
|
||||
LONG result;
|
||||
|
||||
std::wstring subkey = std::wstring(key.wchar_str());
|
||||
std::wstring valname = std::wstring(string.wchar_str());
|
||||
|
||||
result = RegOpenKeyExW((HKEY)reinterpret_cast<HKEY>(static_cast<uintptr_t>(hive)), subkey.c_str(), 0, KEY_SET_VALUE, &hKey);
|
||||
if (result != ERROR_SUCCESS) return false;
|
||||
|
||||
result = RegDeleteValueW(hKey, valname.c_str());
|
||||
RegCloseKey(hKey);
|
||||
|
||||
return result == ERROR_SUCCESS;
|
||||
')
|
||||
#end
|
||||
public static function delete(hive:RegistryHive, key:String, string:String):Bool {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -114,12 +114,13 @@ final class XMLUtil {
|
||||
* @param parentFolder The parent folder
|
||||
* @param defaultAnimType The default animation type
|
||||
*/
|
||||
public static function loadSpriteFromXML(spr:FunkinSprite, node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT):FunkinSprite {
|
||||
public static function loadSpriteFromXML(spr:FunkinSprite, node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, loadGraphic:Bool = true):FunkinSprite {
|
||||
if (parentFolder == null) parentFolder = "";
|
||||
|
||||
spr.name = node.getAtt("name");
|
||||
spr.antialiasing = true;
|
||||
spr.loadSprite(Paths.image('$parentFolder${node.getAtt("sprite").getDefault(spr.name)}', null, true));
|
||||
if (loadGraphic)
|
||||
spr.loadSprite(Paths.image('$parentFolder${node.getAtt("sprite").getDefault(spr.name)}', null, true));
|
||||
|
||||
spr.spriteAnimType = defaultAnimType;
|
||||
if (node.has.type) {
|
||||
@@ -239,10 +240,10 @@ final class XMLUtil {
|
||||
* @param cl The class to create (advanced)
|
||||
* @param args The arguments to pass to the class (advanced)
|
||||
*/
|
||||
public static inline function createSpriteFromXML(node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, ?cl:Class<FunkinSprite>, ?args:Array<Dynamic>):FunkinSprite {
|
||||
public static inline function createSpriteFromXML(node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, ?cl:Class<FunkinSprite>, ?args:Array<Dynamic>, loadGraphic:Bool = true):FunkinSprite {
|
||||
if(cl == null) cl = FunkinSprite;
|
||||
if(args == null) args = [];
|
||||
return loadSpriteFromXML(Type.createInstance(cl, args), node, parentFolder, defaultAnimType);
|
||||
return loadSpriteFromXML(Type.createInstance(cl, args), node, parentFolder, defaultAnimType, loadGraphic);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
import haxe.zip.Entry;
|
||||
import haxe.zip.Reader;
|
||||
|
||||
#if sys
|
||||
#if (!macro && sys)
|
||||
import openfl.display.BitmapData;
|
||||
@@ -9,8 +12,6 @@ import haxe.Exception;
|
||||
import haxe.Json;
|
||||
import haxe.crypto.Crc32;
|
||||
import haxe.zip.Compress;
|
||||
import haxe.zip.Entry;
|
||||
import haxe.zip.Reader;
|
||||
import haxe.zip.Tools;
|
||||
import haxe.zip.Uncompress;
|
||||
import haxe.zip.Writer;
|
||||
@@ -135,12 +136,12 @@ final class ZipUtil {
|
||||
if (prog == null) prog = new ZipProgress();
|
||||
|
||||
try {
|
||||
var curPath:Array<String> = ['$path'];
|
||||
var curPath:Array<String> = [path];
|
||||
var destPath:Array<String> = [];
|
||||
if (prefix != "") {
|
||||
prefix = prefix.replace("\\", "/");
|
||||
while(prefix.charAt(0) == "/") prefix = prefix.substr(1);
|
||||
while(prefix.charAt(prefix.length-1) == "/") prefix = prefix.substr(0, prefix.length-1);
|
||||
while(prefix.charCodeAt(0) == "/".code) prefix = prefix.substr(1);
|
||||
while(prefix.charCodeAt(prefix.length-1) == "/".code) prefix = prefix.substr(0, prefix.length-1);
|
||||
destPath.push(prefix);
|
||||
}
|
||||
|
||||
@@ -160,7 +161,7 @@ final class ZipUtil {
|
||||
} else {
|
||||
// is file, put it in the list
|
||||
var zipPath = '$zipPath/$e';
|
||||
while(zipPath.charAt(0) == "/") zipPath = zipPath.substr(1);
|
||||
while(zipPath.charCodeAt(0) == "/".code) zipPath = zipPath.substr(1);
|
||||
files.push(new StrNameLabel('$path/$e', zipPath));
|
||||
}
|
||||
}
|
||||
@@ -227,20 +228,6 @@ class ZipProgress {
|
||||
public function new() {}
|
||||
}
|
||||
|
||||
class ZipReader extends Reader {
|
||||
public var files:List<Entry>;
|
||||
|
||||
public override function read() {
|
||||
if (files != null) return files;
|
||||
try {
|
||||
var files = super.read();
|
||||
return this.files = files;
|
||||
} catch(e) {
|
||||
}
|
||||
return new List<Entry>();
|
||||
}
|
||||
}
|
||||
|
||||
class ZipWriter extends Writer {
|
||||
public function flush() {
|
||||
o.flush();
|
||||
@@ -265,4 +252,18 @@ class StrNameLabel {
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
#end
|
||||
#end
|
||||
|
||||
class ZipReader extends Reader {
|
||||
public var files:List<Entry>;
|
||||
|
||||
public override function read() {
|
||||
if (files != null) return files;
|
||||
try {
|
||||
var files = super.read();
|
||||
return this.files = files;
|
||||
} catch(e) {
|
||||
}
|
||||
return new List<Entry>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package funkin.backend.week;
|
||||
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.backend.week.WeekData.WeekCharacter;
|
||||
import funkin.backend.week.WeekData;
|
||||
import haxe.xml.Access;
|
||||
|
||||
class Week {
|
||||
public static function loadWeek(weekName:String, loadCharactersData:Bool = true):WeekData {
|
||||
var week:Access = null;
|
||||
try week = new Access(Xml.parse(Assets.getText(Paths.xml('weeks/weeks/$weekName'))).firstElement())
|
||||
catch(e) Logs.trace('Cannot parse week "$weekName.xml": ${Std.string(e)}', ERROR);
|
||||
|
||||
if (week == null) return null;
|
||||
if (!week.has.name) {
|
||||
Logs.trace('Week "${weekName}" has no name attribute', WARNING);
|
||||
return null;
|
||||
}
|
||||
|
||||
var weekObj:WeekData = {
|
||||
xml: week,
|
||||
name: week.att.name,
|
||||
id: weekName,
|
||||
sprite: week.getAtt('sprite').getDefault(weekName),
|
||||
chars: [null, null, null],
|
||||
songs: [],
|
||||
difficulties: ['easy', 'normal', 'hard'],
|
||||
bgColor: week.has.bgColor ? FlxColor.fromString(week.getAtt("bgColor")) : Flags.DEFAULT_WEEK_COLOR
|
||||
};
|
||||
|
||||
var weekName = weekObj.name;
|
||||
for (k => song in week.nodes.song) {
|
||||
if (song == null) continue;
|
||||
try {
|
||||
var name = song.innerData.trim();
|
||||
if (name == "") {
|
||||
Logs.trace('Song at index ${k} in week $weekName has no name. Skipping...', WARNING);
|
||||
continue;
|
||||
}
|
||||
weekObj.songs.push({
|
||||
name: name,
|
||||
hide: song.getAtt('hide').getDefault('false') == "true",
|
||||
displayName: song.getAtt('displayName')
|
||||
});
|
||||
} catch(e) {
|
||||
Logs.trace('Song at index ${k} in week $weekName cannot contain any other XML nodes in its name.', WARNING);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (weekObj.songs.length <= 0) {
|
||||
Logs.trace('Week $weekName has no songs.', WARNING);
|
||||
return null;
|
||||
}
|
||||
|
||||
var diffNodes = week.nodes.difficulty;
|
||||
if (diffNodes.length > 0) {
|
||||
var diffs:Array<String> = [for (e in diffNodes) if (e.has.name) e.att.name];
|
||||
if (diffs.length > 0) weekObj.difficulties = diffs;
|
||||
}
|
||||
|
||||
if (week.has.chars) for (k => e in week.att.chars.split(",")) {
|
||||
var trim = e.trim();
|
||||
weekObj.chars[k] = (trim == "" || e == "none" || e == "null") ? null : (loadCharactersData ? loadWeekCharacter(trim) : {name: trim});
|
||||
}
|
||||
|
||||
return weekObj;
|
||||
}
|
||||
|
||||
public static function loadWeekCharacter(charName:String):WeekCharacter {
|
||||
var char:Access = null;
|
||||
try char = new Access(Xml.parse(Assets.getText(Paths.xml('weeks/characters/$charName'))).firstElement())
|
||||
catch(e) Logs.trace('Cannot parse character "$charName.xml": ${Std.string(e)}', ERROR);
|
||||
if (char == null) return null;
|
||||
|
||||
if (!char.has.name) char.x.set("name", charName);
|
||||
if (!char.has.sprite) char.x.set("sprite", 'menus/storymenu/characters/${char.att.name}');
|
||||
if (!char.has.updateHitbox) char.x.set("updateHitbox", "true");
|
||||
|
||||
return {
|
||||
xml: char,
|
||||
name: char.att.name,
|
||||
spritePath: char.att.sprite,
|
||||
scale: Std.parseFloat(char.getAtt('scale')).getDefault(1),
|
||||
offset: FlxPoint.get(
|
||||
Std.parseFloat(char.getAtt('x')).getDefault(0),
|
||||
Std.parseFloat(char.getAtt('y')).getDefault(0)
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package funkin.backend.week;
|
||||
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.FlxColor;
|
||||
import haxe.xml.Access;
|
||||
|
||||
typedef WeekData = {
|
||||
var ?xml:Access;
|
||||
var name:String; // name SHOULD NOT be used for loading week highscores, its just the name on the right side of the week, remember that next time!! - Nex
|
||||
var id:String; // id IS instead for saving and loading!! - Nex
|
||||
var sprite:String;
|
||||
var chars:Array<WeekCharacter>;
|
||||
var songs:Array<WeekSong>;
|
||||
var difficulties:Array<String>;
|
||||
var bgColor:FlxColor;
|
||||
}
|
||||
|
||||
typedef WeekCharacter = {
|
||||
var ?xml:Access;
|
||||
var name:String;
|
||||
var ?spritePath:String;
|
||||
var ?scale:Float;
|
||||
var ?offset:FlxPoint;
|
||||
// var frames:FlxFramesCollection;
|
||||
}
|
||||
|
||||
typedef WeekSong = {
|
||||
var name:String;
|
||||
var hide:Bool;
|
||||
var ?displayName:String;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import funkin.options.*;
|
||||
class EditorTreeMenu extends TreeMenu {
|
||||
public var bg:FlxBackdrop;
|
||||
public var bgType:String = "default";
|
||||
public var bgMovement:FlxPoint = new FlxPoint();
|
||||
|
||||
public override function create() {
|
||||
super.create();
|
||||
@@ -18,16 +19,16 @@ class EditorTreeMenu extends TreeMenu {
|
||||
bg = new FlxBackdrop();
|
||||
bg.loadGraphic(Paths.image('editors/bgs/${bgType}'));
|
||||
bg.antialiasing = true;
|
||||
setBackgroundRotation(-5);
|
||||
add(bg);
|
||||
}
|
||||
|
||||
public inline function setBackgroundRotation(rotation:Float) {
|
||||
bg.rotation = rotation;
|
||||
bg.velocity.set(85, 0).degrees = bg.rotation;
|
||||
}
|
||||
|
||||
public override function exit() {
|
||||
FlxG.switchState(new MainMenuState());
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float) {
|
||||
super.update(elapsed);
|
||||
bg.x += elapsed * 125;
|
||||
bg.y += elapsed * 125;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ class SaveWarning {
|
||||
if (closingWindow) WindowUtils.resetClosing();
|
||||
} else {
|
||||
WindowUtils.preventClosing = false; WindowUtils.resetClosing();
|
||||
Sys.exit(0);
|
||||
openfl.system.System.exit(0);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -51,7 +51,7 @@ class SaveWarning {
|
||||
if (closingWindow) WindowUtils.resetClosing();
|
||||
} else {
|
||||
WindowUtils.preventClosing = false; WindowUtils.resetClosing();
|
||||
Sys.exit(0);
|
||||
openfl.system.System.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import openfl.geom.Point;
|
||||
import openfl.geom.Rectangle;
|
||||
import openfl.display.BitmapData;
|
||||
import funkin.game.Character;
|
||||
import funkin.game.HealthIcon;
|
||||
import haxe.xml.Access;
|
||||
|
||||
using funkin.backend.utils.BitmapUtil;
|
||||
|
||||
@@ -22,6 +24,12 @@ class CharacterInfoScreen extends UISubstateWindow {
|
||||
public var character:Character;
|
||||
|
||||
public var iconColorPicker:UIIconColorPicker;
|
||||
public var spriteTextBox:UITextBox;
|
||||
public var iconTextBox:UITextBox;
|
||||
public var iconSprite:HealthIcon;
|
||||
public var gameOverCharTextBox:UITextBox;
|
||||
public var antialiasingCheckbox:UICheckbox;
|
||||
public var flipXCheckbox:UICheckbox;
|
||||
public var iconColorWheel:UIColorwheel;
|
||||
|
||||
public var useDurationCheckbox:UICheckbox;
|
||||
@@ -101,17 +109,58 @@ class CharacterInfoScreen extends UISubstateWindow {
|
||||
closeButton.color = 0xFFFF0000;
|
||||
}
|
||||
|
||||
public function saveCharacterInfo() {
|
||||
function updateIcon(icon:String) {
|
||||
if (iconSprite == null) add(iconSprite = new HealthIcon());
|
||||
|
||||
iconSprite.setIcon(icon);
|
||||
var size = Std.int(150 * 0.5);
|
||||
iconSprite.setUnstretchedGraphicSize(size, size, true);
|
||||
iconSprite.updateHitbox();
|
||||
iconSprite.setPosition(iconTextBox.x + iconTextBox.bWidth + 8, iconTextBox.y + (iconTextBox.bHeight / 2) - (iconSprite.height / 2));
|
||||
iconSprite.scrollFactor.set(1, 1);
|
||||
}
|
||||
|
||||
function saveCharacterInfo() {
|
||||
UIUtil.confirmUISelections(this);
|
||||
|
||||
if (onSave != null) onSave({
|
||||
icon: iconColorPicker.iconTextBox.label.text,
|
||||
iconColor: iconColorWheel.curColor,
|
||||
holdTime: useDurationCheckbox.checked ? durationStepper.value : -1,
|
||||
customProperties: [
|
||||
for (val in customPropertiesButtonList.buttons.members)
|
||||
val.propertyText.label.text => val.valueText.label.text
|
||||
]
|
||||
});
|
||||
//for (stepper in [positionXStepper, positionYStepper, cameraXStepper, cameraYStepper, singTimeStepper, scaleStepper])
|
||||
// @:privateAccess stepper.__onChange(stepper.label.text);
|
||||
|
||||
var xml = Xml.createElement("character");
|
||||
xml.set("isPlayer", isPlayerCheckbox.checked ? "true" : "false");
|
||||
xml.set("x", Std.string(positionXStepper.value));
|
||||
xml.set("y", Std.string(positionYStepper.value));
|
||||
xml.set("gameOverChar", gameOverCharTextBox.label.text);
|
||||
xml.set("camx", Std.string(cameraXStepper.value));
|
||||
xml.set("camy", Std.string(cameraYStepper.value));
|
||||
xml.set("holdTime", Std.string(singTimeStepper.value));
|
||||
xml.set("flipX", Std.string(flipXCheckbox.checked));
|
||||
xml.set("icon", iconTextBox.label.text);
|
||||
xml.set("scale", Std.string(scaleStepper.value));
|
||||
xml.set("antialiasing", antialiasingCheckbox.checked ? "true" : "false");
|
||||
xml.set("sprite", spriteTextBox.label.text);
|
||||
if (iconColorWheel.colorChanged)
|
||||
xml.set("color", iconColorWheel.curColor.toWebString());
|
||||
for (val in customPropertiesButtonList.buttons.members)
|
||||
xml.set(val.propertyText.label.text, val.valueText.label.text);
|
||||
|
||||
for (anim in character.animDatas)
|
||||
{
|
||||
var animXml:Xml = Xml.createElement('anim');
|
||||
animXml.set("name", anim.name);
|
||||
animXml.set("anim", anim.anim);
|
||||
animXml.set("loop", Std.string(anim.loop));
|
||||
animXml.set("fps", Std.string(anim.fps));
|
||||
var offset:FlxPoint = character.getAnimOffset(anim.name);
|
||||
animXml.set("x", Std.string(offset.x));
|
||||
animXml.set("y", Std.string(offset.y));
|
||||
offset.put();
|
||||
|
||||
if (anim.indices.length > 0)
|
||||
animXml.set("indices", CoolUtil.formatNumberRange(anim.indices));
|
||||
xml.addChild(animXml);
|
||||
}
|
||||
|
||||
if (onSave != null) onSave(xml);
|
||||
}
|
||||
}
|
||||
@@ -318,7 +318,8 @@ class CompactCharacterButton extends UIButton {
|
||||
autoAlpha = false;
|
||||
|
||||
charIcon = new HealthIcon(funkin.game.Character.getIconFromCharName(char));
|
||||
charIcon.scale.set(0.2, 0.2);
|
||||
var size = Std.int(150 * 0.2);
|
||||
charIcon.setUnstretchedGraphicSize(size, size, true);
|
||||
charIcon.updateHitbox();
|
||||
charIcon.setPosition(10, bHeight/2 - charIcon.height / 2);
|
||||
charIcon.scrollFactor.set(1,1);
|
||||
@@ -330,11 +331,10 @@ class CompactCharacterButton extends UIButton {
|
||||
textBox.antialiasing = true;
|
||||
textBox.onChange = function(char:String) {
|
||||
char = funkin.game.Character.getIconFromCharName(char);
|
||||
var image = Paths.image("icons/" + char);
|
||||
if(!Assets.exists(image))
|
||||
image = Paths.image("icons/" + Flags.DEFAULT_HEALTH_ICON);
|
||||
charIcon.loadGraphic(image, true, 150, 150);
|
||||
charIcon.setIcon(char);
|
||||
charIcon.setUnstretchedGraphicSize(size, size, true);
|
||||
charIcon.updateHitbox();
|
||||
charIcon.setPosition(10, bHeight/2 - charIcon.height / 2);
|
||||
}
|
||||
|
||||
deleteButton = new UIButton(textBox.x + 115 + 16, bHeight/2 - (32/2), null, function () {
|
||||
|
||||
@@ -1423,8 +1423,11 @@ class Charter extends UIState {
|
||||
for(id=>str in strumLines.members)
|
||||
if (str != null) str.y = strumlineInfoBG.y;
|
||||
|
||||
strumlineAddButton.x = 0;
|
||||
strumlineAddButton.y = strumlineInfoBG.y;
|
||||
strumlineLockButton.y = strumlineInfoBG.y;
|
||||
|
||||
strumlineLockButton.text.visible = strumlineLockButton.button.selectable = strumlineLockButton.button.visible = strumLines.members.length > 0;
|
||||
}
|
||||
|
||||
public override function onResize(width:Int, height:Int) {
|
||||
@@ -1558,7 +1561,7 @@ class Charter extends UIState {
|
||||
#if REGION
|
||||
function _file_exit(_) {
|
||||
if (undos.unsaved) SaveWarning.triggerWarning();
|
||||
else {undos = null; FlxG.switchState(new CharterSelection()); PlayState.resetSongInfos(); Charter.instance.__clearStatics();}
|
||||
else {undos = null; FlxG.switchState(new CharterSelection()); Charter.instance.__clearStatics();}
|
||||
}
|
||||
|
||||
function _file_save_all(_) saveEverything();
|
||||
@@ -2145,7 +2148,7 @@ class Charter extends UIState {
|
||||
}
|
||||
|
||||
@:noCompletion public function __clearStatics() {
|
||||
selection = null; undos = null; clipboard = null; playtestInfo = null;
|
||||
selection = null; undos = null; clipboard = null; playtestInfo = null;
|
||||
waveformHandler.destroy(); Charter.waveformHandler = null; autoSaveTimer = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ class CharterAutoSaveUI extends UISliceSprite {
|
||||
bar.follow(this, 10, bHeight-20);
|
||||
bar.alpha = alpha;
|
||||
}
|
||||
progressBar.clipRect = progressBar.clipRect.set(0, 0, progressBar.width*progress,progressBar.height);
|
||||
progressBar.rawClipRect = progressBar.rawClipRect.set(0, 0, progressBar.frameWidth*progress,progressBar.frameHeight);
|
||||
}
|
||||
|
||||
public function appearAnimation() {
|
||||
|
||||
@@ -202,11 +202,11 @@ class CharterEventScreen extends UISubstateWindow {
|
||||
public function saveCurTab() {
|
||||
if (curEvent < 0) return;
|
||||
|
||||
events[curEvent].params = [
|
||||
for(p in paramsFields) {
|
||||
var dataParams = EventsData.getEventParams(events[curEvent].name);
|
||||
var params:Array<Dynamic> = [
|
||||
for (i => p in paramsFields) {
|
||||
if (p is UIDropDown) {
|
||||
var dataParams = EventsData.getEventParams(events[curEvent].name);
|
||||
if (dataParams[paramsFields.indexOf(p)].type == TStrumLine) cast(p, UIDropDown).index;
|
||||
if (dataParams[i].type == TStrumLine) cast(p, UIDropDown).index;
|
||||
else cast(p, UIDropDown).label.text;
|
||||
}
|
||||
else if (p is UINumericStepper) {
|
||||
@@ -227,6 +227,14 @@ class CharterEventScreen extends UISubstateWindow {
|
||||
null;
|
||||
}
|
||||
];
|
||||
|
||||
while(dataParams.length > 0 && {
|
||||
var index = params.length - 1;
|
||||
var dataParam = dataParams[index];
|
||||
dataParam.saveIfDefault == false && params[index] == dataParam.defValue;
|
||||
}) params.pop();
|
||||
|
||||
events[curEvent].params = params;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package funkin.editors.charter;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.backend.chart.ChartData.ChartMetaData;
|
||||
import funkin.editors.extra.PropertyButton;
|
||||
import funkin.game.HealthIcon;
|
||||
|
||||
using StringTools;
|
||||
|
||||
@@ -19,7 +20,7 @@ class CharterMetaDataScreen extends UISubstateWindow {
|
||||
|
||||
public var displayNameTextBox:UITextBox;
|
||||
public var iconTextBox:UITextBox;
|
||||
public var iconSprite:FlxSprite;
|
||||
public var iconSprite:HealthIcon;
|
||||
public var opponentModeCheckbox:UICheckbox;
|
||||
public var coopAllowedCheckbox:UICheckbox;
|
||||
public var colorWheel:UIColorwheel;
|
||||
@@ -84,7 +85,7 @@ class CharterMetaDataScreen extends UISubstateWindow {
|
||||
coopAllowedCheckbox = new UICheckbox(opponentModeCheckbox.x + 150 + 26, opponentModeCheckbox.y, "Co-op Mode", metadata.coopAllowed);
|
||||
add(coopAllowedCheckbox);
|
||||
|
||||
colorWheel = new UIColorwheel(iconTextBox.x, coopAllowedCheckbox.y, metadata.parsedColor);
|
||||
colorWheel = new UIColorwheel(iconTextBox.x, coopAllowedCheckbox.y, metadata.color);
|
||||
add(colorWheel);
|
||||
addLabelOn(colorWheel, "Color");
|
||||
|
||||
@@ -124,22 +125,14 @@ class CharterMetaDataScreen extends UISubstateWindow {
|
||||
}
|
||||
|
||||
function updateIcon(icon:String) {
|
||||
if (iconSprite == null) add(iconSprite = new FlxSprite());
|
||||
if (iconSprite == null) add(iconSprite = new HealthIcon());
|
||||
|
||||
if (iconSprite.animation.exists(icon)) return;
|
||||
@:privateAccess iconSprite.animation.clearAnimations();
|
||||
|
||||
var path:String = Paths.image('icons/$icon');
|
||||
if (!Assets.exists(path)) path = Paths.image('icons/' + Flags.DEFAULT_HEALTH_ICON);
|
||||
|
||||
iconSprite.loadGraphic(path, true, 150, 150);
|
||||
iconSprite.animation.add(icon, [0], 0, false);
|
||||
iconSprite.antialiasing = true;
|
||||
iconSprite.animation.play(icon);
|
||||
|
||||
iconSprite.scale.set(0.5, 0.5);
|
||||
iconSprite.setIcon(icon);
|
||||
var size = Std.int(150 * 0.5);
|
||||
iconSprite.setUnstretchedGraphicSize(size, size, true);
|
||||
iconSprite.updateHitbox();
|
||||
iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2));
|
||||
iconSprite.setPosition(iconTextBox.x + iconTextBox.bWidth + 8, iconTextBox.y + (iconTextBox.bHeight / 2) - (iconSprite.height / 2));
|
||||
iconSprite.scrollFactor.set(1, 1);
|
||||
}
|
||||
|
||||
public function saveMeta() {
|
||||
@@ -157,8 +150,7 @@ class CharterMetaDataScreen extends UISubstateWindow {
|
||||
stepsPerBeat: Std.int(stepsPerBeatStepper.value),
|
||||
displayName: displayNameTextBox.label.text,
|
||||
icon: iconTextBox.label.text,
|
||||
color: colorWheel.curColorString,
|
||||
parsedColor: colorWheel.curColor,
|
||||
color: colorWheel.curColor,
|
||||
opponentModeAllowed: opponentModeCheckbox.checked,
|
||||
coopAllowed: coopAllowedCheckbox.checked,
|
||||
difficulties: [for (diff in difficultiesTextBox.label.text.split(",")) diff.trim()],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package funkin.editors.charter;
|
||||
|
||||
import funkin.backend.chart.Chart;
|
||||
import funkin.backend.chart.ChartData.ChartMetaData;
|
||||
import funkin.backend.chart.ChartData;
|
||||
import funkin.backend.system.framerate.Framerate;
|
||||
@@ -34,16 +35,20 @@ class CharterSelection extends EditorTreeMenu {
|
||||
FlxG.switchState(new Charter(s.name, d));
|
||||
})
|
||||
];
|
||||
list.push(new NewOption("New Difficulty", "Press ACCEPT to create a new difficulty.", function() {
|
||||
#if sys
|
||||
list.push(new NewOption("New Difficulty", "New Difficulty", function() {
|
||||
FlxG.state.openSubState(new ChartCreationScreen(saveChart));
|
||||
}));
|
||||
#end
|
||||
optionsTree.add(new OptionsScreen(s.name, "Select a difficulty to continue.", list));
|
||||
}, s.parsedColor.getDefault(0xFFFFFFFF))
|
||||
}, s.color.getDefault(0xFFFFFFFF))
|
||||
];
|
||||
|
||||
list.insert(0, new NewOption("New Song", "Press ACCEPT to create a new song.", function() {
|
||||
#if sys
|
||||
list.insert(0, new NewOption("New Song", "New Song", function() {
|
||||
FlxG.state.openSubState(new SongCreationScreen(saveSong));
|
||||
}));
|
||||
#end
|
||||
|
||||
main = new OptionsScreen("Chart Editor", "Select a song to modify the charts from.", list);
|
||||
|
||||
@@ -88,11 +93,12 @@ class CharterSelection extends EditorTreeMenu {
|
||||
}
|
||||
}
|
||||
|
||||
public function saveSong(creation:SongCreationData) {
|
||||
#if sys
|
||||
public function saveSong(creation:SongCreationData, ?callback:String -> SongCreationData -> Void) {
|
||||
var songAlreadyExists:Bool = [for (s in freeplayList.songs) s.name.toLowerCase()].contains(creation.meta.name.toLowerCase());
|
||||
|
||||
if (songAlreadyExists) {
|
||||
openSubState(new UIWarningSubstate("Creating Song: Error!", "The song you are trying to create already exists, if you would like to override it delete the song first!", [
|
||||
openSubState(new UIWarningSubstate("Creating Song: Error!", "The song you are trying to create Already exists, if you would like to override it delete the song first!", [
|
||||
{label: "Ok", color: 0xFFFF0000, onClick: function(t) {}}
|
||||
]));
|
||||
return;
|
||||
@@ -109,11 +115,17 @@ class CharterSelection extends EditorTreeMenu {
|
||||
sys.FileSystem.createDirectory('$songFolder/charts');
|
||||
|
||||
// Save Files
|
||||
CoolUtil.safeSaveFile('$songFolder/meta.json', Json.stringify(creation.meta, Flags.JSON_PRETTY_PRINT));
|
||||
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);
|
||||
|
||||
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);
|
||||
#end
|
||||
|
||||
if (callback != null)
|
||||
callback(songFolder, creation);
|
||||
|
||||
var option = new EditorIconOption(creation.meta.name, "Press ACCEPT to choose a difficulty to edit.", creation.meta.icon, function() {
|
||||
curSong = creation.meta;
|
||||
var list:Array<OptionType> = [
|
||||
@@ -122,11 +134,11 @@ class CharterSelection extends EditorTreeMenu {
|
||||
FlxG.switchState(new Charter(creation.meta.name, d));
|
||||
})
|
||||
];
|
||||
list.push(new NewOption("New Difficulty", "Press ACCEPT to create a new difficulty.", function() {
|
||||
list.push(new NewOption("New Difficulty", "New Difficulty", function() {
|
||||
FlxG.state.openSubState(new ChartCreationScreen(saveChart));
|
||||
}));
|
||||
optionsTree.insert(1, new OptionsScreen(creation.meta.name, "Select a difficulty to continue.", list));
|
||||
}, creation.meta.parsedColor.getDefault(0xFFFFFFFF));
|
||||
}, creation.meta.color.getDefault(0xFFFFFFFF));
|
||||
|
||||
// Add to List
|
||||
freeplayList.songs.insert(0, creation.meta);
|
||||
@@ -160,7 +172,8 @@ class CharterSelection extends EditorTreeMenu {
|
||||
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', Json.stringify(meta));
|
||||
CoolUtil.safeSaveFile('$songFolder/meta.json', Chart.makeMetaSaveable(meta));
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
@@ -88,8 +88,7 @@ class CharterStrumline extends UISprite {
|
||||
button = new CharterStrumlineOptions(this);
|
||||
members.push(button);
|
||||
|
||||
vocals = strumLine.vocalsSuffix.length > 0 ? FlxG.sound.load(Paths.voices(PlayState.SONG.meta.name, PlayState.difficulty, strumLine.vocalsSuffix)) : new FlxSound();
|
||||
vocals.group = FlxG.sound.defaultMusicGroup;
|
||||
updateInfo();
|
||||
|
||||
selectedWaveform = -1;
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ class CharterStrumlineButton extends UISprite {
|
||||
|
||||
super.update(elapsed);
|
||||
|
||||
button.updateSpriteRect();
|
||||
if(UIState.state.curContextMenu == null && UIState.state.isOverlapping(button, button.__rect)) {
|
||||
if (UIState.state.curContextMenu == null && UIState.state.isOverlapping(button, button.__rect) && button.visible) {
|
||||
UIState.state.updateSpriteRect(button);
|
||||
buttonScale.set(0.95, 0.95);
|
||||
if (FlxG.mouse.justPressed && onClick != null) {
|
||||
onClick();
|
||||
|
||||
@@ -242,7 +242,8 @@ class CharacterButton extends UIButton {
|
||||
super(x, y, null, null, 250, 54);
|
||||
|
||||
charIcon = new HealthIcon(Character.getIconFromCharName(char));
|
||||
charIcon.scale.set(0.3, 0.3);
|
||||
var size = Std.int(150 * 0.3);
|
||||
charIcon.setUnstretchedGraphicSize(size, size, true);
|
||||
charIcon.updateHitbox();
|
||||
charIcon.setPosition(x + 10, bHeight/2 - charIcon.height / 2);
|
||||
charIcon.scrollFactor.set(1,1);
|
||||
@@ -254,11 +255,10 @@ class CharacterButton extends UIButton {
|
||||
textBox.antialiasing = true;
|
||||
textBox.onChange = function(char:String) {
|
||||
char = Character.getIconFromCharName(char);
|
||||
var image = Paths.image("icons/" + char);
|
||||
if(!Assets.exists(image))
|
||||
image = Paths.image("icons/" + Flags.DEFAULT_HEALTH_ICON);
|
||||
charIcon.loadGraphic(image, true, 150, 150);
|
||||
charIcon.setIcon(char);
|
||||
charIcon.setUnstretchedGraphicSize(size, size, true);
|
||||
charIcon.updateHitbox();
|
||||
charIcon.setPosition(x + 10, bHeight/2 - charIcon.height / 2);
|
||||
}
|
||||
|
||||
deleteButton = new UIButton(textBox.x + 115 + 16, bHeight/2 - (32/2), null, function () {
|
||||
|
||||
@@ -3,17 +3,26 @@ package funkin.editors.charter;
|
||||
import flixel.group.FlxGroup;
|
||||
import flixel.text.FlxText.FlxTextFormat;
|
||||
import flixel.text.FlxText.FlxTextFormatMarkerPair;
|
||||
import funkin.backend.chart.ChartData.ChartMetaData;
|
||||
import funkin.backend.chart.ChartData;
|
||||
import funkin.backend.chart.FNFLegacyParser.SwagSong;
|
||||
import funkin.backend.chart.PsychParser;
|
||||
import funkin.backend.chart.VSliceParser;
|
||||
import funkin.backend.utils.ZipUtil.ZipReader;
|
||||
import funkin.game.HealthIcon;
|
||||
import haxe.Json;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.BytesInput;
|
||||
|
||||
typedef SongCreationData = {
|
||||
var meta:ChartMetaData;
|
||||
var instBytes:Bytes;
|
||||
var voicesBytes:Bytes;
|
||||
@:optional var playerVocals:Bytes;
|
||||
@:optional var oppVocals:Bytes;
|
||||
}
|
||||
|
||||
class SongCreationScreen extends UISubstateWindow {
|
||||
private var onSave:Null<SongCreationData> -> Void = null;
|
||||
private var onSave:Null<SongCreationData> -> Null<String -> SongCreationData -> Void> -> Void = null;
|
||||
|
||||
public var songNameTextBox:UITextBox;
|
||||
public var bpmStepper:UINumericStepper;
|
||||
@@ -21,15 +30,26 @@ class SongCreationScreen extends UISubstateWindow {
|
||||
public var stepsPerBeatStepper :UINumericStepper;
|
||||
public var instExplorer:UIFileExplorer;
|
||||
public var voicesExplorer:UIFileExplorer;
|
||||
public var importFrom:UIButton;
|
||||
|
||||
public var displayNameTextBox:UITextBox;
|
||||
public var iconTextBox:UITextBox;
|
||||
public var iconSprite:FlxSprite;
|
||||
public var iconSprite:HealthIcon;
|
||||
public var opponentModeCheckbox:UICheckbox;
|
||||
public var coopAllowedCheckbox:UICheckbox;
|
||||
public var colorWheel:UIColorwheel;
|
||||
public var difficultiesTextBox:UITextBox;
|
||||
|
||||
public var engineDropdown:UIDropDown;
|
||||
public var createSong:UIButton;
|
||||
|
||||
public var importInstExplorer:UIFileExplorer;
|
||||
public var importVoicesExplorer:UIFileExplorer;
|
||||
|
||||
public var importIdTextBox:UITextBox;
|
||||
public var importChartFile:UIFileExplorer;
|
||||
public var importMetaFile:UIFileExplorer;
|
||||
|
||||
public var backButton:UIButton;
|
||||
public var saveButton:UIButton;
|
||||
public var closeButton:UIButton;
|
||||
@@ -37,11 +57,21 @@ class SongCreationScreen extends UISubstateWindow {
|
||||
public var songDataGroup:FlxGroup = new FlxGroup();
|
||||
public var menuDataGroup:FlxGroup = new FlxGroup();
|
||||
|
||||
public var selectFormatGroup:FlxGroup = new FlxGroup();
|
||||
public var importAudioGroup:FlxGroup = new FlxGroup();
|
||||
public var importDataGroup:FlxGroup = new FlxGroup();
|
||||
|
||||
public var pages:Array<FlxGroup> = [];
|
||||
public var pageSizes:Array<FlxPoint> = [];
|
||||
|
||||
public var importPages:Array<FlxGroup> = [];
|
||||
public var importPageSizes:Array<FlxPoint> = [];
|
||||
|
||||
public var isImporting = false;
|
||||
|
||||
public var curPage:Int = 0;
|
||||
|
||||
public function new(?onSave:SongCreationData->Void) {
|
||||
public function new(?onSave:SongCreationData -> Null<String -> SongCreationData -> Void> -> Void) {
|
||||
super();
|
||||
if (onSave != null) this.onSave = onSave;
|
||||
}
|
||||
@@ -80,10 +110,7 @@ class SongCreationScreen extends UISubstateWindow {
|
||||
stepsPerBeatStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, 4, 1, 0, 1, null, 54);
|
||||
songDataGroup.add(stepsPerBeatStepper);
|
||||
|
||||
var voicesUIText:UIText = null;
|
||||
|
||||
instExplorer = new UIFileExplorer(songNameTextBox.x, songNameTextBox.y + 32 + 36, null, null, Flags.SOUND_EXT, function (path, res) {
|
||||
if (path == null || res == null) return;
|
||||
instExplorer = new UIFileExplorer(songNameTextBox.x, songNameTextBox.y + 32 + 36, null, null, Flags.SOUND_EXT, function (res) {
|
||||
var audioPlayer:UIAudioPlayer = new UIAudioPlayer(instExplorer.x + 8, instExplorer.y + 8, res);
|
||||
instExplorer.members.push(audioPlayer);
|
||||
instExplorer.uiElement = audioPlayer;
|
||||
@@ -102,6 +129,14 @@ class SongCreationScreen extends UISubstateWindow {
|
||||
songDataGroup.add(voicesExplorer);
|
||||
addLabelOn(voicesExplorer, "Vocal Audio File");
|
||||
|
||||
importFrom = new UIButton(windowSpr.x + 20, windowSpr.y + windowSpr.bHeight - 16 - 32, "Import From...", function() {
|
||||
winTitle = "Importing Song";
|
||||
isImporting = true;
|
||||
updatePagesTexts();
|
||||
refreshPages();
|
||||
}, 150);
|
||||
songDataGroup.add(importFrom);
|
||||
|
||||
var menuTitle:UIText;
|
||||
menuDataGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Menus Data (Freeplay/Story)", 28));
|
||||
|
||||
@@ -134,7 +169,64 @@ class SongCreationScreen extends UISubstateWindow {
|
||||
for (checkbox in [opponentModeCheckbox, coopAllowedCheckbox])
|
||||
{checkbox.y += 6; checkbox.x += 4;}
|
||||
|
||||
var menuTitle:UIText;
|
||||
selectFormatGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Import From:", 28));
|
||||
|
||||
selectFormatGroup.add(engineDropdown);
|
||||
addLabelOn(engineDropdown, "Chart Format");
|
||||
|
||||
createSong = new UIButton(windowSpr.x + 20, windowSpr.y + windowSpr.bHeight - 16 - 32, "< Back", function() {
|
||||
winTitle = "Creating New Song";
|
||||
isImporting = false;
|
||||
updatePagesTexts();
|
||||
refreshPages();
|
||||
}, 120);
|
||||
selectFormatGroup.add(createSong);
|
||||
|
||||
var menuTitle:UIText;
|
||||
importAudioGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Add Audios", 28));
|
||||
|
||||
importInstExplorer = new UIFileExplorer(menuTitle.x, menuTitle.y + menuTitle.height + 36, null, null, Flags.SOUND_EXT, function (res) {
|
||||
var audioPlayer:UIAudioPlayer = new UIAudioPlayer(importInstExplorer.x + 8, importInstExplorer.y + 8, res);
|
||||
importInstExplorer.members.push(audioPlayer);
|
||||
importInstExplorer.uiElement = audioPlayer;
|
||||
});
|
||||
importAudioGroup.add(importInstExplorer);
|
||||
addLabelOn(importInstExplorer, "Inst Audio File").applyMarkup(
|
||||
"Inst Audio File $* Required$",
|
||||
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);
|
||||
|
||||
importVoicesExplorer = new UIFileExplorer(importInstExplorer.x + 320 + 26, importInstExplorer.y, null, null, Flags.SOUND_EXT, function (res) {
|
||||
var audioPlayer:UIAudioPlayer = new UIAudioPlayer(importVoicesExplorer.x + 8, importVoicesExplorer.y + 8, res);
|
||||
importVoicesExplorer.members.push(audioPlayer);
|
||||
importVoicesExplorer.uiElement = audioPlayer;
|
||||
});
|
||||
importAudioGroup.add(importVoicesExplorer);
|
||||
addLabelOn(importVoicesExplorer, "Vocal Audio File");
|
||||
|
||||
var menuTitle:UIText;
|
||||
importDataGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Add Data", 28));
|
||||
|
||||
importDataGroup.add(importIdTextBox = new UITextBox(menuTitle.x, menuTitle.y + menuTitle.height + 36));
|
||||
addLabelOn(importIdTextBox, "Song file name").applyMarkup(
|
||||
"Song file name $* Required$",
|
||||
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);
|
||||
|
||||
importChartFile = new UIFileExplorer(importIdTextBox.x, importIdTextBox.y + importIdTextBox.height + 56, null, null, "fnfc", function (_) importIdTextBox.label.text = new haxe.io.Path(importChartFile.filePath).file);
|
||||
importDataGroup.add(importChartFile);
|
||||
addLabelOn(importChartFile, "Data/Chart File").applyMarkup(
|
||||
"Data/Chart File $* Required$",
|
||||
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);
|
||||
|
||||
importMetaFile = new UIFileExplorer(importChartFile.x + 320 + 26, importChartFile.y, null, null, "json");
|
||||
importDataGroup.add(importMetaFile);
|
||||
addLabelOn(importMetaFile, "Meta File").applyMarkup(
|
||||
"Meta File $* Required$",
|
||||
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);
|
||||
|
||||
engineDropdown = new UIDropDown(menuTitle.x, menuTitle.y + menuTitle.height + 36, 480, 32, ["Psych/Legacy FNF", "V-Slice", "V-Slice Project (.fnfc)"]);
|
||||
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() {
|
||||
var pages = isImporting ? importPages : pages;
|
||||
if (curPage == pages.length-1) {
|
||||
saveSongInfo();
|
||||
close();
|
||||
@@ -167,17 +259,41 @@ class SongCreationScreen extends UISubstateWindow {
|
||||
pages.push(cast add(menuDataGroup));
|
||||
pageSizes.push(FlxPoint.get(748 - 32 + 40, 400));
|
||||
|
||||
importPages.push(cast add(selectFormatGroup));
|
||||
importPageSizes.push(FlxPoint.get(500, 250));
|
||||
|
||||
importPages.push(cast add(importAudioGroup));
|
||||
importPageSizes.push(FlxPoint.get(705, 250));
|
||||
|
||||
importPages.push(cast add(importDataGroup));
|
||||
importPageSizes.push(FlxPoint.get(705, 300));
|
||||
|
||||
refreshPages();
|
||||
updatePagesTexts();
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float) {
|
||||
if (curPage == 0) {
|
||||
if (instExplorer.file != null)
|
||||
if (isImporting)
|
||||
{
|
||||
var name = engineDropdown.options[engineDropdown.index];
|
||||
var project = name == "V-Slice Project (.fnfc)";
|
||||
|
||||
if (curPage == 1) {
|
||||
importInstExplorer.selectable = importVoicesExplorer.selectable = !project;
|
||||
saveButton.selectable = project ? true : (importInstExplorer.file != null);
|
||||
} else if (curPage == 2) {
|
||||
importIdTextBox.selectable = !project;
|
||||
importChartFile.fileType = project ? "fnfc" : "json";
|
||||
importMetaFile.selectable = name == "V-Slice";
|
||||
saveButton.selectable = importChartFile.file != null && (!importMetaFile.selectable || importMetaFile.file != null) && (!importIdTextBox.selectable || importIdTextBox.label.text.trim().length > 0);
|
||||
} else
|
||||
saveButton.selectable = true;
|
||||
else saveButton.selectable = false;
|
||||
} else
|
||||
saveButton.selectable = true;
|
||||
} else {
|
||||
if (curPage == 0)
|
||||
saveButton.selectable = (instExplorer.file != null);
|
||||
else
|
||||
saveButton.selectable = true;
|
||||
}
|
||||
|
||||
saveButton.alpha = saveButton.field.alpha = saveButton.selectable ? 1 : 0.4;
|
||||
super.update(elapsed);
|
||||
@@ -185,10 +301,15 @@ class SongCreationScreen extends UISubstateWindow {
|
||||
|
||||
function refreshPages() {
|
||||
for (i=>page in pages)
|
||||
page.visible = page.exists = i == curPage;
|
||||
page.visible = page.exists = i == curPage && !isImporting;
|
||||
|
||||
for (i=>page in importPages)
|
||||
page.visible = page.exists = i == curPage && isImporting;
|
||||
}
|
||||
|
||||
function updatePagesTexts() {
|
||||
var pageSizes = isImporting ? importPageSizes : pageSizes;
|
||||
var pages = isImporting ? importPages : pages;
|
||||
windowSpr.bWidth = Std.int(pageSizes[curPage].x);
|
||||
windowSpr.bHeight = Std.int(pageSizes[curPage].y);
|
||||
|
||||
@@ -196,7 +317,7 @@ class SongCreationScreen extends UISubstateWindow {
|
||||
titleSpr.y = windowSpr.y + ((30 - titleSpr.height) / 2);
|
||||
|
||||
saveButton.field.text = curPage == pages.length-1 ? "Save & Close" : 'Next >';
|
||||
titleSpr.text = 'Creating New Song (${curPage+1}/${pages.length})';
|
||||
titleSpr.text = '$winTitle (${curPage+1}/${pages.length})';
|
||||
|
||||
backButton.field.text = '< Back';
|
||||
backButton.visible = backButton.exists = curPage > 0;
|
||||
@@ -204,51 +325,121 @@ class SongCreationScreen extends UISubstateWindow {
|
||||
backButton.x = (saveButton.x = windowSpr.x + windowSpr.bWidth - 20 - 125) - 20 - saveButton.bWidth;
|
||||
closeButton.x = (curPage > 0 ? backButton : saveButton).x - 20 - saveButton.bWidth;
|
||||
|
||||
for (button in [saveButton, backButton, closeButton])
|
||||
for (button in [saveButton, backButton, closeButton, importFrom, createSong])
|
||||
button.y = windowSpr.y + windowSpr.bHeight - 16 - 32;
|
||||
}
|
||||
|
||||
function updateIcon(icon:String) {
|
||||
if (iconSprite == null) menuDataGroup.add(iconSprite = new FlxSprite());
|
||||
if (iconSprite == null) menuDataGroup.add(iconSprite = new HealthIcon());
|
||||
|
||||
if (iconSprite.animation.exists(icon)) return;
|
||||
@:privateAccess iconSprite.animation.clearAnimations();
|
||||
|
||||
var path:String = Paths.image('icons/$icon');
|
||||
if (!Assets.exists(path)) path = Paths.image('icons/' + Flags.DEFAULT_HEALTH_ICON);
|
||||
|
||||
iconSprite.loadGraphic(path, true, 150, 150);
|
||||
iconSprite.animation.add(icon, [0], 0, false);
|
||||
iconSprite.antialiasing = true;
|
||||
iconSprite.animation.play(icon);
|
||||
|
||||
iconSprite.scale.set(0.5, 0.5);
|
||||
iconSprite.setIcon(icon);
|
||||
var size = Std.int(150 * 0.5);
|
||||
iconSprite.setUnstretchedGraphicSize(size, size, true);
|
||||
iconSprite.updateHitbox();
|
||||
iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2));
|
||||
iconSprite.setPosition(iconTextBox.x + iconTextBox.bWidth + 8, iconTextBox.y + (iconTextBox.bHeight / 2) - (iconSprite.height / 2));
|
||||
iconSprite.scrollFactor.set(1, 1);
|
||||
}
|
||||
|
||||
function saveSongInfo() {
|
||||
UIUtil.confirmUISelections(this);
|
||||
if (isImporting)
|
||||
{
|
||||
try switch(engineDropdown.options[engineDropdown.index])
|
||||
{
|
||||
case "V-Slice Project (.fnfc)":
|
||||
var files:Map<String, Any> = [];
|
||||
for (field in new ZipReader(new BytesInput(importChartFile.file)).read()) {
|
||||
var fileName = field.fileName;
|
||||
var fileContent = field.data;
|
||||
files.set(fileName, fileContent);
|
||||
}
|
||||
saveFromVSlice(files);
|
||||
case "V-Slice":
|
||||
var songId = importIdTextBox.label.text;
|
||||
var files:Map<String, Any> = [];
|
||||
files.set('${songId}-metadata.json', importMetaFile.file);
|
||||
files.set('${songId}-chart.json', importChartFile.file);
|
||||
files.set('Inst.${Flags.SOUND_EXT}', importInstExplorer.file);
|
||||
files.set('Voices.${Flags.SOUND_EXT}', importVoicesExplorer.file);
|
||||
saveFromVSlice(files, songId);
|
||||
default /*"Psych/Legacy FNF"*/:
|
||||
var songId = importIdTextBox.label.text;
|
||||
var oldChart:SwagSong = Json.parse(cast importChartFile.file);
|
||||
var base:ChartData = {
|
||||
strumLines: [],
|
||||
noteTypes: [],
|
||||
events: [],
|
||||
meta: {name: null},
|
||||
scrollSpeed: Flags.DEFAULT_SCROLL_SPEED,
|
||||
stage: Flags.DEFAULT_STAGE,
|
||||
codenameChart: true
|
||||
};
|
||||
PsychParser.parse(oldChart, base);
|
||||
if (onSave != null) onSave({
|
||||
meta: {
|
||||
name: songId,
|
||||
difficulties: [songId],
|
||||
bpm: oldChart.bpm,
|
||||
beatsPerMeasure: oldChart.beatsPerMeasure,
|
||||
stepsPerBeat: oldChart.stepsPerBeat,
|
||||
displayName: oldChart.song
|
||||
},
|
||||
instBytes: importInstExplorer.file,
|
||||
voicesBytes: importVoicesExplorer.file,
|
||||
}, (songFolder:String, creation:SongCreationData) -> {
|
||||
#if sys
|
||||
CoolUtil.safeSaveFile('$songFolder/charts/${songId}.json', Json.stringify(base, Flags.JSON_PRETTY_PRINT));
|
||||
#end
|
||||
});
|
||||
} catch (e) {
|
||||
openSubState(new UIWarningSubstate("Importing Song/Charts: Error!", Std.string(e), [
|
||||
{label: "Ok", color: 0xFFFF0000, onClick: function(t) {}}
|
||||
]));
|
||||
}
|
||||
} else {
|
||||
for (stepper in [bpmStepper, beatsPerMeasureStepper, stepsPerBeatStepper])
|
||||
@:privateAccess stepper.__onChange(stepper.label.text);
|
||||
|
||||
var meta:ChartMetaData = {
|
||||
name: songNameTextBox.label.text,
|
||||
bpm: bpmStepper.value,
|
||||
beatsPerMeasure: Std.int(beatsPerMeasureStepper.value),
|
||||
stepsPerBeat: Std.int(stepsPerBeatStepper.value),
|
||||
displayName: displayNameTextBox.label.text,
|
||||
icon: iconTextBox.label.text,
|
||||
color: colorWheel.curColorString,
|
||||
parsedColor: colorWheel.curColor,
|
||||
opponentModeAllowed: opponentModeCheckbox.checked,
|
||||
coopAllowed: coopAllowedCheckbox.checked,
|
||||
difficulties: [for (diff in difficultiesTextBox.label.text.split(",")) diff.trim()],
|
||||
};
|
||||
var meta:ChartMetaData = {
|
||||
name: songNameTextBox.label.text,
|
||||
bpm: bpmStepper.value,
|
||||
beatsPerMeasure: Std.int(beatsPerMeasureStepper.value),
|
||||
stepsPerBeat: Std.int(stepsPerBeatStepper.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()],
|
||||
};
|
||||
|
||||
if (onSave != null) onSave({
|
||||
meta: meta,
|
||||
instBytes: instExplorer.file,
|
||||
voicesBytes: voicesExplorer.file
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
|
||||
function saveFromVSlice(files:Map<String, Any>, ?name:String) {
|
||||
var songId = name == null && files.exists("manifest.json") ? Json.parse(files.get("manifest.json")).songId : name;
|
||||
var vslicemeta:SwagMetadata = Json.parse(files.get('${songId}-metadata.json'));
|
||||
var vslicechart:NewSwagSong = Json.parse(files.get('${songId}-chart.json'));
|
||||
var playData = vslicemeta.playData;
|
||||
|
||||
var meta:ChartMetaData = {name: songId};
|
||||
var diffCharts:Array<ChartDataWithInfo> = [];
|
||||
VSliceParser.parse(vslicemeta, vslicechart, meta, diffCharts, songId);
|
||||
|
||||
if (onSave != null) onSave({
|
||||
meta: meta,
|
||||
instBytes: instExplorer.file,
|
||||
voicesBytes: voicesExplorer.file
|
||||
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) -> {
|
||||
#if sys
|
||||
for (diff in diffCharts) CoolUtil.safeSaveFile('$songFolder/charts/${diff.diffName}.json', Json.stringify(diff.chart, Flags.JSON_PRETTY_PRINT));
|
||||
#end
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -54,6 +54,7 @@ class UIAudioPlayer extends UIButton {
|
||||
timeBarPlayer = new FlxSprite(timeBar.x, timeBar.y).loadGraphic(Paths.image('editors/ui/audio-time-empty'));
|
||||
timeBarPlayer.colorTransform.color = 0x440364;
|
||||
timeBarPlayer.scale.y = (timeBarPlayer.frameHeight + 2) / timeBarPlayer.frameHeight;
|
||||
timeBarPlayer.pixelPerfectRender = true;
|
||||
members.push(timeBarPlayer);
|
||||
|
||||
timeBarSpr = cast new UISprite(timeBar.x, timeBar.y).makeSolid(timeBar.barWidth, timeBar.barHeight, 0x00FFFFFF);
|
||||
@@ -87,13 +88,13 @@ class UIAudioPlayer extends UIButton {
|
||||
playingSprite.animation.play(sound.playing ? "playing" : "paused");
|
||||
timeText.text = '${FlxStringUtil.formatTime(sound.time/1000, true)} / ${FlxStringUtil.formatTime(sound.length/1000)}';
|
||||
|
||||
if(timeBarPlayer.clipRect == null)
|
||||
timeBarPlayer.clipRect = new FlxRect(0, 0, timeBarPlayer.frameWidth, timeBarPlayer.frameHeight);
|
||||
if(timeBarPlayer.rawClipRect == null)
|
||||
timeBarPlayer.rawClipRect = new FlxRect(0, 0, timeBarPlayer.frameWidth, timeBarPlayer.frameHeight);
|
||||
|
||||
timeBarPlayer.clipRect.x = timeBarPlayer.frameWidth * (sound.time/sound.length);
|
||||
timeBarPlayer.clipRect.width = 2;
|
||||
timeBarPlayer.rawClipRect.x = timeBarPlayer.frameWidth * (sound.time/sound.length);
|
||||
timeBarPlayer.rawClipRect.width = 2;
|
||||
|
||||
timeBarPlayer.clipRect = timeBarPlayer.clipRect;
|
||||
timeBarPlayer.rawClipRect = timeBarPlayer.rawClipRect;
|
||||
|
||||
nextPlayerColor = sound.playing ? 0x732D95 : 0x440364;
|
||||
timeBarPlayer.colorTransform.color = FlxColor.interpolate(timeBarPlayer.colorTransform.color, nextPlayerColor, 1/14);
|
||||
|
||||
@@ -15,16 +15,19 @@ class UIFileExplorer extends UISliceSprite {
|
||||
public var onFile:(String, Bytes)->Void;
|
||||
|
||||
public var uiElement:FlxSprite;
|
||||
|
||||
public var fileType:String = "txt";
|
||||
|
||||
public function new(x:Float, y:Float, ?w:Int, ?h:Int, fileType:String = "txt", ?onFile:(String, Bytes)->Void) {
|
||||
super(x, y, (w != null ? w : 320), (h != null ? h : 58), 'editors/ui/inputbox');
|
||||
this.fileType = fileType;
|
||||
|
||||
if (onFile != null) this.onFile = onFile;
|
||||
|
||||
uploadButton = new UIButton(x + 8, y+ 8, null, function () {
|
||||
var fileDialog = new FileDialog();
|
||||
fileDialog.onSelect.add(loadFile);
|
||||
fileDialog.browse(OPEN, fileType);
|
||||
fileDialog.browse(OPEN, this.fileType);
|
||||
}, bWidth - 16, bHeight - 16);
|
||||
members.push(uploadButton);
|
||||
|
||||
@@ -66,6 +69,7 @@ class UIFileExplorer extends UISliceSprite {
|
||||
}
|
||||
|
||||
public function removeFile() {
|
||||
if (!selectable) return;
|
||||
if (uiElement != null) {
|
||||
members.remove(uiElement);
|
||||
uiElement.destroy();
|
||||
|
||||
@@ -101,30 +101,8 @@ class GameOverSubstate extends MusicBeatSubstate
|
||||
if (__cancelDefault)
|
||||
return;
|
||||
|
||||
if (controls.ACCEPT)
|
||||
{
|
||||
endBullshit();
|
||||
}
|
||||
|
||||
if (controls.BACK)
|
||||
{
|
||||
if (PlayState.chartingMode && Charter.undos.unsaved)
|
||||
game.saveWarn(false);
|
||||
else {
|
||||
PlayState.resetSongInfos();
|
||||
if (Charter.instance != null) Charter.instance.__clearStatics();
|
||||
|
||||
if (FlxG.sound.music != null)
|
||||
FlxG.sound.music.stop();
|
||||
FlxG.sound.music = null;
|
||||
|
||||
if (PlayState.isStoryMode)
|
||||
FlxG.switchState(new StoryMenuState());
|
||||
else
|
||||
FlxG.switchState(new FreeplayState());
|
||||
}
|
||||
|
||||
}
|
||||
if (controls.ACCEPT) endBullshit();
|
||||
if (controls.BACK) exit();
|
||||
|
||||
if (!isEnding && ((!lossSFX.playing) || (character.getAnimName() == "firstDeath" && character.isAnimFinished())) && (FlxG.sound.music == null || !FlxG.sound.music.playing))
|
||||
{
|
||||
@@ -193,6 +171,25 @@ class GameOverSubstate extends MusicBeatSubstate
|
||||
});
|
||||
}
|
||||
|
||||
function exit()
|
||||
{
|
||||
var event = new CancellableEvent();
|
||||
gameoverScript.call('onReturnToMenu', [event]);
|
||||
|
||||
if (event.cancelled)
|
||||
return;
|
||||
|
||||
if (PlayState.chartingMode && Charter.undos.unsaved) game.saveWarn(false);
|
||||
else {
|
||||
if (Charter.instance != null) Charter.instance.__clearStatics();
|
||||
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.stop();
|
||||
FlxG.sound.music = null;
|
||||
|
||||
FlxG.switchState(PlayState.isStoryMode ? new StoryMenuState() : new FreeplayState());
|
||||
}
|
||||
}
|
||||
|
||||
override function destroy()
|
||||
{
|
||||
gameoverScript.call("destroy");
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
package funkin.game;
|
||||
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
import funkin.backend.scripting.events.healthicon.HealthIconChangeEvent;
|
||||
|
||||
class HealthIcon extends FlxSprite
|
||||
class HealthIcon extends FunkinSprite
|
||||
{
|
||||
/**
|
||||
* Used for FreeplayState! If you use it elsewhere, prob gonna annoying
|
||||
* Attaches the icon to a sprite, following it's position
|
||||
*/
|
||||
public var sprTracker:FlxSprite;
|
||||
/**
|
||||
* Where to place the icon in relation to the sprite
|
||||
*
|
||||
* LEFT: Left of the sprite
|
||||
*
|
||||
* CENTER: Center of the sprite
|
||||
*
|
||||
* RIGHT: Right of the sprite
|
||||
*/
|
||||
public var sprTrackerAlignment:TrackerAlignment = RIGHT;
|
||||
/**
|
||||
* Offset of the icon in relation to the sprite
|
||||
*
|
||||
* By default it is set to (10, -30) and is intended to be used with Alphabet
|
||||
*/
|
||||
public var sprTrackerOffset:FlxPoint = new FlxPoint(10, -30);
|
||||
|
||||
/**
|
||||
* The currently showing icon
|
||||
@@ -20,31 +40,60 @@ class HealthIcon extends FlxSprite
|
||||
|
||||
/**
|
||||
* Health steps in this format:
|
||||
* Min Percentage => Frame Index
|
||||
*
|
||||
* Min Percentage => Frame Index / Animation Name
|
||||
*/
|
||||
public var healthSteps:Map<Int, Int> = null;
|
||||
public var healthSteps:Map<Int, OneOfTwo<String, Int>> = null;
|
||||
|
||||
/**
|
||||
* current animation state
|
||||
* Current animation state
|
||||
*/
|
||||
public var curAnimState:Int = -1;
|
||||
public var curAnimState:OneOfTwo<String, Int> = -1;
|
||||
|
||||
/**
|
||||
* The Default Scale For The Icon
|
||||
*
|
||||
* This is what scale the icon should return to when its bump animation is finished
|
||||
*/
|
||||
public var defaultScale:Float = 1;
|
||||
|
||||
/**
|
||||
* Whenever or not the icon is animated or not
|
||||
*/
|
||||
public var animated:Bool = false;
|
||||
|
||||
/**
|
||||
* XML Animated Icon data
|
||||
*
|
||||
* `null` if the icon is not animated or its invalid
|
||||
*/
|
||||
public var xmlData:Xml;
|
||||
|
||||
/**
|
||||
* Extra offsets to add when updating the hitbox
|
||||
*/
|
||||
public var extraOffsets:FlxPoint = FlxPoint.get();
|
||||
|
||||
/**
|
||||
* Helper for HScript who can't make maps
|
||||
* @param steps Something like this: `[[0, 1], [20, 0]]`
|
||||
*
|
||||
* THIS IS DEPRECATED AND WILL BE REMOVED IN THE FUTURE
|
||||
*
|
||||
* Please set the healthSteps directly in the script instead, hscript does support maps
|
||||
*
|
||||
* @param steps Something like this: `[[0, 1], [20, 0]]` or `[[0, "losing"], [20, "neutral"]]` for animated icons
|
||||
*/
|
||||
public function setHealthSteps(steps:Array<Array<Int>>) { // helper for hscript that can't do maps
|
||||
@:dox(hide) @:noCompletion @:deprecated("use healthSteps instead")
|
||||
public function setHealthSteps(steps:Array<Array<OneOfTwo<String, Int>>>):Void { // helper for hscript that can't do maps
|
||||
if (steps == null) return;
|
||||
healthSteps = [];
|
||||
for(s in steps)
|
||||
for (s in steps)
|
||||
if (s.length > 1)
|
||||
healthSteps[s[0]] = s[1];
|
||||
var am = 0;
|
||||
for(k=>e in healthSteps) am++;
|
||||
|
||||
if (am <= 0) healthSteps = [
|
||||
0 => 1, // losing icon
|
||||
20 => 0, // normal icon
|
||||
if (Lambda.count(healthSteps) <= 0) healthSteps = [
|
||||
0 => (this.animated ? "losing" : 1), // losing icon
|
||||
20 => (this.animated ? "neutral" : 0), // normal icon
|
||||
];
|
||||
}
|
||||
|
||||
@@ -58,50 +107,357 @@ class HealthIcon extends FlxSprite
|
||||
scrollFactor.set();
|
||||
}
|
||||
|
||||
public function setIcon(char:String, width:Int = 150, height:Int = 150) {
|
||||
if(curCharacter != char || this.width != width || this.height != height) {
|
||||
curCharacter = char;
|
||||
var path = Paths.image('icons/$char');
|
||||
if (!Assets.exists(path)) path = Paths.image('icons/' + Flags.DEFAULT_HEALTH_ICON);
|
||||
|
||||
loadGraphic(path, true, width, height);
|
||||
|
||||
animation.add(char, [for(i in 0...frames.frames.length) i], 0, false, isPlayer);
|
||||
antialiasing = true;
|
||||
animation.play(char);
|
||||
|
||||
healthSteps = [
|
||||
0 => 1, // losing icon
|
||||
20 => 0, // normal icon
|
||||
];
|
||||
|
||||
if (frames.frames.length >= 3)
|
||||
healthSteps[80] = 2; // winning icon
|
||||
}
|
||||
/**
|
||||
* Called every beat, and causes the icon to become bigger
|
||||
**/
|
||||
public dynamic function bump():Void {
|
||||
var iconScale = Flags.BOP_ICON_SCALE;
|
||||
scale.set(defaultScale * iconScale, defaultScale * iconScale);
|
||||
updateHitbox();
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
/**
|
||||
* Called every frame and causes the icon to become smaller
|
||||
**/
|
||||
public dynamic function updateBump():Void {
|
||||
var iconLerp = Flags.ICON_LERP;
|
||||
scale.set(CoolUtil.fpsLerp(scale.x, defaultScale, iconLerp), CoolUtil.fpsLerp(scale.y, defaultScale, iconLerp));
|
||||
updateHitbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the icon to the specified character
|
||||
* @param char Character to set the icon to
|
||||
* @param allowAnimated Whenever the icon can be animated
|
||||
**/
|
||||
public function setIcon(char:String, allowAnimated:Bool = true):Void {
|
||||
if (curCharacter == char) return;
|
||||
|
||||
var oldIconPath = 'icons/$char';
|
||||
var newIconPath = 'icons/$char/icon';
|
||||
|
||||
if (!Assets.exists(Paths.image(oldIconPath)) && !Assets.exists(Paths.image(newIconPath))) {
|
||||
char = 'face';
|
||||
oldIconPath = 'icons/$char';
|
||||
newIconPath = 'icons/$char/icon';
|
||||
}
|
||||
curCharacter = char;
|
||||
|
||||
var iconPath = Assets.exists(Paths.image(oldIconPath)) ? oldIconPath : newIconPath;
|
||||
var iconXmlPath = Paths.getPath('images/icons/$char/data.xml');
|
||||
var iconFoundData = Assets.exists(iconXmlPath);
|
||||
|
||||
try {
|
||||
xmlData = iconFoundData ? Xml.parse(Assets.getText(iconXmlPath)) : null;
|
||||
} catch(e) {
|
||||
Logs.trace('Error while parsing icon data for $char: ${e.message}', ERROR);
|
||||
xmlData = null;
|
||||
}
|
||||
var xmlValid = xmlData != null && (xmlData = xmlData.firstElement()) != null;
|
||||
|
||||
this.animated = allowAnimated && iconFoundData && xmlValid && Paths.framesExists(Paths.image(newIconPath), true, true, true);
|
||||
|
||||
var iconAmt:Int = 0;
|
||||
var iconSize:Int = 0;
|
||||
var iconIsPlayer = xmlValid ? xmlData.get("facing").getDefault("right").toLowerCase() == "left" : false;
|
||||
|
||||
animateAtlas = null; // reset
|
||||
if (this.animated)
|
||||
loadSprite(Paths.image(newIconPath));
|
||||
else {
|
||||
var iconAsset:FlxGraphic = FlxG.bitmap.add(Paths.image(iconPath));
|
||||
var assetW:Float = iconAsset.width;
|
||||
var assetH:Float = iconAsset.height;
|
||||
|
||||
iconAmt = Math.round(assetW / assetH); // Just in case the icon is in a weird aspect ratio
|
||||
iconSize = Math.floor(assetW / iconAmt);
|
||||
if (iconSize * iconAmt > assetW) {
|
||||
iconSize = Math.floor(assetW / iconAmt);
|
||||
iconAmt = Math.floor(assetW / iconSize);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if(!animation.onFinishEnd.has(animFinishCallback))
|
||||
animation.onFinishEnd.add(animFinishCallback);
|
||||
if(animateAtlas != null && !animateAtlas.anim.onFinishEnd.has(animFinishCallback))
|
||||
animateAtlas.anim.onFinishEnd.add(animFinishCallback);
|
||||
|
||||
var parsedSteps:Map<Int, String> = [];
|
||||
|
||||
antialiasing = true;
|
||||
if (xmlValid) {
|
||||
if (xmlData.exists("antialiasing"))
|
||||
antialiasing = xmlData.get("antialiasing").toLowerCase() == "true";
|
||||
if (xmlData.exists("offsetX"))
|
||||
extraOffsets.x = Std.parseFloat(xmlData.get("offsetX")).getDefault(0);
|
||||
if (xmlData.exists("offsetY"))
|
||||
extraOffsets.y = Std.parseFloat(xmlData.get("offsetY")).getDefault(0);
|
||||
|
||||
for (node in xmlData.elements())
|
||||
switch(node.nodeName) {
|
||||
case "transition":
|
||||
if (this.animated == false) {
|
||||
Logs.trace('Icon ${char} data <transition> is not allowed when not animated', WARNING);
|
||||
continue;
|
||||
}
|
||||
if (!node.exists("anim")) {
|
||||
Logs.trace('Icon ${char} data <transition> is missing anim', WARNING);
|
||||
continue;
|
||||
}
|
||||
if (!node.exists("to")) {
|
||||
Logs.trace('Icon ${char} data <transition> is missing to', WARNING);
|
||||
continue;
|
||||
}
|
||||
if (!node.exists("from")) {
|
||||
Logs.trace('Icon ${char} data <transition> is missing from', WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
var animName = 'from-${node.get("from")}-to-${node.get("to")}';
|
||||
|
||||
var offsetX:Float = 0;
|
||||
var offsetY:Float = 0;
|
||||
if (node.exists("offsetX"))
|
||||
offsetX = Std.parseFloat(node.get("offsetX")).getDefault(0);
|
||||
else if (node.exists("offsetx"))
|
||||
offsetX = Std.parseFloat(node.get("offsetx")).getDefault(0);
|
||||
|
||||
if (node.exists("offsetY"))
|
||||
offsetY = Std.parseFloat(node.get("offsetY")).getDefault(0);
|
||||
else if (node.exists("offsety"))
|
||||
offsetY = Std.parseFloat(node.get("offsety")).getDefault(0);
|
||||
|
||||
addOffset(animName, offsetX, offsetY);
|
||||
|
||||
addAnim(animName, node.get("anim"), Std.parseInt(node.get("fps")).getDefault(24), false); // don't allow looping for transitions
|
||||
if (animateAtlas == null && animation.exists(animName))
|
||||
animation.getByName(animName).flipX = isPlayer != iconIsPlayer;
|
||||
case "anim":
|
||||
if (this.animated == false) {
|
||||
Logs.trace('Icon ${char} data <anim> is not allowed when not animated', WARNING);
|
||||
continue;
|
||||
}
|
||||
if (!node.exists("name")) {
|
||||
Logs.trace('Icon ${char} data <anim> is missing name', WARNING);
|
||||
continue;
|
||||
}
|
||||
if (!node.exists("anim")) {
|
||||
Logs.trace('Icon ${char} data <anim> is missing anim', WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
var animName = node.get("name");
|
||||
|
||||
var offsetX:Float = 0;
|
||||
var offsetY:Float = 0;
|
||||
if (node.exists("offsetX"))
|
||||
offsetX = Std.parseFloat(node.get("offsetX")).getDefault(0);
|
||||
else if (node.exists("offsetx"))
|
||||
offsetX = Std.parseFloat(node.get("offsetx")).getDefault(0);
|
||||
|
||||
if (node.exists("offsetY"))
|
||||
offsetY = Std.parseFloat(node.get("offsetY")).getDefault(0);
|
||||
else if (node.exists("offsety"))
|
||||
offsetY = Std.parseFloat(node.get("offsety")).getDefault(0);
|
||||
|
||||
addOffset(animName, offsetX, offsetY);
|
||||
|
||||
var looped:Bool = false;
|
||||
if (node.exists("looped"))
|
||||
looped = node.get("looped").toLowerCase() == "true";
|
||||
else if (node.exists("loop"))
|
||||
looped = node.get("loop").toLowerCase() == "true";
|
||||
|
||||
addAnim(animName, node.get("anim"), Std.parseInt(node.get("fps")).getDefault(24), looped);
|
||||
if (animateAtlas == null && animation.exists(animName))
|
||||
animation.getByName(animName).flipX = isPlayer != iconIsPlayer;
|
||||
case "step":
|
||||
if (!node.exists("percent")) {
|
||||
Logs.trace('Icon ${char} data <step> is missing percent', WARNING);
|
||||
continue;
|
||||
}
|
||||
if (!node.exists("name")) {
|
||||
Logs.trace('Icon ${char} data <step> is missing name', WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
parsedSteps.set(Std.parseInt(node.get("percent")).getDefault(0), node.get("name"));
|
||||
}
|
||||
}
|
||||
|
||||
if (Lambda.count(parsedSteps) > 0) {
|
||||
healthSteps = parsedSteps;
|
||||
} else {
|
||||
var hasLosing = this.animated ? hasAnim("losing") : iconAmt >= 2;
|
||||
var hasWinning = this.animated ? hasAnim("winning") : iconAmt >= 3;
|
||||
|
||||
if (hasLosing) {
|
||||
healthSteps = [
|
||||
0 => (this.animated ? "losing" : 1), // losing icon
|
||||
20 => (this.animated ? "neutral" : 0), // normal icon
|
||||
];
|
||||
} else {
|
||||
healthSteps = [
|
||||
0 => (this.animated ? "neutral" : 0), // normal icon
|
||||
];
|
||||
}
|
||||
if (hasWinning)
|
||||
healthSteps.set(80, this.animated ? "winning" : 2); // winning icon
|
||||
}
|
||||
var data = getIconAnim(health);
|
||||
if (data.isValid) {
|
||||
if (this.animated)
|
||||
playAnim(data.animState);
|
||||
else
|
||||
animation.curAnim.curFrame = data.animState;
|
||||
curAnimState = data.animState;
|
||||
}
|
||||
|
||||
if (animateAtlas != null) {
|
||||
@:bypassAccessor
|
||||
frameWidth = 150;
|
||||
@:bypassAccessor
|
||||
frameHeight = 150;
|
||||
extraOffsets.x -= frameWidth / 2;
|
||||
extraOffsets.y -= frameHeight / 2;
|
||||
updateHitbox();
|
||||
} else {
|
||||
setGraphicSize(150);
|
||||
updateHitbox();
|
||||
}
|
||||
|
||||
defaultScale = (xmlValid && xmlData.exists("scale")) ? Std.parseFloat(xmlData.get("scale")).getDefault(scale.x) : scale.x;
|
||||
scale.set(defaultScale, defaultScale);
|
||||
updateHitbox();
|
||||
}
|
||||
|
||||
var normalizedNames = ["neutral", "losing", "winning"];
|
||||
private function normalizeAnim(anim:OneOfTwo<String, Int>):OneOfTwo<String, Int> {
|
||||
if(this.animated) {
|
||||
if (anim is Int) {
|
||||
var _:Int = cast anim;
|
||||
if(_ >= 0 && _ < normalizedNames.length)
|
||||
anim = normalizedNames[anim];
|
||||
}
|
||||
} else {
|
||||
if (anim is String) {
|
||||
var _ = normalizedNames.indexOf(cast anim);
|
||||
if(_ >= 0)
|
||||
anim = _;
|
||||
}
|
||||
}
|
||||
return anim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the animation data for a specific health percentage
|
||||
* @param health Health percentage
|
||||
* @return Animation data (-1 if invalid)
|
||||
*/
|
||||
public dynamic function getIconAnim(health:Float):IconAnimData {
|
||||
var i:OneOfTwo<String, Int> = -1;
|
||||
var oldKey:Int = -1;
|
||||
var isValid = false;
|
||||
for (k=>icon in healthSteps) if (k > oldKey && k <= health * 100) {
|
||||
oldKey = k;
|
||||
i = icon;
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
i = normalizeAnim(i);
|
||||
|
||||
return new IconAnimData(i, isValid);
|
||||
}
|
||||
|
||||
override function update(elapsed:Float):Void {
|
||||
super.update(elapsed);
|
||||
|
||||
if (sprTracker != null)
|
||||
setPosition(sprTracker.x + sprTracker.width + 10, sprTracker.y - 30);
|
||||
if (sprTracker != null) {
|
||||
setPosition(sprTracker.x + (switch sprTrackerAlignment {
|
||||
case LEFT: -width;
|
||||
case CENTER: (sprTracker.width - width) / 2;
|
||||
case RIGHT: sprTracker.width;
|
||||
}) + sprTrackerOffset.x, sprTracker.y + sprTrackerOffset.y);
|
||||
}
|
||||
|
||||
if (animation.curAnim != null) {
|
||||
var i:Int = -1;
|
||||
var oldKey:Int = -1;
|
||||
for (k=>icon in healthSteps) if (k > oldKey && k <= health * 100) {
|
||||
oldKey = k;
|
||||
i = icon;
|
||||
}
|
||||
if (i >= 0 && curAnimState != i) {
|
||||
var event = EventManager.get(funkin.backend.scripting.events.healthicon.HealthIconChangeEvent).recycle(i, this);
|
||||
if (animation.curAnim != null || (this.animated && animateAtlas != null)) {
|
||||
var data = getIconAnim(health);
|
||||
var localAnimState = data.animState;
|
||||
|
||||
if (data.isValid && curAnimState != localAnimState) {
|
||||
var event = EventManager.get(HealthIconChangeEvent).recycle(localAnimState, this);
|
||||
funkin.backend.scripting.GlobalScript.event("onHealthIconAnimChange", event);
|
||||
if (!event.cancelled)
|
||||
animation.curAnim.curFrame = event.amount;
|
||||
if (!event.cancelled) {
|
||||
if (this.animated) {
|
||||
var transAnim = 'from-$curAnimState-to-${event.anim}';
|
||||
playAnim(hasAnim(transAnim) ? transAnim : event.anim);
|
||||
} else {
|
||||
if(animation.curAnim != null)
|
||||
animation.curAnim.curFrame = event.anim;
|
||||
}
|
||||
}
|
||||
|
||||
curAnimState = event.amount;
|
||||
curAnimState = localAnimState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function animFinishCallback(anim:String):Void {
|
||||
if (this.animated)
|
||||
if (anim.startsWith("from-"))
|
||||
playAnim(anim.substr(anim.lastIndexOf('-') + 1));
|
||||
}
|
||||
|
||||
override function updateHitbox():Void {
|
||||
super.updateHitbox();
|
||||
offset += extraOffsets;
|
||||
}
|
||||
}
|
||||
|
||||
@:dox(hide)
|
||||
class IconAnimData {
|
||||
public var animState:OneOfTwo<String, Int>;
|
||||
public var isValid:Bool;
|
||||
|
||||
public function new(animState:OneOfTwo<String, Int>, isValid:Bool) {
|
||||
this.animState = animState;
|
||||
this.isValid = isValid;
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return '$animState (Valid: $isValid)';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for `funkin.game.HealthIcon.sprTrackerAlignment`.
|
||||
* This determines the position of the icon in relation to the sprite tracker.
|
||||
**/
|
||||
enum abstract TrackerAlignment(Int) {
|
||||
/**
|
||||
* Left of the sprite tracker
|
||||
*
|
||||
* Mathematically: `tracker.x - icon.width`
|
||||
*/
|
||||
var LEFT = 0;
|
||||
/**
|
||||
* Center of the sprite tracker
|
||||
*
|
||||
* Mathematically: `tracker.x + (tracker.width - icon.width) / 2`
|
||||
*/
|
||||
var CENTER = 1;
|
||||
/**
|
||||
* Right of the sprite tracker
|
||||
*
|
||||
* Mathematically: `tracker.x + tracker.width`
|
||||
*/
|
||||
var RIGHT = 2;
|
||||
}
|
||||
+23
-22
@@ -75,6 +75,11 @@ class Note extends FlxSprite
|
||||
public var scrollSpeed:Null<Float> = null;
|
||||
public var noteAngle:Null<Float> = null;
|
||||
|
||||
public var copyStrumAngle:Bool = true;
|
||||
public var updateNotesPosX:Bool = true;
|
||||
public var updateNotesPosY:Bool = true;
|
||||
public var updateFlipY:Bool = true;
|
||||
|
||||
public var noteType(get, never):String;
|
||||
|
||||
@:dox(hide) public var __strumCameras:Array<FlxCamera> = null;
|
||||
@@ -146,7 +151,9 @@ class Note extends FlxSprite
|
||||
case 0:
|
||||
animation.addByPrefix('scroll', 'purple0');
|
||||
animation.addByPrefix('hold', 'purple hold piece');
|
||||
animation.addByPrefix('holdend', 'pruple end hold');
|
||||
animation.addByPrefix("holdend", "pruple end hold");
|
||||
if (animation.exists("holdend") != true) // null or false
|
||||
animation.addByPrefix('holdend', 'purple hold end');
|
||||
case 1:
|
||||
animation.addByPrefix('scroll', 'blue0');
|
||||
animation.addByPrefix('hold', 'blue hold piece');
|
||||
@@ -191,7 +198,7 @@ class Note extends FlxSprite
|
||||
}
|
||||
|
||||
public var lastScrollSpeed:Null<Float> = null;
|
||||
public var gapFix:Single = 0;
|
||||
public var gapFix:SingleOrFloat = 0;
|
||||
public var useAntialiasingFix(get, set):Bool;
|
||||
inline function set_useAntialiasingFix(v:Bool) {
|
||||
if(v != useAntialiasingFix) {
|
||||
@@ -211,7 +218,7 @@ class Note extends FlxSprite
|
||||
|
||||
override function drawComplex(camera:FlxCamera) {
|
||||
var downscrollCam = (camera is HudCamera ? ({var _:HudCamera=cast camera;_;}).downscroll : false);
|
||||
flipY = (isSustainNote && flipSustain) && (downscrollCam != (__strum != null && __strum.getScrollSpeed(this) < 0));
|
||||
if (updateFlipY) flipY = (isSustainNote && flipSustain) && (downscrollCam != (__strum != null && __strum.getScrollSpeed(this) < 0));
|
||||
if (downscrollCam) {
|
||||
frameOffset.y += __notePosFrameOffset.y * 2;
|
||||
super.drawComplex(camera);
|
||||
@@ -267,30 +274,22 @@ class Note extends FlxSprite
|
||||
public function updateSustain(strum:Strum) {
|
||||
var scrollSpeed = strum.getScrollSpeed(this);
|
||||
|
||||
var len = 0.45 * CoolUtil.quantize(scrollSpeed, 100);
|
||||
|
||||
if (nextSustain != null && lastScrollSpeed != scrollSpeed) {
|
||||
// is long sustain
|
||||
if (lastScrollSpeed != scrollSpeed) {
|
||||
lastScrollSpeed = scrollSpeed;
|
||||
|
||||
scale.y = (sustainLength * len) / frameHeight;
|
||||
updateHitbox();
|
||||
scale.y += gapFix / frameHeight;
|
||||
if (nextSustain != null) {
|
||||
scale.y = (sustainLength * 0.45 * scrollSpeed) / frameHeight;
|
||||
updateHitbox();
|
||||
scale.y += gapFix / frameHeight;
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasGoodHit) return;
|
||||
var t = CoolUtil.bound((Conductor.songPosition - strumTime) / (height) * len, 0, 1);
|
||||
var swagRect = this.clipRect == null ? new FlxRect() : this.clipRect;
|
||||
swagRect.x = 0;
|
||||
swagRect.y = t * frameHeight;
|
||||
swagRect.width = frameWidth;
|
||||
swagRect.height = frameHeight;
|
||||
|
||||
setClipRect(swagRect);
|
||||
updateSustainClip();
|
||||
}
|
||||
|
||||
public inline function setClipRect(rect:FlxRect) {
|
||||
this.clipRect = rect;
|
||||
public function updateSustainClip() if (wasGoodHit) {
|
||||
var t = FlxMath.bound((Conductor.songPosition - strumTime) / height * 0.45 * lastScrollSpeed, 0, 1);
|
||||
var rect = clipRect == null ? FlxRect.get() : clipRect;
|
||||
clipRect = rect.set(0, frameHeight * t, frameWidth, frameHeight * (1 - t));
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
@@ -306,5 +305,7 @@ class Note extends FlxSprite
|
||||
|
||||
public override function destroy() {
|
||||
super.destroy();
|
||||
|
||||
clipRect = FlxDestroyUtil.put(clipRect);
|
||||
}
|
||||
}
|
||||
|
||||
+278
-134
@@ -7,6 +7,7 @@ import flixel.math.FlxPoint;
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.tweens.misc.VarTween;
|
||||
import flixel.ui.FlxBar;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSort;
|
||||
@@ -18,9 +19,9 @@ import funkin.backend.chart.EventsData;
|
||||
import funkin.backend.scripting.DummyScript;
|
||||
import funkin.backend.scripting.Script;
|
||||
import funkin.backend.scripting.ScriptPack;
|
||||
import funkin.backend.scripting.events.*;
|
||||
import funkin.backend.scripting.events.gameplay.*;
|
||||
import funkin.backend.scripting.events.note.*;
|
||||
import funkin.backend.scripting.events.*;
|
||||
import funkin.backend.system.Conductor;
|
||||
import funkin.backend.system.RotatingSpriteGroup;
|
||||
import funkin.editors.SaveWarning;
|
||||
@@ -29,7 +30,7 @@ import funkin.editors.charter.CharterSelection;
|
||||
import funkin.game.SplashHandler;
|
||||
import funkin.game.cutscenes.*;
|
||||
import funkin.menus.*;
|
||||
import funkin.menus.StoryMenuState.WeekData;
|
||||
import funkin.backend.week.WeekData;
|
||||
import funkin.savedata.FunkinSave;
|
||||
import haxe.io.Path;
|
||||
|
||||
@@ -69,15 +70,15 @@ class PlayState extends MusicBeatState
|
||||
*/
|
||||
public static var fromMods:Bool = false;
|
||||
/**
|
||||
* Whenever Charting Mode has been enabled for this song.
|
||||
* Whenever Charting Mode is enabled for this song.
|
||||
*/
|
||||
public static var chartingMode:Bool = false;
|
||||
/**
|
||||
* Whenever the song has been started with opponent mode on.
|
||||
* Whenever the song is started with opponent mode on.
|
||||
*/
|
||||
public static var opponentMode:Bool = Flags.DEFAULT_OPPONENT_MODE;
|
||||
/**
|
||||
* Whenever the song has been started with co-op mode on.
|
||||
* Whenever the song is started with co-op mode on.
|
||||
*/
|
||||
public static var coopMode:Bool = Flags.DEFAULT_COOP_MODE;
|
||||
|
||||
@@ -138,8 +139,8 @@ class PlayState extends MusicBeatState
|
||||
*/
|
||||
public var downscroll(get, set):Bool;
|
||||
|
||||
@:dox(hide) private function set_downscroll(v:Bool) {return camHUD.downscroll = v;}
|
||||
@:dox(hide) private function get_downscroll():Bool {return camHUD.downscroll;}
|
||||
private inline function set_downscroll(v:Bool) return camHUD.downscroll = v;
|
||||
private inline function get_downscroll():Bool return camHUD.downscroll;
|
||||
|
||||
/**
|
||||
* Instrumental sound (Inst.ogg).
|
||||
@@ -225,23 +226,6 @@ class PlayState extends MusicBeatState
|
||||
*/
|
||||
public var doIconBop:Bool = true;
|
||||
|
||||
/**
|
||||
* Whenever cam zooming is enabled, enables on a note hit if not cancelled.
|
||||
*/
|
||||
public var camZooming:Bool = false;
|
||||
/**
|
||||
* Interval of cam zooming (beats).
|
||||
* For example: if set to 4, the camera will zoom every 4 beats.
|
||||
*/
|
||||
public var camZoomingInterval:Int = Flags.DEFAULT_CAM_ZOOM_INTERVAL;
|
||||
/**
|
||||
* How strong the cam zooms should be (defaults to 1)
|
||||
*/
|
||||
public var camZoomingStrength:Float = Flags.DEFAULT_CAM_ZOOM_STRENGTH;
|
||||
/**
|
||||
* Maximum amount of zoom for the camera.
|
||||
*/
|
||||
public var maxCamZoom:Float = Flags.MAX_CAMERA_ZOOM;
|
||||
/**
|
||||
* Current song name (lowercase)
|
||||
*/
|
||||
@@ -263,12 +247,12 @@ class PlayState extends MusicBeatState
|
||||
/**
|
||||
* Current health. Goes from 0 to maxHealth (defaults to 2)
|
||||
*/
|
||||
public var health:Float = 1;
|
||||
public var health(default, set):Float = 1;
|
||||
|
||||
/**
|
||||
* Maximum health the player can have. Defaults to 2.
|
||||
*/
|
||||
@:isVar public var maxHealth(get, set):Float = Flags.DEFAULT_MAX_HEALTH;
|
||||
public var maxHealth(default, set):Float = Flags.DEFAULT_MAX_HEALTH;
|
||||
/**
|
||||
* Current combo.
|
||||
*/
|
||||
@@ -304,6 +288,11 @@ class PlayState extends MusicBeatState
|
||||
* Opponent's icon
|
||||
*/
|
||||
public var iconP2:HealthIcon;
|
||||
/**
|
||||
* Every active icon that will be updated during gameplay (defaults to `iconP1` and `iconP1` between `create` and `postCreate` in scripts)
|
||||
*/
|
||||
public var iconArray:Array<HealthIcon> = [];
|
||||
|
||||
/**
|
||||
* Camera for the HUD (notes, misses).
|
||||
*/
|
||||
@@ -383,6 +372,30 @@ class PlayState extends MusicBeatState
|
||||
*/
|
||||
public var camHUDZoomLerp:Float = Flags.DEFAULT_HUD_ZOOM_LERP;
|
||||
|
||||
/**
|
||||
* Whenever cam zooming is enabled, enables on a note hit if not cancelled.
|
||||
*/
|
||||
public var camZooming:Bool = false;
|
||||
/**
|
||||
* Interval of cam zooming (beats).
|
||||
* For example: if set to 4, the camera will zoom every 4 beats.
|
||||
*/
|
||||
public var camZoomingInterval:Int = Flags.DEFAULT_CAM_ZOOM_INTERVAL;
|
||||
/**
|
||||
* How strong the cam zooms should be (defaults to 1)
|
||||
*/
|
||||
public var camZoomingStrength:Float = Flags.DEFAULT_CAM_ZOOM_STRENGTH;
|
||||
/**
|
||||
* Default multiplier for `maxCamZoom`.
|
||||
*/
|
||||
public var maxCamZoomMult:Float = Flags.MAX_CAMERA_ZOOM_MULT;
|
||||
/**
|
||||
* Maximum amount of zoom for the camera (based on `maxCamZoomMult` and the camera's zoom IF not set).
|
||||
*/
|
||||
public var maxCamZoom(get, default):Float = Math.NaN;
|
||||
|
||||
private inline function get_maxCamZoom() return Math.isNaN(maxCamZoom) ? maxCamZoomMult * defaultCamZoom : maxCamZoom;
|
||||
|
||||
/**
|
||||
* Zoom for the pixel assets.
|
||||
*/
|
||||
@@ -532,10 +545,10 @@ class PlayState extends MusicBeatState
|
||||
*/
|
||||
public function updateRating() {
|
||||
var rating = null;
|
||||
var acc = get_accuracy();
|
||||
var acc = accuracy; // caching since it has a getter with an operation - Nex
|
||||
|
||||
for(e in comboRatings)
|
||||
if (e.percent <= acc && (rating == null || rating.percent < e.percent))
|
||||
if (comboRatings != null && comboRatings.length > 0) for (e in comboRatings)
|
||||
if ((e.percent <= acc && e.maxMisses >= misses) && (rating == null || (rating.percent < e.percent && e.maxMisses >= misses)))
|
||||
rating = e;
|
||||
|
||||
var event = gameAndCharsEvent("onRatingUpdate", EventManager.get(RatingUpdateEvent).recycle(rating, curRating));
|
||||
@@ -543,13 +556,13 @@ class PlayState extends MusicBeatState
|
||||
curRating = event.rating;
|
||||
}
|
||||
|
||||
private inline function get_maxHealth()
|
||||
return this.maxHealth;
|
||||
private function set_maxHealth(v:Float) {
|
||||
if (healthBar != null && healthBar.max == this.maxHealth) {
|
||||
healthBar.setRange(healthBar.min, v);
|
||||
}
|
||||
return this.maxHealth = v;
|
||||
private inline function set_health(v:Float)
|
||||
return health = FlxMath.bound(v, 0, maxHealth);
|
||||
private inline function set_maxHealth(v:Float) {
|
||||
if (healthBar != null && healthBar.max == maxHealth) healthBar.setRange(healthBar.min, v);
|
||||
maxHealth = v;
|
||||
health = health; // running the setter - Nex
|
||||
return maxHealth;
|
||||
}
|
||||
|
||||
private inline function get_curStage()
|
||||
@@ -581,6 +594,7 @@ class PlayState extends MusicBeatState
|
||||
@:dox(hide) override public function create()
|
||||
{
|
||||
Note.__customNoteTypeExists = [];
|
||||
|
||||
// SCRIPTING & DATA INITIALIZATION
|
||||
#if REGION
|
||||
instance = this;
|
||||
@@ -609,8 +623,8 @@ class PlayState extends MusicBeatState
|
||||
detailsText = isStoryMode ? ("Story Mode: " + storyWeek.name) : "Freeplay";
|
||||
|
||||
// Checks if cutscene files exists
|
||||
var cutscenePath = Paths.script('songs/${SONG.meta.name.toLowerCase()}/cutscene');
|
||||
var endCutscenePath = Paths.script('songs/${SONG.meta.name.toLowerCase()}/cutscene-end');
|
||||
var cutscenePath = Paths.script('songs/${SONG.meta.name}/cutscene');
|
||||
var endCutscenePath = Paths.script('songs/${SONG.meta.name}/cutscene-end');
|
||||
if (Assets.exists(cutscenePath)) cutscene = cutscenePath;
|
||||
if (Assets.exists(endCutscenePath)) endCutscene = endCutscenePath;
|
||||
|
||||
@@ -637,10 +651,11 @@ class PlayState extends MusicBeatState
|
||||
// case "":
|
||||
// ADD YOUR HARDCODED SCRIPTS HERE!
|
||||
default:
|
||||
var scriptsFolders:Array<String> = ['songs/${SONG.meta.name.toLowerCase()}/scripts', 'data/charts/', 'songs/'];
|
||||
var normal = 'songs/${SONG.meta.name}/scripts';
|
||||
var scriptsFolders:Array<String> = [normal, normal + '/$difficulty/', 'data/charts/', 'songs/'];
|
||||
|
||||
for(folder in scriptsFolders) {
|
||||
for(file in Paths.getFolderContent(folder, true, fromMods ? MODS : BOTH)) {
|
||||
for (folder in scriptsFolders) {
|
||||
for (file in Paths.getFolderContent(folder, true, fromMods ? MODS : BOTH)) {
|
||||
if (folder == 'data/charts/')
|
||||
Logs.warn('data/charts/ is deprecated and will be removed in the future. Please move script $file to songs/', DARKYELLOW, "PlayState");
|
||||
|
||||
@@ -701,7 +716,7 @@ class PlayState extends MusicBeatState
|
||||
case 2: "girlfriend";
|
||||
}) : strumLine.position;
|
||||
if (strumLine.characters != null) for(k=>charName in strumLine.characters) {
|
||||
var char = new Character(0, 0, charName, stage.isCharFlipped(charPosName, strumLine.type == 1));
|
||||
var char = new Character(0, 0, charName, stage.isCharFlipped(stage.characterPoses[charName] != null ? charName : charPosName, strumLine.type == 1));
|
||||
stage.applyCharStuff(char, charPosName, k);
|
||||
chars.push(char);
|
||||
}
|
||||
@@ -782,9 +797,9 @@ class PlayState extends MusicBeatState
|
||||
if (Flags.DEFAULT_HEALTH != null) health = Flags.DEFAULT_HEALTH;
|
||||
else health = maxHealth / 2;
|
||||
|
||||
iconP1 = new HealthIcon(boyfriend != null ? boyfriend.getIcon() : Flags.DEFAULT_HEALTH_ICON, true);
|
||||
iconP2 = new HealthIcon(dad != null ? dad.getIcon() : Flags.DEFAULT_HEALTH_ICON, false);
|
||||
for(icon in [iconP1, iconP2]) {
|
||||
iconArray.push(iconP1 = new HealthIcon(boyfriend != null ? boyfriend.getIcon() : Flags.DEFAULT_HEALTH_ICON, true));
|
||||
iconArray.push(iconP2 = new HealthIcon(dad != null ? dad.getIcon() : Flags.DEFAULT_HEALTH_ICON, false));
|
||||
for (icon in iconArray) {
|
||||
icon.y = healthBar.y - (icon.height / 2);
|
||||
add(icon);
|
||||
}
|
||||
@@ -801,7 +816,8 @@ class PlayState extends MusicBeatState
|
||||
scoreTxt.alignment = RIGHT;
|
||||
missesTxt.alignment = CENTER;
|
||||
accuracyTxt.alignment = LEFT;
|
||||
updateRatingStuff();
|
||||
if (updateRatingStuff != null)
|
||||
updateRatingStuff();
|
||||
|
||||
for(e in [healthBar, healthBarBG, iconP1, iconP2, scoreTxt, missesTxt, accuracyTxt])
|
||||
e.cameras = [camHUD];
|
||||
@@ -838,7 +854,8 @@ class PlayState extends MusicBeatState
|
||||
|
||||
// Make icons appear in the correct spot during cutscenes
|
||||
healthBar.update(0);
|
||||
updateIconPositions();
|
||||
if (updateIconPositions != null)
|
||||
updateIconPositions();
|
||||
|
||||
__updateNote_event = EventManager.get(NoteUpdateEvent);
|
||||
|
||||
@@ -847,6 +864,7 @@ class PlayState extends MusicBeatState
|
||||
|
||||
/**
|
||||
* Function used to update Discord Presence.
|
||||
*
|
||||
* This function is dynamic, which means you can do `updateDiscordPresence = function() {}` in scripts.
|
||||
*/
|
||||
public dynamic function updateDiscordPresence()
|
||||
@@ -867,7 +885,7 @@ class PlayState extends MusicBeatState
|
||||
return;
|
||||
}
|
||||
|
||||
var songName = SONG.meta.name.toLowerCase();
|
||||
var songName = SONG.meta.name;
|
||||
|
||||
if (cutsceneScriptPath == null)
|
||||
cutsceneScriptPath = Paths.script('songs/$songName/${prefix}cutscene');
|
||||
@@ -1027,10 +1045,11 @@ class PlayState extends MusicBeatState
|
||||
Note.__customNoteTypeExists = [];
|
||||
}
|
||||
|
||||
public static function resetSongInfos() {
|
||||
deathCounter = 0;
|
||||
seenCutscene = false;
|
||||
}
|
||||
@:dox(hide) @:deprecated("scrollSpeedTween is deprecated, use eventsTween['scrollSpeed'] instead")
|
||||
public var scrollSpeedTween(get, set):FlxTween;
|
||||
private inline function get_scrollSpeedTween() return eventsTween.get("scrollSpeed");
|
||||
private inline function set_scrollSpeedTween(val:FlxTween) {eventsTween.set("scrollSpeed", val); return val;}
|
||||
// End of backwards compat
|
||||
|
||||
@:dox(hide) private function generateSong(?songData:ChartData):Void
|
||||
{
|
||||
@@ -1223,7 +1242,7 @@ class PlayState extends MusicBeatState
|
||||
if (SaveWarning.selectionClass != null) FlxG.switchState(Type.createInstance(SaveWarning.selectionClass, []));
|
||||
} else {
|
||||
WindowUtils.preventClosing = false; WindowUtils.resetClosing();
|
||||
Sys.exit(0);
|
||||
openfl.system.System.exit(0);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1236,7 +1255,7 @@ class PlayState extends MusicBeatState
|
||||
if (SaveWarning.selectionClass != null) FlxG.switchState(Type.createInstance(SaveWarning.selectionClass, []));
|
||||
} else {
|
||||
WindowUtils.preventClosing = false; WindowUtils.resetClosing();
|
||||
Sys.exit(0);
|
||||
openfl.system.System.exit(0);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1250,21 +1269,20 @@ class PlayState extends MusicBeatState
|
||||
]));
|
||||
}
|
||||
|
||||
function updateIconPositions() {
|
||||
dynamic function updateIconPositions() {
|
||||
var iconOffset = Flags.ICON_OFFSET;
|
||||
var healthBarPercent = healthBar.percent;
|
||||
|
||||
var center:Float = healthBar.x + healthBar.width * FlxMath.remapToRange(healthBar.percent, 0, 100, 1, 0);
|
||||
var center:Float = healthBar.x + healthBar.width * FlxMath.remapToRange(healthBarPercent, 0, 100, 1, 0);
|
||||
|
||||
iconP1.x = center - iconOffset;
|
||||
iconP2.x = center - (iconP2.width - iconOffset);
|
||||
|
||||
health = CoolUtil.bound(health, 0, maxHealth);
|
||||
|
||||
iconP1.health = healthBar.percent / 100;
|
||||
iconP2.health = 1 - (healthBar.percent / 100);
|
||||
iconP1.health = healthBarPercent / 100;
|
||||
iconP2.health = 1 - (healthBarPercent / 100);
|
||||
}
|
||||
|
||||
function updateRatingStuff() {
|
||||
dynamic function updateRatingStuff() {
|
||||
scoreTxt.text = 'Score:$songScore';
|
||||
missesTxt.text = '${comboBreaks ? "Combo Breaks" : "Misses"}:$misses';
|
||||
|
||||
@@ -1294,45 +1312,37 @@ class PlayState extends MusicBeatState
|
||||
return;
|
||||
}
|
||||
|
||||
updateRatingStuff();
|
||||
if (updateRatingStuff != null)
|
||||
updateRatingStuff();
|
||||
|
||||
if (controls.PAUSE && startedCountdown && canPause)
|
||||
pauseGame();
|
||||
if (canAccessDebugMenus && chartingMode && controls.DEV_ACCESS)
|
||||
FlxG.switchState(new funkin.editors.charter.Charter(SONG.meta.name, difficulty, false));
|
||||
|
||||
if (canAccessDebugMenus) {
|
||||
if (chartingMode && FlxG.keys.justPressed.SEVEN) {
|
||||
FlxG.switchState(new funkin.editors.charter.Charter(SONG.meta.name, difficulty, false));
|
||||
}
|
||||
#if EXPERMENTAL_SCRIPT_RELOADING
|
||||
if (FlxG.keys.justPressed.F5) {
|
||||
Logs.warn('Reloading scripts...', "PlayState");
|
||||
scripts.reload();
|
||||
Logs.warn('Song scripts successfully reloaded.', GREEN, "PlayState");
|
||||
}
|
||||
#end
|
||||
}
|
||||
if (doIconBop)
|
||||
for (icon in iconArray)
|
||||
if (icon.updateBump != null)
|
||||
icon.updateBump();
|
||||
|
||||
if (doIconBop) {
|
||||
var iconLerp = Flags.ICON_LERP;
|
||||
iconP1.scale.set(lerp(iconP1.scale.x, 1, iconLerp), lerp(iconP1.scale.y, 1, iconLerp));
|
||||
iconP2.scale.set(lerp(iconP2.scale.x, 1, iconLerp), lerp(iconP2.scale.y, 1, iconLerp));
|
||||
if (updateIconPositions != null)
|
||||
updateIconPositions();
|
||||
|
||||
iconP1.updateHitbox();
|
||||
iconP2.updateHitbox();
|
||||
}
|
||||
updateIconPositions();
|
||||
|
||||
if (startingSong)
|
||||
{
|
||||
if (startedCountdown)
|
||||
{
|
||||
if (startingSong) {
|
||||
if (startedCountdown) {
|
||||
Conductor.songPosition += Conductor.songOffset + elapsed * 1000;
|
||||
if (Conductor.songPosition >= 0)
|
||||
startSong();
|
||||
}
|
||||
} else if (FlxG.sound.music != null) {
|
||||
var instTime = FlxG.sound.music.time;
|
||||
var isOffsync = vocals.time != instTime || [for(strumLine in strumLines.members) strumLine.vocals.time != instTime].contains(true);
|
||||
var isOffsync:Bool = vocals.time != instTime;
|
||||
if(!isOffsync) {
|
||||
for(strumLine in strumLines.members) {
|
||||
if(strumLine.vocals.time != instTime) {
|
||||
isOffsync = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
__vocalOffsetViolation = Math.max(0, __vocalOffsetViolation + (isOffsync ? elapsed : -elapsed / 2));
|
||||
if (__vocalOffsetViolation > Flags.VOCAL_OFFSET_VIOLATION_THRESHOLD) {
|
||||
resyncVocals();
|
||||
@@ -1343,31 +1353,13 @@ class PlayState extends MusicBeatState
|
||||
while(events.length > 0 && events.last().time <= Conductor.songPosition)
|
||||
executeEvent(events.pop());
|
||||
|
||||
if (generatedMusic && strumLines.members[curCameraTarget] != null)
|
||||
{
|
||||
var pos = FlxPoint.get();
|
||||
var r = 0;
|
||||
for(c in strumLines.members[curCameraTarget].characters) {
|
||||
if (c == null || !c.visible) continue;
|
||||
var cpos = c.getCameraPosition();
|
||||
pos.x += cpos.x;
|
||||
pos.y += cpos.y;
|
||||
r++;
|
||||
//cpos.put();
|
||||
}
|
||||
if (r > 0) {
|
||||
pos.x /= r;
|
||||
pos.y /= r;
|
||||
if (controls.PAUSE && startedCountdown && canPause)
|
||||
pauseGame();
|
||||
|
||||
var event = gameAndCharsEvent("onCameraMove", EventManager.get(CamMoveEvent).recycle(pos, strumLines.members[curCameraTarget], r));
|
||||
if (!event.cancelled)
|
||||
camFollow.setPosition(pos.x, pos.y);
|
||||
}
|
||||
pos.put();
|
||||
}
|
||||
if (generatedMusic)
|
||||
moveCamera();
|
||||
|
||||
if (camZooming)
|
||||
{
|
||||
if (camZooming) {
|
||||
FlxG.camera.zoom = lerp(FlxG.camera.zoom, defaultCamZoom, camGameZoomLerp);
|
||||
camHUD.zoom = lerp(camHUD.zoom, defaultHudZoom, camHUDZoomLerp);
|
||||
}
|
||||
@@ -1401,7 +1393,52 @@ class PlayState extends MusicBeatState
|
||||
scripts.event("postDraw", e);
|
||||
}
|
||||
|
||||
public var scrollSpeedTween:FlxTween = null;
|
||||
public function moveCamera() if (strumLines.members[curCameraTarget] != null) {
|
||||
var data:CamPosData = getStrumlineCamPos(curCameraTarget);
|
||||
if (data.amount > 0) {
|
||||
var event = scripts.event("onCameraMove", EventManager.get(CamMoveEvent).recycle(data.pos, strumLines.members[curCameraTarget], data.amount));
|
||||
if (!event.cancelled)
|
||||
camFollow.setPosition(event.position.x, event.position.y);
|
||||
}
|
||||
data.put();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the camera position of the specified strumline.
|
||||
* @param strumLine The strumline to get the camera position of.
|
||||
* @param pos The position to put the camera position in. If `null`, a new FlxPoint will be created.
|
||||
* @param ignoreInvisible Whenever invisible characters should be ignored.
|
||||
**/
|
||||
public inline function getStrumlineCamPos(strumLine:Int, ?pos:FlxPoint = null, ?ignoreInvisible:Bool = true):CamPosData {
|
||||
return getCharactersCamPos(strumLines.members[strumLine].characters, pos, ignoreInvisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the camera position of the specified characters.
|
||||
* @param chars The characters to get the camera position of.
|
||||
* @param pos The position to put the camera position in. If `null`, a new FlxPoint will be created.
|
||||
* @param ignoreInvisible Whenever invisible characters should be ignored.
|
||||
**/
|
||||
public dynamic function getCharactersCamPos(chars:Array<Character>, ?pos:FlxPoint = null, ?ignoreInvisible:Bool = true):CamPosData {
|
||||
if (pos == null) pos = FlxPoint.get();
|
||||
var amount = 0;
|
||||
for(c in chars) {
|
||||
if (c == null || (ignoreInvisible && !c.visible)) continue;
|
||||
var cpos = c.getCameraPosition();
|
||||
pos.x += cpos.x;
|
||||
pos.y += cpos.y;
|
||||
amount++;
|
||||
//cpos.put(); // not actually in the pool, so no need
|
||||
}
|
||||
if (amount > 0) {
|
||||
pos.x /= amount;
|
||||
pos.y /= amount;
|
||||
}
|
||||
return new CamPosData(pos, amount);
|
||||
}
|
||||
|
||||
|
||||
public var eventsTween:Map<String, FlxTween> = [];
|
||||
|
||||
public function executeEvent(event:ChartEvent) @:privateAccess {
|
||||
if (event == null || event.params == null) return;
|
||||
@@ -1413,12 +1450,92 @@ class PlayState extends MusicBeatState
|
||||
|
||||
switch(event.name) {
|
||||
case "HScript Call":
|
||||
scripts.call(event.params[0], event.params[1].split(','));
|
||||
var scriptPacks:Array<ScriptPack> = [scripts, stateScripts];
|
||||
for (strLine in strumLines.members) for (char in strLine.characters) scriptPacks.push(char.scripts);
|
||||
var args:Array<String> = event.params[1].split(',');
|
||||
|
||||
for (pack in scriptPacks) {
|
||||
pack.call(event.params[0], args);
|
||||
//public functions
|
||||
if (pack.publicVariables.exists(event.params[0])) {
|
||||
var func = pack.publicVariables.get(event.params[0]);
|
||||
if (func != null && Reflect.isFunction(func))
|
||||
Reflect.callMethod(null, func, args);
|
||||
}
|
||||
}
|
||||
|
||||
//static functions
|
||||
if (Script.staticVariables.exists(event.params[0])) {
|
||||
var func = Script.staticVariables.get(event.params[0]);
|
||||
if (func != null && Reflect.isFunction(func))
|
||||
Reflect.callMethod(null, func, args);
|
||||
}
|
||||
case "Camera Movement":
|
||||
var tween = eventsTween.get("cameraMovement");
|
||||
if (tween != null) {
|
||||
if (tween.onComplete != null) tween.onComplete(tween);
|
||||
tween.cancel();
|
||||
}
|
||||
|
||||
curCameraTarget = event.params[0];
|
||||
moveCamera();
|
||||
|
||||
if (strumLines.members[curCameraTarget] != null) {
|
||||
if (event.params[1] == false) FlxG.camera.snapToTarget();
|
||||
else if (event.params[3] != null && event.params[3] != "CLASSIC") { // making more nullchecks in this event because of the default save value being false - Nex
|
||||
var oldFollow = FlxG.camera.followEnabled;
|
||||
FlxG.camera.followEnabled = false;
|
||||
eventsTween.set("cameraMovement", FlxTween.tween(FlxG.camera.scroll, {x: camFollow.x - FlxG.camera.width * 0.5, y: camFollow.y - FlxG.camera.height * 0.5},
|
||||
(Conductor.stepCrochet / 1000) * (event.params[2] == null ? 4 : event.params[2]), {
|
||||
ease: CoolUtil.flxeaseFromString(event.params[3], event.params[4]),
|
||||
onComplete: (_) -> FlxG.camera.followEnabled = oldFollow
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
case "Camera Position":
|
||||
var tween = eventsTween.get("cameraMovement");
|
||||
if (tween != null) {
|
||||
if (tween.onComplete != null) tween.onComplete(tween);
|
||||
tween.cancel();
|
||||
}
|
||||
|
||||
curCameraTarget = -1;
|
||||
var isOffset = event.params[6] == true;
|
||||
camFollow.setPosition(isOffset ? (camFollow.x + event.params[0]) : event.params[0], isOffset ? (camFollow.y + event.params[1]) : event.params[1]);
|
||||
|
||||
if (event.params[2] == false) FlxG.camera.snapToTarget();
|
||||
else if (event.params[4] != null && event.params[4] != "CLASSIC") {
|
||||
var oldFollow = FlxG.camera.followEnabled;
|
||||
FlxG.camera.followEnabled = false;
|
||||
eventsTween.set("cameraMovement", FlxTween.tween(FlxG.camera.scroll, {x: camFollow.x - FlxG.camera.width * 0.5, y: camFollow.y - FlxG.camera.height * 0.5},
|
||||
(Conductor.stepCrochet / 1000) * (event.params[3] == null ? 4 : event.params[3]), {
|
||||
ease: CoolUtil.flxeaseFromString(event.params[4], event.params[5]),
|
||||
onComplete: (_) -> FlxG.camera.followEnabled = oldFollow
|
||||
})
|
||||
);
|
||||
}
|
||||
case "Add Camera Zoom":
|
||||
var camera:FlxCamera = event.params[1] == "camHUD" ? camHUD : camGame;
|
||||
camera.zoom += event.params[0];
|
||||
case "Camera Zoom":
|
||||
var cam = event.params[2] == "camHUD" ? camHUD : camGame;
|
||||
var name = (event.params[2] == "camHUD" ? "camHUD" : "camGame") + ".zoom"; // avoiding having different values from these 2 - Nex
|
||||
var tween = eventsTween.get(name);
|
||||
if (tween != null) tween.cancel();
|
||||
|
||||
var finalZoom:Float = event.params[1] * (event.params[6] == 'direct' ? FlxCamera.defaultZoom : stage.defaultZoom);
|
||||
if (event.params[7] == true) finalZoom *= cam.zoom;
|
||||
|
||||
if (event.params[0] == false) {
|
||||
cam.zoom = finalZoom;
|
||||
if (cam == camHUD) defaultHudZoom = finalZoom;
|
||||
else defaultCamZoom = finalZoom;
|
||||
} else
|
||||
eventsTween.set(name, FlxTween.tween(cam, {zoom: finalZoom}, (Conductor.stepCrochet / 1000) * event.params[3], {ease: CoolUtil.flxeaseFromString(event.params[4], event.params[5]), onUpdate: function(_) {
|
||||
if (cam == camHUD) defaultHudZoom = cam.zoom;
|
||||
else defaultCamZoom = cam.zoom;
|
||||
}}));
|
||||
case "Camera Modulo Change":
|
||||
camZoomingInterval = event.params[0];
|
||||
camZoomingStrength = event.params[1];
|
||||
@@ -1431,12 +1548,16 @@ class PlayState extends MusicBeatState
|
||||
camera.flash(event.params[1], (Conductor.stepCrochet / 1000) * event.params[2], null, true);
|
||||
case "BPM Change": // automatically handled by conductor
|
||||
case "Scroll Speed Change":
|
||||
if (scrollSpeedTween != null) scrollSpeedTween.cancel();
|
||||
var tween = eventsTween.get("scrollSpeedTween");
|
||||
if (tween != null) tween.cancel();
|
||||
|
||||
var finalScroll:Float = event.params[1];
|
||||
if (event.params[5] == true) finalScroll *= scrollSpeed;
|
||||
|
||||
if (event.params[0] == false)
|
||||
scrollSpeed = event.params[1];
|
||||
scrollSpeed = finalScroll;
|
||||
else
|
||||
scrollSpeedTween = FlxTween.tween(this, {scrollSpeed: event.params[1]}, (Conductor.stepCrochet / 1000) * event.params[2], {ease: CoolUtil.flxeaseFromString(event.params[3], event.params[4])});
|
||||
eventsTween.set("scrollSpeedTween", FlxTween.tween(this, {scrollSpeed: finalScroll}, (Conductor.stepCrochet / 1000) * event.params[2], {ease: CoolUtil.flxeaseFromString(event.params[3], event.params[4])}));
|
||||
case "Alt Animation Toggle":
|
||||
var strLine = strumLines.members[event.params[2]];
|
||||
if (strLine != null) {
|
||||
@@ -1451,7 +1572,7 @@ class PlayState extends MusicBeatState
|
||||
case "Play Animation":
|
||||
if (strumLines.members[event.params[0]] != null && strumLines.members[event.params[0]].characters != null)
|
||||
for (char in strumLines.members[event.params[0]].characters)
|
||||
if (char != null) char.playAnim(event.params[1], event.params[2], null);
|
||||
if (char != null) char.playAnim(event.params[1], event.params[2], event.params[3] == "NONE" ? null : event.params[3]);
|
||||
case "Unknown": // nothing
|
||||
}
|
||||
}
|
||||
@@ -1506,6 +1627,7 @@ class PlayState extends MusicBeatState
|
||||
*/
|
||||
public function endSong():Void
|
||||
{
|
||||
endingSong = true;
|
||||
gameAndCharsCall("onSongEnd");
|
||||
canPause = false;
|
||||
inst.volume = 0;
|
||||
@@ -1546,8 +1668,6 @@ class PlayState extends MusicBeatState
|
||||
* Immediately switches to the next song, or goes back to the Story/Freeplay menu.
|
||||
*/
|
||||
public function nextSong() {
|
||||
resetSongInfos();
|
||||
|
||||
if (isStoryMode)
|
||||
{
|
||||
campaignScore += songScore;
|
||||
@@ -1581,7 +1701,7 @@ class PlayState extends MusicBeatState
|
||||
|
||||
FlxG.sound.music.stop();
|
||||
|
||||
__loadSong(storyPlaylist[0].toLowerCase(), difficulty);
|
||||
__loadSong(storyPlaylist[0], difficulty);
|
||||
|
||||
FlxG.switchState(new PlayState());
|
||||
}
|
||||
@@ -1711,9 +1831,9 @@ class PlayState extends MusicBeatState
|
||||
|
||||
var event:NoteHitEvent;
|
||||
if (strumLine != null && !strumLine.cpu)
|
||||
event = EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, true, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, score, note.isSustainNote ? null : accuracy, 0.023, daRating, Options.splashesEnabled && !note.isSustainNote && daRating == "sick");
|
||||
event = EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, true, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, score, note.isSustainNote ? null : accuracy, 0.023, daRating, Options.splashesEnabled && !note.isSustainNote && daRating == "sick", 0.5, true, 0.7, true, true, iconP1);
|
||||
else
|
||||
event = EventManager.get(NoteHitEvent).recycle(false, false, false, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, false, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, 0, null, 0, daRating, false);
|
||||
event = EventManager.get(NoteHitEvent).recycle(false, false, false, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, false, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, 0, null, 0, daRating, false, 0.5, true, 0.7, true, true, iconP2);
|
||||
event.deleteNote = !note.isSustainNote; // work around, to allow sustain notes to be deleted
|
||||
event = scripts.event(strumLine != null && !strumLine.cpu ? "onPlayerHit" : "onDadHit", event);
|
||||
strumLine.onHit.dispatch(event);
|
||||
@@ -1876,14 +1996,9 @@ class PlayState extends MusicBeatState
|
||||
}
|
||||
|
||||
if (doIconBop)
|
||||
{
|
||||
var iconScale = Flags.BOP_ICON_SCALE;
|
||||
iconP1.scale.set(iconScale, iconScale);
|
||||
iconP2.scale.set(iconScale, iconScale);
|
||||
|
||||
iconP1.updateHitbox();
|
||||
iconP2.updateHitbox();
|
||||
}
|
||||
for (icon in iconArray)
|
||||
if (icon.bump != null)
|
||||
icon.bump();
|
||||
|
||||
scripts.call("beatHit", [curBeat]);
|
||||
}
|
||||
@@ -1974,7 +2089,7 @@ class PlayState extends MusicBeatState
|
||||
public static function loadWeek(weekData:WeekData, ?difficulty:String) {
|
||||
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
|
||||
storyWeek = weekData;
|
||||
storyPlaylist = [for(e in weekData.songs) e.name];
|
||||
storyPlaylist = [for (e in weekData.songs) e.name];
|
||||
isStoryMode = true;
|
||||
campaignScore = 0;
|
||||
campaignMisses = 0;
|
||||
@@ -2008,6 +2123,8 @@ class PlayState extends MusicBeatState
|
||||
*/
|
||||
public static function __loadSong(_name:String, _difficulty:String) {
|
||||
difficulty = _difficulty;
|
||||
seenCutscene = false;
|
||||
deathCounter = 0;
|
||||
|
||||
SONG = Chart.parse(_name, _difficulty);
|
||||
fromMods = SONG.fromMods;
|
||||
@@ -2018,8 +2135,10 @@ final class ComboRating {
|
||||
public var percent:Float;
|
||||
public var rating:String;
|
||||
public var color:FlxColor;
|
||||
public var maxMisses:Float; // Float since it could be Math.POSITIVE_INFINITY - Nex
|
||||
|
||||
public function new(percent:Float, rating:String, color:FlxColor) {
|
||||
public function new(?percent:Float, ?rating:String, ?color:FlxColor, ?misses:Float) {
|
||||
maxMisses = misses == null || Math.isNaN(misses) ? Math.POSITIVE_INFINITY : misses;
|
||||
this.percent = percent;
|
||||
this.rating = rating;
|
||||
this.color = color;
|
||||
@@ -2034,3 +2153,28 @@ typedef PlayStateTransitionData = {
|
||||
var camFollowY:Float;
|
||||
var camZoom:Float;
|
||||
}
|
||||
|
||||
class CamPosData {
|
||||
/**
|
||||
* The camera position.
|
||||
**/
|
||||
public var pos:FlxPoint;
|
||||
/**
|
||||
* The amount of characters that was involved in the calculation.
|
||||
**/
|
||||
public var amount:Int;
|
||||
|
||||
public function new(pos:FlxPoint, amount:Int) {
|
||||
this.pos = pos;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the position back into the pool, making it reusable.
|
||||
**/
|
||||
public function put() {
|
||||
if(pos == null) return;
|
||||
pos.put();
|
||||
pos = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package funkin.game;
|
||||
|
||||
class Splash extends FunkinSprite
|
||||
{
|
||||
/**
|
||||
* The current splash strum
|
||||
* WARNING: It can be null
|
||||
*/
|
||||
public var strum:Null<Strum>;
|
||||
|
||||
/**
|
||||
* Shortcut to `strum.ID`
|
||||
* WARNING: It can be null
|
||||
*/
|
||||
public var strumID:Null<Int>;
|
||||
|
||||
public static function copyFrom(source:Splash):Splash
|
||||
{
|
||||
var spr = new Splash();
|
||||
@:privateAccess {
|
||||
spr.setPosition(source.x, source.y);
|
||||
spr.frames = source.frames;
|
||||
if (source.animateAtlas != null && source.atlasPath != null)
|
||||
spr.loadSprite(source.atlasPath);
|
||||
spr.animation.copyFrom(source.animation);
|
||||
spr.visible = source.visible;
|
||||
spr.alpha = source.alpha;
|
||||
spr.antialiasing = source.antialiasing;
|
||||
spr.scale.set(source.scale.x, source.scale.y);
|
||||
spr.scrollFactor.set(source.scrollFactor.x, source.scrollFactor.y);
|
||||
spr.skew.set(source.skew.x, source.skew.y);
|
||||
spr.transformMatrix = source.transformMatrix;
|
||||
spr.matrixExposed = source.matrixExposed;
|
||||
spr.zoomFactor = source.zoomFactor;
|
||||
spr.animOffsets = source.animOffsets.copy();
|
||||
}
|
||||
return spr;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package funkin.game;
|
||||
|
||||
import haxe.xml.Access;
|
||||
|
||||
class SplashGroup extends FlxTypedGroup<FunkinSprite> {
|
||||
class SplashGroup extends FlxTypedGroup<Splash> {
|
||||
/**
|
||||
* Whenever the splash group has successfully loaded or not.
|
||||
*/
|
||||
@@ -51,7 +51,7 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> {
|
||||
var _antialiasing:Bool = true;
|
||||
|
||||
function createSplash(imagePath:String) {
|
||||
var splash = new FunkinSprite();
|
||||
var splash = new Splash();
|
||||
splash.antialiasing = true;
|
||||
splash.active = splash.visible = false;
|
||||
splash.loadSprite(Paths.image(imagePath));
|
||||
@@ -64,7 +64,7 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> {
|
||||
return splash;
|
||||
}
|
||||
|
||||
function setupAnims(xml:Access, splash:FunkinSprite) {
|
||||
function setupAnims(xml:Access, splash:Splash) {
|
||||
for(strum in xml.nodes.strum) {
|
||||
var id:Null<Int> = Std.parseInt(strum.att.id);
|
||||
if (id != null) {
|
||||
@@ -93,12 +93,14 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> {
|
||||
};
|
||||
}
|
||||
|
||||
function pregenerateSplashes(splash:FunkinSprite) {
|
||||
function pregenerateSplashes(splash:Splash) {
|
||||
// make 7 additional splashes
|
||||
for(i in 0...7) {
|
||||
var spr = FunkinSprite.copyFrom(splash);
|
||||
var spr = Splash.copyFrom(splash);
|
||||
spr.animation.finishCallback = function(name:String) {
|
||||
spr.active = spr.visible = false;
|
||||
spr.strum = null;
|
||||
spr.strumID = null;
|
||||
};
|
||||
add(spr);
|
||||
}
|
||||
@@ -112,11 +114,14 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> {
|
||||
return animNames[FlxG.random.int(0, animNames.length - 1)];
|
||||
}
|
||||
|
||||
var __splash:FunkinSprite;
|
||||
var __splash:Splash;
|
||||
public function showOnStrum(strum:Strum) {
|
||||
if (!valid) return null;
|
||||
__splash = recycle();
|
||||
|
||||
__splash.strum = strum;
|
||||
__splash.strumID = strum.ID;
|
||||
|
||||
@:privateAccess
|
||||
__splash.scale.x = __splash.scale.y = _scale * strum.strumLine.strumScale;
|
||||
__splash.alpha = _alpha;
|
||||
|
||||
@@ -2,7 +2,7 @@ package funkin.game;
|
||||
|
||||
import funkin.backend.scripting.events.splash.*;
|
||||
|
||||
class SplashHandler extends FlxTypedGroup<FunkinSprite> {
|
||||
class SplashHandler extends FlxTypedGroup<Splash> {
|
||||
/**
|
||||
* Map containing all of the splashes group.
|
||||
*/
|
||||
|
||||
@@ -130,18 +130,19 @@ class Stage extends FlxBasic implements IBeatReceiver {
|
||||
case "box" | "solid":
|
||||
if (!node.has.name || !node.has.width || !node.has.height) continue;
|
||||
|
||||
var spr = new FunkinSprite(
|
||||
(node.has.x) ? Std.parseFloat(node.att.x).getDefault(0) : 0,
|
||||
(node.has.y) ? Std.parseFloat(node.att.y).getDefault(0) : 0
|
||||
);
|
||||
spr.name = node.getAtt("name");
|
||||
var isSolid = node.name == "solid";
|
||||
|
||||
(node.name == "solid" ? spr.makeSolid : spr.makeGraphic)(
|
||||
var spr = new FunkinSprite();
|
||||
(isSolid ? spr.makeSolid : spr.makeGraphic)(
|
||||
Std.parseInt(node.att.width),
|
||||
Std.parseInt(node.att.height),
|
||||
(node.has.color) ? CoolUtil.getColorFromDynamic(node.att.color) : -1
|
||||
);
|
||||
|
||||
if (isSolid) node.x.remove("updateHitbox");
|
||||
for (a in ["width", "height", "color"]) node.x.remove(a);
|
||||
XMLUtil.loadSpriteFromXML(spr, node, "", NONE, false);
|
||||
|
||||
stageSprites.set(spr.name, spr);
|
||||
addSprite(spr);
|
||||
case "boyfriend" | "bf" | "player":
|
||||
@@ -353,6 +354,11 @@ class Stage extends FlxBasic implements IBeatReceiver {
|
||||
|
||||
public function measureHit(curMeasure:Int) {}
|
||||
|
||||
public override function destroy() {
|
||||
startCam.put();
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of stages that are available to be used.
|
||||
* @param mods Whenever only the mods folder should be checked
|
||||
@@ -469,4 +475,4 @@ typedef OldCharInfo = {
|
||||
var alpha:Float;
|
||||
var zoomFactor:Float;
|
||||
var angle:Float;
|
||||
}
|
||||
}
|
||||
|
||||
+58
-32
@@ -42,6 +42,18 @@ class Strum extends FlxSprite {
|
||||
|
||||
public var lastDrawCameras(default, null):Array<FlxCamera> = [];
|
||||
|
||||
// Copy fields
|
||||
public var copyStrumCamera:Bool = true;
|
||||
public var copyStrumScrollX:Bool = true;
|
||||
public var copyStrumScrollY:Bool = true;
|
||||
public var copyStrumAngle:Bool = true;
|
||||
public var updateNotesPosX:Bool = true;
|
||||
public var updateNotesPosY:Bool = true;
|
||||
public var extraCopyFields(default, set):Array<String> = [];
|
||||
|
||||
private inline function set_extraCopyFields(val:Array<String>)
|
||||
return extraCopyFields = val == null ? [] : val;
|
||||
|
||||
/**
|
||||
* Whenever the strum is pressed.
|
||||
**/
|
||||
@@ -130,47 +142,61 @@ class Strum extends FlxSprite {
|
||||
public function updateNotePosition(daNote:Note) {
|
||||
if (!daNote.exists) return;
|
||||
|
||||
daNote.__strumCameras = lastDrawCameras;
|
||||
daNote.__strum = this;
|
||||
daNote.scrollFactor.set(scrollFactor.x, scrollFactor.y);
|
||||
daNote.__noteAngle = getNotesAngle(daNote);
|
||||
daNote.angle = daNote.isSustainNote ? daNote.__noteAngle : angle;
|
||||
if (copyStrumCamera) daNote.__strumCameras = lastDrawCameras;
|
||||
if (copyStrumScrollX) daNote.scrollFactor.x = scrollFactor.x;
|
||||
if (copyStrumScrollY) daNote.scrollFactor.y = scrollFactor.y;
|
||||
if (copyStrumAngle && daNote.copyStrumAngle) {
|
||||
daNote.__noteAngle = getNotesAngle(daNote);
|
||||
daNote.angle = daNote.isSustainNote ? daNote.__noteAngle : angle;
|
||||
}
|
||||
|
||||
updateNotePos(daNote);
|
||||
for (field in extraCopyFields)
|
||||
CoolUtil.cloneProperty(daNote, field, this); // TODO: make this cached to reduce the reflection calls - Neo
|
||||
}
|
||||
|
||||
private inline function updateNotePos(daNote:Note) {
|
||||
if (daNote.strumRelativePos) {
|
||||
daNote.setPosition((this.width - daNote.width) / 2, (daNote.strumTime - Conductor.songPosition) * (0.45 * CoolUtil.quantize(getScrollSpeed(daNote), 100)));
|
||||
if (daNote.isSustainNote) daNote.y += N_WIDTHDIV2 * (height / Note.swagWidth);
|
||||
} else {
|
||||
var offset = FlxPoint.get(0, (Conductor.songPosition - daNote.strumTime) * (0.45 * CoolUtil.quantize(getScrollSpeed(daNote), 100)));
|
||||
var realOffset = FlxPoint.get(0, 0);
|
||||
var shouldX = updateNotesPosX && daNote.updateNotesPosX;
|
||||
var shouldY = updateNotesPosY && daNote.updateNotesPosY;
|
||||
|
||||
if (daNote.isSustainNote) offset.y -= N_WIDTHDIV2 * (height / Note.swagWidth);
|
||||
|
||||
if (Std.int(daNote.__noteAngle % 360) != 0) {
|
||||
var noteAngleCos = FlxMath.fastCos(daNote.__noteAngle / PIX180);
|
||||
var noteAngleSin = FlxMath.fastSin(daNote.__noteAngle / PIX180);
|
||||
|
||||
var aOffset:FlxPoint = FlxPoint.get(
|
||||
(daNote.origin.x / daNote.scale.x) - daNote.offset.x,
|
||||
(daNote.origin.y / daNote.scale.y) - daNote.offset.y
|
||||
);
|
||||
realOffset.x = -aOffset.x + (noteAngleCos * (offset.x + aOffset.x)) + (noteAngleSin * (offset.y + aOffset.y));
|
||||
realOffset.y = -aOffset.y + (noteAngleSin * (offset.x + aOffset.x)) + (noteAngleCos * (offset.y + aOffset.y));
|
||||
|
||||
aOffset.put();
|
||||
if (shouldX || shouldY) {
|
||||
if (daNote.strumRelativePos) {
|
||||
if (shouldX) daNote.x = (this.width - daNote.width) / 2;
|
||||
if (shouldY) {
|
||||
daNote.y = (daNote.strumTime - Conductor.songPosition) * (0.45 * CoolUtil.quantize(getScrollSpeed(daNote), 100));
|
||||
if (daNote.isSustainNote) daNote.y += N_WIDTHDIV2;
|
||||
}
|
||||
} else {
|
||||
realOffset.x = offset.x;
|
||||
realOffset.y = offset.y;
|
||||
var offset = FlxPoint.get(0, (Conductor.songPosition - daNote.strumTime) * (0.45 * CoolUtil.quantize(getScrollSpeed(daNote), 100)));
|
||||
var realOffset = FlxPoint.get(0, 0);
|
||||
|
||||
if (daNote.isSustainNote) offset.y -= N_WIDTHDIV2;
|
||||
|
||||
if (Std.int(daNote.__noteAngle % 360) != 0) {
|
||||
var noteAngleCos = FlxMath.fastCos(daNote.__noteAngle / PIX180);
|
||||
var noteAngleSin = FlxMath.fastSin(daNote.__noteAngle / PIX180);
|
||||
|
||||
var aOffset:FlxPoint = FlxPoint.get(
|
||||
(daNote.origin.x / daNote.scale.x) - daNote.offset.x,
|
||||
(daNote.origin.y / daNote.scale.y) - daNote.offset.y
|
||||
);
|
||||
realOffset.x = -aOffset.x + (noteAngleCos * (offset.x + aOffset.x)) + (noteAngleSin * (offset.y + aOffset.y));
|
||||
realOffset.y = -aOffset.y + (noteAngleSin * (offset.x + aOffset.x)) + (noteAngleCos * (offset.y + aOffset.y));
|
||||
|
||||
aOffset.put();
|
||||
} else {
|
||||
realOffset.x = offset.x;
|
||||
realOffset.y = offset.y;
|
||||
}
|
||||
realOffset.y *= -1;
|
||||
|
||||
if (shouldX) daNote.x = x + realOffset.x;
|
||||
if (shouldY) daNote.y = y + realOffset.y;
|
||||
|
||||
offset.put();
|
||||
realOffset.put();
|
||||
}
|
||||
realOffset.y *= -1;
|
||||
|
||||
daNote.setPosition(x + realOffset.x, y + realOffset.y);
|
||||
|
||||
offset.put();
|
||||
realOffset.put();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -221,7 +221,7 @@ class StrumLine extends FlxTypedGroup<Strum> {
|
||||
|
||||
if (cpu && __updateNote_event.__autoCPUHit && !daNote.avoid && !daNote.wasGoodHit && daNote.strumTime < __updateNote_songPos) PlayState.instance.goodNoteHit(this, daNote);
|
||||
|
||||
if (daNote.wasGoodHit && daNote.isSustainNote && daNote.strumTime + (daNote.sustainLength) < __updateNote_songPos) {
|
||||
if (daNote.wasGoodHit && daNote.isSustainNote && daNote.strumTime + daNote.sustainLength < __updateNote_songPos) {
|
||||
deleteNote(daNote);
|
||||
return;
|
||||
}
|
||||
@@ -247,8 +247,9 @@ class StrumLine extends FlxTypedGroup<Strum> {
|
||||
var __notePerStrum:Array<Note> = [];
|
||||
|
||||
function __inputProcessPressed(note:Note) {
|
||||
if (__pressed[note.strumID] && note.isSustainNote && note.canBeHit && !note.wasGoodHit) {
|
||||
if (__pressed[note.strumID] && note.isSustainNote && note.strumTime < __updateNote_songPos && !note.wasGoodHit) {
|
||||
PlayState.instance.goodNoteHit(this, note);
|
||||
note.updateSustainClip();
|
||||
}
|
||||
}
|
||||
function __inputProcessJustPressed(note:Note) {
|
||||
@@ -347,8 +348,10 @@ class StrumLine extends FlxTypedGroup<Strum> {
|
||||
* Creates a strum and returns the created strum (needs to be added manually).
|
||||
* @param i Index of the strum
|
||||
* @param animPrefix (Optional) Animation prefix (`left` = `arrowLEFT`, `left press`, `left confirm`).
|
||||
* @param spritePath (Optional) The sprite's graphic path if you don't want the default one.
|
||||
* @param playIntroAnimation (Optional) Whenever the intro animation should be played, by default might be `true` under certain conditions.
|
||||
*/
|
||||
public function createStrum(i:Int, ?animPrefix:String) {
|
||||
public function createStrum(i:Int, ?animPrefix:String, ?spritePath:String, ?playIntroAnimation:Bool) {
|
||||
if (animPrefix == null)
|
||||
animPrefix = strumAnimPrefix[i % strumAnimPrefix.length];
|
||||
var babyArrow:Strum = new Strum(startingPos.x + (Note.swagWidth * strumScale * (data.strumSpacing != null ? data.strumSpacing : 1) * i), startingPos.y + (Note.swagWidth*0.5) - (Note.swagWidth * strumScale * 0.5));
|
||||
@@ -359,7 +362,8 @@ class StrumLine extends FlxTypedGroup<Strum> {
|
||||
babyArrow.scrollSpeed = data.scrollSpeed;
|
||||
|
||||
var event = EventManager.get(StrumCreationEvent).recycle(babyArrow, PlayState.instance.strumLines.members.indexOf(this), i, animPrefix);
|
||||
event.__doAnimation = !MusicBeatState.skipTransIn && (PlayState.instance != null ? PlayState.instance.introLength > 0 : true);
|
||||
event.__doAnimation = playIntroAnimation == null ? (!MusicBeatState.skipTransIn && (PlayState.instance != null ? PlayState.instance.introLength > 0 : true)) : playIntroAnimation;
|
||||
if (spritePath != null) event.sprite = spritePath;
|
||||
if (PlayState.instance != null) event = PlayState.instance.gameAndCharsEvent("onStrumCreation", event);
|
||||
|
||||
if (!event.cancelled) {
|
||||
|
||||
@@ -8,11 +8,11 @@ import funkin.menus.PauseSubState;
|
||||
*/
|
||||
class Cutscene extends MusicBeatSubstate {
|
||||
var __callback:Void->Void;
|
||||
var __pausable:Bool;
|
||||
var game:PlayState = PlayState.instance;
|
||||
|
||||
public var paused:Bool = false;
|
||||
public var pauseItems:Array<String>;
|
||||
public var pausable:Bool;
|
||||
public var skippable(default, set):Bool;
|
||||
|
||||
public function set_skippable(val:Bool):Bool {
|
||||
@@ -26,9 +26,9 @@ class Cutscene extends MusicBeatSubstate {
|
||||
canOpenCustomTransition = true;
|
||||
|
||||
__callback = callback;
|
||||
__pausable = allowPause;
|
||||
|
||||
pauseItems = Flags.DEFAULT_CUTSCENE_PAUSE_ITEMS;
|
||||
pausable = allowPause;
|
||||
skippable = canSkip;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class Cutscene extends MusicBeatSubstate {
|
||||
}
|
||||
|
||||
public function pauseCheck():Bool
|
||||
return controls.PAUSE && __pausable;
|
||||
return controls.PAUSE && pausable;
|
||||
|
||||
var _before:Array<Bool> = [false, true];
|
||||
public function pauseCutscene() {
|
||||
|
||||
@@ -92,7 +92,7 @@ class DialogueCutscene extends ScriptedCutscene {
|
||||
callback: node.getAtt('callback'),
|
||||
changeDefAnim: node.getAtt('changeDefAnim'),
|
||||
speed: Std.parseFloat(node.getAtt("speed")).getDefault(0.05),
|
||||
musicVolume: node.has.musicVolume ? (volume = Std.parseFloat(node.att.speed).getDefault(0.8)) : null,
|
||||
musicVolume: node.has.musicVolume ? (volume = Std.parseFloat(node.att.musicVolume).getDefault(0.8)) : null,
|
||||
changeMusic: node.has.changeMusic ? FlxG.sound.load(Paths.music(node.att.changeMusic), volume, true) : null,
|
||||
playSound: node.has.playSound ? FlxG.sound.load(Paths.sound(node.att.playSound)) : null,
|
||||
nextSound: node.has.nextSound ? FlxG.sound.load(Paths.sound(node.att.nextSound)) : null,
|
||||
@@ -200,7 +200,7 @@ class DialogueCutscene extends ScriptedCutscene {
|
||||
if (curMusic != null && !curMusic.persist) curMusic.destroy();
|
||||
|
||||
super.destroy();
|
||||
cutscene = null;
|
||||
if (cutscene == this) cutscene = null;
|
||||
FlxG.cameras.remove(dialogueCamera);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ class VideoCutscene extends Cutscene {
|
||||
text.destroy();
|
||||
}});
|
||||
|
||||
__pausable = __loaded = true;
|
||||
pausable = __loaded = true;
|
||||
video.play();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import flixel.tweens.FlxEase;
|
||||
import flixel.util.FlxDestroyUtil;
|
||||
|
||||
import funkin.backend.system.Flags;
|
||||
import funkin.Types;
|
||||
|
||||
import funkin.menus.ui.Alphabet;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package funkin.menus;
|
||||
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.backend.FunkinText;
|
||||
|
||||
class BetaWarningState extends MusicBeatState {
|
||||
var titleAlphabet:Alphabet;
|
||||
var disclaimer:FunkinText;
|
||||
|
||||
var transitioning:Bool = false;
|
||||
|
||||
public override function create() {
|
||||
super.create();
|
||||
|
||||
titleAlphabet = new Alphabet(0, 0, "WARNING", true);
|
||||
titleAlphabet.screenCenter(X);
|
||||
add(titleAlphabet);
|
||||
|
||||
disclaimer = new FunkinText(16, titleAlphabet.y + titleAlphabet.height + 10, FlxG.width - 32, "", 32);
|
||||
disclaimer.alignment = CENTER;
|
||||
disclaimer.applyMarkup('This engine is still in a *${Flags.RELEASE_CYCLE}* state. That means *majority of the features* are either *buggy* or *non finished*. If you find any bugs, please report them to the Codename Engine GitHub.\n\nPress ENTER to continue',
|
||||
[
|
||||
new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFFF4444), "*")
|
||||
]
|
||||
);
|
||||
add(disclaimer);
|
||||
|
||||
var off = Std.int((FlxG.height - (disclaimer.y + disclaimer.height)) / 2);
|
||||
disclaimer.y += off;
|
||||
titleAlphabet.y += off;
|
||||
|
||||
DiscordUtil.call("onMenuLoaded", ["Beta Warning"]);
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float) {
|
||||
super.update(elapsed);
|
||||
|
||||
if (controls.ACCEPT && transitioning) {
|
||||
FlxG.camera.stopFX(); FlxG.camera.visible = false;
|
||||
goToTitle();
|
||||
}
|
||||
|
||||
if (controls.ACCEPT && !transitioning) {
|
||||
transitioning = true;
|
||||
CoolUtil.playMenuSFX(CONFIRM);
|
||||
FlxG.camera.flash(FlxColor.WHITE, 1, function() {
|
||||
FlxG.camera.fade(FlxColor.BLACK, 2.5, false, goToTitle);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private function goToTitle() {
|
||||
MusicBeatState.skipTransIn = MusicBeatState.skipTransOut = true;
|
||||
FlxG.switchState(new TitleState());
|
||||
}
|
||||
}
|
||||
@@ -139,6 +139,8 @@ class FreeplayState extends MusicBeatState
|
||||
|
||||
var icon:HealthIcon = new HealthIcon(songs[i].icon);
|
||||
icon.sprTracker = songText;
|
||||
icon.setUnstretchedGraphicSize(150, 150, true);
|
||||
icon.updateHitbox();
|
||||
|
||||
// using a FlxGroup is too much fuss!
|
||||
iconArray.push(icon);
|
||||
@@ -234,7 +236,7 @@ class FreeplayState extends MusicBeatState
|
||||
scoreText.x = coopText.x = scoreBG.x + 4;
|
||||
diffText.x = Std.int(scoreBG.x + ((scoreBG.width - diffText.width) / 2));
|
||||
|
||||
interpColor.fpsLerpTo(songs[curSelected].parsedColor, 0.0625);
|
||||
interpColor.fpsLerpTo(songs[curSelected].color, 0.0625);
|
||||
bg.color = interpColor.color;
|
||||
|
||||
#if PRELOAD_ALL
|
||||
@@ -320,10 +322,9 @@ class FreeplayState extends MusicBeatState
|
||||
}
|
||||
|
||||
public function convertChart() {
|
||||
var curSong = songs[curSelected];
|
||||
trace('Converting ${curSong.name} (${curSong.difficulties[curDifficulty]}) to Codename format...');
|
||||
var chart = Chart.parse(curSong.name, curSong.difficulties[curDifficulty]);
|
||||
Chart.save('${Main.pathBack}assets/songs/${curSong.name}', chart, curSong.difficulties[curDifficulty].toLowerCase());
|
||||
trace('Converting ${songs[curSelected].name} (${songs[curSelected].difficulties[curDifficulty]}) to Codename format...');
|
||||
var chart = Chart.parse(songs[curSelected].name, songs[curSelected].difficulties[curDifficulty]);
|
||||
Chart.save('${Main.pathBack}assets/songs/${songs[curSelected].name}', chart, songs[curSelected].difficulties[curDifficulty]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -346,9 +347,9 @@ class FreeplayState extends MusicBeatState
|
||||
updateScore();
|
||||
|
||||
if (curSong.difficulties.length > 1)
|
||||
diffText.text = '< ${curSong.difficulties[curDifficulty]} >';
|
||||
diffText.text = '< ${curSong.difficulties[curDifficulty].toUpperCase()} >';
|
||||
else
|
||||
diffText.text = validDifficulties ? curSong.difficulties[curDifficulty] : "-";
|
||||
diffText.text = validDifficulties ? curSong.difficulties[curDifficulty].toUpperCase() : "-";
|
||||
}
|
||||
|
||||
function updateScore() {
|
||||
@@ -453,16 +454,10 @@ class FreeplaySonglist {
|
||||
|
||||
public function getSongsFromSource(source:funkin.backend.assets.AssetsLibraryList.AssetSource, useTxt:Bool = true) {
|
||||
var path:String = Paths.txt('freeplaySonglist');
|
||||
var songsFound:Array<String> = [];
|
||||
if (useTxt && Paths.assetsTree.existsSpecific(path, "TEXT", source)) {
|
||||
songsFound = CoolUtil.coolTextFile(Paths.txt('freeplaySonglist'));
|
||||
} else {
|
||||
songsFound = Paths.getFolderDirectories('songs', false, source);
|
||||
}
|
||||
var songsFound:Array<String> = useTxt && Paths.assetsTree.existsSpecific(path, "TEXT", source) ? CoolUtil.coolTextFile(path) : Paths.getFolderDirectories('songs', false, source);
|
||||
|
||||
if (songsFound.length > 0) {
|
||||
for(s in songsFound)
|
||||
songs.push(Chart.loadChartMeta(s, Flags.DEFAULT_DIFFICULTY, source == MODS));
|
||||
for (s in songsFound) songs.push(Chart.loadChartMeta(s, Flags.DEFAULT_DIFFICULTY, source == MODS));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -485,4 +480,4 @@ class FreeplaySonglist {
|
||||
|
||||
return songList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,9 +64,7 @@ class GitarooPause extends MusicBeatState
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayState.resetSongInfos();
|
||||
if (Charter.instance != null) Charter.instance.__clearStatics();
|
||||
|
||||
FlxG.switchState(new MainMenuState());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ class MainMenuState extends MusicBeatState
|
||||
if (!selectedSomethin)
|
||||
{
|
||||
if (canAccessDebugMenus) {
|
||||
if (FlxG.keys.justPressed.SEVEN) {
|
||||
if (controls.DEV_ACCESS) {
|
||||
persistentUpdate = false;
|
||||
persistentDraw = true;
|
||||
openSubState(new funkin.editors.EditorPicker());
|
||||
|
||||
@@ -12,9 +12,15 @@ class ModSwitchMenu extends MusicBeatSubstate {
|
||||
var alphabets:FlxTypedGroup<Alphabet>;
|
||||
var curSelected:Int = 0;
|
||||
|
||||
var subCam:FlxCamera;
|
||||
|
||||
public override function create() {
|
||||
super.create();
|
||||
|
||||
camera = subCam = new FlxCamera();
|
||||
subCam.bgColor = 0;
|
||||
FlxG.cameras.add(subCam, false);
|
||||
|
||||
var bg = new FlxSprite(0, 0).makeSolid(FlxG.width, FlxG.height, 0xFF000000);
|
||||
bg.updateHitbox();
|
||||
bg.scrollFactor.set();
|
||||
@@ -66,5 +72,12 @@ class ModSwitchMenu extends MusicBeatSubstate {
|
||||
}
|
||||
alphabets.members[curSelected].alpha = 1;
|
||||
}
|
||||
|
||||
override function destroy() {
|
||||
super.destroy();
|
||||
|
||||
if (FlxG.cameras.list.contains(subCam))
|
||||
FlxG.cameras.remove(subCam);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
@@ -165,7 +165,6 @@ class PauseSubState extends MusicBeatSubstate
|
||||
if (PlayState.chartingMode && Charter.undos.unsaved)
|
||||
game.saveWarn(false);
|
||||
else {
|
||||
PlayState.resetSongInfos();
|
||||
if (Charter.instance != null) Charter.instance.__clearStatics();
|
||||
|
||||
// prevents certain notes to disappear early when exiting - Nex
|
||||
|
||||
@@ -5,17 +5,20 @@ import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
import funkin.backend.FunkinText;
|
||||
import funkin.backend.scripting.events.CancellableEvent;
|
||||
import funkin.backend.scripting.events.menu.MenuChangeEvent;
|
||||
import funkin.backend.scripting.events.menu.storymenu.*;
|
||||
import funkin.backend.scripting.events.CancellableEvent;
|
||||
import funkin.backend.week.*;
|
||||
import funkin.savedata.FunkinSave;
|
||||
import haxe.io.Path;
|
||||
import haxe.xml.Access;
|
||||
|
||||
class StoryMenuState extends MusicBeatState {
|
||||
public var characters:Map<String, Access> = [];
|
||||
public var characters:Map<String, WeekData.WeekCharacter> = [];
|
||||
public var weeks:Array<WeekData> = [];
|
||||
public var weekList:StoryWeeklist;
|
||||
|
||||
// yes it supports parameters - Nex
|
||||
public var scoreMessage:String = "WEEK SCORE:{0}";
|
||||
@@ -33,7 +36,6 @@ class StoryMenuState extends MusicBeatState {
|
||||
public var blackBar:FlxSprite;
|
||||
|
||||
public var weekBG:FlxSprite;
|
||||
public var defColor:String = "#F9CF51";
|
||||
public var interpColor:FlxInterpolateColor;
|
||||
|
||||
public var lerpScore:Float = 0;
|
||||
@@ -64,7 +66,7 @@ class StoryMenuState extends MusicBeatState {
|
||||
weekTitle.alpha = 0.7;
|
||||
|
||||
weekBG = new FlxSprite(0, 56).makeSolid(FlxG.width, 400, 0xFFFFFFFF);
|
||||
weekBG.color = weeks.length > 0 ? weeks[0].bgColor : FlxColor.fromString(defColor);
|
||||
weekBG.color = weeks.length > 0 ? weeks[0].bgColor : Flags.DEFAULT_WEEK_COLOR;
|
||||
weekBG.updateHitbox();
|
||||
|
||||
weekSprites = new FlxTypedGroup<MenuItem>();
|
||||
@@ -92,19 +94,18 @@ class StoryMenuState extends MusicBeatState {
|
||||
tracklist.color = 0xFFE55777;
|
||||
|
||||
add(weekSprites);
|
||||
for(e in [blackBar, scoreText, weekTitle, weekBG, tracklist]) {
|
||||
e.antialiasing = true;
|
||||
for (e in [blackBar, scoreText, weekTitle, weekBG, tracklist]) {
|
||||
e.scrollFactor.set();
|
||||
add(e);
|
||||
}
|
||||
|
||||
add(characterSprites = new FlxTypedGroup<FunkinSprite>());
|
||||
|
||||
for(i=>week in weeks) {
|
||||
for (i=>week in weeks) {
|
||||
var spr:MenuItem = new MenuItem(0, (i * 120) + 480, 'menus/storymenu/weeks/${week.sprite}');
|
||||
weekSprites.add(spr);
|
||||
|
||||
for(e in week.difficulties) {
|
||||
for (e in week.difficulties) {
|
||||
var le = e.toLowerCase();
|
||||
if (difficultySprites[le] == null) {
|
||||
var diffSprite = new FlxSprite(leftArrow.x + leftArrow.width, leftArrow.y);
|
||||
@@ -163,6 +164,12 @@ class StoryMenuState extends MusicBeatState {
|
||||
weekBG.color = interpColor.color;
|
||||
}
|
||||
|
||||
public override function beatHit(curBeat:Int) {
|
||||
super.beatHit(curBeat);
|
||||
if (characterSprites != null) // reason why I wanted to use a MusicBeatGroup, but eh, whatever - Nex
|
||||
characterSprites.forEachAlive(function(spr) spr.beatHit(curBeat));
|
||||
}
|
||||
|
||||
public function goBack() {
|
||||
var event = event("onGoBack", new CancellableEvent());
|
||||
if (!event.cancelled)
|
||||
@@ -181,13 +188,16 @@ class StoryMenuState extends MusicBeatState {
|
||||
e.targetY = k - curWeek;
|
||||
e.alpha = k == curWeek ? 1.0 : 0.6;
|
||||
}
|
||||
tracklist.text = 'TRACKS\n\n${[for(e in weeks[curWeek].songs) if (!e.hide) e.name].join('\n')}';
|
||||
tracklist.text = 'TRACKS\n\n${[for(e in weeks[curWeek].songs) if (!e.hide) e.displayName.getDefault(e.name).toUpperCase()].join('\n')}';
|
||||
weekTitle.text = weeks[curWeek].name.getDefault("");
|
||||
|
||||
if(characterSprites != null) for(i in 0...3) {
|
||||
var curChar:FunkinSprite; var newChar:Access = characters[weeks[curWeek].chars[i]];
|
||||
if(newChar == null) modifyCharacterAt(i);
|
||||
else if((curChar = characterSprites.members[i]) == null || newChar.getAtt("name") != curChar.name) modifyCharacterAt(i, newChar);
|
||||
if (characterSprites != null) for (i in 0...3) {
|
||||
var char = weeks[curWeek].chars[i];
|
||||
var curChar:FunkinSprite = null;
|
||||
var newChar = null;
|
||||
|
||||
if (char == null || (newChar = characters[char.name]) == null) modifyCharacterAt(i);
|
||||
else if ((curChar = cast characterSprites.members[i]) == null || newChar.name != curChar.name) modifyCharacterAt(i, newChar); // forcing the sprites to be FunkinSprite basically - Nex
|
||||
}
|
||||
|
||||
changeDifficulty(0, true);
|
||||
@@ -195,27 +205,6 @@ class StoryMenuState extends MusicBeatState {
|
||||
MemoryUtil.clearMinor();
|
||||
}
|
||||
|
||||
public function modifyCharacterAt(i:Int, ?node:Access):FunkinSprite {
|
||||
var old = characterSprites.members[i];
|
||||
if(old != null) {
|
||||
characterSprites.remove(old);
|
||||
old.destroy();
|
||||
}
|
||||
|
||||
if(node == null) return null;
|
||||
var curChar:FunkinSprite = XMLUtil.createSpriteFromXML(node, "", BEAT);
|
||||
curChar.offset.x += curChar.x; curChar.offset.y += curChar.y;
|
||||
curChar.setPosition((FlxG.width * 0.25) * (1 + i) - 150, 70);
|
||||
if(characterSprites != null) characterSprites.insert(i, curChar); // Making so many null checks abt this group just in case if mods destroy it - Nex
|
||||
curChar.playAnim("idle", true, DANCE);
|
||||
return curChar;
|
||||
}
|
||||
|
||||
public override function beatHit(curBeat:Int) {
|
||||
super.beatHit(curBeat);
|
||||
if(characterSprites != null) characterSprites.forEachAlive(function(spr) spr.beatHit(curBeat));
|
||||
}
|
||||
|
||||
var __oldDiffName = null;
|
||||
public function changeDifficulty(change:Int, force:Bool = false) {
|
||||
if (change == 0 && !force) return;
|
||||
@@ -244,122 +233,44 @@ class StoryMenuState extends MusicBeatState {
|
||||
}
|
||||
|
||||
public function loadXMLs() {
|
||||
// CoolUtil.coolTextFile(Paths.txt('freeplaySonglist'));
|
||||
var weeks:Array<String> = [];
|
||||
|
||||
switch(Flags.WEEKS_LIST_MOD_MODE) {
|
||||
case 'prepend':
|
||||
getWeeksFromSource(weeks, MODS);
|
||||
getWeeksFromSource(weeks, SOURCE);
|
||||
case 'append':
|
||||
getWeeksFromSource(weeks, SOURCE);
|
||||
getWeeksFromSource(weeks, MODS);
|
||||
default /*case 'override'*/:
|
||||
if (getWeeksFromSource(weeks, MODS))
|
||||
getWeeksFromSource(weeks, SOURCE);
|
||||
}
|
||||
|
||||
for(k=>weekName in weeks) {
|
||||
var week:Access = null;
|
||||
try {
|
||||
week = new Access(Xml.parse(Assets.getText(Paths.xml('weeks/weeks/$weekName'))).firstElement());
|
||||
} catch(e) {
|
||||
Logs.trace('Cannot parse week "$weekName.xml": ${Std.string(e)}`', ERROR);
|
||||
}
|
||||
|
||||
if (week == null) continue;
|
||||
|
||||
if (!week.has.name) {
|
||||
Logs.trace('Story Menu: Week at index ${k} has no name. Skipping...', WARNING);
|
||||
continue;
|
||||
}
|
||||
var weekObj:WeekData = {
|
||||
name: week.att.name,
|
||||
id: weekName,
|
||||
sprite: week.getAtt('sprite').getDefault(weekName),
|
||||
chars: [null, null, null],
|
||||
songs: [],
|
||||
difficulties: ['easy', 'normal', 'hard'],
|
||||
bgColor: FlxColor.fromString(week.getAtt("bgColor").getDefault(defColor))
|
||||
};
|
||||
|
||||
var diffNodes = week.nodes.difficulty;
|
||||
if (diffNodes.length > 0) {
|
||||
var diffs:Array<String> = [];
|
||||
for(e in diffNodes) {
|
||||
if (e.has.name) diffs.push(e.att.name);
|
||||
}
|
||||
if (diffs.length > 0)
|
||||
weekObj.difficulties = diffs;
|
||||
}
|
||||
|
||||
if (week.has.chars) {
|
||||
for(k=>e in week.att.chars.split(",")) {
|
||||
if (e.trim() == "" || e == "none" || e == "null")
|
||||
weekObj.chars[k] = null;
|
||||
else {
|
||||
addCharacter(weekObj.chars[k] = e.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
for(k2=>song in week.nodes.song) {
|
||||
if (song == null) continue;
|
||||
try {
|
||||
var name = song.innerData.trim();
|
||||
if (name == "") {
|
||||
Logs.trace('Story Menu: Song at index ${k2} in week ${weekObj.name} has no name. Skipping...', WARNING);
|
||||
continue;
|
||||
}
|
||||
weekObj.songs.push({
|
||||
name: name,
|
||||
hide: song.getAtt('hide').getDefault('false') == "true"
|
||||
});
|
||||
} catch(e) {
|
||||
Logs.trace('Story Menu: Song at index ${k2} in week ${weekObj.name} cannot contain any other XML nodes in its name.', WARNING);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (weekObj.songs.length <= 0) {
|
||||
Logs.trace('Story Menu: Week ${weekObj.name} has no songs. Skipping...', WARNING);
|
||||
continue;
|
||||
}
|
||||
this.weeks.push(weekObj);
|
||||
}
|
||||
weekList = StoryWeeklist.get(true, false); // will only load week files AND NOT characters too (we will load them later only if needed)!! - Nex
|
||||
weeks = weekList.weeks;
|
||||
for (week in weeks) for (char in week.chars) if (char != null)
|
||||
addCharacter(char.name);
|
||||
}
|
||||
|
||||
public function addCharacter(charName:String) {
|
||||
var char:Access = null;
|
||||
try {
|
||||
char = new Access(Xml.parse(Assets.getText(Paths.xml('weeks/characters/$charName'))).firstElement());
|
||||
} catch(e) {
|
||||
Logs.trace('Story Menu: Cannot parse character "$charName.xml": ${Std.string(e)}`', ERROR);
|
||||
}
|
||||
public function addCharacter(char:OneOfTwo<String, WeekData.WeekCharacter>) {
|
||||
// better to use unsafe casts for going fast - Nex
|
||||
var charObj:WeekData.WeekCharacter = null;
|
||||
var charName:String;
|
||||
|
||||
if(char != null && characters[charName] == null) {
|
||||
if(!char.x.exists("name")) char.x.set("name", charName);
|
||||
if(!char.x.exists("sprite")) char.x.set("sprite", 'menus/storymenu/characters/${charName}');
|
||||
if(!char.x.exists("beatInterval")) char.x.set("beatInterval", "1");
|
||||
if(!char.x.exists("updateHitbox")) char.x.set("updateHitbox", "true");
|
||||
characters[charName] = char;
|
||||
}
|
||||
charName = char is String ? cast char : (charObj = cast char).name;
|
||||
if (characters[charName] != null) return; // will load only if it can be saved inside the map - Nex
|
||||
characters[charName] = charObj == null ? Week.loadWeekCharacter(charName) : charObj;
|
||||
}
|
||||
|
||||
public function getWeeksFromSource(weeks:Array<String>, source:funkin.backend.assets.AssetsLibraryList.AssetSource) {
|
||||
var path:String = Paths.txt('freeplaySonglist');
|
||||
var weeksFound:Array<String> = [];
|
||||
if (Paths.assetsTree.existsSpecific(path, "TEXT", source)) {
|
||||
var trim = "";
|
||||
weeksFound = CoolUtil.coolTextFile(Paths.txt('weeks/weeks'));
|
||||
} else {
|
||||
weeksFound = [for(c in Paths.getFolderContent('data/weeks/weeks/', false, source)) if (Path.extension(c).toLowerCase() == "xml") Path.withoutExtension(c)];
|
||||
}
|
||||
public function modifyCharacterAt(i:Int, ?data:WeekData.WeekCharacter):FunkinSprite {
|
||||
var curChar:FunkinSprite = null;
|
||||
|
||||
if (weeksFound.length > 0) {
|
||||
for(s in weeksFound)
|
||||
weeks.push(s);
|
||||
return false;
|
||||
if (characterSprites != null) {
|
||||
var old = characterSprites.members[i];
|
||||
if (old != null) {
|
||||
characterSprites.remove(old);
|
||||
old.destroy();
|
||||
}
|
||||
|
||||
if (data != null) {
|
||||
curChar = XMLUtil.createSpriteFromXML(data.xml, "", BEAT);
|
||||
curChar.offset.x += curChar.x; curChar.offset.y += curChar.y;
|
||||
curChar.setPosition((FlxG.width * 0.25) * (1 + i) - 150, 70);
|
||||
curChar.playAnim("idle", true, DANCE);
|
||||
characterSprites.insert(i, curChar);
|
||||
} else {
|
||||
//characterSprites.members[i] = null; // cant add nulls to flxgroups, so - Nex
|
||||
characterSprites.insert(i, new FunkinSprite()).visible = false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return curChar;
|
||||
}
|
||||
|
||||
public function selectWeek() {
|
||||
@@ -369,7 +280,7 @@ class StoryMenuState extends MusicBeatState {
|
||||
canSelect = false;
|
||||
CoolUtil.playMenuSFX(CONFIRM);
|
||||
|
||||
if(characterSprites != null)
|
||||
if (characterSprites != null)
|
||||
characterSprites.forEachAlive(function(spr) spr.playAnim("confirm", true, LOCK));
|
||||
|
||||
PlayState.loadWeek(event.week, event.difficulty);
|
||||
@@ -382,21 +293,6 @@ class StoryMenuState extends MusicBeatState {
|
||||
}
|
||||
}
|
||||
|
||||
typedef WeekData = {
|
||||
var name:String; // name SHOULD NOT be used for loading week highscores, its just the name on the right side of the week, remember that next time!! - Nex
|
||||
var id:String; // id IS instead for saving and loading!! - Nex
|
||||
var sprite:String;
|
||||
var chars:Array<String>;
|
||||
var songs:Array<WeekSong>;
|
||||
var difficulties:Array<String>;
|
||||
var bgColor:FlxColor;
|
||||
}
|
||||
|
||||
typedef WeekSong = {
|
||||
var name:String;
|
||||
var hide:Bool;
|
||||
}
|
||||
|
||||
class MenuItem extends FlxSprite
|
||||
{
|
||||
public var targetY:Float = 0;
|
||||
@@ -438,3 +334,42 @@ class MenuItem extends FlxSprite
|
||||
color = (time % 0.1 > 0.05) ? FlxColor.WHITE : 0xFF33ffff;
|
||||
}
|
||||
}
|
||||
|
||||
class StoryWeeklist {
|
||||
public var weeks:Array<WeekData> = [];
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function getWeeksFromSource(source:funkin.backend.assets.AssetsLibraryList.AssetSource, useTxt:Bool = true, loadCharactersData:Bool = true) {
|
||||
var path:String = Paths.txt('weeks/weeks');
|
||||
var weeksFound:Array<String> = useTxt && Paths.assetsTree.existsSpecific(path, "TEXT", source) ? CoolUtil.coolTextFile(path) :
|
||||
[for (c in Paths.getFolderContent('data/weeks/weeks/', false, source)) if (Path.extension(c).toLowerCase() == "xml") Path.withoutExtension(c)];
|
||||
|
||||
if (weeksFound.length > 0) {
|
||||
for (w in weeksFound) {
|
||||
var week = Week.loadWeek(w, loadCharactersData);
|
||||
if (week != null) weeks.push(week);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function get(useTxt:Bool = true, loadCharactersData:Bool = true) {
|
||||
var weekList = new StoryWeeklist();
|
||||
|
||||
switch(Flags.WEEKS_LIST_MOD_MODE) {
|
||||
case 'prepend':
|
||||
weekList.getWeeksFromSource(MODS, useTxt, loadCharactersData);
|
||||
weekList.getWeeksFromSource(SOURCE, useTxt, loadCharactersData);
|
||||
case 'append':
|
||||
weekList.getWeeksFromSource(SOURCE, useTxt, loadCharactersData);
|
||||
weekList.getWeeksFromSource(MODS, useTxt, loadCharactersData);
|
||||
default /*case 'override'*/:
|
||||
if (weekList.getWeeksFromSource(MODS, useTxt, loadCharactersData))
|
||||
weekList.getWeeksFromSource(SOURCE, useTxt, loadCharactersData);
|
||||
}
|
||||
|
||||
return weekList;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package funkin.menus.credits;
|
||||
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.backend.assets.AssetsLibraryList.AssetSource;
|
||||
import funkin.backend.system.github.GitHubContributor.CreditsGitHubContributor;
|
||||
import funkin.options.OptionsScreen;
|
||||
import funkin.options.TreeMenu;
|
||||
import funkin.backend.system.github.GitHubContributor.CreditsGitHubContributor;
|
||||
import funkin.options.type.*;
|
||||
import haxe.xml.Access;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
class CreditsMain extends TreeMenu {
|
||||
var bg:FlxSprite;
|
||||
@@ -21,21 +22,17 @@ class CreditsMain extends TreeMenu {
|
||||
bg.antialiasing = true;
|
||||
add(bg);
|
||||
|
||||
var xmlPath = Paths.xml('config/credits');
|
||||
for(source in [funkin.backend.assets.AssetsLibraryList.AssetSource.SOURCE, funkin.backend.assets.AssetsLibraryList.AssetSource.MODS]) {
|
||||
if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT", source)) {
|
||||
var access:Access = null;
|
||||
try {
|
||||
access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT", source)));
|
||||
} catch(e) {
|
||||
Logs.trace('[CreditsMain] Error while parsing credits.xml: ${Std.string(e)}', ERROR);
|
||||
}
|
||||
for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) {
|
||||
var xmlPath = Paths.xml('config/credits/LIB_$i');
|
||||
|
||||
if (access != null)
|
||||
for(c in parseCreditsFromXML(access, source))
|
||||
items.push(c);
|
||||
if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT")) {
|
||||
var access:Access = null;
|
||||
try access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT")))
|
||||
catch(e) Logs.trace('[CreditsMain] Error while parsing credits.xml: ${Std.string(e)}', ERROR);
|
||||
if (access != null) for (c in parseCreditsFromXML(access)) items.push(c);
|
||||
}
|
||||
}
|
||||
|
||||
items.push(new TextOption("Codename Engine >", "Select this to see all the contributors of the engine!", function() {
|
||||
optionsTree.add(Type.createInstance(CreditsCodename, []));
|
||||
}));
|
||||
@@ -52,7 +49,7 @@ class CreditsMain extends TreeMenu {
|
||||
/**
|
||||
* XML STUFF
|
||||
*/
|
||||
public function parseCreditsFromXML(xml:Access, source:funkin.backend.assets.AssetsLibraryList.AssetSource):Array<OptionType> {
|
||||
public function parseCreditsFromXML(xml:Access, source:AssetSource = BOTH):Array<OptionType> {
|
||||
var credsMenus:Array<OptionType> = [];
|
||||
|
||||
for(node in xml.elements) {
|
||||
|
||||
@@ -52,7 +52,9 @@ class Alphabet extends FlxSpriteGroup
|
||||
public function refreshAlphabetXML(path:String) {
|
||||
AlphaCharacter.__alphaPath = Paths.getAssetsRoot() + path;
|
||||
try {
|
||||
var xml = new Access(Xml.parse(Assets.getText(path)).firstElement());
|
||||
var file = Assets.getText(path);
|
||||
if(file == null) return;
|
||||
var xml = new Access(Xml.parse(file).firstElement());
|
||||
AlphaCharacter.boldAnims = [];
|
||||
AlphaCharacter.letterAnims = [];
|
||||
AlphaCharacter.boldAlphabetPath = AlphaCharacter.letterAlphabetPath = 'ui/alphabet';
|
||||
|
||||
@@ -33,6 +33,7 @@ class Options
|
||||
public static var gameplayShaders:Bool = true;
|
||||
public static var colorHealthBar:Bool = true;
|
||||
public static var lowMemoryMode:Bool = false;
|
||||
public static var devMode:Bool = false;
|
||||
public static var betaUpdates:Bool = false;
|
||||
public static var splashesEnabled:Bool = true;
|
||||
public static var hitWindow:Float = 250;
|
||||
@@ -94,6 +95,8 @@ class Options
|
||||
public static var P1_NOTE_DOWN:Array<FlxKey> = [S];
|
||||
public static var P1_NOTE_UP:Array<FlxKey> = [W];
|
||||
public static var P1_NOTE_RIGHT:Array<FlxKey> = [D];
|
||||
|
||||
// Menus
|
||||
public static var P1_LEFT:Array<FlxKey> = [A];
|
||||
public static var P1_DOWN:Array<FlxKey> = [S];
|
||||
public static var P1_UP:Array<FlxKey> = [W];
|
||||
@@ -101,16 +104,30 @@ class Options
|
||||
public static var P1_ACCEPT:Array<FlxKey> = [ENTER];
|
||||
public static var P1_BACK:Array<FlxKey> = [BACKSPACE];
|
||||
public static var P1_PAUSE:Array<FlxKey> = [ENTER];
|
||||
|
||||
// Misc
|
||||
public static var P1_RESET:Array<FlxKey> = [R];
|
||||
public static var P1_SWITCHMOD:Array<FlxKey> = [TAB];
|
||||
public static var P1_VOLUME_UP:Array<FlxKey> = [];
|
||||
public static var P1_VOLUME_DOWN:Array<FlxKey> = [];
|
||||
public static var P1_VOLUME_MUTE:Array<FlxKey> = [];
|
||||
|
||||
// Debugs
|
||||
public static var P1_DEV_ACCESS:Array<FlxKey> = [SEVEN];
|
||||
public static var P1_DEV_CONSOLE:Array<FlxKey> = [F2];
|
||||
public static var P1_DEV_RELOAD:Array<FlxKey> = [F5];
|
||||
|
||||
/**
|
||||
* PLAYER 2 CONTROLS (ALT)
|
||||
*/
|
||||
* PLAYER 2 CONTROLS (ALT)
|
||||
*/
|
||||
|
||||
// Notes
|
||||
public static var P2_NOTE_LEFT:Array<FlxKey> = [LEFT];
|
||||
public static var P2_NOTE_DOWN:Array<FlxKey> = [DOWN];
|
||||
public static var P2_NOTE_UP:Array<FlxKey> = [UP];
|
||||
public static var P2_NOTE_RIGHT:Array<FlxKey> = [RIGHT];
|
||||
|
||||
// Menus
|
||||
public static var P2_LEFT:Array<FlxKey> = [LEFT];
|
||||
public static var P2_DOWN:Array<FlxKey> = [DOWN];
|
||||
public static var P2_UP:Array<FlxKey> = [UP];
|
||||
@@ -118,16 +135,30 @@ class Options
|
||||
public static var P2_ACCEPT:Array<FlxKey> = [SPACE];
|
||||
public static var P2_BACK:Array<FlxKey> = [ESCAPE];
|
||||
public static var P2_PAUSE:Array<FlxKey> = [ESCAPE];
|
||||
|
||||
// Misc
|
||||
public static var P2_RESET:Array<FlxKey> = [];
|
||||
public static var P2_SWITCHMOD:Array<FlxKey> = [];
|
||||
public static var P2_VOLUME_UP:Array<FlxKey> = [PLUS];
|
||||
public static var P2_VOLUME_DOWN:Array<FlxKey> = [MINUS];
|
||||
public static var P2_VOLUME_MUTE:Array<FlxKey> = [ZERO];
|
||||
|
||||
// Debugs
|
||||
public static var P2_DEV_ACCESS:Array<FlxKey> = [];
|
||||
public static var P2_DEV_CONSOLE:Array<FlxKey> = [];
|
||||
public static var P2_DEV_RELOAD:Array<FlxKey> = [];
|
||||
|
||||
/**
|
||||
* SOLO GETTERS
|
||||
*/
|
||||
* SOLO GETTERS
|
||||
*/
|
||||
|
||||
// Notes
|
||||
public static var SOLO_NOTE_LEFT(get, null):Array<FlxKey>;
|
||||
public static var SOLO_NOTE_DOWN(get, null):Array<FlxKey>;
|
||||
public static var SOLO_NOTE_UP(get, null):Array<FlxKey>;
|
||||
public static var SOLO_NOTE_RIGHT(get, null):Array<FlxKey>;
|
||||
|
||||
// Menus
|
||||
public static var SOLO_LEFT(get, null):Array<FlxKey>;
|
||||
public static var SOLO_DOWN(get, null):Array<FlxKey>;
|
||||
public static var SOLO_UP(get, null):Array<FlxKey>;
|
||||
@@ -135,8 +166,18 @@ class Options
|
||||
public static var SOLO_ACCEPT(get, null):Array<FlxKey>;
|
||||
public static var SOLO_BACK(get, null):Array<FlxKey>;
|
||||
public static var SOLO_PAUSE(get, null):Array<FlxKey>;
|
||||
|
||||
// Misc
|
||||
public static var SOLO_RESET(get, null):Array<FlxKey>;
|
||||
public static var SOLO_SWITCHMOD(get, null):Array<FlxKey>;
|
||||
public static var SOLO_VOLUME_UP(get, null):Array<FlxKey>;
|
||||
public static var SOLO_VOLUME_DOWN(get, null):Array<FlxKey>;
|
||||
public static var SOLO_VOLUME_MUTE(get, null):Array<FlxKey>;
|
||||
|
||||
// Debugs
|
||||
public static var SOLO_DEV_ACCESS(get, null):Array<FlxKey>;
|
||||
public static var SOLO_DEV_CONSOLE(get, null):Array<FlxKey>;
|
||||
public static var SOLO_DEV_RELOAD(get, null):Array<FlxKey>;
|
||||
|
||||
public static function load() {
|
||||
if (__save == null) __save = new FlxSave();
|
||||
@@ -165,6 +206,10 @@ class Options
|
||||
PlayerSettings.solo.setKeyboardScheme(Solo);
|
||||
PlayerSettings.player1.setKeyboardScheme(Duo(true));
|
||||
PlayerSettings.player2.setKeyboardScheme(Duo(false));
|
||||
|
||||
FlxG.sound.volumeUpKeys = SOLO_VOLUME_UP;
|
||||
FlxG.sound.volumeDownKeys = SOLO_VOLUME_DOWN;
|
||||
FlxG.sound.muteKeys = SOLO_VOLUME_MUTE;
|
||||
}
|
||||
|
||||
public static function save() {
|
||||
|
||||
@@ -54,36 +54,24 @@ class OptionsMenu extends TreeMenu {
|
||||
if (o.substate != null) {
|
||||
persistentUpdate = false;
|
||||
persistentDraw = true;
|
||||
if (o.substate is MusicBeatSubstate) {
|
||||
openSubState(o.substate);
|
||||
} else {
|
||||
openSubState(Type.createInstance(o.substate, []));
|
||||
}
|
||||
if (o.substate is MusicBeatSubstate) openSubState(o.substate);
|
||||
else openSubState(Type.createInstance(o.substate, []));
|
||||
} else {
|
||||
if (o.state is OptionsScreen) {
|
||||
optionsTree.add(o.state);
|
||||
} else {
|
||||
optionsTree.add(Type.createInstance(o.state, []));
|
||||
}
|
||||
if (o.state is OptionsScreen) optionsTree.add(o.state);
|
||||
else optionsTree.add(Type.createInstance(o.state, []));
|
||||
}
|
||||
})]);
|
||||
|
||||
var xmlPath = Paths.xml("config/options");
|
||||
for(source in [funkin.backend.assets.AssetsLibraryList.AssetSource.SOURCE, funkin.backend.assets.AssetsLibraryList.AssetSource.MODS]) {
|
||||
if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT", source)) {
|
||||
var access:Access = null;
|
||||
try {
|
||||
access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT", source)));
|
||||
} catch(e) {
|
||||
Logs.trace('Error while parsing options.xml: ${Std.string(e)}', ERROR);
|
||||
}
|
||||
for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) {
|
||||
var xmlPath = Paths.xml('config/options/LIB_$i');
|
||||
|
||||
if (access != null)
|
||||
for(o in parseOptionsFromXML(access))
|
||||
main.add(o);
|
||||
if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT")) {
|
||||
var access:Access = null;
|
||||
try access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT")))
|
||||
catch(e) Logs.trace('Error while parsing options.xml: ${Std.string(e)}', ERROR);
|
||||
if (access != null) for (o in parseOptionsFromXML(access)) main.add(o);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override function exit() {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package funkin.options.categories;
|
||||
|
||||
|
||||
class MiscOptions extends OptionsScreen {
|
||||
public override function new() {
|
||||
super("Miscellaneous", "Use this menu to reset save data or engine settings.");
|
||||
super("Miscellaneous", "Change some other options like enabling Developer Mode, resetting data...");
|
||||
add(new Checkbox(
|
||||
"Developer Mode",
|
||||
"If checked, you will be able to access developer features like certain keybinds, editors, developer options, state reloads, console and more.",
|
||||
"devMode"));
|
||||
#if UPDATE_CHECKING
|
||||
add(new Checkbox(
|
||||
"Enable Nightly Updates",
|
||||
|
||||
@@ -67,6 +67,23 @@ class KeybindsOptions extends MusicBeatSubstate {
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Volume',
|
||||
settings: [
|
||||
{
|
||||
name: 'Up',
|
||||
control: 'VOLUME_UP'
|
||||
},
|
||||
{
|
||||
name: 'Down',
|
||||
control: 'VOLUME_DOWN'
|
||||
},
|
||||
{
|
||||
name: 'Mute',
|
||||
control: 'VOLUME_MUTE'
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Engine',
|
||||
settings: [
|
||||
@@ -75,6 +92,26 @@ class KeybindsOptions extends MusicBeatSubstate {
|
||||
control: 'SWITCHMOD'
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Developer',
|
||||
devModeOnly: true,
|
||||
settings: [
|
||||
{
|
||||
name: 'Developer Menus',
|
||||
control: 'DEV_ACCESS'
|
||||
},
|
||||
#if GLOBAL_SCRIPT // since theyre integrated into global script - Nex
|
||||
{
|
||||
name: 'Open Console',
|
||||
control: 'DEV_CONSOLE'
|
||||
},
|
||||
{
|
||||
name: 'Reload State',
|
||||
control: 'DEV_RELOAD'
|
||||
},
|
||||
#end
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -132,7 +169,9 @@ class KeybindsOptions extends MusicBeatSubstate {
|
||||
for (i in customCategories) categories.push(i);
|
||||
|
||||
var k:Int = 0;
|
||||
for(category in categories) {
|
||||
for (category in categories) {
|
||||
if (category.devModeOnly && !Options.devMode) continue;
|
||||
|
||||
k++;
|
||||
var title = new Alphabet(0, k * 75, category.name, true);
|
||||
title.screenCenter(X);
|
||||
@@ -168,6 +207,10 @@ class KeybindsOptions extends MusicBeatSubstate {
|
||||
}
|
||||
add(alphabets);
|
||||
add(camFollow);
|
||||
|
||||
FlxG.sound.volumeUpKeys = [];
|
||||
FlxG.sound.volumeDownKeys = [];
|
||||
FlxG.sound.muteKeys = [];
|
||||
}
|
||||
|
||||
public override function destroy() {
|
||||
@@ -181,10 +224,8 @@ class KeybindsOptions extends MusicBeatSubstate {
|
||||
public override function update(elapsed:Float) {
|
||||
super.update(elapsed);
|
||||
|
||||
|
||||
if (isSubState) {
|
||||
bg.alpha = lerp(bg.alpha, 0.1, 0.125);
|
||||
} else {
|
||||
if (isSubState) bg.alpha = lerp(bg.alpha, 0.1, 0.125);
|
||||
else {
|
||||
if (curSelected < 4) {
|
||||
if (coloredBG.alpha == 0)
|
||||
coloredBG.color = noteColors[curSelected];
|
||||
@@ -200,11 +241,11 @@ class KeybindsOptions extends MusicBeatSubstate {
|
||||
changeSelection((controls.UP_P ? -1 : 0) + (controls.DOWN_P ? 1 : 0));
|
||||
|
||||
if (controls.BACK) {
|
||||
MusicBeatState.skipTransIn = true;
|
||||
if (isSubState)
|
||||
close();
|
||||
else
|
||||
if (isSubState) close();
|
||||
else {
|
||||
MusicBeatState.skipTransIn = true;
|
||||
FlxG.switchState(new OptionsMenu());
|
||||
}
|
||||
ControlsUtil.resetCustomControls();
|
||||
Options.applyKeybinds();
|
||||
ControlsUtil.loadCustomControls();
|
||||
@@ -251,7 +292,7 @@ class KeybindsOptions extends MusicBeatSubstate {
|
||||
var minH = FlxG.height / 2;
|
||||
var maxH = alphabets.members[alphabets.length-1].y + alphabets.members[alphabets.length-1].height - (FlxG.height / 2);
|
||||
if (minH < maxH)
|
||||
camFollow.setPosition(FlxG.width / 2, CoolUtil.bound(alphabet.y + (alphabet.height / 2) - (35), minH, maxH));
|
||||
camFollow.setPosition(FlxG.width / 2, CoolUtil.bound(alphabet.y + (alphabet.height / 2) - 35, minH, maxH));
|
||||
else
|
||||
camFollow.setPosition(FlxG.width / 2, FlxG.height / 2);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,21 @@ class GithubUserIcon extends FlxSprite
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
#if target.threaded
|
||||
final mutex = new sys.thread.Mutex();
|
||||
#end
|
||||
|
||||
inline function acquireMutex() {
|
||||
#if target.threaded
|
||||
mutex.acquire();
|
||||
#end
|
||||
}
|
||||
inline function releaseMutex() {
|
||||
#if target.threaded
|
||||
mutex.release();
|
||||
#end
|
||||
}
|
||||
|
||||
override function drawComplex(camera:FlxCamera):Void { // Making the image download only if the player actually sees it on the screen - Nex
|
||||
if(waitUntilLoad <= 0) {
|
||||
waitUntilLoad = null;
|
||||
@@ -82,19 +96,19 @@ class GithubUserIcon extends FlxSprite
|
||||
}
|
||||
|
||||
if(bmap != null) try {
|
||||
mutex.acquire(); // Avoiding critical section - Nex
|
||||
acquireMutex(); // Avoiding critical section - Nex
|
||||
var leGraphic:FlxGraphic = FlxG.bitmap.add(bmap, false, key);
|
||||
leGraphic.persist = true;
|
||||
updateDaFunni(leGraphic);
|
||||
bmap = null;
|
||||
mutex.release();
|
||||
releaseMutex();
|
||||
} catch(e) {
|
||||
Logs.traceColored([Logs.logText('Failed to update the pfp for ${user.login}: ${e.message}', RED)], ERROR);
|
||||
}
|
||||
} else {
|
||||
mutex.acquire();
|
||||
acquireMutex();
|
||||
updateDaFunni(bmap);
|
||||
mutex.release();
|
||||
releaseMutex();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ 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;
|
||||
add(iconSpr);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package funkin.options.type;
|
||||
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.game.HealthIcon;
|
||||
|
||||
/**
|
||||
* Option type that has a new button, and is green.
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
package lime.ui;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.Path;
|
||||
import lime._internal.backend.native.NativeCFFI;
|
||||
import lime.app.Event;
|
||||
import lime.graphics.Image;
|
||||
import lime.system.BackgroundWorker;
|
||||
import lime.utils.ArrayBuffer;
|
||||
import lime.utils.Resource;
|
||||
#if hl
|
||||
import hl.Bytes as HLBytes;
|
||||
import hl.NativeArray;
|
||||
#end
|
||||
#if sys
|
||||
import sys.io.File;
|
||||
#end
|
||||
#if (js && html5)
|
||||
import js.html.Blob;
|
||||
#end
|
||||
|
||||
/**
|
||||
Simple file dialog used for asking user where to save a file, or select files to open.
|
||||
|
||||
Example usage:
|
||||
```haxe
|
||||
var fileDialog = new FileDialog();
|
||||
|
||||
fileDialog.onCancel.add( () -> trace("Canceled.") );
|
||||
|
||||
fileDialog.onSave.add( path -> trace("File saved in " + path) );
|
||||
|
||||
fileDialog.onOpen.add( res -> trace("Size of the file = " + (res:haxe.io.Bytes).length) );
|
||||
|
||||
if ( fileDialog.open("jpg", null, "Load file") )
|
||||
trace("File dialog opened, waiting for selection...");
|
||||
else
|
||||
trace("This dialog is unsupported.");
|
||||
```
|
||||
|
||||
Availability note: most file dialog operations are only available on desktop targets, though
|
||||
`save()` is also available in HTML5.
|
||||
**/
|
||||
#if !lime_debug
|
||||
@:fileXml('tags="haxe,release"')
|
||||
@:noDebug
|
||||
#end
|
||||
@:access(lime._internal.backend.native.NativeCFFI)
|
||||
@:access(lime.graphics.Image)
|
||||
class FileDialog
|
||||
{
|
||||
/**
|
||||
Triggers when the user clicks "Cancel" during any operation, or when a function is unsupported
|
||||
(such as `open()` on HTML5).
|
||||
**/
|
||||
public var onCancel = new Event<Void->Void>();
|
||||
|
||||
/**
|
||||
Triggers when `open()` is successful. The `lime.utils.Resource` contains the file's data, and can
|
||||
be implicitly cast to `haxe.io.Bytes`.
|
||||
**/
|
||||
public var onOpen = new Event<Resource->Void>();
|
||||
|
||||
/**
|
||||
Triggers when `open()` is successful. The `lime.utils.Resource` contains the file's data, and can
|
||||
be implicitly cast to `haxe.io.Bytes`, the String is the path to the file.
|
||||
**/
|
||||
public var onOpenFile = new Event<(Resource, String)->Void>(); // Added by @NeeEoo
|
||||
|
||||
/**
|
||||
Triggers when `save()` is successful. The `String` is the path to the saved file.
|
||||
**/
|
||||
public var onSave = new Event<String->Void>();
|
||||
|
||||
/**
|
||||
Triggers when `browse()` is successful and `type` is anything other than
|
||||
`FileDialogType.OPEN_MULTIPLE`. The `String` is the path to the selected file.
|
||||
**/
|
||||
public var onSelect = new Event<String->Void>();
|
||||
|
||||
/**
|
||||
Triggers when `browse()` is successful and `type` is `FileDialogType.OPEN_MULTIPLE`. The
|
||||
`Array<String>` contains all selected file paths.
|
||||
**/
|
||||
public var onSelectMultiple = new Event<Array<String>->Void>();
|
||||
|
||||
public function new() {}
|
||||
|
||||
/**
|
||||
Opens a file selection dialog. If successful, either `onSelect` or `onSelectMultiple` will trigger
|
||||
with the result(s).
|
||||
|
||||
This function only works on desktop targets, and will return `false` otherwise.
|
||||
@param type Type of the file dialog: `OPEN`, `SAVE`, `OPEN_DIRECTORY` or `OPEN_MULTIPLE`.
|
||||
@param filter A filter to use when browsing. Asterisks are treated as wildcards. For example,
|
||||
`"*.jpg"` will match any file ending in `.jpg`.
|
||||
@param defaultPath The directory in which to start browsing and/or the default filename to
|
||||
suggest. Defaults to `Sys.getCwd()`, with no default filename.
|
||||
@param title The title to give the dialog window.
|
||||
@return Whether `browse()` is supported on this target.
|
||||
**/
|
||||
public function browse(type:FileDialogType = null, filter:String = null, defaultPath:String = null, title:String = null):Bool
|
||||
{
|
||||
if (type == null) type = FileDialogType.OPEN;
|
||||
|
||||
#if desktop
|
||||
var worker = new BackgroundWorker();
|
||||
|
||||
worker.doWork.add(function(_)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case OPEN:
|
||||
#if linux
|
||||
if (title == null) title = "Open File";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
#if hl
|
||||
var bytes = NativeCFFI.lime_file_dialog_open_file(title, filter, defaultPath);
|
||||
if (bytes != null)
|
||||
{
|
||||
path = @:privateAccess String.fromUTF8(cast bytes);
|
||||
}
|
||||
#else
|
||||
path = NativeCFFI.lime_file_dialog_open_file(title, filter, defaultPath);
|
||||
#end
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
|
||||
case OPEN_MULTIPLE:
|
||||
#if linux
|
||||
if (title == null) title = "Open Files";
|
||||
#end
|
||||
|
||||
var paths = null;
|
||||
#if (!macro && lime_cffi)
|
||||
#if hl
|
||||
var bytes:NativeArray<HLBytes> = cast NativeCFFI.lime_file_dialog_open_files(title, filter, defaultPath);
|
||||
if (bytes != null)
|
||||
{
|
||||
paths = [];
|
||||
for (i in 0...bytes.length)
|
||||
{
|
||||
paths[i] = @:privateAccess String.fromUTF8(bytes[i]);
|
||||
}
|
||||
}
|
||||
#else
|
||||
paths = NativeCFFI.lime_file_dialog_open_files(title, filter, defaultPath);
|
||||
#end
|
||||
#end
|
||||
|
||||
worker.sendComplete(paths);
|
||||
|
||||
case OPEN_DIRECTORY:
|
||||
#if linux
|
||||
if (title == null) title = "Open Directory";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
#if hl
|
||||
var bytes = NativeCFFI.lime_file_dialog_open_directory(title, filter, defaultPath);
|
||||
if (bytes != null)
|
||||
{
|
||||
path = @:privateAccess String.fromUTF8(cast bytes);
|
||||
}
|
||||
#else
|
||||
path = NativeCFFI.lime_file_dialog_open_directory(title, filter, defaultPath);
|
||||
#end
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
|
||||
case SAVE:
|
||||
#if linux
|
||||
if (title == null) title = "Save File";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
#if hl
|
||||
var bytes = NativeCFFI.lime_file_dialog_save_file(title, filter, defaultPath);
|
||||
if (bytes != null)
|
||||
{
|
||||
path = @:privateAccess String.fromUTF8(cast bytes);
|
||||
}
|
||||
#else
|
||||
path = NativeCFFI.lime_file_dialog_save_file(title, filter, defaultPath);
|
||||
#end
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
}
|
||||
});
|
||||
|
||||
worker.onComplete.add(function(result)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case OPEN, OPEN_DIRECTORY, SAVE:
|
||||
var path:String = cast result;
|
||||
|
||||
if (path != null)
|
||||
{
|
||||
// Makes sure the filename ends with extension
|
||||
if (type == SAVE && filter != null && path.indexOf(".") == -1)
|
||||
{
|
||||
path += "." + filter;
|
||||
}
|
||||
|
||||
onSelect.dispatch(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
onCancel.dispatch();
|
||||
}
|
||||
|
||||
case OPEN_MULTIPLE:
|
||||
var paths:Array<String> = cast result;
|
||||
|
||||
if (paths != null && paths.length > 0)
|
||||
{
|
||||
onSelectMultiple.dispatch(paths);
|
||||
}
|
||||
else
|
||||
{
|
||||
onCancel.dispatch();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
worker.run();
|
||||
|
||||
return true;
|
||||
#else
|
||||
onCancel.dispatch();
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Shows an open file dialog. If successful, `onOpen` will trigger with the file contents.
|
||||
|
||||
This function only works on desktop targets, and will return `false` otherwise.
|
||||
@param filter A filter to use when browsing. Asterisks are treated as wildcards. For example,
|
||||
`"*.jpg"` will match any file ending in `.jpg`.
|
||||
@param defaultPath The directory in which to start browsing and/or the default filename to
|
||||
suggest. Defaults to `Sys.getCwd()`, with no default filename.
|
||||
@param title The title to give the dialog window.
|
||||
@return Whether `open()` is supported on this target.
|
||||
**/
|
||||
public function open(filter:String = null, defaultPath:String = null, title:String = null):Bool
|
||||
{
|
||||
#if (desktop && sys)
|
||||
var worker = new BackgroundWorker();
|
||||
|
||||
worker.doWork.add(function(_)
|
||||
{
|
||||
#if linux
|
||||
if (title == null) title = "Open File";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
#if hl
|
||||
var bytes = NativeCFFI.lime_file_dialog_open_file(title, filter, defaultPath);
|
||||
if (bytes != null) path = @:privateAccess String.fromUTF8(cast bytes);
|
||||
#else
|
||||
path = NativeCFFI.lime_file_dialog_open_file(title, filter, defaultPath);
|
||||
#end
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
});
|
||||
|
||||
worker.onComplete.add(function(path:String)
|
||||
{
|
||||
if (path != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = File.getBytes(path);
|
||||
onOpen.dispatch(data);
|
||||
onOpenFile.dispatch(data, path); // Added by @NeeEoo
|
||||
return;
|
||||
}
|
||||
catch (e:Dynamic) {}
|
||||
}
|
||||
|
||||
onCancel.dispatch();
|
||||
});
|
||||
|
||||
worker.run();
|
||||
|
||||
return true;
|
||||
#else
|
||||
onCancel.dispatch();
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Shows an open file dialog. If successful, `onSave` will trigger with the selected path.
|
||||
|
||||
This function only works on desktop and HMTL5 targets, and will return `false` otherwise.
|
||||
@param data The file contents, in `haxe.io.Bytes` format. (Implicit casting possible.)
|
||||
@param filter A filter to use when browsing. Asterisks are treated as wildcards. For example,
|
||||
`"*.jpg"` will match any file ending in `.jpg`. Used only if targeting deskop.
|
||||
@param defaultPath The directory in which to start browsing and/or the default filename to
|
||||
suggest. When targeting destkop, this defaults to `Sys.getCwd()` with no default filename. When targeting
|
||||
HTML5, this defaults to the browser's download directory, with a default filename based on the MIME type.
|
||||
@param title The title to give the dialog window.
|
||||
@param type The default MIME type of the file, in case the type can't be determined from the
|
||||
file data. Used only if targeting HTML5.
|
||||
@return Whether `save()` is supported on this target.
|
||||
**/
|
||||
public function save(data:Resource, filter:String = null, defaultPath:String = null, title:String = null, type:String = "application/octet-stream"):Bool
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
onCancel.dispatch();
|
||||
return false;
|
||||
}
|
||||
|
||||
#if (desktop && sys)
|
||||
var worker = new BackgroundWorker();
|
||||
|
||||
worker.doWork.add(function(_)
|
||||
{
|
||||
#if linux
|
||||
if (title == null) title = "Save File";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
#if hl
|
||||
var bytes = NativeCFFI.lime_file_dialog_save_file(title, filter, defaultPath);
|
||||
path = @:privateAccess String.fromUTF8(cast bytes);
|
||||
#else
|
||||
path = NativeCFFI.lime_file_dialog_save_file(title, filter, defaultPath);
|
||||
#end
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
});
|
||||
|
||||
worker.onComplete.add(function(path:String)
|
||||
{
|
||||
if (path != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.saveBytes(path, data);
|
||||
onSave.dispatch(path);
|
||||
return;
|
||||
}
|
||||
catch (e:Dynamic) {}
|
||||
}
|
||||
|
||||
onCancel.dispatch();
|
||||
});
|
||||
|
||||
worker.run();
|
||||
|
||||
return true;
|
||||
#elseif (js && html5)
|
||||
// TODO: Cleaner API for mimeType detection
|
||||
|
||||
var defaultExtension = "";
|
||||
|
||||
if (Image.__isPNG(data))
|
||||
{
|
||||
type = "image/png";
|
||||
defaultExtension = ".png";
|
||||
}
|
||||
else if (Image.__isJPG(data))
|
||||
{
|
||||
type = "image/jpeg";
|
||||
defaultExtension = ".jpg";
|
||||
}
|
||||
else if (Image.__isGIF(data))
|
||||
{
|
||||
type = "image/gif";
|
||||
defaultExtension = ".gif";
|
||||
}
|
||||
else if (Image.__isWebP(data))
|
||||
{
|
||||
type = "image/webp";
|
||||
defaultExtension = ".webp";
|
||||
}
|
||||
|
||||
var path = defaultPath != null ? Path.withoutDirectory(defaultPath) : "download" + defaultExtension;
|
||||
var buffer = (data : Bytes).getData();
|
||||
buffer = buffer.slice(0, (data : Bytes).length);
|
||||
|
||||
#if commonjs
|
||||
untyped #if haxe4 js.Syntax.code #else __js__ #end ("require ('file-saver')")(new Blob([buffer], {type: type}), path, true);
|
||||
#else
|
||||
untyped window.saveAs(new Blob([buffer], {type: type}), path, true);
|
||||
#end
|
||||
onSave.dispatch(path);
|
||||
return true;
|
||||
#else
|
||||
onCancel.dispatch();
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
}
|
||||
@@ -695,7 +695,8 @@ class Sound extends EventDispatcher
|
||||
// var samples = (__buffer.data.length * 8) / (__buffer.channels * __buffer.bitsPerSample);
|
||||
// return Std.int(samples / __buffer.sampleRate * 1000);
|
||||
var samples = (Int64.make(0, __buffer.data.length) * Int64.ofInt(8)) / Int64.ofInt(__buffer.channels * __buffer.bitsPerSample);
|
||||
var value = samples / Int64.ofInt(__buffer.sampleRate) * Int64.ofInt(1000);
|
||||
var div = Int64.divMod(samples * Int64.ofInt(1000), Int64.ofInt(__buffer.sampleRate));
|
||||
var value = (div.quotient + (div.modulus / __buffer.sampleRate));
|
||||
return Int64.toInt(value);
|
||||
}
|
||||
else if (__buffer.__srcVorbisFile != null)
|
||||
|
||||
Reference in New Issue
Block a user