mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-07-12 22:28:03 +00:00
Merge branch 'rewrite/master' into preloader-polish
This commit is contained in:
commit
c23f97917c
|
@ -183,6 +183,7 @@
|
||||||
<haxedef name="haxeui_focus_out_on_click" />
|
<haxedef name="haxeui_focus_out_on_click" />
|
||||||
<!-- Required to use haxe.ui.backend.flixel.UIState with build macros. -->
|
<!-- Required to use haxe.ui.backend.flixel.UIState with build macros. -->
|
||||||
<haxedef name="haxeui_dont_impose_base_class" />
|
<haxedef name="haxeui_dont_impose_base_class" />
|
||||||
|
<haxedef name="HARDCODED_CREDITS" />
|
||||||
|
|
||||||
<!-- Skip the Intro -->
|
<!-- Skip the Intro -->
|
||||||
<section if="debug">
|
<section if="debug">
|
||||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit f462b181f13c016f79e022d08b4d23f61d4a7e8b
|
Subproject commit 3ccfe33acef6e62c40317af583af764838544a24
|
2
hmm.json
2
hmm.json
|
@ -139,7 +139,7 @@
|
||||||
"name": "openfl",
|
"name": "openfl",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "f229d76361c7e31025a048fe7909847f75bb5d5e",
|
"ref": "228c1b5063911e2ad75cef6e3168ef0a4b9f9134",
|
||||||
"url": "https://github.com/FunkinCrew/openfl"
|
"url": "https://github.com/FunkinCrew/openfl"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -59,6 +59,7 @@ abstract Tallies(RawTallies)
|
||||||
totalNotes: 0,
|
totalNotes: 0,
|
||||||
totalNotesHit: 0,
|
totalNotesHit: 0,
|
||||||
maxCombo: 0,
|
maxCombo: 0,
|
||||||
|
score: 0,
|
||||||
isNewHighscore: false
|
isNewHighscore: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,6 +82,9 @@ typedef RawTallies =
|
||||||
var good:Int;
|
var good:Int;
|
||||||
var sick:Int;
|
var sick:Int;
|
||||||
var maxCombo:Int;
|
var maxCombo:Int;
|
||||||
|
|
||||||
|
var score:Int;
|
||||||
|
|
||||||
var isNewHighscore:Bool;
|
var isNewHighscore:Bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,7 +9,7 @@ import openfl.utils.Assets as OpenFlAssets;
|
||||||
*/
|
*/
|
||||||
class Paths
|
class Paths
|
||||||
{
|
{
|
||||||
static var currentLevel:String;
|
static var currentLevel:Null<String> = null;
|
||||||
|
|
||||||
public static function setCurrentLevel(name:String):Void
|
public static function setCurrentLevel(name:String):Void
|
||||||
{
|
{
|
||||||
|
|
|
@ -265,10 +265,16 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||||
@:allow(flixel.sound.FlxSoundGroup)
|
@:allow(flixel.sound.FlxSoundGroup)
|
||||||
override function updateTransform():Void
|
override function updateTransform():Void
|
||||||
{
|
{
|
||||||
_transform.volume = #if FLX_SOUND_SYSTEM ((FlxG.sound.muted || this.muted) ? 0 : 1) * FlxG.sound.volume * #end
|
if (_transform != null)
|
||||||
(group != null ? group.volume : 1) * _volume * _volumeAdjust;
|
{
|
||||||
|
_transform.volume = #if FLX_SOUND_SYSTEM ((FlxG.sound.muted || this.muted) ? 0 : 1) * FlxG.sound.volume * #end
|
||||||
|
(group != null ? group.volume : 1) * _volume * _volumeAdjust;
|
||||||
|
}
|
||||||
|
|
||||||
if (_channel != null) _channel.soundTransform = _transform;
|
if (_channel != null)
|
||||||
|
{
|
||||||
|
_channel.soundTransform = _transform;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clone():FunkinSound
|
public function clone():FunkinSound
|
||||||
|
@ -315,6 +321,13 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FlxG.sound.music != null)
|
||||||
|
{
|
||||||
|
FlxG.sound.music.fadeTween?.cancel();
|
||||||
|
FlxG.sound.music.stop();
|
||||||
|
FlxG.sound.music.kill();
|
||||||
|
}
|
||||||
|
|
||||||
if (params?.mapTimeChanges ?? true)
|
if (params?.mapTimeChanges ?? true)
|
||||||
{
|
{
|
||||||
var songMusicData:Null<SongMusicData> = SongRegistry.instance.parseMusicData(key);
|
var songMusicData:Null<SongMusicData> = SongRegistry.instance.parseMusicData(key);
|
||||||
|
@ -329,13 +342,6 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FlxG.sound.music != null)
|
|
||||||
{
|
|
||||||
FlxG.sound.music.fadeTween?.cancel();
|
|
||||||
FlxG.sound.music.stop();
|
|
||||||
FlxG.sound.music.kill();
|
|
||||||
}
|
|
||||||
|
|
||||||
var music = FunkinSound.load(Paths.music('$key/$key'), params?.startingVolume ?? 1.0, params.loop ?? true, false, true);
|
var music = FunkinSound.load(Paths.music('$key/$key'), params?.startingVolume ?? 1.0, params.loop ?? true, false, true);
|
||||||
if (music != null)
|
if (music != null)
|
||||||
{
|
{
|
||||||
|
@ -391,10 +397,10 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||||
sound._label = 'unknown';
|
sound._label = 'unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (autoPlay) sound.play();
|
||||||
sound.volume = volume;
|
sound.volume = volume;
|
||||||
sound.group = FlxG.sound.defaultSoundGroup;
|
sound.group = FlxG.sound.defaultSoundGroup;
|
||||||
sound.persist = true;
|
sound.persist = true;
|
||||||
if (autoPlay) sound.play();
|
|
||||||
|
|
||||||
// Call onLoad() because the sound already loaded
|
// Call onLoad() because the sound already loaded
|
||||||
if (onLoad != null && sound._sound != null) onLoad();
|
if (onLoad != null && sound._sound != null) onLoad();
|
||||||
|
@ -402,10 +408,16 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||||
return sound;
|
return sound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:nullSafety(Off)
|
||||||
public override function destroy():Void
|
public override function destroy():Void
|
||||||
{
|
{
|
||||||
// trace('[FunkinSound] Destroying sound "${this._label}"');
|
// trace('[FunkinSound] Destroying sound "${this._label}"');
|
||||||
super.destroy();
|
super.destroy();
|
||||||
|
if (fadeTween != null)
|
||||||
|
{
|
||||||
|
fadeTween.cancel();
|
||||||
|
fadeTween = null;
|
||||||
|
}
|
||||||
FlxTween.cancelTweensOf(this);
|
FlxTween.cancelTweensOf(this);
|
||||||
this._label = 'unknown';
|
this._label = 'unknown';
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,7 +150,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||||
/**
|
/**
|
||||||
* Stop all the sounds in the group.
|
* Stop all the sounds in the group.
|
||||||
*/
|
*/
|
||||||
public function stop()
|
public function stop():Void
|
||||||
{
|
{
|
||||||
if (members != null)
|
if (members != null)
|
||||||
{
|
{
|
||||||
|
@ -160,7 +160,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function destroy()
|
public override function destroy():Void
|
||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
super.destroy();
|
super.destroy();
|
||||||
|
@ -178,9 +178,14 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||||
|
|
||||||
function get_time():Float
|
function get_time():Float
|
||||||
{
|
{
|
||||||
if (getFirstAlive() != null) return getFirstAlive().time;
|
if (getFirstAlive() != null)
|
||||||
|
{
|
||||||
|
return getFirstAlive().time;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_time(time:Float):Float
|
function set_time(time:Float):Float
|
||||||
|
@ -195,16 +200,26 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||||
|
|
||||||
function get_playing():Bool
|
function get_playing():Bool
|
||||||
{
|
{
|
||||||
if (getFirstAlive() != null) return getFirstAlive().playing;
|
if (getFirstAlive() != null)
|
||||||
|
{
|
||||||
|
return getFirstAlive().playing;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_volume():Float
|
function get_volume():Float
|
||||||
{
|
{
|
||||||
if (getFirstAlive() != null) return getFirstAlive().volume;
|
if (getFirstAlive() != null)
|
||||||
|
{
|
||||||
|
return getFirstAlive().volume;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
return 1;
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// in PlayState, adjust the code so that it only mutes the player1 vocal tracks?
|
// in PlayState, adjust the code so that it only mutes the player1 vocal tracks?
|
||||||
|
|
|
@ -159,10 +159,18 @@ class VoicesGroup extends SoundGroup
|
||||||
|
|
||||||
public override function destroy():Void
|
public override function destroy():Void
|
||||||
{
|
{
|
||||||
playerVoices.destroy();
|
if (playerVoices != null)
|
||||||
playerVoices = null;
|
{
|
||||||
opponentVoices.destroy();
|
playerVoices.destroy();
|
||||||
opponentVoices = null;
|
playerVoices = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opponentVoices != null)
|
||||||
|
{
|
||||||
|
opponentVoices.destroy();
|
||||||
|
opponentVoices = null;
|
||||||
|
}
|
||||||
|
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -325,12 +325,3 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A pair of a file name and its contents.
|
|
||||||
*/
|
|
||||||
typedef JsonFile =
|
|
||||||
{
|
|
||||||
fileName:String,
|
|
||||||
contents:String
|
|
||||||
};
|
|
||||||
|
|
10
source/funkin/data/JsonFile.hx
Normal file
10
source/funkin/data/JsonFile.hx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package funkin.data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pair of a file name and its contents.
|
||||||
|
*/
|
||||||
|
typedef JsonFile =
|
||||||
|
{
|
||||||
|
fileName:String,
|
||||||
|
contents:String
|
||||||
|
};
|
|
@ -1,5 +1,7 @@
|
||||||
package funkin.data.freeplay;
|
package funkin.data.freeplay;
|
||||||
|
|
||||||
|
import funkin.data.animation.AnimationData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A type definition for the data for an album of songs.
|
* A type definition for the data for an album of songs.
|
||||||
* It includes things like what graphics to display in Freeplay.
|
* It includes things like what graphics to display in Freeplay.
|
||||||
|
@ -33,4 +35,11 @@ typedef AlbumData =
|
||||||
* The album title will be displayed below the album art in Freeplay.
|
* The album title will be displayed below the album art in Freeplay.
|
||||||
*/
|
*/
|
||||||
public var albumTitleAsset:String;
|
public var albumTitleAsset:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional array of animations for the album title.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default([])
|
||||||
|
public var albumTitleAnimations:Array<AnimationData>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -427,7 +427,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||||
return ScriptedSong.listScriptClasses();
|
return ScriptedSong.listScriptClasses();
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadEntryMetadataFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
|
function loadEntryMetadataFile(id:String, ?variation:String):Null<JsonFile>
|
||||||
{
|
{
|
||||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||||
var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}');
|
var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}');
|
||||||
|
@ -442,7 +442,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||||
return {fileName: entryFilePath, contents: rawJson};
|
return {fileName: entryFilePath, contents: rawJson};
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadMusicDataFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
|
function loadMusicDataFile(id:String, ?variation:String):Null<JsonFile>
|
||||||
{
|
{
|
||||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||||
var entryFilePath:String = Paths.file('music/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}.json');
|
var entryFilePath:String = Paths.file('music/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}.json');
|
||||||
|
@ -460,7 +460,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||||
return openfl.Assets.exists(entryFilePath);
|
return openfl.Assets.exists(entryFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadEntryChartFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
|
function loadEntryChartFile(id:String, ?variation:String):Null<JsonFile>
|
||||||
{
|
{
|
||||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||||
var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-chart${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}');
|
var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-chart${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}');
|
||||||
|
|
|
@ -8,7 +8,12 @@ import funkin.modding.IScriptedClass;
|
||||||
*/
|
*/
|
||||||
class ScriptEventDispatcher
|
class ScriptEventDispatcher
|
||||||
{
|
{
|
||||||
public static function callEvent(target:IScriptedClass, event:ScriptEvent):Void
|
/**
|
||||||
|
* Invoke the given event hook on the given scripted class.
|
||||||
|
* @param target The target class to call script hooks on.
|
||||||
|
* @param event The event, which determines the script hook to call and provides parameters for it.
|
||||||
|
*/
|
||||||
|
public static function callEvent(target:Null<IScriptedClass>, event:ScriptEvent):Void
|
||||||
{
|
{
|
||||||
if (target == null || event == null) return;
|
if (target == null || event == null) return;
|
||||||
|
|
||||||
|
|
|
@ -3,18 +3,18 @@ package funkin.play;
|
||||||
import flixel.FlxG;
|
import flixel.FlxG;
|
||||||
import flixel.FlxObject;
|
import flixel.FlxObject;
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import funkin.audio.FunkinSound;
|
import flixel.input.touch.FlxTouch;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
|
import funkin.audio.FunkinSound;
|
||||||
import funkin.graphics.FunkinSprite;
|
import funkin.graphics.FunkinSprite;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
import funkin.modding.events.ScriptEventDispatcher;
|
import funkin.modding.events.ScriptEventDispatcher;
|
||||||
import funkin.play.character.BaseCharacter;
|
import funkin.play.character.BaseCharacter;
|
||||||
import funkin.play.PlayState;
|
|
||||||
import funkin.util.MathUtil;
|
|
||||||
import funkin.ui.freeplay.FreeplayState;
|
import funkin.ui.freeplay.FreeplayState;
|
||||||
import funkin.ui.MusicBeatSubState;
|
import funkin.ui.MusicBeatSubState;
|
||||||
import funkin.ui.story.StoryMenuState;
|
import funkin.ui.story.StoryMenuState;
|
||||||
|
import funkin.util.MathUtil;
|
||||||
import openfl.utils.Assets;
|
import openfl.utils.Assets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,13 +23,14 @@ import openfl.utils.Assets;
|
||||||
*
|
*
|
||||||
* The newest implementation uses a substate, which prevents having to reload the song and stage each reset.
|
* The newest implementation uses a substate, which prevents having to reload the song and stage each reset.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class GameOverSubState extends MusicBeatSubState
|
class GameOverSubState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The currently active GameOverSubState.
|
* The currently active GameOverSubState.
|
||||||
* There should be only one GameOverSubState in existance at a time, we can use a singleton.
|
* There should be only one GameOverSubState in existance at a time, we can use a singleton.
|
||||||
*/
|
*/
|
||||||
public static var instance:GameOverSubState = null;
|
public static var instance:Null<GameOverSubState> = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which alternate animation on the character to use.
|
* Which alternate animation on the character to use.
|
||||||
|
@ -37,7 +38,7 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
* For example, playing a different animation when BF dies in Week 4
|
* For example, playing a different animation when BF dies in Week 4
|
||||||
* or Pico dies in Weekend 1.
|
* or Pico dies in Weekend 1.
|
||||||
*/
|
*/
|
||||||
public static var animationSuffix:String = "";
|
public static var animationSuffix:String = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which alternate game over music to use.
|
* Which alternate game over music to use.
|
||||||
|
@ -45,17 +46,19 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
* For example, the bf-pixel script sets this to `-pixel`
|
* For example, the bf-pixel script sets this to `-pixel`
|
||||||
* and the pico-playable script sets this to `Pico`.
|
* and the pico-playable script sets this to `Pico`.
|
||||||
*/
|
*/
|
||||||
public static var musicSuffix:String = "";
|
public static var musicSuffix:String = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which alternate "blue ball" sound effect to use.
|
* Which alternate "blue ball" sound effect to use.
|
||||||
*/
|
*/
|
||||||
public static var blueBallSuffix:String = "";
|
public static var blueBallSuffix:String = '';
|
||||||
|
|
||||||
|
static var blueballed:Bool = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The boyfriend character.
|
* The boyfriend character.
|
||||||
*/
|
*/
|
||||||
var boyfriend:BaseCharacter;
|
var boyfriend:Null<BaseCharacter> = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The invisible object in the scene which the camera focuses on.
|
* The invisible object in the scene which the camera focuses on.
|
||||||
|
@ -82,7 +85,8 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
|
|
||||||
var transparent:Bool;
|
var transparent:Bool;
|
||||||
|
|
||||||
final CAMERA_ZOOM_DURATION:Float = 0.5;
|
static final CAMERA_ZOOM_DURATION:Float = 0.5;
|
||||||
|
|
||||||
var targetCameraZoom:Float = 1.0;
|
var targetCameraZoom:Float = 1.0;
|
||||||
|
|
||||||
public function new(params:GameOverParams)
|
public function new(params:GameOverParams)
|
||||||
|
@ -91,6 +95,8 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
|
|
||||||
this.isChartingMode = params?.isChartingMode ?? false;
|
this.isChartingMode = params?.isChartingMode ?? false;
|
||||||
transparent = params.transparent;
|
transparent = params.transparent;
|
||||||
|
|
||||||
|
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,14 +107,15 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
animationSuffix = '';
|
animationSuffix = '';
|
||||||
musicSuffix = '';
|
musicSuffix = '';
|
||||||
blueBallSuffix = '';
|
blueBallSuffix = '';
|
||||||
|
blueballed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
override public function create():Void
|
public override function create():Void
|
||||||
{
|
{
|
||||||
if (instance != null)
|
if (instance != null)
|
||||||
{
|
{
|
||||||
// TODO: Do something in this case? IDK.
|
// TODO: Do something in this case? IDK.
|
||||||
trace('WARNING: GameOverSubState instance already exists. This should not happen.');
|
FlxG.log.warn('WARNING: GameOverSubState instance already exists. This should not happen.');
|
||||||
}
|
}
|
||||||
instance = this;
|
instance = this;
|
||||||
|
|
||||||
|
@ -121,7 +128,7 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
var playState = PlayState.instance;
|
var playState = PlayState.instance;
|
||||||
|
|
||||||
// Add a black background to the screen.
|
// Add a black background to the screen.
|
||||||
var bg = new FunkinSprite().makeSolidColor(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
||||||
// We make this transparent so that we can see the stage underneath during debugging,
|
// We make this transparent so that we can see the stage underneath during debugging,
|
||||||
// but it's normally opaque.
|
// but it's normally opaque.
|
||||||
bg.alpha = transparent ? 0.25 : 1.0;
|
bg.alpha = transparent ? 0.25 : 1.0;
|
||||||
|
@ -138,21 +145,10 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
boyfriend.isDead = true;
|
boyfriend.isDead = true;
|
||||||
add(boyfriend);
|
add(boyfriend);
|
||||||
boyfriend.resetCharacter();
|
boyfriend.resetCharacter();
|
||||||
|
|
||||||
// Assign a camera follow point to the boyfriend's position.
|
|
||||||
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
|
|
||||||
cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x;
|
|
||||||
cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y;
|
|
||||||
var offsets:Array<Float> = boyfriend.getDeathCameraOffsets();
|
|
||||||
cameraFollowPoint.x += offsets[0];
|
|
||||||
cameraFollowPoint.y += offsets[1];
|
|
||||||
add(cameraFollowPoint);
|
|
||||||
|
|
||||||
FlxG.camera.target = null;
|
|
||||||
FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.01);
|
|
||||||
targetCameraZoom = PlayState?.instance?.currentStage?.camZoom * boyfriend.getDeathCameraZoom();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCameraTarget();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Set up the audio
|
// Set up the audio
|
||||||
//
|
//
|
||||||
|
@ -161,6 +157,27 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
Conductor.instance.update(0);
|
Conductor.instance.update(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:nullSafety(Off)
|
||||||
|
function setCameraTarget():Void
|
||||||
|
{
|
||||||
|
// Assign a camera follow point to the boyfriend's position.
|
||||||
|
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
|
||||||
|
cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x;
|
||||||
|
cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y;
|
||||||
|
var offsets:Array<Float> = boyfriend.getDeathCameraOffsets();
|
||||||
|
cameraFollowPoint.x += offsets[0];
|
||||||
|
cameraFollowPoint.y += offsets[1];
|
||||||
|
add(cameraFollowPoint);
|
||||||
|
|
||||||
|
FlxG.camera.target = null;
|
||||||
|
FlxG.camera.follow(cameraFollowPoint, LOCKON, Constants.DEFAULT_CAMERA_FOLLOW_RATE / 2);
|
||||||
|
targetCameraZoom = (PlayState?.instance?.currentStage?.camZoom ?? 1.0) * boyfriend.getDeathCameraZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forcibly reset the camera zoom level to that of the current stage.
|
||||||
|
* This prevents camera zoom events from adversely affecting the game over state.
|
||||||
|
*/
|
||||||
public function resetCameraZoom():Void
|
public function resetCameraZoom():Void
|
||||||
{
|
{
|
||||||
// Apply camera zoom level from stage data.
|
// Apply camera zoom level from stage data.
|
||||||
|
@ -175,7 +192,7 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
hasStartedAnimation = true;
|
hasStartedAnimation = true;
|
||||||
|
|
||||||
if (PlayState.instance.isMinimalMode)
|
if (boyfriend == null || PlayState.instance.isMinimalMode)
|
||||||
{
|
{
|
||||||
// Play the "blue balled" sound. May play a variant if one has been assigned.
|
// Play the "blue balled" sound. May play a variant if one has been assigned.
|
||||||
playBlueBalledSFX();
|
playBlueBalledSFX();
|
||||||
|
@ -205,10 +222,10 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
// MOBILE ONLY: Restart the level when tapping Boyfriend.
|
// MOBILE ONLY: Restart the level when tapping Boyfriend.
|
||||||
if (FlxG.onMobile)
|
if (FlxG.onMobile)
|
||||||
{
|
{
|
||||||
var touch = FlxG.touches.getFirst();
|
var touch:FlxTouch = FlxG.touches.getFirst();
|
||||||
if (touch != null)
|
if (touch != null)
|
||||||
{
|
{
|
||||||
if (touch.overlaps(boyfriend))
|
if (boyfriend == null || touch.overlaps(boyfriend))
|
||||||
{
|
{
|
||||||
confirmDeath();
|
confirmDeath();
|
||||||
}
|
}
|
||||||
|
@ -228,7 +245,7 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
blueballed = false;
|
blueballed = false;
|
||||||
PlayState.instance.deathCounter = 0;
|
PlayState.instance.deathCounter = 0;
|
||||||
// PlayState.seenCutscene = false; // old thing...
|
// PlayState.seenCutscene = false; // old thing...
|
||||||
gameOverMusic.stop();
|
if (gameOverMusic != null) gameOverMusic.stop();
|
||||||
|
|
||||||
if (isChartingMode)
|
if (isChartingMode)
|
||||||
{
|
{
|
||||||
|
@ -238,11 +255,11 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
else if (PlayStatePlaylist.isStoryMode)
|
else if (PlayStatePlaylist.isStoryMode)
|
||||||
{
|
{
|
||||||
FlxG.switchState(() -> new StoryMenuState());
|
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FlxG.switchState(() -> new FreeplayState());
|
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(sticker)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +269,7 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
// This enables the stepHit and beatHit events.
|
// This enables the stepHit and beatHit events.
|
||||||
Conductor.instance.update(gameOverMusic.time);
|
Conductor.instance.update(gameOverMusic.time);
|
||||||
}
|
}
|
||||||
else
|
else if (boyfriend != null)
|
||||||
{
|
{
|
||||||
if (PlayState.instance.isMinimalMode)
|
if (PlayState.instance.isMinimalMode)
|
||||||
{
|
{
|
||||||
|
@ -299,7 +316,7 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
isEnding = true;
|
isEnding = true;
|
||||||
startDeathMusic(1.0, true); // isEnding changes this function's behavior.
|
startDeathMusic(1.0, true); // isEnding changes this function's behavior.
|
||||||
|
|
||||||
if (PlayState.instance.isMinimalMode) {}
|
if (PlayState.instance.isMinimalMode || boyfriend == null) {}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
boyfriend.playAnimation('deathConfirm' + animationSuffix, true);
|
boyfriend.playAnimation('deathConfirm' + animationSuffix, true);
|
||||||
|
@ -313,7 +330,7 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
|
FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
|
||||||
PlayState.instance.needsReset = true;
|
PlayState.instance.needsReset = true;
|
||||||
|
|
||||||
if (PlayState.instance.isMinimalMode) {}
|
if (PlayState.instance.isMinimalMode || boyfriend == null) {}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Readd Boyfriend to the stage.
|
// Readd Boyfriend to the stage.
|
||||||
|
@ -332,7 +349,7 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function dispatchEvent(event:ScriptEvent)
|
public override function dispatchEvent(event:ScriptEvent):Void
|
||||||
{
|
{
|
||||||
super.dispatchEvent(event);
|
super.dispatchEvent(event);
|
||||||
|
|
||||||
|
@ -345,11 +362,11 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
function resolveMusicPath(suffix:String, starting:Bool = false, ending:Bool = false):Null<String>
|
function resolveMusicPath(suffix:String, starting:Bool = false, ending:Bool = false):Null<String>
|
||||||
{
|
{
|
||||||
var basePath = 'gameplay/gameover/gameOver';
|
var basePath:String = 'gameplay/gameover/gameOver';
|
||||||
if (starting) basePath += 'Start';
|
if (ending) basePath += 'End';
|
||||||
else if (ending) basePath += 'End';
|
else if (starting) basePath += 'Start';
|
||||||
|
|
||||||
var musicPath = Paths.music(basePath + suffix);
|
var musicPath:String = Paths.music(basePath + suffix);
|
||||||
while (!Assets.exists(musicPath) && suffix.length > 0)
|
while (!Assets.exists(musicPath) && suffix.length > 0)
|
||||||
{
|
{
|
||||||
suffix = suffix.split('-').slice(0, -1).join('-');
|
suffix = suffix.split('-').slice(0, -1).join('-');
|
||||||
|
@ -362,23 +379,26 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the death music at the appropriate volume.
|
* Starts the death music at the appropriate volume.
|
||||||
* @param startingVolume
|
* @param startingVolume The initial volume for the music.
|
||||||
|
* @param force Whether or not to force the music to restart.
|
||||||
*/
|
*/
|
||||||
public function startDeathMusic(startingVolume:Float = 1, force:Bool = false):Void
|
public function startDeathMusic(startingVolume:Float = 1, force:Bool = false):Void
|
||||||
{
|
{
|
||||||
var musicPath = resolveMusicPath(musicSuffix, isStarting, isEnding);
|
var musicPath:Null<String> = resolveMusicPath(musicSuffix, isStarting, isEnding);
|
||||||
var onComplete = null;
|
var onComplete:() -> Void = () -> {};
|
||||||
|
|
||||||
if (isStarting)
|
if (isStarting)
|
||||||
{
|
{
|
||||||
if (musicPath == null)
|
if (musicPath == null)
|
||||||
{
|
{
|
||||||
|
// Looked for starting music and didn't find it. Use middle music instead.
|
||||||
isStarting = false;
|
isStarting = false;
|
||||||
musicPath = resolveMusicPath(musicSuffix, isStarting, isEnding);
|
musicPath = resolveMusicPath(musicSuffix, isStarting, isEnding);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
onComplete = function() {
|
onComplete = function() {
|
||||||
isStarting = false;
|
isStarting = true;
|
||||||
// We need to force to ensure that the non-starting music plays.
|
// We need to force to ensure that the non-starting music plays.
|
||||||
startDeathMusic(1.0, true);
|
startDeathMusic(1.0, true);
|
||||||
};
|
};
|
||||||
|
@ -387,13 +407,16 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
|
|
||||||
if (musicPath == null)
|
if (musicPath == null)
|
||||||
{
|
{
|
||||||
trace('Could not find game over music!');
|
FlxG.log.warn('[GAMEOVER] Could not find game over music at path ($musicPath)!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (gameOverMusic == null || !gameOverMusic.playing || force)
|
else if (gameOverMusic == null || !gameOverMusic.playing || force)
|
||||||
{
|
{
|
||||||
if (gameOverMusic != null) gameOverMusic.stop();
|
if (gameOverMusic != null) gameOverMusic.stop();
|
||||||
|
|
||||||
gameOverMusic = FunkinSound.load(musicPath);
|
gameOverMusic = FunkinSound.load(musicPath);
|
||||||
|
if (gameOverMusic == null) return;
|
||||||
|
|
||||||
gameOverMusic.volume = startingVolume;
|
gameOverMusic.volume = startingVolume;
|
||||||
gameOverMusic.looped = !(isEnding || isStarting);
|
gameOverMusic.looped = !(isEnding || isStarting);
|
||||||
gameOverMusic.onComplete = onComplete;
|
gameOverMusic.onComplete = onComplete;
|
||||||
|
@ -406,13 +429,11 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static var blueballed:Bool = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play the sound effect that occurs when
|
* Play the sound effect that occurs when
|
||||||
* boyfriend's testicles get utterly annihilated.
|
* boyfriend's testicles get utterly annihilated.
|
||||||
*/
|
*/
|
||||||
public static function playBlueBalledSFX()
|
public static function playBlueBalledSFX():Void
|
||||||
{
|
{
|
||||||
blueballed = true;
|
blueballed = true;
|
||||||
if (Assets.exists(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix)))
|
if (Assets.exists(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix)))
|
||||||
|
@ -431,7 +452,7 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
* Week 7-specific hardcoded behavior, to play a custom death quote.
|
* Week 7-specific hardcoded behavior, to play a custom death quote.
|
||||||
* TODO: Make this a module somehow.
|
* TODO: Make this a module somehow.
|
||||||
*/
|
*/
|
||||||
function playJeffQuote()
|
function playJeffQuote():Void
|
||||||
{
|
{
|
||||||
var randomCensor:Array<Int> = [];
|
var randomCensor:Array<Int> = [];
|
||||||
|
|
||||||
|
@ -446,20 +467,27 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function destroy()
|
public override function destroy():Void
|
||||||
{
|
{
|
||||||
super.destroy();
|
super.destroy();
|
||||||
if (gameOverMusic != null) gameOverMusic.stop();
|
if (gameOverMusic != null)
|
||||||
gameOverMusic = null;
|
{
|
||||||
|
gameOverMusic.stop();
|
||||||
|
gameOverMusic = null;
|
||||||
|
}
|
||||||
|
blueballed = false;
|
||||||
instance = null;
|
instance = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function toString():String
|
public override function toString():String
|
||||||
{
|
{
|
||||||
return "GameOverSubState";
|
return 'GameOverSubState';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters used to instantiate a GameOverSubState.
|
||||||
|
*/
|
||||||
typedef GameOverParams =
|
typedef GameOverParams =
|
||||||
{
|
{
|
||||||
var isChartingMode:Bool;
|
var isChartingMode:Bool;
|
||||||
|
|
|
@ -12,6 +12,7 @@ import flixel.tweens.FlxTween;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
import funkin.audio.FunkinSound;
|
import funkin.audio.FunkinSound;
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.data.song.SongRegistry;
|
||||||
|
import funkin.ui.freeplay.FreeplayState;
|
||||||
import funkin.graphics.FunkinSprite;
|
import funkin.graphics.FunkinSprite;
|
||||||
import funkin.play.cutscene.VideoCutscene;
|
import funkin.play.cutscene.VideoCutscene;
|
||||||
import funkin.play.PlayState;
|
import funkin.play.PlayState;
|
||||||
|
@ -72,8 +73,8 @@ class PauseSubState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
static final PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE:Array<PauseMenuEntry> = [
|
static final PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE:Array<PauseMenuEntry> = [
|
||||||
{text: 'Resume', callback: resume},
|
{text: 'Resume', callback: resume},
|
||||||
{text: 'Restart Cutscene', callback: restartVideoCutscene},
|
|
||||||
{text: 'Skip Cutscene', callback: skipVideoCutscene},
|
{text: 'Skip Cutscene', callback: skipVideoCutscene},
|
||||||
|
{text: 'Restart Cutscene', callback: restartVideoCutscene},
|
||||||
{text: 'Exit to Menu', callback: quitToMenu},
|
{text: 'Exit to Menu', callback: quitToMenu},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -230,7 +231,7 @@ class PauseSubState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
function startPauseMusic():Void
|
function startPauseMusic():Void
|
||||||
{
|
{
|
||||||
var pauseMusicPath:String = Paths.music('breakfast$musicSuffix');
|
var pauseMusicPath:String = Paths.music('breakfast$musicSuffix/breakfast$musicSuffix');
|
||||||
pauseMusic = FunkinSound.load(pauseMusicPath, true, true);
|
pauseMusic = FunkinSound.load(pauseMusicPath, true, true);
|
||||||
|
|
||||||
if (pauseMusic == null)
|
if (pauseMusic == null)
|
||||||
|
@ -567,6 +568,8 @@ class PauseSubState extends MusicBeatSubState
|
||||||
PlayStatePlaylist.campaignDifficulty = difficulty;
|
PlayStatePlaylist.campaignDifficulty = difficulty;
|
||||||
PlayState.instance.currentDifficulty = PlayStatePlaylist.campaignDifficulty;
|
PlayState.instance.currentDifficulty = PlayStatePlaylist.campaignDifficulty;
|
||||||
|
|
||||||
|
FreeplayState.rememberedDifficulty = difficulty;
|
||||||
|
|
||||||
PlayState.instance.needsReset = true;
|
PlayState.instance.needsReset = true;
|
||||||
|
|
||||||
state.close();
|
state.close();
|
||||||
|
@ -658,7 +661,7 @@ class PauseSubState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(null, sticker)));
|
state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(null, sticker)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -728,6 +728,10 @@ class PlayState extends MusicBeatSubState
|
||||||
#end
|
#end
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
|
// This step ensures z-indexes are applied properly,
|
||||||
|
// and it's important to call it last so all elements get affected.
|
||||||
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function draw():Void
|
public override function draw():Void
|
||||||
|
@ -1562,7 +1566,7 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
if (PlayState.instance.isMinimalMode) return;
|
if (PlayState.instance.isMinimalMode) return;
|
||||||
// Apply camera zoom level from stage data.
|
// Apply camera zoom level from stage data.
|
||||||
defaultCameraZoom = currentStage.camZoom;
|
defaultCameraZoom = currentStage?.camZoom ?? 1.0;
|
||||||
currentCameraZoom = defaultCameraZoom;
|
currentCameraZoom = defaultCameraZoom;
|
||||||
FlxG.camera.zoom = currentCameraZoom;
|
FlxG.camera.zoom = currentCameraZoom;
|
||||||
|
|
||||||
|
@ -1720,8 +1724,6 @@ class PlayState extends MusicBeatSubState
|
||||||
playerStrumline.fadeInArrows();
|
playerStrumline.fadeInArrows();
|
||||||
opponentStrumline.fadeInArrows();
|
opponentStrumline.fadeInArrows();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2731,7 +2733,7 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
public function endSong(rightGoddamnNow:Bool = false):Void
|
public function endSong(rightGoddamnNow:Bool = false):Void
|
||||||
{
|
{
|
||||||
FlxG.sound.music.volume = 0;
|
if (FlxG.sound.music != null) FlxG.sound.music.volume = 0;
|
||||||
vocals.volume = 0;
|
vocals.volume = 0;
|
||||||
mayPauseGame = false;
|
mayPauseGame = false;
|
||||||
|
|
||||||
|
@ -2749,6 +2751,8 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
deathCounter = 0;
|
deathCounter = 0;
|
||||||
|
|
||||||
|
var isNewHighscore = false;
|
||||||
|
|
||||||
if (currentSong != null && currentSong.validScore)
|
if (currentSong != null && currentSong.validScore)
|
||||||
{
|
{
|
||||||
// crackhead double thingie, sets whether was new highscore, AND saves the song!
|
// crackhead double thingie, sets whether was new highscore, AND saves the song!
|
||||||
|
@ -2779,11 +2783,14 @@ class PlayState extends MusicBeatSubState
|
||||||
#if newgrounds
|
#if newgrounds
|
||||||
NGio.postScore(score, currentSong.id);
|
NGio.postScore(score, currentSong.id);
|
||||||
#end
|
#end
|
||||||
|
isNewHighscore = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PlayStatePlaylist.isStoryMode)
|
if (PlayStatePlaylist.isStoryMode)
|
||||||
{
|
{
|
||||||
|
isNewHighscore = false;
|
||||||
|
|
||||||
PlayStatePlaylist.campaignScore += songScore;
|
PlayStatePlaylist.campaignScore += songScore;
|
||||||
|
|
||||||
// Pop the next song ID from the list.
|
// Pop the next song ID from the list.
|
||||||
|
@ -2792,18 +2799,6 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (targetSongId == null)
|
if (targetSongId == null)
|
||||||
{
|
{
|
||||||
FunkinSound.playMusic('freakyMenu',
|
|
||||||
{
|
|
||||||
overrideExisting: true,
|
|
||||||
restartTrack: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// transIn = FlxTransitionableState.defaultTransIn;
|
|
||||||
// transOut = FlxTransitionableState.defaultTransOut;
|
|
||||||
|
|
||||||
// TODO: Rework week unlock logic.
|
|
||||||
// StoryMenuState.weekUnlocked[Std.int(Math.min(storyWeek + 1, StoryMenuState.weekUnlocked.length - 1))] = true;
|
|
||||||
|
|
||||||
if (currentSong.validScore)
|
if (currentSong.validScore)
|
||||||
{
|
{
|
||||||
NGio.unlockMedal(60961);
|
NGio.unlockMedal(60961);
|
||||||
|
@ -2833,6 +2828,7 @@ class PlayState extends MusicBeatSubState
|
||||||
#if newgrounds
|
#if newgrounds
|
||||||
NGio.postScore(score, 'Level ${PlayStatePlaylist.campaignId}');
|
NGio.postScore(score, 'Level ${PlayStatePlaylist.campaignId}');
|
||||||
#end
|
#end
|
||||||
|
isNewHighscore = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2844,11 +2840,11 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
if (rightGoddamnNow)
|
if (rightGoddamnNow)
|
||||||
{
|
{
|
||||||
moveToResultsScreen();
|
moveToResultsScreen(isNewHighscore);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
zoomIntoResultsScreen();
|
zoomIntoResultsScreen(isNewHighscore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2909,11 +2905,11 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
if (rightGoddamnNow)
|
if (rightGoddamnNow)
|
||||||
{
|
{
|
||||||
moveToResultsScreen();
|
moveToResultsScreen(isNewHighscore);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
zoomIntoResultsScreen();
|
zoomIntoResultsScreen(isNewHighscore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2987,7 +2983,7 @@ class PlayState extends MusicBeatSubState
|
||||||
/**
|
/**
|
||||||
* Play the camera zoom animation and then move to the results screen once it's done.
|
* Play the camera zoom animation and then move to the results screen once it's done.
|
||||||
*/
|
*/
|
||||||
function zoomIntoResultsScreen():Void
|
function zoomIntoResultsScreen(isNewHighscore:Bool):Void
|
||||||
{
|
{
|
||||||
trace('WENT TO RESULTS SCREEN!');
|
trace('WENT TO RESULTS SCREEN!');
|
||||||
|
|
||||||
|
@ -3044,7 +3040,7 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
ease: FlxEase.expoIn,
|
ease: FlxEase.expoIn,
|
||||||
onComplete: function(_) {
|
onComplete: function(_) {
|
||||||
moveToResultsScreen();
|
moveToResultsScreen(isNewHighscore);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3053,7 +3049,7 @@ class PlayState extends MusicBeatSubState
|
||||||
/**
|
/**
|
||||||
* Move to the results screen right goddamn now.
|
* Move to the results screen right goddamn now.
|
||||||
*/
|
*/
|
||||||
function moveToResultsScreen():Void
|
function moveToResultsScreen(isNewHighscore:Bool):Void
|
||||||
{
|
{
|
||||||
persistentUpdate = false;
|
persistentUpdate = false;
|
||||||
vocals.stop();
|
vocals.stop();
|
||||||
|
@ -3065,7 +3061,24 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
storyMode: PlayStatePlaylist.isStoryMode,
|
storyMode: PlayStatePlaylist.isStoryMode,
|
||||||
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
|
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
|
||||||
tallies: talliesToUse,
|
scoreData:
|
||||||
|
{
|
||||||
|
score: songScore,
|
||||||
|
tallies:
|
||||||
|
{
|
||||||
|
sick: Highscore.tallies.sick,
|
||||||
|
good: Highscore.tallies.good,
|
||||||
|
bad: Highscore.tallies.bad,
|
||||||
|
shit: Highscore.tallies.shit,
|
||||||
|
missed: Highscore.tallies.missed,
|
||||||
|
combo: Highscore.tallies.combo,
|
||||||
|
maxCombo: Highscore.tallies.maxCombo,
|
||||||
|
totalNotesHit: Highscore.tallies.totalNotesHit,
|
||||||
|
totalNotes: Highscore.tallies.totalNotes,
|
||||||
|
},
|
||||||
|
accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
|
||||||
|
},
|
||||||
|
isNewHighscore: isNewHighscore
|
||||||
});
|
});
|
||||||
res.camera = camHUD;
|
res.camera = camHUD;
|
||||||
openSubState(res);
|
openSubState(res);
|
||||||
|
@ -3212,7 +3225,10 @@ class PlayState extends MusicBeatSubState
|
||||||
// Don't go back in time to before the song started.
|
// Don't go back in time to before the song started.
|
||||||
targetTimeMs = Math.max(0, targetTimeMs);
|
targetTimeMs = Math.max(0, targetTimeMs);
|
||||||
|
|
||||||
FlxG.sound.music.time = targetTimeMs;
|
if (FlxG.sound.music != null)
|
||||||
|
{
|
||||||
|
FlxG.sound.music.time = targetTimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
handleSkippedNotes();
|
handleSkippedNotes();
|
||||||
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
|
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
|
||||||
|
|
|
@ -5,12 +5,13 @@ package funkin.play;
|
||||||
*
|
*
|
||||||
* TODO: Add getters/setters for all these properties to validate them.
|
* TODO: Add getters/setters for all these properties to validate them.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class PlayStatePlaylist
|
class PlayStatePlaylist
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Whether the game is currently in Story Mode. If false, we are in Free Play Mode.
|
* Whether the game is currently in Story Mode. If false, we are in Free Play Mode.
|
||||||
*/
|
*/
|
||||||
public static var isStoryMode(default, default):Bool = false;
|
public static var isStoryMode:Bool = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The loist of upcoming songs to be played.
|
* The loist of upcoming songs to be played.
|
||||||
|
@ -31,8 +32,9 @@ class PlayStatePlaylist
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The internal ID of the current playlist, for example `week4` or `weekend-1`.
|
* The internal ID of the current playlist, for example `week4` or `weekend-1`.
|
||||||
|
* @default `null`, used when no playlist is loaded
|
||||||
*/
|
*/
|
||||||
public static var campaignId:String = 'unknown';
|
public static var campaignId:Null<String> = null;
|
||||||
|
|
||||||
public static var campaignDifficulty:String = Constants.DEFAULT_DIFFICULTY;
|
public static var campaignDifficulty:String = Constants.DEFAULT_DIFFICULTY;
|
||||||
|
|
||||||
|
@ -45,7 +47,7 @@ class PlayStatePlaylist
|
||||||
playlistSongIds = [];
|
playlistSongIds = [];
|
||||||
campaignScore = 0;
|
campaignScore = 0;
|
||||||
campaignTitle = 'UNKNOWN';
|
campaignTitle = 'UNKNOWN';
|
||||||
campaignId = 'unknown';
|
campaignId = null;
|
||||||
campaignDifficulty = Constants.DEFAULT_DIFFICULTY;
|
campaignDifficulty = Constants.DEFAULT_DIFFICULTY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package funkin.play;
|
package funkin.play;
|
||||||
|
|
||||||
|
import funkin.util.MathUtil;
|
||||||
import funkin.ui.story.StoryMenuState;
|
import funkin.ui.story.StoryMenuState;
|
||||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
|
@ -16,6 +17,8 @@ import flixel.tweens.FlxTween;
|
||||||
import funkin.audio.FunkinSound;
|
import funkin.audio.FunkinSound;
|
||||||
import flixel.util.FlxGradient;
|
import flixel.util.FlxGradient;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
|
import funkin.save.Save;
|
||||||
|
import funkin.save.Save.SaveScoreData;
|
||||||
import funkin.graphics.shaders.LeftMaskShader;
|
import funkin.graphics.shaders.LeftMaskShader;
|
||||||
import funkin.play.components.TallyCounter;
|
import funkin.play.components.TallyCounter;
|
||||||
|
|
||||||
|
@ -42,12 +45,15 @@ class ResultState extends MusicBeatSubState
|
||||||
|
|
||||||
override function create():Void
|
override function create():Void
|
||||||
{
|
{
|
||||||
if (params.tallies.sick == params.tallies.totalNotesHit
|
/*
|
||||||
&& params.tallies.maxCombo == params.tallies.totalNotesHit) resultsVariation = PERFECT;
|
if (params.scoreData.sick == params.scoreData.totalNotesHit
|
||||||
else if (params.tallies.missed + params.tallies.bad + params.tallies.shit >= params.tallies.totalNotes * 0.50)
|
&& params.scoreData.maxCombo == params.scoreData.totalNotesHit) resultsVariation = PERFECT;
|
||||||
resultsVariation = SHIT; // if more than half of your song was missed, bad, or shit notes, you get shit ending!
|
else if (params.scoreData.missed + params.scoreData.bad + params.scoreData.shit >= params.scoreData.totalNotes * 0.50)
|
||||||
else
|
resultsVariation = SHIT; // if more than half of your song was missed, bad, or shit notes, you get shit ending!
|
||||||
resultsVariation = NORMAL;
|
else
|
||||||
|
resultsVariation = NORMAL;
|
||||||
|
*/
|
||||||
|
resultsVariation = NORMAL;
|
||||||
|
|
||||||
FunkinSound.playMusic('results$resultsVariation',
|
FunkinSound.playMusic('results$resultsVariation',
|
||||||
{
|
{
|
||||||
|
@ -130,12 +136,16 @@ class ResultState extends MusicBeatSubState
|
||||||
|
|
||||||
var diffSpr:String = switch (PlayState.instance.currentDifficulty)
|
var diffSpr:String = switch (PlayState.instance.currentDifficulty)
|
||||||
{
|
{
|
||||||
case 'EASY':
|
case 'easy':
|
||||||
'difEasy';
|
'difEasy';
|
||||||
case 'NORMAL':
|
case 'normal':
|
||||||
'difNormal';
|
'difNormal';
|
||||||
case 'HARD':
|
case 'hard':
|
||||||
'difHard';
|
'difHard';
|
||||||
|
case 'erect':
|
||||||
|
'difErect';
|
||||||
|
case 'nightmare':
|
||||||
|
'difNightmare';
|
||||||
case _:
|
case _:
|
||||||
'difNormal';
|
'difNormal';
|
||||||
}
|
}
|
||||||
|
@ -195,29 +205,33 @@ class ResultState extends MusicBeatSubState
|
||||||
* NOTE: We display how many notes were HIT, not how many notes there were in total.
|
* NOTE: We display how many notes were HIT, not how many notes there were in total.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
var totalHit:TallyCounter = new TallyCounter(375, hStuf * 3, params.tallies.totalNotesHit);
|
var totalHit:TallyCounter = new TallyCounter(375, hStuf * 3, params.scoreData.tallies.totalNotesHit);
|
||||||
ratingGrp.add(totalHit);
|
ratingGrp.add(totalHit);
|
||||||
|
|
||||||
var maxCombo:TallyCounter = new TallyCounter(375, hStuf * 4, params.tallies.maxCombo);
|
var maxCombo:TallyCounter = new TallyCounter(375, hStuf * 4, params.scoreData.tallies.maxCombo);
|
||||||
ratingGrp.add(maxCombo);
|
ratingGrp.add(maxCombo);
|
||||||
|
|
||||||
hStuf += 2;
|
hStuf += 2;
|
||||||
var extraYOffset:Float = 5;
|
var extraYOffset:Float = 5;
|
||||||
var tallySick:TallyCounter = new TallyCounter(230, (hStuf * 5) + extraYOffset, params.tallies.sick, 0xFF89E59E);
|
var tallySick:TallyCounter = new TallyCounter(230, (hStuf * 5) + extraYOffset, params.scoreData.tallies.sick, 0xFF89E59E);
|
||||||
ratingGrp.add(tallySick);
|
ratingGrp.add(tallySick);
|
||||||
|
|
||||||
var tallyGood:TallyCounter = new TallyCounter(210, (hStuf * 6) + extraYOffset, params.tallies.good, 0xFF89C9E5);
|
var tallyGood:TallyCounter = new TallyCounter(210, (hStuf * 6) + extraYOffset, params.scoreData.tallies.good, 0xFF89C9E5);
|
||||||
ratingGrp.add(tallyGood);
|
ratingGrp.add(tallyGood);
|
||||||
|
|
||||||
var tallyBad:TallyCounter = new TallyCounter(190, (hStuf * 7) + extraYOffset, params.tallies.bad, 0xFFE6CF8A);
|
var tallyBad:TallyCounter = new TallyCounter(190, (hStuf * 7) + extraYOffset, params.scoreData.tallies.bad, 0xFFE6CF8A);
|
||||||
ratingGrp.add(tallyBad);
|
ratingGrp.add(tallyBad);
|
||||||
|
|
||||||
var tallyShit:TallyCounter = new TallyCounter(220, (hStuf * 8) + extraYOffset, params.tallies.shit, 0xFFE68C8A);
|
var tallyShit:TallyCounter = new TallyCounter(220, (hStuf * 8) + extraYOffset, params.scoreData.tallies.shit, 0xFFE68C8A);
|
||||||
ratingGrp.add(tallyShit);
|
ratingGrp.add(tallyShit);
|
||||||
|
|
||||||
var tallyMissed:TallyCounter = new TallyCounter(260, (hStuf * 9) + extraYOffset, params.tallies.missed, 0xFFC68AE6);
|
var tallyMissed:TallyCounter = new TallyCounter(260, (hStuf * 9) + extraYOffset, params.scoreData.tallies.missed, 0xFFC68AE6);
|
||||||
ratingGrp.add(tallyMissed);
|
ratingGrp.add(tallyMissed);
|
||||||
|
|
||||||
|
var score:TallyCounter = new TallyCounter(825, 630, params.scoreData.score, RIGHT);
|
||||||
|
score.scale.set(2, 2);
|
||||||
|
ratingGrp.add(score);
|
||||||
|
|
||||||
for (ind => rating in ratingGrp.members)
|
for (ind => rating in ratingGrp.members)
|
||||||
{
|
{
|
||||||
rating.visible = false;
|
rating.visible = false;
|
||||||
|
@ -235,9 +249,16 @@ class ResultState extends MusicBeatSubState
|
||||||
scorePopin.animation.play("score");
|
scorePopin.animation.play("score");
|
||||||
scorePopin.visible = true;
|
scorePopin.visible = true;
|
||||||
|
|
||||||
highscoreNew.visible = true;
|
if (params.isNewHighscore)
|
||||||
highscoreNew.animation.play("new");
|
{
|
||||||
FlxTween.tween(highscoreNew, {y: highscoreNew.y + 10}, 0.8, {ease: FlxEase.quartOut});
|
highscoreNew.visible = true;
|
||||||
|
highscoreNew.animation.play("new");
|
||||||
|
FlxTween.tween(highscoreNew, {y: highscoreNew.y + 10}, 0.8, {ease: FlxEase.quartOut});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
highscoreNew.visible = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (resultsVariation)
|
switch (resultsVariation)
|
||||||
|
@ -276,8 +297,6 @@ class ResultState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (params.tallies.isNewHighscore) trace("ITS A NEW HIGHSCORE!!!");
|
|
||||||
|
|
||||||
super.create();
|
super.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,7 +384,7 @@ class ResultState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new FreeplayState(null, sticker)));
|
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(null, sticker)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,8 +412,13 @@ typedef ResultsStateParams =
|
||||||
*/
|
*/
|
||||||
var title:String;
|
var title:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the displayed score is a new highscore
|
||||||
|
*/
|
||||||
|
var isNewHighscore:Bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The score, accuracy, and judgements.
|
* The score, accuracy, and judgements.
|
||||||
*/
|
*/
|
||||||
var tallies:Highscore.Tallies;
|
var scoreData:SaveScoreData;
|
||||||
};
|
};
|
||||||
|
|
|
@ -85,7 +85,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||||
comboSpr.velocity.y -= 150;
|
comboSpr.velocity.y -= 150;
|
||||||
comboSpr.velocity.x += FlxG.random.int(1, 10);
|
comboSpr.velocity.x += FlxG.random.int(1, 10);
|
||||||
|
|
||||||
add(comboSpr);
|
// add(comboSpr);
|
||||||
|
|
||||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,6 +6,8 @@ import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||||
import flixel.math.FlxMath;
|
import flixel.math.FlxMath;
|
||||||
import flixel.tweens.FlxEase;
|
import flixel.tweens.FlxEase;
|
||||||
import flixel.tweens.FlxTween;
|
import flixel.tweens.FlxTween;
|
||||||
|
import flixel.text.FlxText.FlxTextAlign;
|
||||||
|
import funkin.util.MathUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Numerical counters used next to each judgement in the Results screen.
|
* Numerical counters used next to each judgement in the Results screen.
|
||||||
|
@ -13,18 +15,23 @@ import flixel.tweens.FlxTween;
|
||||||
class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
{
|
{
|
||||||
public var curNumber:Float = 0;
|
public var curNumber:Float = 0;
|
||||||
|
|
||||||
public var neededNumber:Int = 0;
|
public var neededNumber:Int = 0;
|
||||||
|
|
||||||
public var flavour:Int = 0xFFFFFFFF;
|
public var flavour:Int = 0xFFFFFFFF;
|
||||||
|
|
||||||
public function new(x:Float, y:Float, neededNumber:Int = 0, ?flavour:Int = 0xFFFFFFFF)
|
public var align:FlxTextAlign = FlxTextAlign.LEFT;
|
||||||
|
|
||||||
|
public function new(x:Float, y:Float, neededNumber:Int = 0, ?flavour:Int = 0xFFFFFFFF, align:FlxTextAlign = FlxTextAlign.LEFT)
|
||||||
{
|
{
|
||||||
super(x, y);
|
super(x, y);
|
||||||
|
|
||||||
|
this.align = align;
|
||||||
|
|
||||||
this.flavour = flavour;
|
this.flavour = flavour;
|
||||||
|
|
||||||
this.neededNumber = neededNumber;
|
this.neededNumber = neededNumber;
|
||||||
drawNumbers();
|
|
||||||
|
if (curNumber == neededNumber) drawNumbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmr:Float = 0;
|
var tmr:Float = 0;
|
||||||
|
@ -41,6 +48,8 @@ class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
var seperatedScore:Array<Int> = [];
|
var seperatedScore:Array<Int> = [];
|
||||||
var tempCombo:Int = Math.round(curNumber);
|
var tempCombo:Int = Math.round(curNumber);
|
||||||
|
|
||||||
|
var fullNumberDigits:Int = Std.int(Math.max(1, Math.ceil(MathUtil.logBase(10, neededNumber))));
|
||||||
|
|
||||||
while (tempCombo != 0)
|
while (tempCombo != 0)
|
||||||
{
|
{
|
||||||
seperatedScore.push(tempCombo % 10);
|
seperatedScore.push(tempCombo % 10);
|
||||||
|
@ -55,7 +64,13 @@ class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
{
|
{
|
||||||
if (ind >= members.length)
|
if (ind >= members.length)
|
||||||
{
|
{
|
||||||
var numb:TallyNumber = new TallyNumber(ind * 43, 0, num);
|
var xPos = ind * (43 * this.scale.x);
|
||||||
|
if (this.align == FlxTextAlign.RIGHT)
|
||||||
|
{
|
||||||
|
xPos -= (fullNumberDigits * (43 * this.scale.x));
|
||||||
|
}
|
||||||
|
var numb:TallyNumber = new TallyNumber(xPos, 0, num);
|
||||||
|
numb.scale.set(this.scale.x, this.scale.y);
|
||||||
add(numb);
|
add(numb);
|
||||||
numb.color = flavour;
|
numb.color = flavour;
|
||||||
}
|
}
|
||||||
|
|
|
@ -295,6 +295,11 @@ class Strumline extends FlxSpriteGroup
|
||||||
{
|
{
|
||||||
if (noteData.length == 0) return;
|
if (noteData.length == 0) return;
|
||||||
|
|
||||||
|
// Ensure note data gets reset if the song happens to loop.
|
||||||
|
// NOTE: I had to remove this line because it was causing notes visible during the countdown to be placed multiple times.
|
||||||
|
// I don't remember what bug I was trying to fix by adding this.
|
||||||
|
// if (conductorInUse.currentStep == 0) nextNoteIndex = 0;
|
||||||
|
|
||||||
var songStart:Float = PlayState.instance?.startTimestamp ?? 0.0;
|
var songStart:Float = PlayState.instance?.startTimestamp ?? 0.0;
|
||||||
var hitWindowStart:Float = Conductor.instance.songPosition - Constants.HIT_WINDOW_MS;
|
var hitWindowStart:Float = Conductor.instance.songPosition - Constants.HIT_WINDOW_MS;
|
||||||
var renderWindowStart:Float = Conductor.instance.songPosition + RENDER_DISTANCE_MS;
|
var renderWindowStart:Float = Conductor.instance.songPosition + RENDER_DISTANCE_MS;
|
||||||
|
@ -822,7 +827,7 @@ class Strumline extends FlxSpriteGroup
|
||||||
{
|
{
|
||||||
// The note sprite pool is full and all note splashes are active.
|
// The note sprite pool is full and all note splashes are active.
|
||||||
// We have to create a new note.
|
// We have to create a new note.
|
||||||
result = new SustainTrail(0, 100, noteStyle);
|
result = new SustainTrail(0, 0, noteStyle);
|
||||||
this.holdNotes.add(result);
|
this.holdNotes.add(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -223,7 +223,8 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||||
|
|
||||||
if (propSprite.frames == null || propSprite.frames.numFrames == 0)
|
if (propSprite.frames == null || propSprite.frames.numFrames == 0)
|
||||||
{
|
{
|
||||||
trace(' ERROR: Could not build texture for prop.');
|
@:privateAccess
|
||||||
|
trace(' ERROR: Could not build texture for prop. Check the asset path (${Paths.currentLevel ?? 'default'}, ${dataProp.assetPath}).');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,13 @@ import funkin.save.migrator.SaveDataMigrator;
|
||||||
import funkin.ui.debug.charting.ChartEditorState.ChartEditorLiveInputStyle;
|
import funkin.ui.debug.charting.ChartEditorState.ChartEditorLiveInputStyle;
|
||||||
import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme;
|
import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme;
|
||||||
import thx.semver.Version;
|
import thx.semver.Version;
|
||||||
|
import funkin.util.SerializerUtil;
|
||||||
|
|
||||||
@:nullSafety
|
@:nullSafety
|
||||||
class Save
|
class Save
|
||||||
{
|
{
|
||||||
// Version 2.0.2 adds attributes to `optionsChartEditor`, that should return default values if they are null.
|
// Version 2.0.2 adds attributes to `optionsChartEditor`, that should return default values if they are null.
|
||||||
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.2";
|
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.3";
|
||||||
public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x";
|
public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x";
|
||||||
|
|
||||||
// We load this version's saves from a new save path, to maintain SOME level of backwards compatibility.
|
// We load this version's saves from a new save path, to maintain SOME level of backwards compatibility.
|
||||||
|
@ -391,6 +392,22 @@ class Save
|
||||||
*/
|
*/
|
||||||
public function getLevelScore(levelId:String, difficultyId:String = 'normal'):Null<SaveScoreData>
|
public function getLevelScore(levelId:String, difficultyId:String = 'normal'):Null<SaveScoreData>
|
||||||
{
|
{
|
||||||
|
if (data.scores?.levels == null)
|
||||||
|
{
|
||||||
|
if (data.scores == null)
|
||||||
|
{
|
||||||
|
data.scores =
|
||||||
|
{
|
||||||
|
songs: [],
|
||||||
|
levels: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data.scores.levels = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var level = data.scores.levels.get(levelId);
|
var level = data.scores.levels.get(levelId);
|
||||||
if (level == null)
|
if (level == null)
|
||||||
{
|
{
|
||||||
|
@ -641,6 +658,9 @@ class Save
|
||||||
{
|
{
|
||||||
trace("[SAVE] Loading save from slot " + slot + "...");
|
trace("[SAVE] Loading save from slot " + slot + "...");
|
||||||
|
|
||||||
|
// Prevent crashes if the save data is corrupted.
|
||||||
|
SerializerUtil.initSerializer();
|
||||||
|
|
||||||
FlxG.save.bind('$SAVE_NAME${slot}', SAVE_PATH);
|
FlxG.save.bind('$SAVE_NAME${slot}', SAVE_PATH);
|
||||||
|
|
||||||
if (FlxG.save.isEmpty())
|
if (FlxG.save.isEmpty())
|
||||||
|
@ -650,9 +670,9 @@ class Save
|
||||||
if (legacySaveData != null)
|
if (legacySaveData != null)
|
||||||
{
|
{
|
||||||
trace('[SAVE] Found legacy save data, converting...');
|
trace('[SAVE] Found legacy save data, converting...');
|
||||||
var gameSave = SaveDataMigrator.migrate(legacySaveData);
|
var gameSave = SaveDataMigrator.migrateFromLegacy(legacySaveData);
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
FlxG.save.mergeData(gameSave.data);
|
FlxG.save.mergeData(gameSave.data, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -664,7 +684,7 @@ class Save
|
||||||
trace('[SAVE] Loaded save data.');
|
trace('[SAVE] Loaded save data.');
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
var gameSave = SaveDataMigrator.migrate(FlxG.save.data);
|
var gameSave = SaveDataMigrator.migrate(FlxG.save.data);
|
||||||
FlxG.save.mergeData(gameSave.data);
|
FlxG.save.mergeData(gameSave.data, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package funkin.save.migrator;
|
||||||
import funkin.save.Save;
|
import funkin.save.Save;
|
||||||
import funkin.save.migrator.RawSaveData_v1_0_0;
|
import funkin.save.migrator.RawSaveData_v1_0_0;
|
||||||
import thx.semver.Version;
|
import thx.semver.Version;
|
||||||
|
import funkin.util.StructureUtil;
|
||||||
import funkin.util.VersionUtil;
|
import funkin.util.VersionUtil;
|
||||||
|
|
||||||
@:nullSafety
|
@:nullSafety
|
||||||
|
@ -26,7 +27,7 @@ class SaveDataMigrator
|
||||||
if (VersionUtil.validateVersion(version, Save.SAVE_DATA_VERSION_RULE))
|
if (VersionUtil.validateVersion(version, Save.SAVE_DATA_VERSION_RULE))
|
||||||
{
|
{
|
||||||
// Simply import the structured data.
|
// Simply import the structured data.
|
||||||
var save:Save = new Save(inputData);
|
var save:Save = new Save(StructureUtil.deepMerge(Save.getDefault(), inputData));
|
||||||
return save;
|
return save;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
34
source/funkin/ui/credits/CreditsData.hx
Normal file
34
source/funkin/ui/credits/CreditsData.hx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package funkin.ui.credits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The members of the Funkin' Crew, organized by their roles.
|
||||||
|
*/
|
||||||
|
typedef CreditsData =
|
||||||
|
{
|
||||||
|
var entries:Array<CreditsDataRole>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The members of a specific role on the Funkin' Crew.
|
||||||
|
*/
|
||||||
|
typedef CreditsDataRole =
|
||||||
|
{
|
||||||
|
@:optional
|
||||||
|
var header:String;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default([])
|
||||||
|
var body:Array<CreditsDataMember>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default(false)
|
||||||
|
var appendBackers:Bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A member of a specific person on the Funkin' Crew.
|
||||||
|
*/
|
||||||
|
typedef CreditsDataMember =
|
||||||
|
{
|
||||||
|
var line:String;
|
||||||
|
}
|
139
source/funkin/ui/credits/CreditsDataHandler.hx
Normal file
139
source/funkin/ui/credits/CreditsDataHandler.hx
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
package funkin.ui.credits;
|
||||||
|
|
||||||
|
import funkin.data.JsonFile;
|
||||||
|
|
||||||
|
using StringTools;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
|
class CreditsDataHandler
|
||||||
|
{
|
||||||
|
public static final BACKER_PUBLIC_URL:String = 'https://funkin.me/backers';
|
||||||
|
|
||||||
|
#if HARDCODED_CREDITS
|
||||||
|
static final CREDITS_DATA_PATH:String = "assets/exclude/data/credits.json";
|
||||||
|
#else
|
||||||
|
static final CREDITS_DATA_PATH:String = "assets/data/credits.json";
|
||||||
|
#end
|
||||||
|
|
||||||
|
public static function debugPrint(data:Null<CreditsData>):Void
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
trace('CreditsData(NULL)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.entries == null || data.entries.length == 0)
|
||||||
|
{
|
||||||
|
trace('CreditsData(EMPTY)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entryCount = data.entries.length;
|
||||||
|
var lineCount = 0;
|
||||||
|
for (entry in data.entries)
|
||||||
|
{
|
||||||
|
lineCount += entry?.body?.length ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
trace('CreditsData($entryCount entries containing $lineCount lines)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If for some reason the full credits won't load,
|
||||||
|
* use this hardcoded data for the original Funkin' Crew.
|
||||||
|
*
|
||||||
|
* @return `CreditsData`
|
||||||
|
*/
|
||||||
|
public static inline function getFallback():CreditsData
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
header: 'Founders',
|
||||||
|
body: [
|
||||||
|
{line: 'ninjamuffin99'},
|
||||||
|
{line: 'PhantomArcade'},
|
||||||
|
{line: 'KawaiSprite'},
|
||||||
|
{line: 'evilsk8r'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Kickstarter Backers',
|
||||||
|
appendBackers: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fetchBackerEntries():Array<String>
|
||||||
|
{
|
||||||
|
// TODO: Replace this with a web request.
|
||||||
|
// We can't just grab the current Kickstarter data and include it in builds,
|
||||||
|
// because we don't want to deadname people who haven't logged into the portal yet.
|
||||||
|
// It can be async and paginated for performance!
|
||||||
|
return ['See the list of backers at $BACKER_PUBLIC_URL.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
#if HARDCODED_CREDITS
|
||||||
|
/**
|
||||||
|
* The data for the credits.
|
||||||
|
* Hardcoded into game via a macro at compile time.
|
||||||
|
*/
|
||||||
|
public static final CREDITS_DATA:Null<CreditsData> = #if macro null #else CreditsDataMacro.loadCreditsData() #end;
|
||||||
|
#else
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data for the credits.
|
||||||
|
* Loaded dynamically from the game folder when needed.
|
||||||
|
* Nullable because data may fail to parse.
|
||||||
|
*/
|
||||||
|
public static var CREDITS_DATA(get, default):Null<CreditsData> = null;
|
||||||
|
|
||||||
|
static function get_CREDITS_DATA():Null<CreditsData>
|
||||||
|
{
|
||||||
|
if (CREDITS_DATA == null) CREDITS_DATA = parseCreditsData(fetchCreditsData());
|
||||||
|
|
||||||
|
return CREDITS_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function fetchCreditsData():funkin.data.JsonFile
|
||||||
|
{
|
||||||
|
var rawJson:String = openfl.Assets.getText(CREDITS_DATA_PATH).trim();
|
||||||
|
|
||||||
|
return {
|
||||||
|
fileName: CREDITS_DATA_PATH,
|
||||||
|
contents: rawJson
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static function parseCreditsData(file:JsonFile):Null<CreditsData>
|
||||||
|
{
|
||||||
|
#if !macro
|
||||||
|
if (file.contents == null) return null;
|
||||||
|
|
||||||
|
var parser = new json2object.JsonParser<CreditsData>();
|
||||||
|
parser.ignoreUnknownVariables = false;
|
||||||
|
trace('[CREDITS] Parsing credits data from ${CREDITS_DATA_PATH}');
|
||||||
|
parser.fromJson(file.contents, file.fileName);
|
||||||
|
|
||||||
|
if (parser.errors.length > 0)
|
||||||
|
{
|
||||||
|
printErrors(parser.errors, file.fileName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parser.value;
|
||||||
|
#else
|
||||||
|
return null;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
static function printErrors(errors:Array<json2object.Error>, id:String = ''):Void
|
||||||
|
{
|
||||||
|
trace('[CREDITS] Failed to parse credits data: ${id}');
|
||||||
|
|
||||||
|
for (error in errors)
|
||||||
|
funkin.data.DataError.printError(error);
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
}
|
67
source/funkin/ui/credits/CreditsDataMacro.hx
Normal file
67
source/funkin/ui/credits/CreditsDataMacro.hx
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package funkin.ui.credits;
|
||||||
|
|
||||||
|
#if macro
|
||||||
|
import haxe.macro.Context;
|
||||||
|
#end
|
||||||
|
|
||||||
|
@:access(funkin.ui.credits.CreditsDataHandler)
|
||||||
|
class CreditsDataMacro
|
||||||
|
{
|
||||||
|
public static macro function loadCreditsData():haxe.macro.Expr.ExprOf<CreditsData>
|
||||||
|
{
|
||||||
|
#if !display
|
||||||
|
trace('Hardcoding credits data...');
|
||||||
|
var json = CreditsDataMacro.fetchJSON();
|
||||||
|
|
||||||
|
if (json == null)
|
||||||
|
{
|
||||||
|
Context.info('[WARN] Could not fetch JSON data for credits.', Context.currentPos());
|
||||||
|
return macro $v{CreditsDataHandler.getFallback()};
|
||||||
|
}
|
||||||
|
|
||||||
|
var creditsData = CreditsDataMacro.parseJSON(json);
|
||||||
|
|
||||||
|
if (creditsData == null)
|
||||||
|
{
|
||||||
|
Context.info('[WARN] Could not parse JSON data for credits.', Context.currentPos());
|
||||||
|
return macro $v{CreditsDataHandler.getFallback()};
|
||||||
|
}
|
||||||
|
|
||||||
|
CreditsDataHandler.debugPrint(creditsData);
|
||||||
|
return macro $v{creditsData};
|
||||||
|
// return macro $v{null};
|
||||||
|
#else
|
||||||
|
// `#if display` is used for code completion. In this case we return
|
||||||
|
// a minimal value to keep code completion fast.
|
||||||
|
return macro $v{CreditsDataHandler.getFallback()};
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
#if macro
|
||||||
|
static function fetchJSON():Null<String>
|
||||||
|
{
|
||||||
|
return sys.io.File.getContent(CreditsDataHandler.CREDITS_DATA_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the JSON data for the credits.
|
||||||
|
*
|
||||||
|
* @param json The string data to parse.
|
||||||
|
* @return The parsed data.
|
||||||
|
*/
|
||||||
|
static function parseJSON(json:String):Null<CreditsData>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// TODO: Use something with better validation but that still works at macro time.
|
||||||
|
return haxe.Json.parse(json);
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
trace('[ERROR] Failed to parse JSON data for credits.');
|
||||||
|
trace(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
}
|
213
source/funkin/ui/credits/CreditsState.hx
Normal file
213
source/funkin/ui/credits/CreditsState.hx
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
package funkin.ui.credits;
|
||||||
|
|
||||||
|
import flixel.text.FlxText;
|
||||||
|
import flixel.util.FlxColor;
|
||||||
|
import funkin.audio.FunkinSound;
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import flixel.group.FlxSpriteGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state used to display the credits scroll.
|
||||||
|
* AAA studios often fail to credit properly, and we're better than them!
|
||||||
|
*/
|
||||||
|
class CreditsState extends MusicBeatState
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The height the credits should start at.
|
||||||
|
* Make this an instanced variable so it gets set by the constructor.
|
||||||
|
*/
|
||||||
|
final STARTING_HEIGHT = FlxG.height;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The padding on each side of the screen.
|
||||||
|
*/
|
||||||
|
static final SCREEN_PAD = 24;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The width of the screen the credits should maximally fill up.
|
||||||
|
* Make this an instanced variable so it gets set by the constructor.
|
||||||
|
*/
|
||||||
|
final FULL_WIDTH = FlxG.width - (SCREEN_PAD * 2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The font to use to display the text.
|
||||||
|
* To use a font from the `assets` folder, use `Paths.font(...)`.
|
||||||
|
* Choose something that will render Unicode properly.
|
||||||
|
*/
|
||||||
|
static final CREDITS_FONT = 'Arial';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of the font.
|
||||||
|
*/
|
||||||
|
static final CREDITS_FONT_SIZE = 48;
|
||||||
|
|
||||||
|
static final CREDITS_HEADER_FONT_SIZE = 72;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color of the text itself.
|
||||||
|
*/
|
||||||
|
static final CREDITS_FONT_COLOR = FlxColor.WHITE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color of the text's outline.
|
||||||
|
*/
|
||||||
|
static final CREDITS_FONT_STROKE_COLOR = FlxColor.BLACK;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The speed the credits scroll at, in pixels per second.
|
||||||
|
*/
|
||||||
|
static final CREDITS_SCROLL_BASE_SPEED = 25.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The speed the credits scroll at while the button is held, in pixels per second.
|
||||||
|
*/
|
||||||
|
static final CREDITS_SCROLL_FAST_SPEED = CREDITS_SCROLL_BASE_SPEED * 4.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual sprites and text used to display the credits.
|
||||||
|
*/
|
||||||
|
var creditsGroup:FlxSpriteGroup;
|
||||||
|
|
||||||
|
var scrollPaused:Bool = false;
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function create():Void
|
||||||
|
{
|
||||||
|
super.create();
|
||||||
|
|
||||||
|
// Background
|
||||||
|
var bg = new FlxSprite(Paths.image('menuDesat'));
|
||||||
|
bg.scrollFactor.x = 0;
|
||||||
|
bg.scrollFactor.y = 0;
|
||||||
|
bg.setGraphicSize(Std.int(FlxG.width));
|
||||||
|
bg.updateHitbox();
|
||||||
|
bg.x = 0;
|
||||||
|
bg.y = 0;
|
||||||
|
bg.visible = true;
|
||||||
|
bg.color = 0xFFB57EDC; // Lavender
|
||||||
|
add(bg);
|
||||||
|
|
||||||
|
// TODO: Once we need to display Kickstarter backers,
|
||||||
|
// make this use a recycled pool so we don't kill peformance.
|
||||||
|
creditsGroup = new FlxSpriteGroup();
|
||||||
|
creditsGroup.x = SCREEN_PAD;
|
||||||
|
creditsGroup.y = STARTING_HEIGHT;
|
||||||
|
|
||||||
|
buildCreditsGroup();
|
||||||
|
|
||||||
|
add(creditsGroup);
|
||||||
|
|
||||||
|
// Music
|
||||||
|
FunkinSound.playMusic('freeplayRandom',
|
||||||
|
{
|
||||||
|
startingVolume: 0.0,
|
||||||
|
overrideExisting: true,
|
||||||
|
restartTrack: true,
|
||||||
|
loop: true
|
||||||
|
});
|
||||||
|
FlxG.sound.music.fadeIn(2, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCreditsGroup():Void
|
||||||
|
{
|
||||||
|
var y = 0;
|
||||||
|
|
||||||
|
for (entry in CreditsDataHandler.CREDITS_DATA.entries)
|
||||||
|
{
|
||||||
|
if (entry.header != null)
|
||||||
|
{
|
||||||
|
creditsGroup.add(buildCreditsLine(entry.header, y, true, CreditsSide.Center));
|
||||||
|
y += CREDITS_HEADER_FONT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (line in entry?.body ?? [])
|
||||||
|
{
|
||||||
|
creditsGroup.add(buildCreditsLine(line.line, y, false, CreditsSide.Center));
|
||||||
|
y += CREDITS_FONT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.appendBackers)
|
||||||
|
{
|
||||||
|
var backers = CreditsDataHandler.fetchBackerEntries();
|
||||||
|
for (backer in backers)
|
||||||
|
{
|
||||||
|
creditsGroup.add(buildCreditsLine(backer, y, false, CreditsSide.Center));
|
||||||
|
y += CREDITS_FONT_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Padding between each role.
|
||||||
|
y += CREDITS_FONT_SIZE * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCreditsLine(text:String, yPos:Float, header:Bool, side:CreditsSide = CreditsSide.Center):FlxText
|
||||||
|
{
|
||||||
|
// CreditsSide.Center: Full screen width
|
||||||
|
// CreditsSide.Left: Left half of screen
|
||||||
|
// CreditsSide.Right: Right half of screen
|
||||||
|
var xPos = (side == CreditsSide.Right) ? (FULL_WIDTH / 2) : 0;
|
||||||
|
var width = (side == CreditsSide.Center) ? FULL_WIDTH : (FULL_WIDTH / 2);
|
||||||
|
var size = header ? CREDITS_HEADER_FONT_SIZE : CREDITS_FONT_SIZE;
|
||||||
|
|
||||||
|
var creditsLine:FlxText = new FlxText(xPos, yPos, width, text);
|
||||||
|
creditsLine.setFormat(CREDITS_FONT, size, CREDITS_FONT_COLOR, FlxTextAlign.CENTER, FlxTextBorderStyle.OUTLINE, CREDITS_FONT_STROKE_COLOR, true);
|
||||||
|
|
||||||
|
return creditsLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function update(elapsed:Float):Void
|
||||||
|
{
|
||||||
|
super.update(elapsed);
|
||||||
|
|
||||||
|
if (!scrollPaused)
|
||||||
|
{
|
||||||
|
// TODO: Replace with whatever the special note button is.
|
||||||
|
if (controls.ACCEPT || FlxG.keys.pressed.SPACE)
|
||||||
|
{
|
||||||
|
// Move the whole group.
|
||||||
|
creditsGroup.y -= CREDITS_SCROLL_FAST_SPEED * elapsed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Move the whole group.
|
||||||
|
creditsGroup.y -= CREDITS_SCROLL_BASE_SPEED * elapsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controls.BACK || hasEnded())
|
||||||
|
{
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
else if (controls.PAUSE)
|
||||||
|
{
|
||||||
|
scrollPaused = !scrollPaused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasEnded():Bool
|
||||||
|
{
|
||||||
|
return creditsGroup.y < -creditsGroup.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
function exit():Void
|
||||||
|
{
|
||||||
|
FlxG.switchState(new funkin.ui.mainmenu.MainMenuState());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function destroy():Void
|
||||||
|
{
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CreditsSide
|
||||||
|
{
|
||||||
|
Left;
|
||||||
|
Center;
|
||||||
|
Right;
|
||||||
|
}
|
|
@ -6,15 +6,14 @@ import flixel.addons.transition.FlxTransitionableState;
|
||||||
import flixel.FlxCamera;
|
import flixel.FlxCamera;
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.FlxSubState;
|
import flixel.FlxSubState;
|
||||||
import flixel.graphics.FlxGraphic;
|
|
||||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||||
import funkin.graphics.FunkinCamera;
|
|
||||||
import flixel.group.FlxSpriteGroup;
|
import flixel.group.FlxSpriteGroup;
|
||||||
import flixel.input.keyboard.FlxKey;
|
import flixel.input.keyboard.FlxKey;
|
||||||
import flixel.input.mouse.FlxMouseEvent;
|
import flixel.input.mouse.FlxMouseEvent;
|
||||||
import flixel.math.FlxMath;
|
import flixel.math.FlxMath;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import flixel.math.FlxRect;
|
import flixel.math.FlxRect;
|
||||||
|
import flixel.sound.FlxSound;
|
||||||
import flixel.system.debug.log.LogStyle;
|
import flixel.system.debug.log.LogStyle;
|
||||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||||
import flixel.text.FlxText;
|
import flixel.text.FlxText;
|
||||||
|
@ -26,26 +25,19 @@ import flixel.util.FlxSort;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
import funkin.audio.FunkinSound;
|
import funkin.audio.FunkinSound;
|
||||||
import funkin.audio.visualize.PolygonSpectogram;
|
import funkin.audio.visualize.PolygonSpectogram;
|
||||||
import funkin.audio.visualize.PolygonSpectogram;
|
|
||||||
import funkin.audio.VoicesGroup;
|
import funkin.audio.VoicesGroup;
|
||||||
import funkin.audio.waveform.WaveformSprite;
|
import funkin.audio.waveform.WaveformSprite;
|
||||||
import funkin.data.notestyle.NoteStyleRegistry;
|
import funkin.data.notestyle.NoteStyleRegistry;
|
||||||
import funkin.data.song.SongData.SongCharacterData;
|
import funkin.data.song.SongData.SongCharacterData;
|
||||||
import funkin.data.song.SongData.SongCharacterData;
|
|
||||||
import funkin.data.song.SongData.SongChartData;
|
|
||||||
import funkin.data.song.SongData.SongChartData;
|
import funkin.data.song.SongData.SongChartData;
|
||||||
import funkin.data.song.SongData.SongEventData;
|
import funkin.data.song.SongData.SongEventData;
|
||||||
import funkin.data.song.SongData.SongEventData;
|
|
||||||
import funkin.data.song.SongData.SongMetadata;
|
import funkin.data.song.SongData.SongMetadata;
|
||||||
import funkin.data.song.SongData.SongMetadata;
|
|
||||||
import funkin.data.song.SongData.SongNoteData;
|
|
||||||
import funkin.data.song.SongData.SongNoteData;
|
import funkin.data.song.SongData.SongNoteData;
|
||||||
import funkin.data.song.SongData.SongOffsets;
|
import funkin.data.song.SongData.SongOffsets;
|
||||||
import funkin.data.song.SongDataUtils;
|
import funkin.data.song.SongDataUtils;
|
||||||
import funkin.data.song.SongDataUtils;
|
|
||||||
import funkin.data.song.SongRegistry;
|
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.data.song.SongRegistry;
|
||||||
import funkin.data.stage.StageData;
|
import funkin.data.stage.StageData;
|
||||||
|
import funkin.graphics.FunkinCamera;
|
||||||
import funkin.graphics.FunkinSprite;
|
import funkin.graphics.FunkinSprite;
|
||||||
import funkin.input.Cursor;
|
import funkin.input.Cursor;
|
||||||
import funkin.input.TurboKeyHandler;
|
import funkin.input.TurboKeyHandler;
|
||||||
|
@ -56,13 +48,12 @@ import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
import funkin.play.components.HealthIcon;
|
import funkin.play.components.HealthIcon;
|
||||||
import funkin.play.notes.NoteSprite;
|
import funkin.play.notes.NoteSprite;
|
||||||
import funkin.play.PlayState;
|
import funkin.play.PlayState;
|
||||||
|
import funkin.play.PlayStatePlaylist;
|
||||||
import funkin.play.song.Song;
|
import funkin.play.song.Song;
|
||||||
import funkin.save.Save;
|
import funkin.save.Save;
|
||||||
import funkin.ui.debug.charting.commands.AddEventsCommand;
|
import funkin.ui.debug.charting.commands.AddEventsCommand;
|
||||||
import funkin.ui.debug.charting.commands.AddNotesCommand;
|
import funkin.ui.debug.charting.commands.AddNotesCommand;
|
||||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
|
||||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
|
||||||
import funkin.ui.debug.charting.commands.CopyItemsCommand;
|
import funkin.ui.debug.charting.commands.CopyItemsCommand;
|
||||||
import funkin.ui.debug.charting.commands.CutItemsCommand;
|
import funkin.ui.debug.charting.commands.CutItemsCommand;
|
||||||
import funkin.ui.debug.charting.commands.DeselectAllItemsCommand;
|
import funkin.ui.debug.charting.commands.DeselectAllItemsCommand;
|
||||||
|
@ -95,6 +86,7 @@ import funkin.ui.debug.charting.toolboxes.ChartEditorOffsetsToolbox;
|
||||||
import funkin.ui.haxeui.components.CharacterPlayer;
|
import funkin.ui.haxeui.components.CharacterPlayer;
|
||||||
import funkin.ui.haxeui.HaxeUIState;
|
import funkin.ui.haxeui.HaxeUIState;
|
||||||
import funkin.ui.mainmenu.MainMenuState;
|
import funkin.ui.mainmenu.MainMenuState;
|
||||||
|
import funkin.ui.transition.LoadingState;
|
||||||
import funkin.util.Constants;
|
import funkin.util.Constants;
|
||||||
import funkin.util.FileUtil;
|
import funkin.util.FileUtil;
|
||||||
import funkin.util.logging.CrashHandler;
|
import funkin.util.logging.CrashHandler;
|
||||||
|
@ -119,7 +111,6 @@ import haxe.ui.containers.Grid;
|
||||||
import haxe.ui.containers.HBox;
|
import haxe.ui.containers.HBox;
|
||||||
import haxe.ui.containers.menus.Menu;
|
import haxe.ui.containers.menus.Menu;
|
||||||
import haxe.ui.containers.menus.MenuBar;
|
import haxe.ui.containers.menus.MenuBar;
|
||||||
import haxe.ui.containers.menus.MenuBar;
|
|
||||||
import haxe.ui.containers.menus.MenuCheckBox;
|
import haxe.ui.containers.menus.MenuCheckBox;
|
||||||
import haxe.ui.containers.menus.MenuItem;
|
import haxe.ui.containers.menus.MenuItem;
|
||||||
import haxe.ui.containers.ScrollView;
|
import haxe.ui.containers.ScrollView;
|
||||||
|
@ -130,7 +121,6 @@ import haxe.ui.core.Screen;
|
||||||
import haxe.ui.events.DragEvent;
|
import haxe.ui.events.DragEvent;
|
||||||
import haxe.ui.events.MouseEvent;
|
import haxe.ui.events.MouseEvent;
|
||||||
import haxe.ui.events.UIEvent;
|
import haxe.ui.events.UIEvent;
|
||||||
import haxe.ui.events.UIEvent;
|
|
||||||
import haxe.ui.focus.FocusManager;
|
import haxe.ui.focus.FocusManager;
|
||||||
import haxe.ui.Toolkit;
|
import haxe.ui.Toolkit;
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
|
@ -5338,30 +5328,31 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
this.error("Could Not Playtest", 'Got an error trying to playtest the song.\n${e}');
|
this.error('Could Not Playtest', 'Got an error trying to playtest the song.\n${e}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Rework asset system so we can remove this.
|
// TODO: Rework asset system so we can remove this jank.
|
||||||
switch (currentSongStage)
|
switch (currentSongStage)
|
||||||
{
|
{
|
||||||
case 'mainStage':
|
case 'mainStage':
|
||||||
Paths.setCurrentLevel('week1');
|
PlayStatePlaylist.campaignId = 'week1';
|
||||||
case 'spookyMansion':
|
case 'spookyMansion':
|
||||||
Paths.setCurrentLevel('week2');
|
PlayStatePlaylist.campaignId = 'week2';
|
||||||
case 'phillyTrain':
|
case 'phillyTrain':
|
||||||
Paths.setCurrentLevel('week3');
|
PlayStatePlaylist.campaignId = 'week3';
|
||||||
case 'limoRide':
|
case 'limoRide':
|
||||||
Paths.setCurrentLevel('week4');
|
PlayStatePlaylist.campaignId = 'week4';
|
||||||
case 'mallXmas' | 'mallEvil':
|
case 'mallXmas' | 'mallEvil':
|
||||||
Paths.setCurrentLevel('week5');
|
PlayStatePlaylist.campaignId = 'week5';
|
||||||
case 'school' | 'schoolEvil':
|
case 'school' | 'schoolEvil':
|
||||||
Paths.setCurrentLevel('week6');
|
PlayStatePlaylist.campaignId = 'week6';
|
||||||
case 'tankmanBattlefield':
|
case 'tankmanBattlefield':
|
||||||
Paths.setCurrentLevel('week7');
|
PlayStatePlaylist.campaignId = 'week7';
|
||||||
case 'phillyStreets' | 'phillyBlazin' | 'phillyBlazin2':
|
case 'phillyStreets' | 'phillyBlazin' | 'phillyBlazin2':
|
||||||
Paths.setCurrentLevel('weekend1');
|
PlayStatePlaylist.campaignId = 'weekend1';
|
||||||
}
|
}
|
||||||
|
Paths.setCurrentLevel(PlayStatePlaylist.campaignId);
|
||||||
|
|
||||||
subStateClosed.add(reviveUICamera);
|
subStateClosed.add(reviveUICamera);
|
||||||
subStateClosed.add(resetConductorAfterTest);
|
subStateClosed.add(resetConductorAfterTest);
|
||||||
|
@ -5369,7 +5360,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
FlxTransitionableState.skipNextTransIn = false;
|
FlxTransitionableState.skipNextTransIn = false;
|
||||||
FlxTransitionableState.skipNextTransOut = false;
|
FlxTransitionableState.skipNextTransOut = false;
|
||||||
|
|
||||||
var targetState = new PlayState(
|
var targetStateParams =
|
||||||
{
|
{
|
||||||
targetSong: targetSong,
|
targetSong: targetSong,
|
||||||
targetDifficulty: selectedDifficulty,
|
targetDifficulty: selectedDifficulty,
|
||||||
|
@ -5380,14 +5371,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
startTimestamp: startTimestamp,
|
startTimestamp: startTimestamp,
|
||||||
playbackRate: playbackRate,
|
playbackRate: playbackRate,
|
||||||
overrideMusic: true,
|
overrideMusic: true,
|
||||||
});
|
};
|
||||||
|
|
||||||
// Override music.
|
// Override music.
|
||||||
if (audioInstTrack != null)
|
if (audioInstTrack != null)
|
||||||
{
|
{
|
||||||
FlxG.sound.music = audioInstTrack;
|
FlxG.sound.music = audioInstTrack;
|
||||||
}
|
}
|
||||||
targetState.vocals = audioVocalTrackGroup;
|
|
||||||
|
|
||||||
// Kill and replace the UI camera so it doesn't get destroyed during the state transition.
|
// Kill and replace the UI camera so it doesn't get destroyed during the state transition.
|
||||||
uiCamera.kill();
|
uiCamera.kill();
|
||||||
|
@ -5397,7 +5387,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
this.persistentUpdate = false;
|
this.persistentUpdate = false;
|
||||||
this.persistentDraw = false;
|
this.persistentDraw = false;
|
||||||
stopWelcomeMusic();
|
stopWelcomeMusic();
|
||||||
openSubState(targetState);
|
|
||||||
|
LoadingState.loadPlayState(targetStateParams, false, true, function(targetState) {
|
||||||
|
targetState.vocals = audioVocalTrackGroup;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -171,10 +171,7 @@ class LatencyState extends MusicBeatSubState
|
||||||
trace(FlxG.sound.music._channel.position);
|
trace(FlxG.sound.music._channel.position);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#if FLX_DEBUG
|
// localConductor.update(swagSong.time, false);
|
||||||
funnyStatsGraph.update(FlxG.sound.music.time % 500);
|
|
||||||
realStats.update(swagSong.getTimeWithDiff() % 500);
|
|
||||||
#end
|
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.S)
|
if (FlxG.keys.justPressed.S)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package funkin.ui.freeplay;
|
package funkin.ui.freeplay;
|
||||||
|
|
||||||
import funkin.data.freeplay.AlbumData;
|
import funkin.data.freeplay.AlbumData;
|
||||||
|
import funkin.data.animation.AnimationData;
|
||||||
import funkin.data.freeplay.AlbumRegistry;
|
import funkin.data.freeplay.AlbumRegistry;
|
||||||
import funkin.data.IRegistryEntry;
|
import funkin.data.IRegistryEntry;
|
||||||
import flixel.graphics.FlxGraphic;
|
import flixel.graphics.FlxGraphic;
|
||||||
|
@ -75,6 +76,16 @@ class Album implements IRegistryEntry<AlbumData>
|
||||||
return _data.albumTitleAsset;
|
return _data.albumTitleAsset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function hasAlbumTitleAnimations()
|
||||||
|
{
|
||||||
|
return _data.albumTitleAnimations.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAlbumTitleAnimations():Array<AnimationData>
|
||||||
|
{
|
||||||
|
return _data.albumTitleAnimations;
|
||||||
|
}
|
||||||
|
|
||||||
public function toString():String
|
public function toString():String
|
||||||
{
|
{
|
||||||
return 'Album($id)';
|
return 'Album($id)';
|
||||||
|
|
|
@ -7,6 +7,7 @@ import flixel.tweens.FlxTween;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
import flixel.tweens.FlxEase;
|
import flixel.tweens.FlxEase;
|
||||||
import funkin.data.freeplay.AlbumRegistry;
|
import funkin.data.freeplay.AlbumRegistry;
|
||||||
|
import funkin.util.assets.FlxAnimationUtil;
|
||||||
import funkin.graphics.FunkinSprite;
|
import funkin.graphics.FunkinSprite;
|
||||||
import funkin.util.SortUtil;
|
import funkin.util.SortUtil;
|
||||||
import openfl.utils.Assets;
|
import openfl.utils.Assets;
|
||||||
|
@ -21,9 +22,9 @@ class AlbumRoll extends FlxSpriteGroup
|
||||||
* The ID of the album to display.
|
* The ID of the album to display.
|
||||||
* Modify this value to automatically update the album art and title.
|
* Modify this value to automatically update the album art and title.
|
||||||
*/
|
*/
|
||||||
public var albumId(default, set):String;
|
public var albumId(default, set):Null<String>;
|
||||||
|
|
||||||
function set_albumId(value:String):String
|
function set_albumId(value:Null<String>):Null<String>
|
||||||
{
|
{
|
||||||
if (this.albumId != value)
|
if (this.albumId != value)
|
||||||
{
|
{
|
||||||
|
@ -65,6 +66,17 @@ class AlbumRoll extends FlxSpriteGroup
|
||||||
*/
|
*/
|
||||||
function updateAlbum():Void
|
function updateAlbum():Void
|
||||||
{
|
{
|
||||||
|
if (albumId == null)
|
||||||
|
{
|
||||||
|
albumArt.visible = false;
|
||||||
|
albumTitle.visible = false;
|
||||||
|
if (titleTimer != null)
|
||||||
|
{
|
||||||
|
titleTimer.cancel();
|
||||||
|
titleTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
albumData = AlbumRegistry.instance.fetchEntry(albumId);
|
albumData = AlbumRegistry.instance.fetchEntry(albumId);
|
||||||
|
|
||||||
if (albumData == null)
|
if (albumData == null)
|
||||||
|
@ -94,7 +106,15 @@ class AlbumRoll extends FlxSpriteGroup
|
||||||
|
|
||||||
if (Assets.exists(Paths.image(albumData.getAlbumTitleAssetKey())))
|
if (Assets.exists(Paths.image(albumData.getAlbumTitleAssetKey())))
|
||||||
{
|
{
|
||||||
albumTitle.loadGraphic(Paths.image(albumData.getAlbumTitleAssetKey()));
|
if (albumData.hasAlbumTitleAnimations())
|
||||||
|
{
|
||||||
|
albumTitle.loadSparrow(albumData.getAlbumTitleAssetKey());
|
||||||
|
FlxAnimationUtil.addAtlasAnimations(albumTitle, albumData.getAlbumTitleAnimations());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
albumTitle.loadGraphic(Paths.image(albumData.getAlbumTitleAssetKey()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -155,6 +175,8 @@ class AlbumRoll extends FlxSpriteGroup
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var titleTimer:Null<FlxTimer> = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play the intro animation on the album art.
|
* Play the intro animation on the album art.
|
||||||
*/
|
*/
|
||||||
|
@ -164,7 +186,14 @@ class AlbumRoll extends FlxSpriteGroup
|
||||||
FlxTween.tween(albumArt, {x: 950, y: 320, angle: -340}, 0.5, {ease: FlxEase.elasticOut});
|
FlxTween.tween(albumArt, {x: 950, y: 320, angle: -340}, 0.5, {ease: FlxEase.elasticOut});
|
||||||
|
|
||||||
albumTitle.visible = false;
|
albumTitle.visible = false;
|
||||||
new FlxTimer().start(0.75, function(_) {
|
|
||||||
|
if (titleTimer != null)
|
||||||
|
{
|
||||||
|
titleTimer.cancel();
|
||||||
|
titleTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
titleTimer = new FlxTimer().start(0.75, function(_) {
|
||||||
showTitle();
|
showTitle();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -179,6 +208,8 @@ class AlbumRoll extends FlxSpriteGroup
|
||||||
public function showTitle():Void
|
public function showTitle():Void
|
||||||
{
|
{
|
||||||
albumTitle.visible = true;
|
albumTitle.visible = true;
|
||||||
|
albumTitle.animation.play('active');
|
||||||
|
albumTitle.animation.finishCallback = (_) -> albumTitle.animation.play('idle');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -27,8 +27,8 @@ class DJBoyfriend extends FlxAtlasSprite
|
||||||
|
|
||||||
var gotSpooked:Bool = false;
|
var gotSpooked:Bool = false;
|
||||||
|
|
||||||
static final SPOOK_PERIOD:Float = 10.0;
|
static final SPOOK_PERIOD:Float = 120.0;
|
||||||
static final TV_PERIOD:Float = 10.0;
|
static final TV_PERIOD:Float = 180.0;
|
||||||
|
|
||||||
// Time since dad last SPOOKED you.
|
// Time since dad last SPOOKED you.
|
||||||
var timeSinceSpook:Float = 0;
|
var timeSinceSpook:Float = 0;
|
||||||
|
@ -43,7 +43,14 @@ class DJBoyfriend extends FlxAtlasSprite
|
||||||
switch (name)
|
switch (name)
|
||||||
{
|
{
|
||||||
case "Boyfriend DJ watchin tv OG":
|
case "Boyfriend DJ watchin tv OG":
|
||||||
if (number == 85) runTvLogic();
|
if (number == 80)
|
||||||
|
{
|
||||||
|
FunkinSound.playOnce(Paths.sound('remote_click'));
|
||||||
|
}
|
||||||
|
if (number == 85)
|
||||||
|
{
|
||||||
|
runTvLogic();
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -219,19 +226,17 @@ class DJBoyfriend extends FlxAtlasSprite
|
||||||
if (cartoonSnd == null)
|
if (cartoonSnd == null)
|
||||||
{
|
{
|
||||||
// tv is OFF, but getting turned on
|
// tv is OFF, but getting turned on
|
||||||
// Eric got FUCKING TROLLED there is no `tv_on` or `channel_switch` sound!
|
FunkinSound.playOnce(Paths.sound('tv_on'), 1.0, function() {
|
||||||
// FunkinSound.playOnce(Paths.sound('tv_on'), 1.0, function() {
|
loadCartoon();
|
||||||
// });
|
});
|
||||||
loadCartoon();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// plays it smidge after the click
|
// plays it smidge after the click
|
||||||
// new FlxTimer().start(0.1, function(_) {
|
FunkinSound.playOnce(Paths.sound('channel_switch'), 1.0, function() {
|
||||||
// // FunkinSound.playOnce(Paths.sound('channel_switch'));
|
cartoonSnd.destroy();
|
||||||
// });
|
loadCartoon();
|
||||||
cartoonSnd.destroy();
|
});
|
||||||
loadCartoon();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadCartoon();
|
// loadCartoon();
|
||||||
|
|
|
@ -133,8 +133,8 @@ class FreeplayState extends MusicBeatSubState
|
||||||
|
|
||||||
var stickerSubState:StickerSubState;
|
var stickerSubState:StickerSubState;
|
||||||
|
|
||||||
static var rememberedDifficulty:Null<String> = Constants.DEFAULT_DIFFICULTY;
|
public static var rememberedDifficulty:Null<String> = Constants.DEFAULT_DIFFICULTY;
|
||||||
static var rememberedSongId:Null<String> = null;
|
public static var rememberedSongId:Null<String> = 'tutorial';
|
||||||
|
|
||||||
public function new(?params:FreeplayStateParams, ?stickers:StickerSubState)
|
public function new(?params:FreeplayStateParams, ?stickers:StickerSubState)
|
||||||
{
|
{
|
||||||
|
@ -145,7 +145,7 @@ class FreeplayState extends MusicBeatSubState
|
||||||
stickerSubState = stickers;
|
stickerSubState = stickers;
|
||||||
}
|
}
|
||||||
|
|
||||||
super();
|
super(FlxColor.TRANSPARENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function create():Void
|
override function create():Void
|
||||||
|
@ -380,7 +380,7 @@ class FreeplayState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
|
|
||||||
albumRoll = new AlbumRoll();
|
albumRoll = new AlbumRoll();
|
||||||
albumRoll.albumId = 'volume1';
|
albumRoll.albumId = null;
|
||||||
add(albumRoll);
|
add(albumRoll);
|
||||||
|
|
||||||
albumRoll.applyExitMovers(exitMovers);
|
albumRoll.applyExitMovers(exitMovers);
|
||||||
|
@ -536,21 +536,18 @@ class FreeplayState extends MusicBeatSubState
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var currentFilter:SongFilter = null;
|
||||||
|
var currentFilteredSongs:Array<FreeplaySongData> = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given the current filter, rebuild the current song list.
|
* Given the current filter, rebuild the current song list.
|
||||||
*
|
*
|
||||||
* @param filterStuff A filter to apply to the song list (regex, startswith, all, favorite)
|
* @param filterStuff A filter to apply to the song list (regex, startswith, all, favorite)
|
||||||
* @param force
|
* @param force
|
||||||
|
* @param onlyIfChanged Only apply the filter if the song list has changed
|
||||||
*/
|
*/
|
||||||
public function generateSongList(?filterStuff:SongFilter, force:Bool = false):Void
|
public function generateSongList(filterStuff:Null<SongFilter>, force:Bool = false, onlyIfChanged:Bool = true):Void
|
||||||
{
|
{
|
||||||
curSelected = 1;
|
|
||||||
|
|
||||||
for (cap in grpCapsules.members)
|
|
||||||
{
|
|
||||||
cap.kill();
|
|
||||||
}
|
|
||||||
|
|
||||||
var tempSongs:Array<FreeplaySongData> = songs;
|
var tempSongs:Array<FreeplaySongData> = songs;
|
||||||
|
|
||||||
if (filterStuff != null)
|
if (filterStuff != null)
|
||||||
|
@ -582,6 +579,35 @@ class FreeplayState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter further by current selected difficulty.
|
||||||
|
if (currentDifficulty != null)
|
||||||
|
{
|
||||||
|
tempSongs = tempSongs.filter(song -> {
|
||||||
|
if (song == null) return true; // Random
|
||||||
|
return song.songDifficulties.contains(currentDifficulty);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onlyIfChanged)
|
||||||
|
{
|
||||||
|
// == performs equality by reference
|
||||||
|
if (tempSongs.isEqualUnordered(currentFilteredSongs)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only now do we know that the filter is actually changing.
|
||||||
|
|
||||||
|
rememberedSongId = grpCapsules.members[curSelected]?.songData?.songId ?? rememberedSongId;
|
||||||
|
|
||||||
|
for (cap in grpCapsules.members)
|
||||||
|
{
|
||||||
|
cap.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFilter = filterStuff;
|
||||||
|
|
||||||
|
currentFilteredSongs = tempSongs;
|
||||||
|
curSelected = 0;
|
||||||
|
|
||||||
var hsvShader:HSVShader = new HSVShader();
|
var hsvShader:HSVShader = new HSVShader();
|
||||||
|
|
||||||
var randomCapsule:SongMenuItem = grpCapsules.recycle(SongMenuItem);
|
var randomCapsule:SongMenuItem = grpCapsules.recycle(SongMenuItem);
|
||||||
|
@ -658,11 +684,12 @@ class FreeplayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.F)
|
if (FlxG.keys.justPressed.F)
|
||||||
{
|
{
|
||||||
if (songs[curSelected] != null)
|
var targetSong = grpCapsules.members[curSelected]?.songData;
|
||||||
|
if (targetSong != null)
|
||||||
{
|
{
|
||||||
var realShit:Int = curSelected;
|
var realShit:Int = curSelected;
|
||||||
songs[curSelected].isFav = !songs[curSelected].isFav;
|
targetSong.isFav = !targetSong.isFav;
|
||||||
if (songs[curSelected].isFav)
|
if (targetSong.isFav)
|
||||||
{
|
{
|
||||||
FlxTween.tween(grpCapsules.members[realShit], {angle: 360}, 0.4,
|
FlxTween.tween(grpCapsules.members[realShit], {angle: 360}, 0.4,
|
||||||
{
|
{
|
||||||
|
@ -854,11 +881,13 @@ class FreeplayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
dj.resetAFKTimer();
|
dj.resetAFKTimer();
|
||||||
changeDiff(-1);
|
changeDiff(-1);
|
||||||
|
generateSongList(currentFilter, true);
|
||||||
}
|
}
|
||||||
if (controls.UI_RIGHT_P && !FlxG.keys.pressed.CONTROL)
|
if (controls.UI_RIGHT_P && !FlxG.keys.pressed.CONTROL)
|
||||||
{
|
{
|
||||||
dj.resetAFKTimer();
|
dj.resetAFKTimer();
|
||||||
changeDiff(1);
|
changeDiff(1);
|
||||||
|
generateSongList(currentFilter, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controls.BACK && !typing.hasFocus)
|
if (controls.BACK && !typing.hasFocus)
|
||||||
|
@ -877,6 +906,8 @@ class FreeplayState extends MusicBeatSubState
|
||||||
|
|
||||||
for (spr in grpSpr)
|
for (spr in grpSpr)
|
||||||
{
|
{
|
||||||
|
if (spr == null) continue;
|
||||||
|
|
||||||
var funnyMoveShit:MoveData = moveData;
|
var funnyMoveShit:MoveData = moveData;
|
||||||
|
|
||||||
if (moveData.x == null) funnyMoveShit.x = spr.x;
|
if (moveData.x == null) funnyMoveShit.x = spr.x;
|
||||||
|
@ -899,7 +930,7 @@ class FreeplayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (Type.getClass(FlxG.state) == MainMenuState)
|
if (Type.getClass(FlxG.state) == MainMenuState)
|
||||||
{
|
{
|
||||||
FlxG.state.persistentUpdate = true;
|
FlxG.state.persistentUpdate = false;
|
||||||
FlxG.state.persistentDraw = true;
|
FlxG.state.persistentDraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -908,6 +939,11 @@ class FreeplayState extends MusicBeatSubState
|
||||||
FlxTransitionableState.skipNextTransOut = true;
|
FlxTransitionableState.skipNextTransOut = true;
|
||||||
if (Type.getClass(FlxG.state) == MainMenuState)
|
if (Type.getClass(FlxG.state) == MainMenuState)
|
||||||
{
|
{
|
||||||
|
FunkinSound.playMusic('freakyMenu',
|
||||||
|
{
|
||||||
|
overrideExisting: true,
|
||||||
|
restartTrack: false
|
||||||
|
});
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -926,7 +962,7 @@ class FreeplayState extends MusicBeatSubState
|
||||||
public override function destroy():Void
|
public override function destroy():Void
|
||||||
{
|
{
|
||||||
super.destroy();
|
super.destroy();
|
||||||
var daSong:Null<FreeplaySongData> = songs[curSelected];
|
var daSong:Null<FreeplaySongData> = currentFilteredSongs[curSelected];
|
||||||
if (daSong != null)
|
if (daSong != null)
|
||||||
{
|
{
|
||||||
clearDaCache(daSong.songName);
|
clearDaCache(daSong.songName);
|
||||||
|
@ -948,10 +984,10 @@ class FreeplayState extends MusicBeatSubState
|
||||||
|
|
||||||
currentDifficulty = diffIdsCurrent[currentDifficultyIndex];
|
currentDifficulty = diffIdsCurrent[currentDifficultyIndex];
|
||||||
|
|
||||||
var daSong:Null<FreeplaySongData> = songs[curSelected];
|
var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected].songData;
|
||||||
if (daSong != null)
|
if (daSong != null)
|
||||||
{
|
{
|
||||||
var songScore:SaveScoreData = Save.instance.getSongScore(songs[curSelected].songId, currentDifficulty);
|
var songScore:SaveScoreData = Save.instance.getSongScore(grpCapsules.members[curSelected].songData.songId, currentDifficulty);
|
||||||
intendedScore = songScore?.score ?? 0;
|
intendedScore = songScore?.score ?? 0;
|
||||||
intendedCompletion = songScore?.accuracy ?? 0.0;
|
intendedCompletion = songScore?.accuracy ?? 0.0;
|
||||||
rememberedDifficulty = currentDifficulty;
|
rememberedDifficulty = currentDifficulty;
|
||||||
|
@ -1015,7 +1051,7 @@ class FreeplayState extends MusicBeatSubState
|
||||||
albumRoll.setDifficultyStars(daSong?.songRating);
|
albumRoll.setDifficultyStars(daSong?.songRating);
|
||||||
|
|
||||||
// Set the album graphic and play the animation if relevant.
|
// Set the album graphic and play the animation if relevant.
|
||||||
var newAlbumId:String = daSong?.albumId ?? Constants.DEFAULT_ALBUM_ID;
|
var newAlbumId:String = daSong?.albumId;
|
||||||
if (albumRoll.albumId != newAlbumId)
|
if (albumRoll.albumId != newAlbumId)
|
||||||
{
|
{
|
||||||
albumRoll.albumId = newAlbumId;
|
albumRoll.albumId = newAlbumId;
|
||||||
|
@ -1103,6 +1139,12 @@ class FreeplayState extends MusicBeatSubState
|
||||||
targetVariation: targetVariation,
|
targetVariation: targetVariation,
|
||||||
practiceMode: false,
|
practiceMode: false,
|
||||||
minimalMode: false,
|
minimalMode: false,
|
||||||
|
|
||||||
|
#if (debug || FORCE_DEBUG_VERSION)
|
||||||
|
botPlayMode: FlxG.keys.pressed.SHIFT,
|
||||||
|
#else
|
||||||
|
botPlayMode: false,
|
||||||
|
#end
|
||||||
// TODO: Make these an option! It's currently only accessible via chart editor.
|
// TODO: Make these an option! It's currently only accessible via chart editor.
|
||||||
// startTimestamp: 0.0,
|
// startTimestamp: 0.0,
|
||||||
// playbackRate: 0.5,
|
// playbackRate: 0.5,
|
||||||
|
@ -1115,10 +1157,12 @@ class FreeplayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
if (rememberedSongId != null)
|
if (rememberedSongId != null)
|
||||||
{
|
{
|
||||||
curSelected = songs.findIndex(function(song) {
|
curSelected = currentFilteredSongs.findIndex(function(song) {
|
||||||
if (song == null) return false;
|
if (song == null) return false;
|
||||||
return song.songId == rememberedSongId;
|
return song.songId == rememberedSongId;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (curSelected == -1) curSelected = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rememberedDifficulty != null)
|
if (rememberedDifficulty != null)
|
||||||
|
@ -1127,7 +1171,7 @@ class FreeplayState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the difficulty star count on the right.
|
// Set the difficulty star count on the right.
|
||||||
var daSong:Null<FreeplaySongData> = songs[curSelected];
|
var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected]?.songData;
|
||||||
albumRoll.setDifficultyStars(daSong?.songRating ?? 0);
|
albumRoll.setDifficultyStars(daSong?.songRating ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1156,8 +1200,10 @@ class FreeplayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
intendedScore = 0;
|
intendedScore = 0;
|
||||||
intendedCompletion = 0.0;
|
intendedCompletion = 0.0;
|
||||||
|
diffIdsCurrent = diffIdsTotal;
|
||||||
rememberedSongId = null;
|
rememberedSongId = null;
|
||||||
rememberedDifficulty = null;
|
rememberedDifficulty = null;
|
||||||
|
albumRoll.albumId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (index => capsule in grpCapsules.members)
|
for (index => capsule in grpCapsules.members)
|
||||||
|
@ -1195,12 +1241,33 @@ class FreeplayState extends MusicBeatSubState
|
||||||
});
|
});
|
||||||
if (didReplace)
|
if (didReplace)
|
||||||
{
|
{
|
||||||
|
FunkinSound.playMusic('freakyMenu',
|
||||||
|
{
|
||||||
|
startingVolume: 0.0,
|
||||||
|
overrideExisting: true,
|
||||||
|
restartTrack: false
|
||||||
|
});
|
||||||
FlxG.sound.music.fadeIn(2, 0, 0.8);
|
FlxG.sound.music.fadeIn(2, 0, 0.8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
grpCapsules.members[curSelected].selected = true;
|
grpCapsules.members[curSelected].selected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an instance of `FreeplayState` that is above the `MainMenuState`.
|
||||||
|
* @return The MainMenuState with the FreeplayState as a substate.
|
||||||
|
*/
|
||||||
|
public static function build(?params:FreeplayStateParams, ?stickers:StickerSubState):MusicBeatState
|
||||||
|
{
|
||||||
|
var result = new MainMenuState();
|
||||||
|
result.persistentUpdate = false;
|
||||||
|
result.persistentDraw = true;
|
||||||
|
|
||||||
|
result.openSubState(new FreeplayState(params, stickers));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1307,7 +1374,7 @@ class FreeplaySongData
|
||||||
public var songName(default, null):String = '';
|
public var songName(default, null):String = '';
|
||||||
public var songCharacter(default, null):String = '';
|
public var songCharacter(default, null):String = '';
|
||||||
public var songRating(default, null):Int = 0;
|
public var songRating(default, null):Int = 0;
|
||||||
public var albumId(default, null):String = '';
|
public var albumId(default, null):Null<String> = null;
|
||||||
|
|
||||||
public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY;
|
public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY;
|
||||||
public var displayedVariations(default, null):Array<String> = [Constants.DEFAULT_VARIATION];
|
public var displayedVariations(default, null):Array<String> = [Constants.DEFAULT_VARIATION];
|
||||||
|
@ -1341,7 +1408,15 @@ class FreeplaySongData
|
||||||
this.songName = songDifficulty.songName;
|
this.songName = songDifficulty.songName;
|
||||||
this.songCharacter = songDifficulty.characters.opponent;
|
this.songCharacter = songDifficulty.characters.opponent;
|
||||||
this.songRating = songDifficulty.difficultyRating;
|
this.songRating = songDifficulty.difficultyRating;
|
||||||
this.albumId = songDifficulty.album;
|
if (songDifficulty.album == null)
|
||||||
|
{
|
||||||
|
FlxG.log.warn('No album for: ${songDifficulty.songName}');
|
||||||
|
this.albumId = Constants.DEFAULT_ALBUM_ID;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.albumId = songDifficulty.album;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,12 +51,10 @@ class MainMenuState extends MusicBeatState
|
||||||
transIn = FlxTransitionableState.defaultTransIn;
|
transIn = FlxTransitionableState.defaultTransIn;
|
||||||
transOut = FlxTransitionableState.defaultTransOut;
|
transOut = FlxTransitionableState.defaultTransOut;
|
||||||
|
|
||||||
if (!(FlxG?.sound?.music?.playing ?? false))
|
playMenuMusic();
|
||||||
{
|
|
||||||
playMenuMusic();
|
|
||||||
}
|
|
||||||
|
|
||||||
persistentUpdate = persistentDraw = true;
|
persistentUpdate = false;
|
||||||
|
persistentDraw = true;
|
||||||
|
|
||||||
var bg:FlxSprite = new FlxSprite(Paths.image('menuBG'));
|
var bg:FlxSprite = new FlxSprite(Paths.image('menuBG'));
|
||||||
bg.scrollFactor.x = 0;
|
bg.scrollFactor.x = 0;
|
||||||
|
@ -109,14 +107,21 @@ class MainMenuState extends MusicBeatState
|
||||||
});
|
});
|
||||||
|
|
||||||
#if CAN_OPEN_LINKS
|
#if CAN_OPEN_LINKS
|
||||||
|
// In order to prevent popup blockers from triggering,
|
||||||
|
// we need to open the link as an immediate result of a keypress event,
|
||||||
|
// so we can't wait for the flicker animation to complete.
|
||||||
var hasPopupBlocker = #if web true #else false #end;
|
var hasPopupBlocker = #if web true #else false #end;
|
||||||
createMenuItem('donate', 'mainmenu/donate', selectDonate, hasPopupBlocker);
|
createMenuItem('merch', 'mainmenu/merch', selectMerch, hasPopupBlocker);
|
||||||
#end
|
#end
|
||||||
|
|
||||||
createMenuItem('options', 'mainmenu/options', function() {
|
createMenuItem('options', 'mainmenu/options', function() {
|
||||||
startExitState(() -> new funkin.ui.options.OptionsState());
|
startExitState(() -> new funkin.ui.options.OptionsState());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
createMenuItem('credits', 'mainmenu/credits', function() {
|
||||||
|
startExitState(() -> new funkin.ui.credits.CreditsState());
|
||||||
|
});
|
||||||
|
|
||||||
// Reset position of menu items.
|
// Reset position of menu items.
|
||||||
var spacing = 160;
|
var spacing = 160;
|
||||||
var top = (FlxG.height - (spacing * (menuItems.length - 1))) / 2;
|
var top = (FlxG.height - (spacing * (menuItems.length - 1))) / 2;
|
||||||
|
@ -125,6 +130,9 @@ class MainMenuState extends MusicBeatState
|
||||||
var menuItem = menuItems.members[i];
|
var menuItem = menuItems.members[i];
|
||||||
menuItem.x = FlxG.width / 2;
|
menuItem.x = FlxG.width / 2;
|
||||||
menuItem.y = top + spacing * i;
|
menuItem.y = top + spacing * i;
|
||||||
|
menuItem.scrollFactor.x = 0.0;
|
||||||
|
// This one affects how much the menu items move when you scroll between them.
|
||||||
|
menuItem.scrollFactor.y = 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetCamStuff();
|
resetCamStuff();
|
||||||
|
@ -212,6 +220,11 @@ class MainMenuState extends MusicBeatState
|
||||||
{
|
{
|
||||||
WindowUtil.openURL(Constants.URL_ITCH);
|
WindowUtil.openURL(Constants.URL_ITCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectMerch()
|
||||||
|
{
|
||||||
|
WindowUtil.openURL(Constants.URL_MERCH);
|
||||||
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
#if newgrounds
|
#if newgrounds
|
||||||
|
@ -311,8 +324,6 @@ class MainMenuState extends MusicBeatState
|
||||||
// Open the debug menu, defaults to ` / ~
|
// Open the debug menu, defaults to ` / ~
|
||||||
if (controls.DEBUG_MENU)
|
if (controls.DEBUG_MENU)
|
||||||
{
|
{
|
||||||
this.persistentUpdate = false;
|
|
||||||
this.persistentDraw = false;
|
|
||||||
FlxG.state.openSubState(new DebugMenuSubState());
|
FlxG.state.openSubState(new DebugMenuSubState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import funkin.ui.MusicBeatState;
|
||||||
*/
|
*/
|
||||||
class AttractState extends MusicBeatState
|
class AttractState extends MusicBeatState
|
||||||
{
|
{
|
||||||
static final ATTRACT_VIDEO_PATH:String = Paths.videos('kickstarterTrailer');
|
static final ATTRACT_VIDEO_PATH:String = Paths.stripLibrary(Paths.videos('kickstarterTrailer', 'shared'));
|
||||||
|
|
||||||
public override function create():Void
|
public override function create():Void
|
||||||
{
|
{
|
||||||
|
@ -29,10 +29,12 @@ class AttractState extends MusicBeatState
|
||||||
}
|
}
|
||||||
|
|
||||||
#if html5
|
#if html5
|
||||||
|
trace('Playing web video ${ATTRACT_VIDEO_PATH}');
|
||||||
playVideoHTML5(ATTRACT_VIDEO_PATH);
|
playVideoHTML5(ATTRACT_VIDEO_PATH);
|
||||||
#end
|
#end
|
||||||
|
|
||||||
#if hxCodec
|
#if hxCodec
|
||||||
|
trace('Playing native video ${ATTRACT_VIDEO_PATH}');
|
||||||
playVideoNative(ATTRACT_VIDEO_PATH);
|
playVideoNative(ATTRACT_VIDEO_PATH);
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
|
@ -220,7 +220,7 @@ class TitleState extends MusicBeatState
|
||||||
|
|
||||||
function playMenuMusic():Void
|
function playMenuMusic():Void
|
||||||
{
|
{
|
||||||
var shouldFadeIn = (FlxG.sound.music == null);
|
var shouldFadeIn:Bool = (FlxG.sound.music == null);
|
||||||
// Load music. Includes logic to handle BPM changes.
|
// Load music. Includes logic to handle BPM changes.
|
||||||
FunkinSound.playMusic('freakyMenu',
|
FunkinSound.playMusic('freakyMenu',
|
||||||
{
|
{
|
||||||
|
@ -229,7 +229,7 @@ class TitleState extends MusicBeatState
|
||||||
restartTrack: true
|
restartTrack: true
|
||||||
});
|
});
|
||||||
// Fade from 0.0 to 0.7 over 4 seconds
|
// Fade from 0.0 to 0.7 over 4 seconds
|
||||||
if (shouldFadeIn) FlxG.sound.music.fadeIn(4.0, 0.0, 0.7);
|
if (shouldFadeIn) FlxG.sound.music.fadeIn(4.0, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIntroTextShit():Array<Array<String>>
|
function getIntroTextShit():Array<Array<String>>
|
||||||
|
@ -290,18 +290,6 @@ class TitleState extends MusicBeatState
|
||||||
// do controls.PAUSE | controls.ACCEPT instead?
|
// do controls.PAUSE | controls.ACCEPT instead?
|
||||||
var pressedEnter:Bool = FlxG.keys.justPressed.ENTER;
|
var pressedEnter:Bool = FlxG.keys.justPressed.ENTER;
|
||||||
|
|
||||||
if (FlxG.onMobile)
|
|
||||||
{
|
|
||||||
for (touch in FlxG.touches.list)
|
|
||||||
{
|
|
||||||
if (touch.justPressed)
|
|
||||||
{
|
|
||||||
FlxG.switchState(() -> new FreeplayState());
|
|
||||||
pressedEnter = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var gamepad:FlxGamepad = FlxG.gamepads.lastActive;
|
var gamepad:FlxGamepad = FlxG.gamepads.lastActive;
|
||||||
|
|
||||||
if (gamepad != null)
|
if (gamepad != null)
|
||||||
|
|
|
@ -22,10 +22,12 @@ import openfl.filters.ShaderFilter;
|
||||||
import openfl.utils.Assets;
|
import openfl.utils.Assets;
|
||||||
import flixel.util.typeLimit.NextState;
|
import flixel.util.typeLimit.NextState;
|
||||||
|
|
||||||
class LoadingState extends MusicBeatState
|
class LoadingState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
inline static var MIN_TIME = 1.0;
|
inline static var MIN_TIME = 1.0;
|
||||||
|
|
||||||
|
var asSubState:Bool = false;
|
||||||
|
|
||||||
var target:NextState;
|
var target:NextState;
|
||||||
var playParams:Null<PlayStateParams>;
|
var playParams:Null<PlayStateParams>;
|
||||||
var stopMusic:Bool = false;
|
var stopMusic:Bool = false;
|
||||||
|
@ -178,7 +180,16 @@ class LoadingState extends MusicBeatState
|
||||||
FlxG.sound.music = null;
|
FlxG.sound.music = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
FlxG.switchState(target);
|
if (asSubState)
|
||||||
|
{
|
||||||
|
this.close();
|
||||||
|
// We will assume the target is a valid substate.
|
||||||
|
FlxG.state.openSubState(cast target);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FlxG.switchState(target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static function getSongPath():String
|
static function getSongPath():String
|
||||||
|
@ -190,17 +201,41 @@ class LoadingState extends MusicBeatState
|
||||||
* Starts the transition to a new `PlayState` to start a new song.
|
* Starts the transition to a new `PlayState` to start a new song.
|
||||||
* First switches to the `LoadingState` if assets need to be loaded.
|
* First switches to the `LoadingState` if assets need to be loaded.
|
||||||
* @param params The parameters for the next `PlayState`.
|
* @param params The parameters for the next `PlayState`.
|
||||||
|
* @param asSubState Whether to open as a substate rather than switching to the `PlayState`.
|
||||||
* @param shouldStopMusic Whether to stop the current music while loading.
|
* @param shouldStopMusic Whether to stop the current music while loading.
|
||||||
*/
|
*/
|
||||||
public static function loadPlayState(params:PlayStateParams, shouldStopMusic = false):Void
|
public static function loadPlayState(params:PlayStateParams, shouldStopMusic = false, asSubState = false, ?onConstruct:PlayState->Void):Void
|
||||||
{
|
{
|
||||||
Paths.setCurrentLevel(PlayStatePlaylist.campaignId);
|
Paths.setCurrentLevel(PlayStatePlaylist.campaignId);
|
||||||
var playStateCtor:NextState = () -> new PlayState(params);
|
var playStateCtor:() -> PlayState = function() {
|
||||||
|
return new PlayState(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (onConstruct != null)
|
||||||
|
{
|
||||||
|
playStateCtor = function() {
|
||||||
|
var result = new PlayState(params);
|
||||||
|
onConstruct(result);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#if NO_PRELOAD_ALL
|
#if NO_PRELOAD_ALL
|
||||||
// Switch to loading state while we load assets (default on HTML5 target).
|
// Switch to loading state while we load assets (default on HTML5 target).
|
||||||
var loadStateCtor:NextState = () -> new LoadingState(playStateCtor, shouldStopMusic, params);
|
var loadStateCtor = function() {
|
||||||
FlxG.switchState(loadStateCtor);
|
var result = new LoadingState(playStateCtor, shouldStopMusic, params);
|
||||||
|
@:privateAccess
|
||||||
|
result.asSubState = asSubState;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (asSubState)
|
||||||
|
{
|
||||||
|
FlxG.state.openSubState(cast loadStateCtor());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FlxG.switchState(loadStateCtor);
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
// All assets preloaded, switch directly to play state (defualt on other targets).
|
// All assets preloaded, switch directly to play state (defualt on other targets).
|
||||||
if (shouldStopMusic && FlxG.sound.music != null)
|
if (shouldStopMusic && FlxG.sound.music != null)
|
||||||
|
@ -210,11 +245,40 @@ class LoadingState extends MusicBeatState
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load and cache the song's charts.
|
// Load and cache the song's charts.
|
||||||
if (params?.targetSong != null)
|
// Don't do this if we already provided the music and charts.
|
||||||
|
if (params?.targetSong != null && !params.overrideMusic)
|
||||||
{
|
{
|
||||||
params.targetSong.cacheCharts(true);
|
params.targetSong.cacheCharts(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var shouldPreloadLevelAssets:Bool = !(params?.minimalMode ?? false);
|
||||||
|
|
||||||
|
if (shouldPreloadLevelAssets) preloadLevelAssets();
|
||||||
|
|
||||||
|
if (asSubState)
|
||||||
|
{
|
||||||
|
FlxG.state.openSubState(cast playStateCtor());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FlxG.switchState(playStateCtor);
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
#if NO_PRELOAD_ALL
|
||||||
|
static function isSoundLoaded(path:String):Bool
|
||||||
|
{
|
||||||
|
return Assets.cache.hasSound(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function isLibraryLoaded(library:String):Bool
|
||||||
|
{
|
||||||
|
return Assets.getLibrary(library) != null;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static function preloadLevelAssets():Void
|
||||||
|
{
|
||||||
// TODO: This section is a hack! Redo this later when we have a proper asset caching system.
|
// TODO: This section is a hack! Redo this later when we have a proper asset caching system.
|
||||||
FunkinSprite.preparePurgeCache();
|
FunkinSprite.preparePurgeCache();
|
||||||
FunkinSprite.cacheTexture(Paths.image('combo'));
|
FunkinSprite.cacheTexture(Paths.image('combo'));
|
||||||
|
@ -247,7 +311,10 @@ class LoadingState extends MusicBeatState
|
||||||
// List all image assets in the level's library.
|
// List all image assets in the level's library.
|
||||||
// This is crude and I want to remove it when we have a proper asset caching system.
|
// This is crude and I want to remove it when we have a proper asset caching system.
|
||||||
// TODO: Get rid of this junk!
|
// TODO: Get rid of this junk!
|
||||||
var library = openfl.utils.Assets.getLibrary(PlayStatePlaylist.campaignId);
|
var library = PlayStatePlaylist.campaignId != null ? openfl.utils.Assets.getLibrary(PlayStatePlaylist.campaignId) : null;
|
||||||
|
|
||||||
|
if (library == null) return; // We don't need to do anymore precaching.
|
||||||
|
|
||||||
var assets = library.list(lime.utils.AssetType.IMAGE);
|
var assets = library.list(lime.utils.AssetType.IMAGE);
|
||||||
trace('Got ${assets.length} assets: ${assets}');
|
trace('Got ${assets.length} assets: ${assets}');
|
||||||
|
|
||||||
|
@ -278,20 +345,6 @@ class LoadingState extends MusicBeatState
|
||||||
// FunkinSprite.cacheAllSongTextures(stage)
|
// FunkinSprite.cacheAllSongTextures(stage)
|
||||||
|
|
||||||
FunkinSprite.purgeCache();
|
FunkinSprite.purgeCache();
|
||||||
|
|
||||||
FlxG.switchState(playStateCtor);
|
|
||||||
#end
|
|
||||||
}
|
|
||||||
|
|
||||||
#if NO_PRELOAD_ALL
|
|
||||||
static function isSoundLoaded(path:String):Bool
|
|
||||||
{
|
|
||||||
return Assets.cache.hasSound(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
static function isLibraryLoaded(library:String):Bool
|
|
||||||
{
|
|
||||||
return Assets.getLibrary(library) != null;
|
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,11 @@ class Constants
|
||||||
*/
|
*/
|
||||||
// ==============================
|
// ==============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link to buy merch for the game.
|
||||||
|
*/
|
||||||
|
public static final URL_MERCH:String = 'https://needlejuicerecords.com/pages/friday-night-funkin';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preloader sitelock.
|
* Preloader sitelock.
|
||||||
* Matching is done by `FlxStringUtil.getDomain`, so any URL on the domain will work.
|
* Matching is done by `FlxStringUtil.getDomain`, so any URL on the domain will work.
|
||||||
|
@ -181,6 +186,12 @@ class Constants
|
||||||
*/
|
*/
|
||||||
public static final DEFAULT_DIFFICULTY_LIST:Array<String> = ['easy', 'normal', 'hard'];
|
public static final DEFAULT_DIFFICULTY_LIST:Array<String> = ['easy', 'normal', 'hard'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all difficulties used by the base game.
|
||||||
|
* Includes Erect and Nightmare.
|
||||||
|
*/
|
||||||
|
public static final DEFAULT_DIFFICULTY_LIST_FULL:Array<String> = ['easy', 'normal', 'hard', 'erect', 'nightmare'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default player character for charts.
|
* Default player character for charts.
|
||||||
*/
|
*/
|
||||||
|
@ -514,4 +525,10 @@ class Constants
|
||||||
* The vertical offset of the strumline from the top edge of the screen.
|
* The vertical offset of the strumline from the top edge of the screen.
|
||||||
*/
|
*/
|
||||||
public static final STRUMLINE_Y_OFFSET:Float = 24;
|
public static final STRUMLINE_Y_OFFSET:Float = 24;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rate at which the camera lerps to its target.
|
||||||
|
* 0.04 = 4% of distance per frame.
|
||||||
|
*/
|
||||||
|
public static final DEFAULT_CAMERA_FOLLOW_RATE:Float = 0.04;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,31 @@ class SerializerUtil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function initSerializer():Void
|
||||||
|
{
|
||||||
|
haxe.Unserializer.DEFAULT_RESOLVER = new FunkinTypeResolver();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize a Haxe object using the built-in Serializer.
|
||||||
|
* @param input The object to serialize
|
||||||
|
* @return The serialized object as a string
|
||||||
|
*/
|
||||||
|
public static function fromHaxeObject(input:Dynamic):String
|
||||||
|
{
|
||||||
|
return haxe.Serializer.run(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a serialized Haxe object back into a Haxe object.
|
||||||
|
* @param input The serialized object as a string
|
||||||
|
* @return The deserialized object
|
||||||
|
*/
|
||||||
|
public static function toHaxeObject(input:String):Dynamic
|
||||||
|
{
|
||||||
|
return haxe.Unserializer.run(input);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customize how certain types are serialized when converting to JSON.
|
* Customize how certain types are serialized when converting to JSON.
|
||||||
*/
|
*/
|
||||||
|
@ -90,3 +115,26 @@ class SerializerUtil
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FunkinTypeResolver
|
||||||
|
{
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
// Blank constructor.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resolveClass(name:String):Class<Dynamic>
|
||||||
|
{
|
||||||
|
if (name == 'Dynamic')
|
||||||
|
{
|
||||||
|
FlxG.log.warn('Found invalid class type in save data, indicates partial save corruption.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Type.resolveClass(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
public function resolveEnum(name:String):Enum<Dynamic>
|
||||||
|
{
|
||||||
|
return Type.resolveEnum(name);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
125
source/funkin/util/StructureUtil.hx
Normal file
125
source/funkin/util/StructureUtil.hx
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
package funkin.util;
|
||||||
|
|
||||||
|
import funkin.util.tools.MapTools;
|
||||||
|
import haxe.DynamicAccess;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for working with anonymous structures.
|
||||||
|
*/
|
||||||
|
class StructureUtil
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Merge two structures, with the second overwriting the first.
|
||||||
|
* Performs a SHALLOW clone, where child structures are not merged.
|
||||||
|
* @param a The base structure.
|
||||||
|
* @param b The new structure.
|
||||||
|
* @return The merged structure.
|
||||||
|
*/
|
||||||
|
public static function merge(a:Dynamic, b:Dynamic):Dynamic
|
||||||
|
{
|
||||||
|
var result:DynamicAccess<Dynamic> = Reflect.copy(a);
|
||||||
|
|
||||||
|
for (field in Reflect.fields(b))
|
||||||
|
{
|
||||||
|
result.set(field, Reflect.field(b, field));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function toMap(a:Dynamic):haxe.ds.Map<String, Dynamic>
|
||||||
|
{
|
||||||
|
var result:haxe.ds.Map<String, Dynamic> = [];
|
||||||
|
|
||||||
|
for (field in Reflect.fields(a))
|
||||||
|
{
|
||||||
|
result.set(field, Reflect.field(a, field));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isMap(a:Dynamic):Bool
|
||||||
|
{
|
||||||
|
return Std.isOfType(a, haxe.Constraints.IMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isObject(a:Dynamic):Bool
|
||||||
|
{
|
||||||
|
switch (Type.typeof(a))
|
||||||
|
{
|
||||||
|
case TObject:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isPrimitive(a:Dynamic):Bool
|
||||||
|
{
|
||||||
|
switch (Type.typeof(a))
|
||||||
|
{
|
||||||
|
case TInt | TFloat | TBool:
|
||||||
|
return true;
|
||||||
|
case TClass(c):
|
||||||
|
return false;
|
||||||
|
case TEnum(e):
|
||||||
|
return false;
|
||||||
|
case TObject:
|
||||||
|
return false;
|
||||||
|
case TFunction:
|
||||||
|
return false;
|
||||||
|
case TNull:
|
||||||
|
return true;
|
||||||
|
case TUnknown:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge two structures, with the second overwriting the first.
|
||||||
|
* Performs a DEEP clone, where child structures are also merged recursively.
|
||||||
|
* @param a The base structure.
|
||||||
|
* @param b The new structure.
|
||||||
|
* @return The merged structure.
|
||||||
|
*/
|
||||||
|
public static function deepMerge(a:Dynamic, b:Dynamic):Dynamic
|
||||||
|
{
|
||||||
|
if (a == null) return b;
|
||||||
|
if (b == null) return null;
|
||||||
|
if (isPrimitive(a) && isPrimitive(b)) return b;
|
||||||
|
if (isMap(b))
|
||||||
|
{
|
||||||
|
if (isMap(a))
|
||||||
|
{
|
||||||
|
return MapTools.merge(a, b);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return StructureUtil.toMap(a).merge(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!Reflect.isObject(a) || !Reflect.isObject(b)) return b;
|
||||||
|
|
||||||
|
var result:DynamicAccess<Dynamic> = Reflect.copy(a);
|
||||||
|
|
||||||
|
for (field in Reflect.fields(b))
|
||||||
|
{
|
||||||
|
if (Reflect.isObject(b))
|
||||||
|
{
|
||||||
|
// Note that isObject also returns true for class instances,
|
||||||
|
// but we just assume that's not a problem here.
|
||||||
|
result.set(field, deepMerge(Reflect.field(result, field), Reflect.field(b, field)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If we're here, b[field] is a primitive.
|
||||||
|
result.set(field, Reflect.field(b, field));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,6 +33,24 @@ class MapTools
|
||||||
return map.copy();
|
return map.copy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new map which is a combination of the two given maps.
|
||||||
|
* @param a The base map.
|
||||||
|
* @param b The other map. The values from this take precedence.
|
||||||
|
* @return The combined map.
|
||||||
|
*/
|
||||||
|
public static function merge<K, T>(a:Map<K, T>, b:Map<K, T>):Map<K, T>
|
||||||
|
{
|
||||||
|
var result = a.copy();
|
||||||
|
|
||||||
|
for (pair in b.keyValueIterator())
|
||||||
|
{
|
||||||
|
result.set(pair.key, pair.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new array with clones of all elements of the given array, to prevent modifying the original.
|
* Create a new array with clones of all elements of the given array, to prevent modifying the original.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue