Skip to content
34 changes: 24 additions & 10 deletions source/funkin/backend/system/CommandLineHandler.hx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,37 @@ package funkin.backend.system;
#if sys
import sys.FileSystem;
final class CommandLineHandler {
@:noPrivateAccess
private static function __showHelpText():Void {
// Just put it all in a string array =)

final STRINGS:Array<String> = [
"--- Codename Engine Command Line Help ---",
"",
"-help | Show this help",
#if MOD_SUPPORT
"-mod [mod name] | Load a specific mod",
"-modfolder [path] | Sets the mod folder path",
"-addonsfolder [path] | Sets the addons folder path",
#end
"-nocolor | Disables colors in the terminal",
"-nogpubitmap | Forces GPU only bitmaps off",
"-nocwdfix | Turns off automatic working directory fix"
];

for (s in STRINGS) {
Sys.println(s);
}
}

public static function parseCommandLine(cmd:Array<String>) {
var i:Int = 0;
while(i < cmd.length) {
switch(cmd[i]) {
case null:
break;
case "-h" | "-help" | "help":
Sys.println("-- Codename Engine Command Line help --");
Sys.println("-help | Show this help");
#if MOD_SUPPORT
Sys.println("-mod [mod name] | Load a specific mod");
Sys.println("-modfolder [path] | Sets the mod folder path");
Sys.println("-addonsfolder [path] | Sets the addons folder path");
#end
Sys.println("-nocolor | Disables colors in the terminal");
Sys.println("-nogpubitmap | Forces GPU only bitmaps off");
Sys.println("-nocwdfix | Turns off automatic working directory fix");
__showHelpText();
Sys.exit(0);
#if MOD_SUPPORT
case "-m" | "-mod" | "-currentmod":
Expand Down
15 changes: 2 additions & 13 deletions source/funkin/backend/system/updating/AsyncUpdater.hx
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,8 @@ class AsyncUpdater {
}
#end


#if windows
public static var executableGitHubName:String = "update-windows.exe";
public static var executableName:String = "CodenameEngine.exe";
#end
#if linux
public static var executableGitHubName:String = "update-linux";
public static var executableName:String = "CodenameEngine";
#end
#if mac
public static var executableGitHubName:String = "update-mac";
public static var executableName:String = "CodenameEngine";
#end
public static var executableName:String = UpdateUtil.getExecName();
public static var executableGitHubName:String = UpdateUtil.getGitExecName();

public var releases:Array<GitHubRelease>;
public var progress:UpdaterProgress = new UpdaterProgress();
Expand Down
15 changes: 15 additions & 0 deletions source/funkin/backend/system/updating/UpdateUtil.hx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ class UpdateUtil {
Thread.create(checkForUpdates.bind(true, false));
}

public static function getExecName():String
{
// Since previously, both linux and mac don't have the .exe extension, we might as well just compress this to a '#else' clause.
return #if windows "CodenameEngine.exe" #else "CodenameEngine" #end; // weird syntax??? :skull: haxe is just full of surprises
}

public static function getGitExecName():String
{
// Only add the suffix of the platform we need to get.
var target:String = #if windows "windows.exe" #end
#if mac "mac" #end
#if linux "linux" #end;
return 'update-${target}';
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find these comments unnecessary


public static function waitForUpdates(force = false, callback:UpdateCheckCallback->Void, lazy = false) {
if (__mutex.tryAcquire()) {
__mutex.release();
Expand Down
152 changes: 152 additions & 0 deletions source/funkin/game/Countdown.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package funkin.game;

import flixel.tweens.misc.VarTween;
import flixel.tweens.FlxTween;
import flixel.sound.FlxSound;
import funkin.backend.scripting.events.gameplay.CountdownEvent;

enum CountdownAnimationPreset {

/**
* The default animation for Codename Engine's countdown.
*/
DEFAULT;

/**
* The classic animation, similar to Funkin's countdown.
*/
CLASSIC;

/**
* A more enhanced version of Funkin's countdown animation.
*/
BEATING;

}

typedef CountdownParams = {

/**
* The CountdownEvent to be used.
*/
var event:CountdownEvent;

/**
* Whether the countdown should be visible or not.
*/
var enabled:Bool;

/**
* Whether each tick from the countdown should play a sound.
*/
var playSound:Bool;

/**
* The animation preset to be used for the countdown.
*/
var animationPreset:CountdownAnimationPreset;

/**
* The duration of the countdown's animation.
*/
var duration:Float;

/**
* The speed of the countdown's animation. The lower it is, the slower it goes and vice-versa.
*/
var speed:Float;

}

class Countdown extends FlxTypedSpriteGroup<FlxSprite> {
public var event:CountdownEvent;
public var enabled:Bool;
public var playSound:Bool;
public var animationPreset:CountdownAnimationPreset;
public var duration:Float;
public var speed:Float;

/**
* Create a new Countdown component.
* @param params
*/
public function new(params:CountdownParams) {
super();

this.event = params.event;
this.enabled = params.enabled;
this.playSound = params.playSound;
this.animationPreset = params.animationPreset;
this.duration = params.duration;
this.speed = params.speed;

this.__createSprite();
}

@:noPrivateAccess
private function __createSprite():Void {
Copy link
Member

@FuroYT FuroYT Oct 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a function? Also why is it private within private access which hscript will bypass

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello! Sorry if it took a while since I didn't think this'd get really accepted as a pr, hehe.

I've been coding Haxe for a bit now, and I had this unnecessary habit of putting @:noPrivateAccess with private functions. I'll remove the annotation real quick.

if (!this.enabled) {
return;
}

var sprite:FlxSprite = null;
var sound:FlxSound = null;
var tween:FlxTween = null;

if (!this.event.cancelled) {
if (this.event.spritePath != null) {
// Add 1.0 to defaultSize if animationPreset is BEATING.
var defaultSize:Float = this.event.scale;
var isBeatingPreset:Bool = (this.animationPreset == BEATING);
var targetSize:Float = defaultSize + ((!isBeatingPreset) ? 0.0 : 0.15);

var spr = this.event.spritePath;

if (!Assets.exists(spr)) {
spr = Paths.image('$spr');
}

sprite = new FunkinSprite().loadAnimatedGraphic(spr);
sprite.scale.set(targetSize, targetSize);
sprite.scrollFactor.set();
sprite.antialiasing = this.event.antialiasing;
sprite.updateHitbox();
sprite.screenCenter();
add(sprite);

switch(this.animationPreset) {
case CLASSIC:
tween = __createTween(sprite, {alpha: 0}, FlxEase.cubeInOut);
case BEATING:
tween = __createTween(sprite, {alpha: 0, "scale.x": defaultSize, "scale.y": defaultSize}, FlxEase.expoOut);
default: // DEFAULT
tween = __createTween(sprite, {y: sprite.y + 100, alpha: 0}, FlxEase.cubeInOut);
}
}

if (this.event.soundPath != null && this.playSound) {
var sfx = this.event.soundPath;
if (!Assets.exists(sfx)) {
sfx = Paths.sound(sfx);
}
sound = FlxG.sound.play(sfx, this.event.volume);
}

this.event.sprite = sprite;
this.event.sound = sound;
this.event.spriteTween = tween;
this.event.cancelled = false;
}
}

@:noPrivateAccess
private function __createTween(sprite:FlxSprite, values:Dynamic, easing:EaseFunction):VarTween {
return FlxTween.tween(sprite, values, (this.duration / this.speed), {
ease: easing,
onComplete: function(twn:FlxTween) {
sprite.destroy();
remove(sprite, true);
}
});
}
}
59 changes: 16 additions & 43 deletions source/funkin/game/PlayState.hx
Original file line number Diff line number Diff line change
Expand Up @@ -996,50 +996,23 @@ class PlayState extends MusicBeatState
* Creates a fake countdown.
*/
public function countdown(swagCounter:Int) {
var event:CountdownEvent = gameAndCharsEvent("onCountdown", EventManager.get(CountdownEvent).recycle(
swagCounter,
1,
introSounds[swagCounter],
introSprites[swagCounter],
0.6, true, null, null, null));

var sprite:FlxSprite = null;
var sound:FlxSound = null;
var tween:FlxTween = null;

if (!event.cancelled) {
if (event.spritePath != null) {
var spr = event.spritePath;
if (!Assets.exists(spr)) spr = Paths.image('$spr');

sprite = new FunkinSprite().loadAnimatedGraphic(spr);
sprite.scrollFactor.set();
sprite.scale.set(event.scale, event.scale);
sprite.updateHitbox();
sprite.screenCenter();
sprite.antialiasing = event.antialiasing;
add(sprite);
tween = FlxTween.tween(sprite, {y: sprite.y + 100, alpha: 0}, Conductor.crochet / 1000, {
ease: FlxEase.cubeInOut,
onComplete: function(twn:FlxTween)
{
sprite.destroy();
remove(sprite, true);
}
});
}
if (event.soundPath != null) {
var sfx = event.soundPath;
if (!Assets.exists(sfx)) sfx = Paths.sound(sfx);
sound = FlxG.sound.play(sfx, event.volume);
}
}
event.sprite = sprite;
event.sound = sound;
event.spriteTween = tween;
event.cancelled = false;
var countdown:Countdown = new Countdown({
event: gameAndCharsEvent("onCountdown", EventManager.get(CountdownEvent).recycle(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do wish to address this particular hunk. What would be the use case of not being able to manage most of the properties of Countdown in the event itself, like with the other events in PlayState?

Copy link
Author

@Equinoxtic Equinoxtic Aug 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, yeah, now that I think about it, it does feel like it's set up a little oddly. I thought of making an event as a parameter and just handling it (the event) within the Countdown class itself. Now that this has been brought up I start to wonder whether or not it'll break some backend functionality but it seemed to work as intended.

Reverting back to old CNE code where it has an event variable (in PlayState's countdown() method) and using that in the event parameter when making a new Countdown makes no difference (readability wise, I think it's better).

It's an odd setup I know 😅, and my apologies if I replied late. I was asleep by that time. I'll try coming up with an alternative solution later. For now, this is all I've really got.

swagCounter,
1,
introSounds[swagCounter],
introSprites[swagCounter],
0.6, true, null, null, null)),
enabled: true,
playSound: true,
animationPreset: DEFAULT,
duration: (Conductor.crochet / 1000),
speed: 1.0
});
countdown.cameras = [camHUD];
add(countdown);

gameAndCharsEvent("onPostCountdown", event);
gameAndCharsEvent("onPostCountdown", countdown.event);
}

@:dox(hide) function startSong():Void
Expand Down