Merge branch 'main' into pr/397

This commit is contained in:
Ne_Eo
2025-06-06 20:39:24 +02:00
140 changed files with 471 additions and 238 deletions
@@ -26,10 +26,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();
}
+15 -23
View File
@@ -87,30 +87,25 @@ class Chart {
return data;
}
public static function loadChartMeta(songName:String, difficulty:String = "normal", fromMods:Bool = true) {
public static function loadChartMeta(songName:String, difficulty:String = "normal", fromMods:Bool = true):ChartMetaData {
var songNameLower = songName.toLowerCase();
var metaPath = Paths.file('songs/${songNameLower}/meta.json');
var metaDiffPath = Paths.file('songs/${songNameLower}/meta-${difficulty.toLowerCase()}.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 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;
}
if (data == null)
data = {
name: songName,
bpm: 100
};
if (data == null) data = {
name: songName,
bpm: 100
};
data.setFieldDefault("name", songName);
data.setFieldDefault("beatsPerMeasure", 4);
data.setFieldDefault("stepsPerBeat", 4);
@@ -165,11 +160,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);
}
/**
@@ -230,8 +223,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;
}
@@ -1,6 +1,6 @@
package funkin.backend.scripting.events;
import funkin.menus.StoryMenuState.WeekData;
import funkin.backend.week.WeekData;
final class WeekSelectEvent extends CancellableEvent {
/**
+8
View File
@@ -30,6 +30,14 @@ class Main extends Sprite
// make this empty once you guys are done with the project.
// good luck /gen <3 @crowplexus
public static final releaseCycle:String = "Beta";
// add a version number in dis shid rn
public static var releaseVersion(get, default):String = null;
public static function get_releaseVersion():String {
if (releaseVersion != null)
return releaseVersion;
return lime.app.Application.current.meta.get('version');
}
public static var instance:Main;
+63 -17
View File
@@ -8,6 +8,13 @@ import funkin.menus.TitleState;
import funkin.menus.BetaWarningState;
import funkin.backend.chart.EventsData;
import flixel.FlxState;
import haxe.io.Path;
@dox(hide)
typedef AddonInfo = {
var name:String;
var path:String;
}
/**
* Simple state used for loading the game
@@ -32,25 +39,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
MusicBeatTransition.script = "";
@@ -11,7 +11,7 @@ class CodenameBuildField extends TextField {
defaultTextFormat = Framerate.textFormat;
autoSize = LEFT;
multiline = wordWrap = false;
text = 'Codename Engine ${Main.releaseCycle}\nCommit ${GitCommitMacro.commitNumber} (${GitCommitMacro.commitHash})';
text = 'Codename Engine ${Main.releaseCycle}\nVersion ${Main.releaseVersion}';
selectable = false;
}
}
@@ -49,6 +49,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}';
@@ -58,10 +77,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 isnt able to use the args but it still works
if (process.exitCode() != 0) throw 'Could not fetch CPU information';
+14 -4
View File
@@ -92,7 +92,7 @@ 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",
@@ -117,13 +117,23 @@ class MemoryUtil {
22 => "DDR2 FB-DIMM",
24 => "DDR3",
25 => "FBD2",
26 => "DDR4"
26 => "DDR4",
27 => "LPDDR",
28 => "LPDDR2",
29 => "LPDDR3",
30 => "LPDDR4",
31 => "Logical Non-volatile device",
32 => "HBM",
33 => "HBM2",
34 => "DDR5",
35 => "LPDDR5",
36 => "HBM3",
];
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: (.+)/;
+120
View File
@@ -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;
}
}
+86
View File
@@ -0,0 +1,86 @@
package funkin.backend.week;
import flixel.math.FlxPoint;
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']
};
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;
return {
xml: char,
name: charName,
spritePath: Paths.image(char.getAtt('sprite').getDefault('menus/storymenu/characters/${charName}')),
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)
)
};
}
}
+29
View File
@@ -0,0 +1,29 @@
package funkin.backend.week;
import flixel.math.FlxPoint;
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>;
}
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
View File
@@ -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();
@@ -16,16 +17,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;
}
}
+22 -3
View File
@@ -221,8 +221,21 @@ class HealthIcon extends FunkinSprite
}
var animName = 'from-${node.get("from")}-to-${node.get("to")}';
if (node.exists("offsetX") || node.exists("offsetY"))
addOffset(animName, Std.parseFloat(node.get("offsetX")).getDefault(0), Std.parseFloat(node.get("offsetY")).getDefault(0));
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;
@@ -256,7 +269,13 @@ class HealthIcon extends FunkinSprite
addOffset(animName, offsetX, offsetY);
addAnim(animName, node.get("anim"), Std.parseInt(node.get("fps")).getDefault(24), node.get("looped").getDefault("true").toLowerCase() == "true");
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":
+4 -4
View File
@@ -12,7 +12,7 @@ import funkin.backend.chart.Chart;
import funkin.backend.chart.ChartData;
import funkin.game.SplashHandler;
import funkin.backend.scripting.DummyScript;
import funkin.menus.StoryMenuState.WeekData;
import funkin.backend.week.WeekData;
import funkin.backend.FunkinText;
import funkin.backend.scripting.Script;
import funkin.backend.scripting.ScriptPack;
@@ -624,7 +624,7 @@ class PlayState extends MusicBeatState
// ADD YOUR HARDCODED SCRIPTS HERE!
default:
var normal = 'songs/${SONG.meta.name.toLowerCase()}/scripts';
var scriptsFolders:Array<String> = [normal, normal + '/$difficulty/', 'data/charts/', 'songs/'];
var scriptsFolders:Array<String> = [normal, normal + '/${difficulty.toLowerCase()}/', 'data/charts/', 'songs/'];
for (folder in scriptsFolders) {
for (file in Paths.getFolderContent(folder, true, fromMods ? MODS : BOTH)) {
@@ -1525,6 +1525,7 @@ class PlayState extends MusicBeatState
*/
public function endSong():Void
{
endingSong = true;
scripts.call("onSongEnd");
canPause = false;
inst.volume = 0;
@@ -1971,7 +1972,7 @@ class PlayState extends MusicBeatState
*/
public static function loadWeek(weekData:WeekData, difficulty:String = "normal") {
storyWeek = weekData;
storyPlaylist = [for(e in weekData.songs) e.name];
storyPlaylist = [for (e in weekData.songs) e.name];
isStoryMode = true;
campaignScore = 0;
campaignMisses = 0;
@@ -2004,7 +2005,6 @@ class PlayState extends MusicBeatState
*/
public static function __loadSong(name:String, difficulty:String) {
PlayState.difficulty = difficulty;
PlayState.SONG = Chart.parse(name, difficulty);
PlayState.fromMods = PlayState.SONG.fromMods;
}
@@ -197,7 +197,7 @@ class DialogueCutscene extends Cutscene {
dialogueScript.destroy();
super.destroy();
cutscene = null;
if (cutscene == this) cutscene = null;
FlxG.cameras.remove(dialogueCamera);
}
}
+2 -8
View File
@@ -439,16 +439,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, "normal", source == MODS));
for (s in songsFound) songs.push(Chart.loadChartMeta(s, "normal", source == MODS));
return false;
}
return true;
+1 -1
View File
@@ -76,7 +76,7 @@ class MainMenuState extends MusicBeatState
FlxG.camera.follow(camFollow, null, 0.06);
versionText = new FunkinText(5, FlxG.height - 2, 0, 'Codename Engine v${Application.current.meta.get('version')}\nCommit ${funkin.backend.system.macros.GitCommitMacro.commitNumber} (${funkin.backend.system.macros.GitCommitMacro.commitHash})\n[${controls.getKeyName(SWITCHMOD)}] Open Mods menu\n');
versionText = new FunkinText(5, FlxG.height - 2, 0, 'Codename Engine v${Main.releaseVersion}\nCommit ${funkin.backend.system.macros.GitCommitMacro.commitNumber} (${funkin.backend.system.macros.GitCommitMacro.commitHash})\n[${controls.getKeyName(SWITCHMOD)}] Open Mods menu\n');
versionText.y -= versionText.height;
versionText.scrollFactor.set();
add(versionText);
+66 -155
View File
@@ -1,19 +1,21 @@
package funkin.menus;
import funkin.savedata.FunkinSave;
import haxe.io.Path;
import funkin.backend.scripting.events.*;
import flixel.util.FlxTimer;
import flixel.math.FlxPoint;
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 haxe.xml.Access;
import flixel.text.FlxText;
import funkin.backend.scripting.events.*;
import funkin.backend.week.*;
import funkin.savedata.FunkinSave;
import haxe.io.Path;
class StoryMenuState extends MusicBeatState {
public var characters:Map<String, MenuCharacter> = [];
public var characters:Map<String, WeekData.WeekCharacter> = [];
public var weeks:Array<WeekData> = [];
public var weekList:StoryWeeklist;
public var scoreText:FlxText;
public var tracklist:FlxText;
@@ -84,22 +86,20 @@ class StoryMenuState extends MusicBeatState {
tracklist.color = 0xFFE55777;
add(weekSprites);
for(e in [blackBar, scoreText, weekTitle, weekBG, tracklist]) {
for (e in [blackBar, scoreText, weekTitle, weekBG, tracklist]) {
e.scrollFactor.set();
add(e);
}
characterSprites = new FlxTypedGroup<MenuCharacterSprite>();
for(i in 0...3) {
characterSprites.add(new MenuCharacterSprite(i));
}
for (i in 0...3) characterSprites.add(new MenuCharacterSprite(i));
add(characterSprites);
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);
@@ -171,11 +171,13 @@ 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.toUpperCase()].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("");
for(i in 0...3)
characterSprites.members[i].changeCharacter(characters[weeks[curWeek].chars[i]]);
for (i in 0...3) {
var char = weeks[curWeek].chars[i]; // will use the characters map in any case since a map is for sure easier to edit for mods, we didnt load characters by default anyways - Nex
characterSprites.members[i].changeCharacter(char == null ? null : characters[char.name]);
}
changeDifficulty(0, true);
@@ -210,121 +212,25 @@ class StoryMenuState extends MusicBeatState {
}
public function loadXMLs() {
// CoolUtil.coolTextFile(Paths.txt('freeplaySonglist'));
var weeks:Array<String> = [];
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']
};
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);
}
if (char == null) return;
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 (characters[charName] != null) return;
var charObj:MenuCharacter = {
spritePath: Paths.image(char.getAtt('sprite').getDefault('menus/storymenu/characters/${charName}')),
scale: Std.parseFloat(char.getAtt('scale')).getDefault(1),
xml: char,
offset: FlxPoint.get(
Std.parseFloat(char.getAtt('x')).getDefault(0),
Std.parseFloat(char.getAtt('y')).getDefault(0)
)
};
characters[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)];
}
if (weeksFound.length > 0) {
for(s in weeksFound)
weeks.push(s);
return false;
}
return true;
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 override function destroy() {
super.destroy();
for(e in characters)
for (e in characters)
if (e != null && e.offset != null)
e.offset.put();
}
@@ -350,28 +256,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>;
}
typedef WeekSong = {
var name:String;
var hide:Bool;
}
typedef MenuCharacter = {
var spritePath:String;
var xml:Access;
var scale:Float;
var offset:FlxPoint;
// var frames:FlxFramesCollection;
}
class MenuCharacterSprite extends FlxSprite
{
public var character:String;
@@ -385,19 +269,15 @@ class MenuCharacterSprite extends FlxSprite
antialiasing = true;
}
public var oldChar:MenuCharacter = null;
public var oldChar:WeekData.WeekCharacter = null;
public function changeCharacter(data:MenuCharacter) {
visible = (data != null);
if (!visible)
return;
public function changeCharacter(data:WeekData.WeekCharacter) {
if (!(visible = (data != null && data.xml != null))) return;
if (oldChar != (oldChar = data)) {
CoolUtil.loadAnimatedGraphic(this, data.spritePath);
for(e in data.xml.nodes.anim) {
if (e.getAtt("name") == "idle")
animation.remove("idle");
for (e in data.xml.nodes.anim) {
if (e.getAtt("name") == "idle") animation.remove("idle");
XMLUtil.addXMLAnimation(this, e);
}
animation.play("idle");
@@ -410,6 +290,7 @@ class MenuCharacterSprite extends FlxSprite
}
}
}
class MenuItem extends FlxSprite
{
public var targetY:Float = 0;
@@ -451,3 +332,33 @@ 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();
if (weekList.getWeeksFromSource(MODS, useTxt, loadCharactersData))
weekList.getWeeksFromSource(SOURCE, useTxt, loadCharactersData);
return weekList;
}
}
+2 -1
View File
@@ -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)