mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-03-21 09:29:41 +00:00
A bunch of Freeplay visual fixes
This commit is contained in:
parent
78fb6e1188
commit
7511de1e7a
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 5a0c8da32111571648b8fd07deabe2f78ae99eaa
|
||||
Subproject commit dc226333655b7ec841b213968ef5264278fbcd63
|
33
source/funkin/Assets.hx
Normal file
33
source/funkin/Assets.hx
Normal file
|
@ -0,0 +1,33 @@
|
|||
package funkin;
|
||||
|
||||
/**
|
||||
* A wrapper around `openfl.utils.Assets` which disallows access to the harmful functions.
|
||||
* Later we'll add Funkin-specific caching to this.
|
||||
*/
|
||||
class Assets
|
||||
{
|
||||
public static function getText(path:String):String
|
||||
{
|
||||
return openfl.utils.Assets.getText(path);
|
||||
}
|
||||
|
||||
public static function getMusic(path:String):openfl.media.Sound
|
||||
{
|
||||
return openfl.utils.Assets.getMusic(path);
|
||||
}
|
||||
|
||||
public static function getBitmapData(path:String):openfl.display.BitmapData
|
||||
{
|
||||
return openfl.utils.Assets.getBitmapData(path);
|
||||
}
|
||||
|
||||
public static function getBytes(path:String):haxe.io.Bytes
|
||||
{
|
||||
return openfl.utils.Assets.getBytes(path);
|
||||
}
|
||||
|
||||
public static function list(type:openfl.utils.AssetType):Array<String>
|
||||
{
|
||||
return openfl.utils.Assets.list(type);
|
||||
}
|
||||
}
|
|
@ -31,6 +31,13 @@ class PlayerData
|
|||
@:default(false)
|
||||
public var showUnownedChars:Bool = false;
|
||||
|
||||
/**
|
||||
* Which freeplay style to use for this character.
|
||||
*/
|
||||
@:optional
|
||||
@:default("bf")
|
||||
public var freeplayStyle:String = Constants.DEFAULT_FREEPLAY_STYLE;
|
||||
|
||||
/**
|
||||
* Data for displaying this character in the Freeplay menu.
|
||||
* If null, display no DJ.
|
||||
|
@ -105,6 +112,9 @@ class PlayerFreeplayDJData
|
|||
@:jignored
|
||||
var prefixToOffsetsMap:Map<String, Array<Float>>;
|
||||
|
||||
@:optional
|
||||
var charSelect:Null<PlayerFreeplayDJCharSelectData>;
|
||||
|
||||
@:optional
|
||||
var cartoon:Null<PlayerFreeplayDJCartoonData>;
|
||||
|
||||
|
@ -237,6 +247,11 @@ class PlayerFreeplayDJData
|
|||
{
|
||||
return fistPump?.loopBadEndFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getCharSelectTransitionDelay():Float
|
||||
{
|
||||
return charSelect?.transitionDelay ?? 0.25;
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerCharSelectData
|
||||
|
@ -300,6 +315,11 @@ typedef PlayerResultsAnimationData =
|
|||
var loopFrameLabel:Null<String>;
|
||||
};
|
||||
|
||||
typedef PlayerFreeplayDJCharSelectData =
|
||||
{
|
||||
var transitionDelay:Float;
|
||||
}
|
||||
|
||||
typedef PlayerFreeplayDJCartoonData =
|
||||
{
|
||||
var soundClickFrame:Int;
|
||||
|
|
|
@ -264,11 +264,20 @@ class SongOffsets implements ICloneable<SongOffsets>
|
|||
@:default([])
|
||||
public var vocals:Map<String, Float>;
|
||||
|
||||
public function new(instrumental:Float = 0.0, ?altInstrumentals:Map<String, Float>, ?vocals:Map<String, Float>)
|
||||
/**
|
||||
* The offset, in milliseconds, to apply to the songs vocals, relative to each alternate instrumental.
|
||||
* This is useful for the circumstance where, for example, an alt instrumental has a few seconds of lead in before the song starts.
|
||||
*/
|
||||
@:optional
|
||||
@:default([])
|
||||
public var altVocals:Map<String, Map<String, Float>>;
|
||||
|
||||
public function new(instrumental:Float = 0.0, ?altInstrumentals:Map<String, Float>, ?vocals:Map<String, Float>, ?altVocals:Map<String, Map<String, Float>>)
|
||||
{
|
||||
this.instrumental = instrumental;
|
||||
this.altInstrumentals = altInstrumentals == null ? new Map<String, Float>() : altInstrumentals;
|
||||
this.vocals = vocals == null ? new Map<String, Float>() : vocals;
|
||||
this.altVocals = altVocals == null ? new Map<String, Map<String, Float>>() : altVocals;
|
||||
}
|
||||
|
||||
public function getInstrumentalOffset(?instrumental:String):Float
|
||||
|
|
|
@ -105,23 +105,6 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
return this.currentAnimation;
|
||||
}
|
||||
|
||||
/**
|
||||
* `anim.finished` always returns false on looping animations,
|
||||
* but this function will return true if we are on the last frame of the looping animation.
|
||||
*/
|
||||
public function isLoopFinished():Bool
|
||||
{
|
||||
if (this.anim == null) return false;
|
||||
if (!this.anim.isPlaying) return false;
|
||||
|
||||
// Reverse animation finished.
|
||||
if (this.anim.reversed && this.anim.curFrame == 0) return true;
|
||||
// Forward animation finished.
|
||||
if (!this.anim.reversed && this.anim.curFrame >= (this.anim.length - 1)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var _completeAnim:Bool = false;
|
||||
|
||||
var fr:FlxKeyFrame = null;
|
||||
|
@ -142,6 +125,8 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
// Skip if not allowed to play animations.
|
||||
if ((!canPlayOtherAnims && !ignoreOther)) return;
|
||||
|
||||
if (anim == null) return;
|
||||
|
||||
if (id == null || id == '') id = this.currentAnimation;
|
||||
|
||||
if (this.currentAnimation == id && !restart)
|
||||
|
@ -189,10 +174,16 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
// Move to the first frame of the animation.
|
||||
// goToFrameLabel(id);
|
||||
trace('Playing animation $id');
|
||||
this.anim.play(id, restart, false, startFrame);
|
||||
goToFrameLabel(id);
|
||||
|
||||
fr = anim.getFrameLabel(id);
|
||||
if (this.anim.symbolDictionary.exists(id) || (this.anim.getByName(id) != null))
|
||||
{
|
||||
this.anim.play(id, restart, false, startFrame);
|
||||
}
|
||||
// Only call goToFrameLabel if there is a frame label with that name. This prevents annoying warnings!
|
||||
if (getFrameLabelNames().indexOf(id) != -1)
|
||||
{
|
||||
goToFrameLabel(id);
|
||||
fr = anim.getFrameLabel(id);
|
||||
}
|
||||
|
||||
anim.curFrame += startFrame;
|
||||
this.currentAnimation = id;
|
||||
|
@ -218,6 +209,8 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
*/
|
||||
public function isLoopComplete():Bool
|
||||
{
|
||||
if (this.anim == null) return false;
|
||||
if (!this.anim.isPlaying) return false;
|
||||
return (anim.reversed && anim.curFrame == 0 || !(anim.reversed) && (anim.curFrame) >= (anim.length - 1));
|
||||
}
|
||||
|
||||
|
@ -244,6 +237,18 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
this.anim.goToFrameLabel(label);
|
||||
}
|
||||
|
||||
function getFrameLabelNames(?layer:haxe.extern.EitherType<Int, String> = null)
|
||||
{
|
||||
var labels = this.anim.getFrameLabels(layer);
|
||||
var array = [];
|
||||
for (label in labels)
|
||||
{
|
||||
array.push(label.name);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
function getNextFrameLabel(label:String):String
|
||||
{
|
||||
return listAnimations()[(getLabelIndex(label) + 1) % listAnimations().length];
|
||||
|
@ -272,7 +277,7 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
{
|
||||
onAnimationFrame.dispatch(currentAnimation, frame);
|
||||
|
||||
if (fr != null && frame > (fr.index + fr.duration - 1) || isLoopFinished())
|
||||
if (fr != null && frame > (fr.index + fr.duration - 1) || isLoopComplete())
|
||||
{
|
||||
anim.pause();
|
||||
_onAnimationComplete();
|
||||
|
|
|
@ -73,6 +73,22 @@ interface INoteScriptedClass extends IScriptedClass
|
|||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a set of callbacks available to scripted classes which represent sprites synced with the BPM.
|
||||
*/
|
||||
interface IBPMSyncedScriptedClass extends IScriptedClass
|
||||
{
|
||||
/**
|
||||
* Called once every step of the song.
|
||||
*/
|
||||
public function onStepHit(event:SongTimeScriptEvent):Void;
|
||||
|
||||
/**
|
||||
* Called once every beat of the song.
|
||||
*/
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Developer note:
|
||||
*
|
||||
|
@ -86,7 +102,7 @@ interface INoteScriptedClass extends IScriptedClass
|
|||
/**
|
||||
* Defines a set of callbacks available to scripted classes that involve the lifecycle of the Play State.
|
||||
*/
|
||||
interface IPlayStateScriptedClass extends INoteScriptedClass
|
||||
interface IPlayStateScriptedClass extends INoteScriptedClass extends IBPMSyncedScriptedClass
|
||||
{
|
||||
/**
|
||||
* Called when the game is paused.
|
||||
|
@ -136,16 +152,6 @@ interface IPlayStateScriptedClass extends INoteScriptedClass
|
|||
*/
|
||||
public function onSongEvent(event:SongEventScriptEvent):Void;
|
||||
|
||||
/**
|
||||
* Called once every step of the song.
|
||||
*/
|
||||
public function onStepHit(event:SongTimeScriptEvent):Void;
|
||||
|
||||
/**
|
||||
* Called once every beat of the song.
|
||||
*/
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void;
|
||||
|
||||
/**
|
||||
* Called when the countdown of the song starts.
|
||||
*/
|
||||
|
|
|
@ -235,6 +235,10 @@ class PolymodHandler
|
|||
|
||||
Polymod.addImportAlias('funkin.data.event.SongEventSchema', funkin.data.event.SongEventSchema.SongEventSchemaRaw);
|
||||
|
||||
// `lime.utils.Assets` literally just has a private `resolveClass` function for some reason? so we replace it with our own.
|
||||
Polymod.addImportAlias('lime.utils.Assets', funkin.Assets);
|
||||
Polymod.addImportAlias('openfl.utils.Assets', funkin.Assets);
|
||||
|
||||
// Add blacklisting for prohibited classes and packages.
|
||||
|
||||
// `Sys`
|
||||
|
@ -269,11 +273,6 @@ class PolymodHandler
|
|||
// System.load() can load malicious DLLs
|
||||
Polymod.blacklistImport('lime.system.System');
|
||||
|
||||
// `lime.utils.Assets`
|
||||
// Literally just has a private `resolveClass` function for some reason?
|
||||
Polymod.blacklistImport('lime.utils.Assets');
|
||||
Polymod.blacklistImport('openfl.utils.Assets');
|
||||
|
||||
// `openfl.desktop.NativeProcess`
|
||||
// Can load native processes on the host operating system.
|
||||
Polymod.blacklistImport('openfl.desktop.NativeProcess');
|
||||
|
|
|
@ -94,6 +94,21 @@ class ScriptEventDispatcher
|
|||
}
|
||||
}
|
||||
|
||||
if (Std.isOfType(target, IBPMSyncedScriptedClass))
|
||||
{
|
||||
var t:IBPMSyncedScriptedClass = cast(target, IBPMSyncedScriptedClass);
|
||||
switch (event.type)
|
||||
{
|
||||
case SONG_BEAT_HIT:
|
||||
t.onBeatHit(cast event);
|
||||
return;
|
||||
case SONG_STEP_HIT:
|
||||
t.onStepHit(cast event);
|
||||
return;
|
||||
default: // Continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (Std.isOfType(target, IPlayStateScriptedClass))
|
||||
{
|
||||
var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass);
|
||||
|
@ -102,12 +117,6 @@ class ScriptEventDispatcher
|
|||
case NOTE_GHOST_MISS:
|
||||
t.onNoteGhostMiss(cast event);
|
||||
return;
|
||||
case SONG_BEAT_HIT:
|
||||
t.onBeatHit(cast event);
|
||||
return;
|
||||
case SONG_STEP_HIT:
|
||||
t.onStepHit(cast event);
|
||||
return;
|
||||
case SONG_START:
|
||||
t.onSongStart(event);
|
||||
return;
|
||||
|
|
|
@ -7,10 +7,12 @@ import flixel.math.FlxMath;
|
|||
import funkin.util.FramesJSFLParser;
|
||||
import funkin.util.FramesJSFLParser.FramesJSFLInfo;
|
||||
import funkin.util.FramesJSFLParser.FramesJSFLFrame;
|
||||
import funkin.modding.IScriptedClass.IBPMSyncedScriptedClass;
|
||||
import flixel.math.FlxMath;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.vis.dsp.SpectralAnalyzer;
|
||||
|
||||
class CharSelectGF extends FlxAtlasSprite
|
||||
class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
|
||||
{
|
||||
var fadeTimer:Float = 0;
|
||||
var fadingStatus:FadeStatus = OFF;
|
||||
|
@ -54,6 +56,7 @@ class CharSelectGF extends FlxAtlasSprite
|
|||
default:
|
||||
}
|
||||
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
if (FlxG.keys.justPressed.J)
|
||||
{
|
||||
alpha = 1;
|
||||
|
@ -65,8 +68,27 @@ class CharSelectGF extends FlxAtlasSprite
|
|||
alpha = 0;
|
||||
fadingStatus = FADE_IN;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
public function onStepHit(event:SongTimeScriptEvent):Void {}
|
||||
|
||||
var danceEvery:Int = 2;
|
||||
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void
|
||||
{
|
||||
// TODO: There's a minor visual bug where there's a little stutter.
|
||||
// This happens because the animation is getting restarted while it's already playing.
|
||||
// I tried make this not interrupt an existing idle,
|
||||
// but isAnimationFinished() and isLoopComplete() both don't work! What the hell?
|
||||
// danceEvery isn't necessary if that gets fixed.
|
||||
if (getCurrentAnimation() == "idle" && (event.beat % danceEvery == 0))
|
||||
{
|
||||
trace('GF beat hit');
|
||||
playAnimation("idle", true, false, false);
|
||||
}
|
||||
};
|
||||
|
||||
override public function draw()
|
||||
{
|
||||
if (analyzer != null) drawFFT();
|
||||
|
@ -160,18 +182,27 @@ class CharSelectGF extends FlxAtlasSprite
|
|||
}
|
||||
|
||||
// We don't need to update any anims if we didn't change GF
|
||||
if (prevGF == curGF) return;
|
||||
if (prevGF != curGF)
|
||||
{
|
||||
loadAtlas(Paths.animateAtlas("charSelect/" + curGF + "Chill"));
|
||||
|
||||
loadAtlas(Paths.animateAtlas("charSelect/" + curGF + "Chill"));
|
||||
animInInfo = FramesJSFLParser.parse(Paths.file("images/charSelect/" + curGF + "AnimInfo/" + curGF + "In.txt"));
|
||||
animOutInfo = FramesJSFLParser.parse(Paths.file("images/charSelect/" + curGF + "AnimInfo/" + curGF + "Out.txt"));
|
||||
}
|
||||
|
||||
animInInfo = FramesJSFLParser.parse(Paths.file("images/charSelect/" + curGF + "AnimInfo/" + curGF + "In.txt"));
|
||||
animOutInfo = FramesJSFLParser.parse(Paths.file("images/charSelect/" + curGF + "AnimInfo/" + curGF + "Out.txt"));
|
||||
|
||||
playAnimation("idle", true, false, true);
|
||||
playAnimation("idle", true, false, false);
|
||||
// addFrameCallback(getNextFrameLabel("idle"), () -> playAnimation("idle", true, false, false));
|
||||
|
||||
updateHitbox();
|
||||
}
|
||||
|
||||
public function onScriptEvent(event:ScriptEvent):Void {};
|
||||
|
||||
public function onCreate(event:ScriptEvent):Void {};
|
||||
|
||||
public function onDestroy(event:ScriptEvent):Void {};
|
||||
|
||||
public function onUpdate(event:UpdateScriptEvent):Void {};
|
||||
}
|
||||
|
||||
enum FadeStatus
|
||||
|
|
|
@ -3,8 +3,10 @@ package funkin.ui.charSelect;
|
|||
import flixel.FlxSprite;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import flxanimate.animate.FlxKeyFrame;
|
||||
import funkin.modding.IScriptedClass.IBPMSyncedScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
|
||||
class CharSelectPlayer extends FlxAtlasSprite
|
||||
class CharSelectPlayer extends FlxAtlasSprite implements IBPMSyncedScriptedClass
|
||||
{
|
||||
var desLp:FlxKeyFrame = null;
|
||||
|
||||
|
@ -18,11 +20,20 @@ class CharSelectPlayer extends FlxAtlasSprite
|
|||
switch (animLabel)
|
||||
{
|
||||
case "slidein":
|
||||
if (hasAnimation("slidein idle point")) playAnimation("slidein idle point", true, false, false);
|
||||
if (hasAnimation("slidein idle point"))
|
||||
{
|
||||
playAnimation("slidein idle point", true, false, false);
|
||||
}
|
||||
else
|
||||
playAnimation("idle", true, false, true);
|
||||
{
|
||||
// Handled by onBeatHit now
|
||||
playAnimation("idle", true, false, false);
|
||||
}
|
||||
case "slidein idle point":
|
||||
playAnimation("idle", true, false, true);
|
||||
// Handled by onBeatHit now
|
||||
playAnimation("idle", true, false, false);
|
||||
case "idle":
|
||||
trace('Waiting for onBeatHit');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -31,6 +42,22 @@ class CharSelectPlayer extends FlxAtlasSprite
|
|||
});
|
||||
}
|
||||
|
||||
public function onStepHit(event:SongTimeScriptEvent):Void {}
|
||||
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void
|
||||
{
|
||||
// TODO: There's a minor visual bug where there's a little stutter.
|
||||
// This happens because the animation is getting restarted while it's already playing.
|
||||
// I tried make this not interrupt an existing idle,
|
||||
// but isAnimationFinished() and isLoopComplete() both don't work! What the hell?
|
||||
// danceEvery isn't necessary if that gets fixed.
|
||||
if (getCurrentAnimation() == "idle")
|
||||
{
|
||||
trace('Player beat hit');
|
||||
playAnimation("idle", true, false, false);
|
||||
}
|
||||
};
|
||||
|
||||
public function updatePosition(str:String)
|
||||
{
|
||||
switch (str)
|
||||
|
@ -61,4 +88,12 @@ class CharSelectPlayer extends FlxAtlasSprite
|
|||
|
||||
updatePosition(str);
|
||||
}
|
||||
|
||||
public function onScriptEvent(event:ScriptEvent):Void {};
|
||||
|
||||
public function onCreate(event:ScriptEvent):Void {};
|
||||
|
||||
public function onDestroy(event:ScriptEvent):Void {};
|
||||
|
||||
public function onUpdate(event:UpdateScriptEvent):Void {};
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
|
||||
var stageSpr:FlxSprite = new FlxSprite(-40, 391);
|
||||
stageSpr.frames = Paths.getSparrowAtlas("charSelect/charSelectStage");
|
||||
stageSpr.animation.addByPrefix("idle", "stage", 24, true);
|
||||
stageSpr.animation.addByPrefix("idle", "stage full instance 1", 24, true);
|
||||
stageSpr.animation.play("idle");
|
||||
add(stageSpr);
|
||||
|
||||
|
@ -195,14 +195,14 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
|
||||
var dipshitBlur:FlxSprite = new FlxSprite(419, -65);
|
||||
dipshitBlur.frames = Paths.getSparrowAtlas("charSelect/dipshitBlur");
|
||||
dipshitBlur.animation.addByPrefix('idle', "CHOOSE vertical", 24, true);
|
||||
dipshitBlur.animation.addByPrefix('idle', "CHOOSE vertical offset instance 1", 24, true);
|
||||
dipshitBlur.blend = BlendMode.ADD;
|
||||
dipshitBlur.animation.play("idle");
|
||||
add(dipshitBlur);
|
||||
|
||||
var dipshitBacking:FlxSprite = new FlxSprite(423, -17);
|
||||
dipshitBacking.frames = Paths.getSparrowAtlas("charSelect/dipshitBacking");
|
||||
dipshitBacking.animation.addByPrefix('idle', "CHOOSE horizontal", 24, true);
|
||||
dipshitBacking.animation.addByPrefix('idle', "CHOOSE horizontal offset instance 1", 24, true);
|
||||
dipshitBacking.blend = BlendMode.ADD;
|
||||
dipshitBacking.animation.play("idle");
|
||||
add(dipshitBacking);
|
||||
|
@ -261,14 +261,14 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
cursorConfirmed = new FlxSprite(0, 0);
|
||||
cursorConfirmed.scrollFactor.set();
|
||||
cursorConfirmed.frames = Paths.getSparrowAtlas("charSelect/charSelectorConfirm");
|
||||
cursorConfirmed.animation.addByPrefix("idle", "cursor ACCEPTED", 24, true);
|
||||
cursorConfirmed.animation.addByPrefix("idle", "cursor ACCEPTED instance 1", 24, true);
|
||||
cursorConfirmed.visible = false;
|
||||
add(cursorConfirmed);
|
||||
|
||||
cursorDenied = new FlxSprite(0, 0);
|
||||
cursorDenied.scrollFactor.set();
|
||||
cursorDenied.frames = Paths.getSparrowAtlas("charSelect/charSelectorDenied");
|
||||
cursorDenied.animation.addByPrefix("idle", "cursor DENIED", 24, false);
|
||||
cursorDenied.animation.addByPrefix("idle", "cursor DENIED instance 1", 24, false);
|
||||
cursorDenied.visible = false;
|
||||
add(cursorDenied);
|
||||
|
||||
|
@ -289,8 +289,6 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
FlxG.debugger.addTrackerProfile(new TrackerProfile(CharSelectSubState, ["curChar", "grpXSpread", "grpYSpread"]));
|
||||
FlxG.debugger.track(this);
|
||||
|
||||
FlxG.sound.playMusic(Paths.music('charSelect/charSelectMusic'));
|
||||
|
||||
camFollow = new FlxObject(0, 0, 1, 1);
|
||||
add(camFollow);
|
||||
camFollow.screenCenter();
|
||||
|
@ -567,6 +565,16 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
cursorDarkBlue.y = MathUtil.coolLerp(cursorDarkBlue.y, cursorLocIntended.y, lerpAmnt * 0.2);
|
||||
}
|
||||
|
||||
public override function dispatchEvent(event:ScriptEvent):Void
|
||||
{
|
||||
// super.dispatchEvent(event) dispatches event to module scripts.
|
||||
super.dispatchEvent(event);
|
||||
|
||||
// Dispatch events (like onBeatHit) to props
|
||||
ScriptEventDispatcher.callEvent(playerChill, event);
|
||||
ScriptEventDispatcher.callEvent(gfChill, event);
|
||||
}
|
||||
|
||||
function spamOnStep():Void
|
||||
{
|
||||
if (spamUp || spamDown || spamLeft || spamRight)
|
||||
|
|
|
@ -15,7 +15,9 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
{
|
||||
// Represents the sprite's current status.
|
||||
// Without state machines I would have driven myself crazy years ago.
|
||||
public var currentState:FreeplayDJState = Intro;
|
||||
// Made this PRIVATE so we can keep track of everything that can alter the state!
|
||||
// Add a function to this class if you want to edit this value from outside.
|
||||
private var currentState:FreeplayDJState = Intro;
|
||||
|
||||
// A callback activated when the intro animation finishes.
|
||||
public var onIntroDone:FlxSignal = new FlxSignal();
|
||||
|
@ -378,7 +380,7 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
|
||||
public function toCharSelect():Void
|
||||
{
|
||||
if (hasAnimation('charSelect'))
|
||||
if (hasAnimation(playableCharData.getAnimationPrefix('charSelect')))
|
||||
{
|
||||
currentState = CharSelect;
|
||||
var animPrefix = playableCharData.getAnimationPrefix('charSelect');
|
||||
|
|
|
@ -224,12 +224,14 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
currentCharacterId = params?.character ?? rememberedCharacterId;
|
||||
var fetchPlayableCharacter = function():PlayableCharacter {
|
||||
var result = PlayerRegistry.instance.fetchEntry(params?.character ?? rememberedCharacterId);
|
||||
if (result == null) throw 'No valid playable character with id ${params?.character}';
|
||||
var targetCharId = params?.character ?? rememberedCharacterId;
|
||||
var result = PlayerRegistry.instance.fetchEntry(targetCharId);
|
||||
if (result == null) throw 'No valid playable character with id ${targetCharId}';
|
||||
return result;
|
||||
};
|
||||
currentCharacter = fetchPlayableCharacter();
|
||||
|
||||
styleData = FreeplayStyleRegistry.instance.fetchEntry(currentCharacter.getFreeplayStyleID());
|
||||
rememberedCharacterId = currentCharacter?.id ?? Constants.DEFAULT_CHARACTER;
|
||||
|
||||
fromResultsParams = params?.fromResults;
|
||||
|
@ -317,6 +319,9 @@ class FreeplayState extends MusicBeatSubState
|
|||
isDebug = true;
|
||||
#end
|
||||
|
||||
// Block input until the intro finishes.
|
||||
busy = true;
|
||||
|
||||
// Add a null entry that represents the RANDOM option
|
||||
songs.push(null);
|
||||
|
||||
|
@ -645,13 +650,14 @@ class FreeplayState extends MusicBeatSubState
|
|||
// be careful not to "add()" things in here unless it's to a group that's already added to the state
|
||||
// otherwise it won't be properly attatched to funnyCamera (relavent code should be at the bottom of create())
|
||||
var onDJIntroDone = function() {
|
||||
busy = false;
|
||||
|
||||
// when boyfriend hits dat shiii
|
||||
|
||||
albumRoll.playIntro();
|
||||
var daSong = grpCapsules.members[curSelected].songData;
|
||||
albumRoll.albumId = daSong?.albumId;
|
||||
|
||||
|
||||
FlxTween.tween(grpDifficulties, {x: 90}, 0.6, {ease: FlxEase.quartOut});
|
||||
|
||||
diffSelLeft.visible = true;
|
||||
|
@ -1187,6 +1193,158 @@ class FreeplayState extends MusicBeatSubState
|
|||
});
|
||||
}
|
||||
|
||||
function tryOpenCharSelect():Void
|
||||
{
|
||||
// Check if we have ACCESS to character select!
|
||||
trace('Is Pico unlocked? ${PlayerRegistry.instance.fetchEntry('pico')?.isUnlocked()}');
|
||||
trace('Number of characters: ${PlayerRegistry.instance.countUnlockedCharacters()}');
|
||||
|
||||
if (PlayerRegistry.instance.countUnlockedCharacters() > 1)
|
||||
{
|
||||
trace('Opening character select!');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Not enough characters unlocked to open character select!');
|
||||
FunkinSound.playOnce(Paths.sound('cancelMenu'));
|
||||
return;
|
||||
}
|
||||
|
||||
busy = true;
|
||||
|
||||
FunkinSound.playOnce(Paths.sound('confirmMenu'));
|
||||
|
||||
if (dj != null)
|
||||
{
|
||||
dj.toCharSelect();
|
||||
}
|
||||
|
||||
// Get this character's transition delay, with a reasonable default.
|
||||
var transitionDelay:Float = currentCharacter.getFreeplayDJData()?.getCharSelectTransitionDelay() ?? 0.25;
|
||||
|
||||
new FlxTimer().start(transitionDelay, _ -> {
|
||||
transitionToCharSelect();
|
||||
});
|
||||
}
|
||||
|
||||
function transitionToCharSelect():Void
|
||||
{
|
||||
var transitionGradient = new FlxSprite(0, 720).loadGraphic(Paths.image('freeplay/transitionGradient'));
|
||||
transitionGradient.scale.set(1280, 1);
|
||||
transitionGradient.updateHitbox();
|
||||
transitionGradient.cameras = [rankCamera];
|
||||
exitMoversCharSel.set([transitionGradient],
|
||||
{
|
||||
y: -720,
|
||||
speed: 0.8,
|
||||
wait: 0.1
|
||||
});
|
||||
add(transitionGradient);
|
||||
for (index => capsule in grpCapsules.members)
|
||||
{
|
||||
var distFromSelected:Float = Math.abs(index - curSelected) - 1;
|
||||
if (distFromSelected < 5)
|
||||
{
|
||||
capsule.doLerp = false;
|
||||
exitMoversCharSel.set([capsule],
|
||||
{
|
||||
y: -250,
|
||||
speed: 0.8,
|
||||
wait: 0.1
|
||||
});
|
||||
}
|
||||
}
|
||||
fadeShader.fade(1.0, 0.0, 0.8, {ease: FlxEase.quadIn});
|
||||
FlxG.sound.music.fadeOut(0.9, 0);
|
||||
new FlxTimer().start(0.9, _ -> {
|
||||
FlxG.switchState(new funkin.ui.charSelect.CharSelectSubState());
|
||||
});
|
||||
for (grpSpr in exitMoversCharSel.keys())
|
||||
{
|
||||
var moveData:Null<MoveData> = exitMoversCharSel.get(grpSpr);
|
||||
if (moveData == null) continue;
|
||||
|
||||
for (spr in grpSpr)
|
||||
{
|
||||
if (spr == null) continue;
|
||||
|
||||
var funnyMoveShit:MoveData = moveData;
|
||||
|
||||
var moveDataY = funnyMoveShit.y ?? spr.y;
|
||||
var moveDataSpeed = funnyMoveShit.speed ?? 0.2;
|
||||
var moveDataWait = funnyMoveShit.wait ?? 0.0;
|
||||
|
||||
FlxTween.tween(spr, {y: moveDataY + spr.y}, moveDataSpeed, {ease: FlxEase.backIn});
|
||||
}
|
||||
}
|
||||
backingCard?.enterCharSel();
|
||||
}
|
||||
|
||||
function enterFromCharSel():Void
|
||||
{
|
||||
busy = true;
|
||||
if (_parentState != null) _parentState.persistentDraw = false;
|
||||
|
||||
var transitionGradient = new FlxSprite(0, 720).loadGraphic(Paths.image('freeplay/transitionGradient'));
|
||||
transitionGradient.scale.set(1280, 1);
|
||||
transitionGradient.updateHitbox();
|
||||
transitionGradient.cameras = [rankCamera];
|
||||
exitMoversCharSel.set([transitionGradient],
|
||||
{
|
||||
y: -720,
|
||||
speed: 1.5,
|
||||
wait: 0.1
|
||||
});
|
||||
add(transitionGradient);
|
||||
// FlxTween.tween(transitionGradient, {alpha: 0}, 1, {ease: FlxEase.circIn});
|
||||
// for (index => capsule in grpCapsules.members)
|
||||
// {
|
||||
// var distFromSelected:Float = Math.abs(index - curSelected) - 1;
|
||||
// if (distFromSelected < 5)
|
||||
// {
|
||||
// capsule.doLerp = false;
|
||||
// exitMoversCharSel.set([capsule],
|
||||
// {
|
||||
// y: -250,
|
||||
// speed: 0.8,
|
||||
// wait: 0.1
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
fadeShader.fade(0.0, 1.0, 0.8, {ease: FlxEase.quadIn});
|
||||
for (grpSpr in exitMoversCharSel.keys())
|
||||
{
|
||||
var moveData:Null<MoveData> = exitMoversCharSel.get(grpSpr);
|
||||
if (moveData == null) continue;
|
||||
|
||||
for (spr in grpSpr)
|
||||
{
|
||||
if (spr == null) continue;
|
||||
|
||||
var funnyMoveShit:MoveData = moveData;
|
||||
|
||||
var moveDataY = funnyMoveShit.y ?? spr.y;
|
||||
var moveDataSpeed = funnyMoveShit.speed ?? 0.2;
|
||||
var moveDataWait = funnyMoveShit.wait ?? 0.0;
|
||||
|
||||
spr.y += moveDataY;
|
||||
FlxTween.tween(spr, {y: spr.y - moveDataY}, moveDataSpeed * 1.2,
|
||||
{
|
||||
ease: FlxEase.expoOut,
|
||||
onComplete: function(_) {
|
||||
for (index => capsule in grpCapsules.members)
|
||||
{
|
||||
capsule.doLerp = true;
|
||||
fromCharSelect = false;
|
||||
busy = false;
|
||||
albumRoll.applyExitMovers(exitMovers, exitMoversCharSel);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var touchY:Float = 0;
|
||||
var touchX:Float = 0;
|
||||
var dxTouch:Float = 0;
|
||||
|
@ -1247,32 +1405,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
if (controls.FREEPLAY_CHAR_SELECT && !busy)
|
||||
{
|
||||
// Check if we have ACCESS to character select!
|
||||
trace('Is Pico unlocked? ${PlayerRegistry.instance.fetchEntry('pico')?.isUnlocked()}');
|
||||
trace('Number of characters: ${PlayerRegistry.instance.countUnlockedCharacters()}');
|
||||
|
||||
if (PlayerRegistry.instance.countUnlockedCharacters() > 1)
|
||||
{
|
||||
if (dj != null)
|
||||
{
|
||||
busy = true;
|
||||
// Transition to character select after animation
|
||||
dj.onCharSelectComplete = function() {
|
||||
FlxG.switchState(new funkin.ui.charSelect.CharSelectSubState());
|
||||
}
|
||||
dj.toCharSelect();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Transition to character select immediately
|
||||
FlxG.switchState(new funkin.ui.charSelect.CharSelectSubState());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Not enough characters unlocked to open character select!');
|
||||
FunkinSound.playOnce(Paths.sound('cancelMenu'));
|
||||
}
|
||||
tryOpenCharSelect();
|
||||
}
|
||||
|
||||
if (controls.FREEPLAY_FAVORITE && !busy)
|
||||
|
|
|
@ -9,6 +9,7 @@ import funkin.play.scoring.Scoring.ScoringRank;
|
|||
* An object used to retrieve data about a playable character (also known as "weeks").
|
||||
* Can be scripted to override each function, for custom behavior.
|
||||
*/
|
||||
@:nullSafety
|
||||
class PlayableCharacter implements IRegistryEntry<PlayerData>
|
||||
{
|
||||
/**
|
||||
|
@ -19,7 +20,7 @@ class PlayableCharacter implements IRegistryEntry<PlayerData>
|
|||
/**
|
||||
* Playable character data as parsed from the JSON file.
|
||||
*/
|
||||
public final _data:PlayerData;
|
||||
public final _data:Null<PlayerData>;
|
||||
|
||||
/**
|
||||
* @param id The ID of the JSON file to parse.
|
||||
|
@ -41,7 +42,7 @@ class PlayableCharacter implements IRegistryEntry<PlayerData>
|
|||
public function getName():String
|
||||
{
|
||||
// TODO: Maybe add localization support?
|
||||
return _data.name;
|
||||
return _data?.name ?? "Unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,7 +51,7 @@ class PlayableCharacter implements IRegistryEntry<PlayerData>
|
|||
*/
|
||||
public function getOwnedCharacterIds():Array<String>
|
||||
{
|
||||
return _data.ownedChars;
|
||||
return _data?.ownedChars ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,17 +60,17 @@ class PlayableCharacter implements IRegistryEntry<PlayerData>
|
|||
*/
|
||||
public function shouldShowUnownedChars():Bool
|
||||
{
|
||||
return _data.showUnownedChars;
|
||||
return _data?.showUnownedChars ?? false;
|
||||
}
|
||||
|
||||
public function shouldShowCharacter(id:String):Bool
|
||||
{
|
||||
if (_data.ownedChars.contains(id))
|
||||
if (getOwnedCharacterIds().contains(id))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_data.showUnownedChars)
|
||||
if (shouldShowUnownedChars())
|
||||
{
|
||||
var result = !PlayerRegistry.instance.isCharacterOwned(id);
|
||||
return result;
|
||||
|
@ -78,19 +79,25 @@ class PlayableCharacter implements IRegistryEntry<PlayerData>
|
|||
return false;
|
||||
}
|
||||
|
||||
public function getFreeplayDJData():PlayerFreeplayDJData
|
||||
public function getFreeplayStyleID():String
|
||||
{
|
||||
return _data.freeplayDJ;
|
||||
return _data?.freeplayStyle ?? Constants.DEFAULT_FREEPLAY_STYLE;
|
||||
}
|
||||
|
||||
public function getFreeplayDJData():Null<PlayerFreeplayDJData>
|
||||
{
|
||||
return _data?.freeplayDJ;
|
||||
}
|
||||
|
||||
public function getFreeplayDJText(index:Int):String
|
||||
{
|
||||
return _data.freeplayDJ.getFreeplayDJText(index);
|
||||
// Silly little placeholder
|
||||
return _data?.freeplayDJ?.getFreeplayDJText(index) ?? 'GET FREAKY ON A FRIDAY';
|
||||
}
|
||||
|
||||
public function getCharSelectData():PlayerCharSelectData
|
||||
public function getCharSelectData():Null<PlayerCharSelectData>
|
||||
{
|
||||
return _data.charSelect;
|
||||
return _data?.charSelect;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,7 +106,7 @@ class PlayableCharacter implements IRegistryEntry<PlayerData>
|
|||
*/
|
||||
public function getResultsAnimationDatas(rank:ScoringRank):Array<PlayerResultsAnimationData>
|
||||
{
|
||||
if (_data.results == null)
|
||||
if (_data == null || _data.results == null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
@ -124,7 +131,7 @@ class PlayableCharacter implements IRegistryEntry<PlayerData>
|
|||
*/
|
||||
public function isUnlocked():Bool
|
||||
{
|
||||
return _data.unlocked;
|
||||
return _data?.unlocked ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -110,7 +110,17 @@ class MainMenuState extends MusicBeatState
|
|||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
|
||||
openSubState(new FreeplayState());
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// Debug function: Hold SHIFT when selecting Freeplay to swap character without the char select menu
|
||||
var targetCharacter:Null<String> = (FlxG.keys.pressed.SHIFT) ? (FreeplayState.rememberedCharacterId == "pico" ? "bf" : "pico") : null;
|
||||
#else
|
||||
var targetCharacter:Null<String> = null;
|
||||
#end
|
||||
|
||||
openSubState(new FreeplayState(
|
||||
{
|
||||
character: targetCharacter
|
||||
}));
|
||||
});
|
||||
|
||||
#if CAN_OPEN_LINKS
|
||||
|
|
|
@ -258,6 +258,11 @@ class Constants
|
|||
*/
|
||||
public static final DEFAULT_NOTE_STYLE:String = 'funkin';
|
||||
|
||||
/**
|
||||
* The default freeplay style for characters.
|
||||
*/
|
||||
public static final DEFAULT_FREEPLAY_STYLE:String = 'bf';
|
||||
|
||||
/**
|
||||
* The default pixel note style for songs.
|
||||
*/
|
||||
|
|
|
@ -33,4 +33,19 @@ class ReflectUtil
|
|||
{
|
||||
return Type.getClassName(Type.getClass(obj));
|
||||
}
|
||||
|
||||
public static function getAnonymousFieldsOf(obj:Dynamic):Array<String>
|
||||
{
|
||||
return Reflect.fields(obj);
|
||||
}
|
||||
|
||||
public static function getAnonymousField(obj:Dynamic, name:String):Dynamic
|
||||
{
|
||||
return Reflect.field(obj, name);
|
||||
}
|
||||
|
||||
public static function hasAnonymousField(obj:Dynamic, name:String):Bool
|
||||
{
|
||||
return Reflect.hasField(obj, name);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue