Merge remote-tracking branch 'public/main'

This commit is contained in:
⍚~Nex
2025-07-06 04:40:21 +02:00
54 changed files with 862 additions and 515 deletions
+3
View File
@@ -0,0 +1,3 @@
# These are supported funding model platforms
ko_fi: codename_engine
+4 -4
View File
@@ -7,7 +7,7 @@ jobs:
build:
name: Linux Build
permissions: write-all
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- name: Pulling the source
uses: actions/checkout@v2
@@ -35,13 +35,13 @@ jobs:
- name: Building the game
run: |
haxelib run lime build linux
- name: Tar files
run: tar -zcvf CodenameEngine.tar.gz -C export/release/linux/bin .
# - name: Tar files
# run: tar -zcvf CodenameEngine.tar.gz -C export/release/linux/bin .
- name: Uploading artifact (entire build)
uses: actions/upload-artifact@v4
with:
name: Codename Engine
path: CodenameEngine.tar.gz
path: export/release/linux/bin/
# - name: Uploading artifact (executable)
# uses: actions/upload-artifact@v4
# with:
-1
View File
@@ -84,7 +84,6 @@ If you don't have a github account please go onto https://fnf-cne-devs.github.io
<details>
<summary><h2>Credits</h2></summary>
- Credits to [Ne_Eo](https://twitter.com/Ne_Eo_Twitch) and the [3D-HaxeFlixel](https://github.com/lunarcleint/3D-HaxeFlixel) repository for Away3D Flixel support
- Credits to the [FlxAnimate](https://github.com/Dot-Stuff/flxanimate) team for the Animate Atlas support
- Credits to Smokey555 for the backup Animate Atlas to spritesheet code
- Credits to MAJigsaw77 for [hxvlc](https://github.com/MAJigsaw77/hxvlc) (video cutscene/mp4 support) and [hxdiscord_rpc](https://github.com/MAJigsaw77/hxdiscord_rpc) (discord rpc integration)
+1 -1
View File
@@ -1,4 +1,4 @@
{
"clientID": "1027994136193810442",
"clientID": "1383853614589673472",
"logoKey": "icon"
}
-3
View File
@@ -1,3 +0,0 @@
//
function update(elapsed:Float)
if (FlxG.keys.justPressed.F5) FlxG.resetState();
+26 -35
View File
@@ -18,9 +18,7 @@ public var daPixelZoom = PlayState.daPixelZoom;
* UI
*/
function onNoteCreation(event) {
if (event.note.strumLine == playerStrums && !pixelNotesForBF) return;
if (event.note.strumLine == cpuStrums && !pixelNotesForDad) return;
if ((event.note.strumLine == playerStrums && !pixelNotesForBF) || (event.note.strumLine == cpuStrums && !pixelNotesForDad)) return;
event.cancel();
var note = event.note;
@@ -34,18 +32,14 @@ function onNoteCreation(event) {
}
note.scale.set(daPixelZoom, daPixelZoom);
note.updateHitbox();
note.antialiasing = false;
}
function onPostNoteCreation(event) {
var splashes = event.note;
if (pixelSplashes)
splashes.splash = "pixel-default";
}
function onPostNoteCreation(event) if (pixelSplashes)
event.note.splash = "pixel-default";
function onStrumCreation(event) {
if (event.player == 1 && !pixelNotesForBF) return;
if (event.player == 0 && !pixelNotesForDad) return;
if ((event.player == 1 && !pixelNotesForBF) || (event.player == 0 && !pixelNotesForDad)) return;
event.cancel();
var strum = event.strum;
@@ -56,6 +50,7 @@ function onStrumCreation(event) {
strum.scale.set(daPixelZoom, daPixelZoom);
strum.updateHitbox();
strum.antialiasing = false;
}
function onCountdown(event) {
@@ -86,9 +81,9 @@ function onPlayerHit(event:NoteHitEvent) {
* CAMERA HACKS!!
*/
function postCreate() {
if (enablePauseMenu) {
if (enablePauseMenu)
PauseSubState.script = 'data/scripts/week6-pause';
}
if (enableCameraHacks) {
camGame.pixelPerfectRender = true;
camGame.antialiasing = false;
@@ -104,8 +99,8 @@ function postCreate() {
}
}
function onStartCountdown() {
/*var newNoteCamera = new HudCamera();
/*function onStartCountdown() {
var newNoteCamera = new HudCamera();
newNoteCamera.bgColor = 0; // transparent
FlxG.cameras.add(newNoteCamera, false);
@@ -119,8 +114,8 @@ function onStartCountdown() {
i++;
}
}
makeCameraPixely(newNoteCamera);*/
}
makeCameraPixely(newNoteCamera);
}*/
/**
* Use this to make any camera pixelly (you wont be able to zoom with it anymore!)
@@ -145,33 +140,29 @@ function destroy() {
FlxG.game.stage.quality = oldStageQuality;
}
function pixelCam(cam) {
function pixelCam(cam)
makeCameraPixely(cam);
}
var pixellyCameras = [];
var pixellyShaders = [];
function postUpdate(elapsed) {
for(e in pixellyCameras) {
if (Std.isOfType(e, HudCamera))
e.downscroll = camHUD.downscroll;
}
if (enableCameraHacks) {
for(p in strumLines)
p.notes.forEach(function(n) {
if(n.isSustainNote) return; // hacky fix for hold
n.y -= n.y % daPixelZoom;
n.x -= n.x % daPixelZoom;
});
function postUpdate() {
for (e in pixellyCameras) if (Std.isOfType(e, HudCamera))
e.downscroll = camHUD.downscroll;
if (enableCameraHacks) for (p in strumLines) {
p.notes.forEach(function(n) {
if(n.isSustainNote) return; // hacky fix for hold
n.y -= n.y % daPixelZoom;
n.x -= n.x % daPixelZoom;
});
}
var zoom = 1 / daPixelZoom / Math.min(FlxG.scaleMode.scale.x, FlxG.scaleMode.scale.y);
for(e in pixellyCameras) {
for (e in pixellyCameras) {
if (!e.exists) continue;
e.zoom = zoom;
}
for(e in pixellyShaders) {
for (e in pixellyShaders)
e.pixelZoom = zoom;
}
}
}
+36 -48
View File
@@ -3,7 +3,7 @@ import flixel.text.FlxText;
import flixel.text.FlxTextBorderStyle;
import flixel.util.FlxAxes;
var pixelScript:Script;
var pixelScript:Script = game.scripts.getByName("pixel.hx");
var pauseCam = new FlxCamera();
var bg:FlxSprite;
@@ -19,47 +19,40 @@ function create(event) {
event.music = isThorns ? "pixel/LunchboxScary" : "pixel/breakfast";
cameras = [];
pixelScript = game.scripts.getByName("pixel.hx");
pixelScript.call("pixelCam", [pauseCam]);
// allowing this pause script even if the pixel script is not loaded - Nex
pixelScript?.call("pixelCam", [pauseCam]);
FlxG.cameras.add(pauseCam, false);
cameras = [pauseCam];
pauseCam.bgColor = isThorns ? 0x88000000 : 0x88FF99CC;
pauseCam.alpha = 0;
bg = new FlxSprite(44 * 6, 14 * 6);
bg.loadGraphic(Paths.image('stages/school/pause/bg'));
if (isThorns)
bg.color = 0xFF000000;
bg = new FlxSprite(44 * 6, 14 * 6, Paths.image('stages/school/pause/bg'));
bg.antialiasing = false;
if (isThorns) bg.color = 0xFF000000;
bg.scale.set(6, 6);
bg.updateHitbox();
bg.scale.y = 4;
bg.cameras = [pauseCam];
add(bg);
songText = new FlxText(0, 22 * 6, 0, "Pause", 8, false);
confText(songText);
add(songText);
var i = 2;
for(e in menuItems) {
text = new FlxText(0, (22 * 6) + (i * 9 * 6), 0, e, 8, false);
for (i=>e in menuItems) {
text = new FlxText(0, (22 * 6) + ((i+2) * 9 * 6), 0, e, 8, false);
confText(text);
add(text);
texts.push(text);
i++;
}
hand = new FlxSprite().loadGraphic(Paths.image('stages/school/ui/hand_textbox'));
hand.antialiasing = false;
hand.scale.set(6, 6);
hand.updateHitbox();
add(hand);
FlxTween.tween(bg, {"scale.y": 6}, 0.75, {ease: FlxEase.elasticOut});
FlxTween.tween(bg.scale, {y: 6}, 0.75, {ease: FlxEase.elasticOut});
cameras = [pauseCam];
FlxG.sound.play(Paths.sound(isThorns ? 'pixel/ANGRY' : 'pixel/clickText'));
}
@@ -70,55 +63,50 @@ function confText(text) {
text.screenCenter(FlxAxes.X);
text.borderStyle = FlxTextBorderStyle.OUTLINE;
if (!isThorns) text.borderColor = 0xFF953E3E;
text.antialiasing = false;
texts.push(text);
add(text);
}
function destroy() {
if(FlxG.cameras.list.contains(pauseCam))
FlxG.cameras.remove(pauseCam);
}
function destroy() if (FlxG.cameras.list.contains(pauseCam))
FlxG.cameras.remove(pauseCam);
var canDoShit = true;
var time:Float = 0;
function update(elapsed) {
pixelScript.call("postUpdate", [elapsed]);
pixelScript?.call("postUpdate", [elapsed]);
pauseCam.alpha = lerp(pauseCam.alpha, 1, 0.25);
time += elapsed;
var curText = texts[curSelected];
var curText = texts[curSelected + 1];
hand.setPosition(curText.x - hand.width - 18 + (Math.sin(time * Math.PI * 2) * 12), curText.y + (text.height - hand.height) - 6);
hand.x -= hand.x % 6;
hand.y -= hand.y % 6;
if (!canDoShit) return;
var oldSec = curSelected;
changeSelection((controls.UP_P ? -1 : 0) + (controls.DOWN_P ? 1 : 0) - FlxG.mouse.wheel);
if (oldSec != curSelected)
FlxG.sound.play(Paths.sound(isThorns ? 'pixel/type' : 'pixel/pixelText'));
if (controls.ACCEPT) enterOption();
}
if (controls.ACCEPT) {
FlxG.sound.play(Paths.sound(isThorns ? 'pixel/ANGRY' : 'pixel/clickText'));
var option = menuItems[curSelected];
if (option == "Resume" || option == "Exit to menu") {
var scrollSFX = FlxG.sound.load(Paths.sound(isThorns ? 'pixel/type' : 'pixel/pixelText'));
function changeSelection(change) if (canDoShit) { // this overrides the function inside of the normal pause btw, so no event gets called - Nex
curSelected = FlxMath.wrap(curSelected + change, 0, menuItems.length - 1);
if (change != 0) scrollSFX.play();
}
var enterSFX = FlxG.sound.load(Paths.sound(isThorns ? 'pixel/ANGRY' : 'pixel/clickText'));
function enterOption() if (canDoShit) {
var option = menuItems[curSelected];
enterSFX.play();
switch(option) {
case "Resume", "Exit to menu":
canDoShit = false;
for(t in texts) t.visible = false;
hand.visible = songText.visible = false;
FlxTween.tween(bg, {"scale.y": 0}, 0.125, {ease: FlxEase.cubeOut, onComplete: function() {
selectOption();
}});
} else {
selectOption();
}
FlxTween.tween(bg.scale, {y: 0}, 0.125, {ease: FlxEase.cubeOut, onComplete: selectOption});
default: selectOption();
}
}
function changeSelection(change) { // this overrides the function inside of the normal pause btw, so no event gets called - Nex
curSelected += change;
if (curSelected < 0)
curSelected = menuItems.length - 1;
if (curSelected >= menuItems.length)
curSelected = 0;
}
+2 -8
View File
@@ -3,13 +3,7 @@ var sound:Sound;
function create()
sound = Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, !Options.naughtyness ? [1, 3, 8, 13, 17, 21] : null));
function beatHit(cur:Int) {
if(cur != 0) return;
function beatHit(cur:Int) if (cur == 0) {
FlxG.sound.play(sound, 1, false, null, true, () -> if (!isEnding) FlxG.sound.music.fadeIn(4, 0.2, 1));
FlxG.sound.music.volume = 0.2;
FlxG.sound.play(sound, 1, false, null, true, function()
{
if (!isEnding)
FlxG.sound.music.fadeIn(4, 0.2, 1);
});
}
+2 -2
View File
@@ -1,7 +1,7 @@
var tankAngle:Float = FlxG.random.int(-90, 45);
var tankSpeed:Float = FlxG.random.float(5, 7);
function postCreate() if(PlayState.isStoryMode)
function postCreate() if (PlayState.isStoryMode)
GameOverSubstate.script = 'data/scripts/week7-balledLines';
function update(elapsed:Float) {
@@ -12,7 +12,7 @@ function update(elapsed:Float) {
}
function onPostCountdown(event) {
if(!playCutscenes || event.sprite == null) return;
if (!playCutscenes || event.sprite == null) return;
var lastIndex:Int = members.indexOf(event.sprite);
remove(event.sprite);
insert(lastIndex + 4, event.sprite);
+2 -4
View File
@@ -15,10 +15,8 @@ class Compiler {
}
private static function __build(args:Array<String>, arg:Array<String>) {
arg.insert(0, "lime");
arg.insert(0, "run");
for(a in args)
arg.push(a);
for (a in args) arg.push(a);
arg = ['run', 'lime'].concat(arg);
Sys.command("haxelib", arg);
}
+1 -1
View File
@@ -5,7 +5,7 @@
title="Friday Night Funkin' - Codename Engine" main="funkin.backend.system.Main"
file="CodenameEngine" packageName="com.yoshman29.codenameengine"
package="com.yoshman29.codenameengine"
version="0.1.0" company="Yoshman29" />
version="0.1.0-legacy" company="Yoshman29" />
<!--
CHANGE THE SAVE PATH & NAME FOR YOUR MOD HERE!
+11 -9
View File
@@ -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_DEBUG_RELOAD:Bool = true;
inline function get_controls():Controls
return PlayerSettings.solo.controls;
inline function get_controlsP1():Controls
@@ -151,16 +153,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_DEBUG_RELOAD && controls.DEBUG_RELOAD)) {
Logs.trace("Reloading Current State...", INFO, YELLOW);
FlxG.resetState();
}
if (subState != null)
subState.tryUpdate(elapsed);
}
override function create()
{
loadScript();
@@ -221,10 +227,6 @@ class MusicBeatState extends FlxState implements IBeatReceiver
override function update(elapsed:Float)
{
// TODO: DEBUG MODE!!
if (FlxG.keys.justPressed.F5) {
loadScript();
}
call("update", [elapsed]);
super.update(elapsed);
+8 -8
View File
@@ -134,15 +134,19 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver
call("postUpdate", [elapsed]);
}
if (_requestSubStateReset)
{
// if (subState == null && (MusicBeatState.ALLOW_DEBUG_RELOAD && controls.DEBUG_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() {
@@ -179,10 +183,6 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver
override function update(elapsed:Float)
{
// TODO: DEBUG MODE!!
if (FlxG.keys.justPressed.F5) {
loadScript();
}
call("update", [elapsed]);
super.update(elapsed);
}
@@ -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();
}
+4 -4
View File
@@ -268,13 +268,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;
}
+28 -33
View File
@@ -82,30 +82,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 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);
@@ -116,7 +115,6 @@ 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/${songName}/charts/', false, fromMods ? MODS : SOURCE)) if (Path.extension(f.toUpperCase()) == "JSON") Path.withoutExtension(f)];
@@ -159,11 +157,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);
}
/**
@@ -224,8 +220,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;
}
@@ -260,33 +255,33 @@ 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;
}
public static function filterChartForSaving(chart:ChartData, ?saveMetaInChart:Null<Bool>, ?saveEventsInChart:Null<Bool>):ChartData {
var data = Reflect.copy(chart); // make a copy of the chart to leave the OG intact
if (saveMetaInChart != true) {
data.meta = null;
} else {
data.meta = Reflect.copy(chart.meta); // also make a copy of the metadata to leave the OG intact.
if(data.meta != null && Reflect.hasField(data.meta, "parsedColor")) Reflect.deleteField(data.meta, "parsedColor");
}
data.meta = saveMetaInChart != true ? null : data.meta = Reflect.copy(chart.meta); // also make a copy of the metadata to leave the OG intact.
data.events = saveEventsInChart != true ? null : Reflect.copy(chart.events); // same here once again
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 = {
+1 -4
View File
@@ -21,15 +21,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;
// NOT TO BE EXPORTED
public var ?parsedColor:FlxColor;
}
typedef ChartStrumLine = {
+27 -12
View File
@@ -12,6 +12,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,16 +48,12 @@ class GlobalScript {
});
FlxG.signals.postUpdate.add(function() {
call("postUpdate", [FlxG.elapsed]);
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_DEBUG_RELOAD = _lastAllow_Reload;
}
if (FlxG.keys.justPressed.F2)
NativeAPI.allocConsole();
});
@@ -74,13 +72,29 @@ 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) {
var resetKey = FlxG.keys.justPressed.F5; // we default the key to F5, but really this shouldn't matter, as every state will be at minimum a MusicBeatState.
if ((FlxG.state is MusicBeatState)) resetKey = cast(FlxG.state, MusicBeatState).controls.DEBUG_RELOAD;
// If we want, we could just make reseting GlobalScript it's own keybind, but for now this works.
if (resetKey) {
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_DEBUG_RELOAD;
MusicBeatState.ALLOW_DEBUG_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 {
@@ -93,6 +107,7 @@ class GlobalScript {
if (scripts != null)
scripts.call(name, args);
}
public static function onModSwitch(newMod:String) {
call("destroy");
scripts = FlxDestroyUtil.destroy(scripts);
@@ -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;
}
@@ -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 {
/**
+8
View File
@@ -24,6 +24,9 @@ enum Control
PAUSE;
//CHEAT;
SWITCHMOD;
// Debugs
DEBUG_RELOAD;
}
enum KeyboardScheme
@@ -150,6 +153,11 @@ class Controls extends FlxActionSet
@:pressed("switchmod") public var SWITCHMOD_HOLD(get, set): Bool;
@:justReleased("switchmod") public var SWITCHMOD_R(get, set): Bool;
@:gamepad([GUIDE]) // the center button (like the PS button on a playstation controller) on the gamepad
@:justPressed("debug-reload") public var DEBUG_RELOAD(get, set): Bool;
@:pressed("debug-reload") public var DEBUG_RELOAD_HOLD(get, set): Bool;
@:justReleased("debug-reload") public var DEBUG_RELOAD_R(get, set): Bool;
var byName:Map<String, FlxActionDigital> = [];
public var gamepadsAdded:Array<Int> = [];
+3 -1
View File
@@ -79,7 +79,7 @@ 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
"../../../../../../../"
@@ -175,6 +175,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;
+66 -26
View File
@@ -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) {
@@ -35,25 +40,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();
@@ -65,14 +109,10 @@ class MainState extends FlxState {
EventsData.reloadEvents();
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}';
}
@@ -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 isn't able to use the args but it still works
if (process.exitCode() != 0) throw 'Could not fetch CPU information';
+5 -1
View File
@@ -425,9 +425,13 @@ 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;
+3 -3
View File
@@ -123,7 +123,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",
@@ -162,9 +162,9 @@ 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: (.+)/;
+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;
}
}
+5 -4
View File
@@ -114,12 +114,13 @@ 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) {
@@ -234,10 +235,10 @@ 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);
}
/**
+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;
}
}
+7 -4
View File
@@ -600,7 +600,7 @@ class Charter extends UIState {
// Just for now until i add event stacking -lunar
try {__relinkUndos();}
catch (e) {Logs.trace('Failed to relink undos: ${Std.string(e)}', ERROR);}
__applyPlaytestInfo();
}
@@ -1291,8 +1291,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;
}
var zoom(default, set):Float = 0;
@@ -1347,7 +1350,7 @@ class Charter extends UIState {
#if sys
CoolUtil.safeSaveFile(
'${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/meta.json',
Json.stringify(PlayState.SONG.meta == null ? {} : PlayState.SONG.meta, null, Flags.JSON_PRETTY_PRINT)
PlayState.SONG.meta == null ? null : Chart.makeMetaSaveable(PlayState.SONG.meta)
);
#else
_file_meta_saveas(_);
@@ -1355,7 +1358,7 @@ class Charter extends UIState {
}
function _file_meta_saveas(_) {
openSubState(new SaveSubstate(Json.stringify(PlayState.SONG.meta == null ? {} : PlayState.SONG.meta, null, Flags.JSON_PRETTY_PRINT), { // always pretty print meta
openSubState(new SaveSubstate(PlayState.SONG.meta == null ? null : Chart.makeMetaSaveable(PlayState.SONG.meta), { // always pretty print meta
defaultSaveFile: 'meta.json'
}));
}
@@ -1650,7 +1653,7 @@ class Charter extends UIState {
t.icon = (Options.charterLowDetailWaveforms = !Options.charterLowDetailWaveforms) ? 1 : 0;
for (shader in waveformHandler.waveShaders) shader.data.lowDetail.value = [Options.charterLowDetailWaveforms];
}
inline function _snap_increasesnap(_) changequant(1);
inline function _snap_decreasesnap(_) changequant(-1);
inline function _snap_resetsnap(_) setquant(16);
@@ -85,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");
@@ -151,8 +151,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;
@@ -40,7 +41,7 @@ class CharterSelection extends EditorTreeMenu {
}));
#end
optionsTree.add(new OptionsScreen(s.name, "Select a difficulty to continue.", list));
}, s.parsedColor.getDefault(0xFFFFFFFF))
}, s.color.getDefault(0xFFFFFFFF))
];
#if sys
@@ -114,7 +115,7 @@ 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);
@@ -137,7 +138,7 @@ class CharterSelection extends EditorTreeMenu {
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);
@@ -171,7 +172,7 @@ 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
@@ -58,8 +58,8 @@ class CharterStrumlineButton extends UISprite {
super.update(elapsed);
UIState.state.updateSpriteRect(button);
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();
@@ -405,8 +405,7 @@ class SongCreationScreen 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()],
+22 -3
View File
@@ -222,8 +222,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;
@@ -257,7 +270,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":
+16 -20
View File
@@ -151,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');
@@ -272,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 = FlxMath.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
@@ -311,5 +305,7 @@ class Note extends FlxSprite
public override function destroy() {
super.destroy();
clipRect = FlxDestroyUtil.put(clipRect);
}
}
+5 -12
View File
@@ -30,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;
@@ -1322,16 +1322,8 @@ class PlayState extends MusicBeatState
if (updateRatingStuff != null)
updateRatingStuff();
if (canAccessDebugMenus) {
if (chartingMode && FlxG.keys.justPressed.SEVEN) {
FlxG.switchState(new funkin.editors.charter.Charter(SONG.meta.name, difficulty, false));
}
/*if (FlxG.keys.justPressed.F5) {
Logs.warn('Reloading scripts...', "PlayState");
scripts.reload();
Logs.warn('Song scripts successfully reloaded.', GREEN, "PlayState");
}*/
}
if (canAccessDebugMenus && chartingMode && FlxG.keys.justPressed.SEVEN)
FlxG.switchState(new funkin.editors.charter.Charter(SONG.meta.name, difficulty, false));
if (doIconBop)
for (icon in iconArray)
@@ -1642,6 +1634,7 @@ class PlayState extends MusicBeatState
*/
public function endSong():Void
{
endingSong = true;
gameAndCharsCall("onSongEnd");
canPause = false;
inst.volume = 0;
@@ -2103,7 +2096,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;
+39
View File
@@ -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;
}
}
+11 -6
View File
@@ -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.
*/
@@ -46,7 +46,7 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> {
}
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));
@@ -56,7 +56,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) {
@@ -85,12 +85,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);
}
@@ -103,11 +105,14 @@ class SplashGroup extends FlxTypedGroup<FunkinSprite> {
return animationNames[id][FlxG.random.int(0, animationNames[id].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;
__splash.cameras = strum.lastDrawCameras;
__splash.setPosition(strum.x + ((strum.width - __splash.width) / 2), strum.y + ((strum.height - __splash.height) / 2));
__splash.active = __splash.visible = true;
+1 -1
View File
@@ -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.
*/
+15 -4
View File
@@ -45,7 +45,7 @@ class Stage extends FlxBasic implements IBeatReceiver {
for (k=>e in stageSprites) script.set(k, e);
public function prepareInfos(node:Access)
return XMLImportedScriptInfo.prepareInfos(node, PlayState.instance.scripts, (infos) -> xmlImportedScripts.push(infos));
return XMLImportedScriptInfo.prepareInfos(node, PlayState.instance != null ? PlayState.instance.scripts : null, (infos) -> xmlImportedScripts.push(infos));
public function new(stage:String, ?state:FlxState) {
super();
@@ -111,17 +111,28 @@ class Stage extends FlxBasic implements IBeatReceiver {
case "box" | "solid":
if (!node.has.name || !node.has.width || !node.has.height) continue;
var spr = new FlxSprite(
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
);
(node.name == "solid" ? spr.makeSolid : spr.makeGraphic)(
// just to make sure, its better to apply these before it gets inside of xmlUtil (because of mainly updateHitbox i guess), so im removing them before it gets passed - Nex
var toRemove = ["x", "y", "width", "height", "color"];
var isSolid = node.name == "solid";
(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) toRemove.push("updateHitbox"); // makesolid already calls updateHitbox - Nex
for (a in toRemove) node.x.remove(a);
XMLUtil.loadSpriteFromXML(spr, node, "", NONE, false);
if (!node.has.zoomfactor && PlayState.instance != null)
spr.initialZoom = PlayState.instance.defaultCamZoom;
stageSprites.set(node.getAtt("name"), spr);
state.add(spr);
spr;
@@ -363,4 +374,4 @@ typedef StageCharPosInfo = {
var y:Float;
var flip:Bool;
var scroll:Float;
}
}
+3 -2
View File
@@ -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) {
@@ -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);
}
}
+4 -10
View File
@@ -236,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
@@ -453,16 +453,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 +479,4 @@ class FreeplaySonglist {
return songList;
}
}
}
+13
View File
@@ -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
+96 -159
View File
@@ -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,17 @@ 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
trace(newChar != null ? newChar.name : null, curChar != null ? curChar.name : null);
}
changeDifficulty(0, true);
@@ -195,27 +206,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 +234,45 @@ 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();
}
trace(data);
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 +282,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 +295,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 +336,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;
}
}
+12 -15
View File
@@ -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) {
+46 -6
View File
@@ -73,12 +73,16 @@ class Options
public static var charterAutoSavesSeparateFolder:Bool = false;
/**
* PLAYER 1 CONTROLS
*/
* PLAYER 1 CONTROLS
*/
// Notes
public static var P1_NOTE_LEFT:Array<FlxKey> = [A];
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];
@@ -86,16 +90,28 @@ 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_DEBUG_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];
@@ -103,16 +119,28 @@ 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> = [NUMPADPLUS];
public static var P2_VOLUME_DOWN:Array<FlxKey> = [NUMPADMINUS];
public static var P2_VOLUME_MUTE:Array<FlxKey> = [NUMPADZERO];
// Debugs
public static var P2_DEBUG_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>;
@@ -120,8 +148,16 @@ 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_DEBUG_RELOAD(get, null):Array<FlxKey>;
public static function load() {
if (__save == null) __save = new FlxSave();
@@ -150,6 +186,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() {
+11 -23
View File
@@ -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() {
@@ -66,6 +66,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: [
@@ -74,6 +91,15 @@ class KeybindsOptions extends MusicBeatSubstate {
control: 'SWITCHMOD'
},
]
},
{
name: 'DEBUG',
settings: [
{
name: 'Reload',
control: 'DEBUG_RELOAD'
},
]
}
];
@@ -162,6 +188,10 @@ class KeybindsOptions extends MusicBeatSubstate {
}
add(alphabets);
add(camFollow);
FlxG.sound.volumeUpKeys = [];
FlxG.sound.volumeDownKeys = [];
FlxG.sound.muteKeys = [];
}
public override function destroy() {
@@ -175,10 +205,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];
@@ -194,11 +222,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());
}
Options.applyKeybinds();
Options.save();
return;
+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)