1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-03-21 09:29:41 +00:00

assets submod

This commit is contained in:
Cameron Taylor 2024-09-05 00:20:55 -04:00
commit 5c44e61ce1
41 changed files with 2622 additions and 445 deletions

View file

@ -4,14 +4,28 @@ All notable changes will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.5.0] - 2024-08-??
## [0.5.0] - 2024-09-12
### Added
- Added a new Character Select screen to switch between playable characters in Freeplay
- Modding isn't 100% there but we're working on it!
- Added Pico as a playable character! Unlock him by completing Weekend 1 (if you haven't already done that)
- The songs from Weekend 1 have moved; you must now switch to Pico in Freeplay to access them
- Added ## new Pico remixes! Access them by selecting Pico from in the Character Select screen
- Added 10 new Pico remixes! Access them by selecting Pico from in the Character Select screen
- Bopeebo (Pico Mix)
- Fresh (Pico Mix)
- DadBattle (Pico Mix)
- Spookeez (Pico Mix)
- South (Pico Mix)
- Philly Nice (Pico Mix)
- Blammed (Pico Mix)
- Eggnog (Pico Mix)
- Ugh (Pico Mix)
- Guns (Pico Mix)
- Added 1 new Boyfriend remix! Access it by selecting Pico from in the Character Select screen
- Darnell (BF Mix)
- Added 2 new Erect remixes! Access them by switching difficulty on the song
- Cocoa Erect
- Ugh Erect
- Implemented support for a new Instrumental Selector in Freeplay
- Beating a Pico remix lets you use that instrumental when playing as Boyfriend
- Added the first batch of Erect Stages! These graphical overhauls of the original stages will be used when playing Erect remixes and Pico remixes

2
assets

@ -1 +1 @@
Subproject commit e80d92ac484fe432ab34d45b22158f5d565afb0f
Subproject commit 5e5e01fbed1e2d5979f983e195b9c0dfbf852b91

38
source/funkin/Assets.hx Normal file
View file

@ -0,0 +1,38 @@
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 exists(path:String, ?type:openfl.utils.AssetType):Bool
{
return openfl.utils.Assets.exists(path, type);
}
public static function list(type:openfl.utils.AssetType):Array<String>
{
return openfl.utils.Assets.list(type);
}
}

View file

@ -19,6 +19,7 @@ import funkin.play.PlayStatePlaylist;
import openfl.display.BitmapData;
import funkin.data.story.level.LevelRegistry;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.data.freeplay.style.FreeplayStyleRegistry;
import funkin.data.event.SongEventRegistry;
import funkin.data.stage.StageRegistry;
import funkin.data.dialogue.conversation.ConversationRegistry;
@ -170,6 +171,7 @@ class InitState extends FlxState
ConversationRegistry.instance.loadEntries();
DialogueBoxRegistry.instance.loadEntries();
SpeakerRegistry.instance.loadEntries();
FreeplayStyleRegistry.instance.loadEntries();
AlbumRegistry.instance.loadEntries();
StageRegistry.instance.loadEntries();

View file

@ -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
@ -253,6 +268,8 @@ class PlayerCharSelectData
typedef PlayerResultsData =
{
var music:PlayerResultsMusicData;
var perfect:Array<PlayerResultsAnimationData>;
var excellent:Array<PlayerResultsAnimationData>;
var great:Array<PlayerResultsAnimationData>;
@ -260,6 +277,27 @@ typedef PlayerResultsData =
var loss:Array<PlayerResultsAnimationData>;
};
typedef PlayerResultsMusicData =
{
@:optional
var PERFECT_GOLD:String;
@:optional
var PERFECT:String;
@:optional
var EXCELLENT:String;
@:optional
var GREAT:String;
@:optional
var GOOD:String;
@:optional
var SHIT:String;
}
typedef PlayerResultsAnimationData =
{
/**
@ -300,6 +338,11 @@ typedef PlayerResultsAnimationData =
var loopFrameLabel:Null<String>;
};
typedef PlayerFreeplayDJCharSelectData =
{
var transitionDelay:Float;
}
typedef PlayerFreeplayDJCartoonData =
{
var soundClickFrame:Int;

View file

@ -0,0 +1,9 @@
# Freeplay Style Data Schema Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0]
Initial release.

View file

@ -0,0 +1,48 @@
package funkin.data.freeplay.style;
import funkin.data.animation.AnimationData;
/**
* A type definition for the data for an album of songs.
* It includes things like what graphics to display in Freeplay.
* @see https://lib.haxe.org/p/json2object/
*/
typedef FreeplayStyleData =
{
/**
* Semantic version for style data.
*/
public var version:String;
/**
* Asset key for the background image.
*/
public var bgAsset:String;
/**
* Asset key for the difficulty selector image.
*/
public var selectorAsset:String;
/**
* Asset key for the numbers shown at the top right of the screen.
*/
public var numbersAsset:String;
/**
* Asset key for the freeplay capsules.
*/
public var capsuleAsset:String;
/**
* Color data for the capsule text outline.
* the order of this array goes as follows: [DESELECTED, SELECTED]
*/
public var capsuleTextColors:Array<String>;
/**
* Delay time after confirming a song selection, before entering PlayState.
* Useful for letting longer animations play out.
*/
public var startDelay:Float;
}

View file

@ -0,0 +1,84 @@
package funkin.data.freeplay.style;
import funkin.ui.freeplay.FreeplayStyle;
import funkin.data.freeplay.style.FreeplayStyleData;
import funkin.ui.freeplay.ScriptedFreeplayStyle;
class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleData>
{
/**
* The current version string for the style data format.
* Handle breaking changes by incrementing this value
* and adding migration to the `migrateStyleData()` function.
*/
public static final FREEPLAYSTYLE_DATA_VERSION:thx.semver.Version = '1.0.0';
public static final FREEPLAYSTYLE_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
public static final instance:FreeplayStyleRegistry = new FreeplayStyleRegistry();
public function new()
{
super('FREEPLAYSTYLE', 'ui/freeplay/styles', FREEPLAYSTYLE_DATA_VERSION_RULE);
}
/**
* Read, parse, and validate the JSON data and produce the corresponding data object.
* @param id The ID of the entry to load.
* @return The parsed data object.
*/
public function parseEntryData(id:String):Null<FreeplayStyleData>
{
// JsonParser does not take type parameters,
// otherwise this function would be in BaseRegistry.
var parser:json2object.JsonParser<FreeplayStyleData> = new json2object.JsonParser<FreeplayStyleData>();
parser.ignoreUnknownVariables = false;
switch (loadEntryFile(id))
{
case {fileName: fileName, contents: contents}:
parser.fromJson(contents, fileName);
default:
return null;
}
if (parser.errors.length > 0)
{
printErrors(parser.errors, id);
return null;
}
return parser.value;
}
/**
* Parse and validate the JSON data and produce the corresponding data object.
*
* NOTE: Must be implemented on the implementation class.
* @param contents The JSON as a string.
* @param fileName An optional file name for error reporting.
* @return The parsed data object.
*/
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<FreeplayStyleData>
{
var parser:json2object.JsonParser<FreeplayStyleData> = new json2object.JsonParser<FreeplayStyleData>();
parser.ignoreUnknownVariables = false;
parser.fromJson(contents, fileName);
if (parser.errors.length > 0)
{
printErrors(parser.errors, fileName);
return null;
}
return parser.value;
}
function createScriptedEntry(clsName:String):FreeplayStyle
{
return ScriptedFreeplayStyle.init(clsName, 'unknown');
}
function getScriptedClassNames():Array<String>
{
return ScriptedFreeplayStyle.listScriptClasses();
}
}

View file

@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- If the value isn't present, it will use the `playData.characters.opponent`, but if it is present, it will be used (even if it's empty, in which case no vocals will be used for the opponent)
- Added `playData.characters.playerVocals` to specify which vocal track(s) to play for the player.
- If the value isn't present, it will use the `playData.characters.player`, but if it is present, it will be used (even if it's empty, in which case no vocals will be used for the player)
- Added `offsets.altVocals` field to apply vocal offsets when alternate instrumentals are used.
## [2.2.3]
### Added

View file

@ -257,18 +257,27 @@ class SongOffsets implements ICloneable<SongOffsets>
public var altInstrumentals:Map<String, Float>;
/**
* The offset, in milliseconds, to apply to the song's vocals, relative to the chart.
* The offset, in milliseconds, to apply to the song's vocals, relative to the song's base instrumental.
* These are applied ON TOP OF the instrumental offset.
*/
@:optional
@: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
@ -293,11 +302,19 @@ class SongOffsets implements ICloneable<SongOffsets>
return value;
}
public function getVocalOffset(charId:String):Float
public function getVocalOffset(charId:String, ?instrumental:String):Float
{
if (!this.vocals.exists(charId)) return 0.0;
return this.vocals.get(charId);
if (instrumental == null)
{
if (!this.vocals.exists(charId)) return 0.0;
return this.vocals.get(charId);
}
else
{
if (!this.altVocals.exists(instrumental)) return 0.0;
if (!this.altVocals.get(instrumental).exists(charId)) return 0.0;
return this.altVocals.get(instrumental).get(charId);
}
}
public function setVocalOffset(charId:String, value:Float):Float
@ -320,7 +337,7 @@ class SongOffsets implements ICloneable<SongOffsets>
*/
public function toString():String
{
return 'SongOffsets(${this.instrumental}ms, ${this.altInstrumentals}, ${this.vocals})';
return 'SongOffsets(${this.instrumental}ms, ${this.altInstrumentals}, ${this.vocals}, ${this.altVocals})';
}
}

View file

@ -20,7 +20,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
* Handle breaking changes by incrementing this value
* and adding migration to the `migrateStageData()` function.
*/
public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.3";
public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.4";
public static final SONG_METADATA_VERSION_RULE:thx.semver.VersionRule = "2.2.x";

View file

@ -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();

View file

@ -1,12 +1,25 @@
package funkin.graphics.shaders;
import flixel.system.FlxAssets.FlxShader;
import flixel.util.FlxColor;
class AngleMask extends FlxShader
{
public var extraColor(default, set):FlxColor = 0xFFFFFFFF;
function set_extraColor(value:FlxColor):FlxColor
{
extraTint.value = [value.redFloat, value.greenFloat, value.blueFloat];
this.extraColor = value;
return this.extraColor;
}
@:glFragmentSource('
#pragma header
uniform vec3 extraTint;
uniform vec2 endPosition;
vec2 hash22(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
@ -69,6 +82,7 @@ class AngleMask extends FlxShader
void main() {
vec4 col = antialias(openfl_TextureCoordv);
col.xyz = col.xyz * extraTint.xyz;
// col.xyz = gamma(col.xyz);
gl_FragColor = col;
}')
@ -77,5 +91,6 @@ class AngleMask extends FlxShader
super();
endPosition.value = [90, 100]; // 100 AS DEFAULT WORKS NICELY FOR FREEPLAY?
extraTint.value = [1, 1, 1];
}
}

View file

@ -0,0 +1,51 @@
package funkin.graphics.shaders;
import flixel.system.FlxAssets.FlxShader;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
class BlueFade extends FlxShader
{
public var fadeVal(default, set):Float;
function set_fadeVal(val:Float):Float
{
fadeAmt.value = [val];
fadeVal = val;
// trace(fadeVal);
return val;
}
public function fade(startAmt:Float = 0, targetAmt:Float = 1, duration:Float, _options:TweenOptions):Void
{
fadeVal = startAmt;
FlxTween.tween(this, {fadeVal: targetAmt}, duration, _options);
}
@:glFragmentSource('
#pragma header
// Value from (0, 1)
uniform float fadeAmt;
// fade the image to blue as it fades to black
void main()
{
vec4 tex = flixel_texture2D(bitmap, openfl_TextureCoordv);
vec4 finalColor = mix(vec4(vec4(0.0, 0.0, tex.b, tex.a) * fadeAmt), vec4(tex * fadeAmt), fadeAmt);
// Output to screen
gl_FragColor = finalColor;
}
')
public function new()
{
super();
this.fadeVal = 1;
}
}

View file

@ -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.
*/

View file

@ -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');

View file

@ -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;

View file

@ -668,7 +668,7 @@ class PlayState extends MusicBeatSubState
// Prepare the current song's instrumental and vocals to be played.
if (!overrideMusic && currentChart != null)
{
currentChart.cacheInst();
currentChart.cacheInst(currentInstrumental);
currentChart.cacheVocals();
}
@ -677,7 +677,7 @@ class PlayState extends MusicBeatSubState
if (currentChart.offsets != null)
{
Conductor.instance.instrumentalOffset = currentChart.offsets.getInstrumentalOffset();
Conductor.instance.instrumentalOffset = currentChart.offsets.getInstrumentalOffset(currentInstrumental);
}
Conductor.instance.mapTimeChanges(currentChart.timeChanges);
@ -860,7 +860,7 @@ class PlayState extends MusicBeatSubState
{
// Stop the vocals if they already exist.
if (vocals != null) vocals.stop();
vocals = currentChart.buildVocals();
vocals = currentChart.buildVocals(currentInstrumental);
if (vocals.members.length == 0)
{
@ -1791,7 +1791,7 @@ class PlayState extends MusicBeatSubState
{
// Stop the vocals if they already exist.
if (vocals != null) vocals.stop();
vocals = currentChart.buildVocals();
vocals = currentChart.buildVocals(currentInstrumental);
if (vocals.members.length == 0)
{

View file

@ -404,12 +404,12 @@ class ResultState extends MusicBeatSubState
// }
new FlxTimer().start(rank.getMusicDelay(), _ -> {
if (rank.hasMusicIntro())
var introMusic:String = Paths.music(getMusicPath(playerCharacter, rank) + '/' + getMusicPath(playerCharacter, rank) + '-intro');
if (Assets.exists(introMusic))
{
// Play the intro music.
var introMusic:String = Paths.music(rank.getMusicPath() + '/' + rank.getMusicPath() + '-intro');
FunkinSound.load(introMusic, 1.0, false, true, true, () -> {
FunkinSound.playMusic(rank.getMusicPath(),
FunkinSound.playMusic(getMusicPath(playerCharacter, rank),
{
startingVolume: 1.0,
overrideExisting: true,
@ -420,7 +420,7 @@ class ResultState extends MusicBeatSubState
}
else
{
FunkinSound.playMusic(rank.getMusicPath(),
FunkinSound.playMusic(getMusicPath(playerCharacter, rank),
{
startingVolume: 1.0,
overrideExisting: true,
@ -441,6 +441,11 @@ class ResultState extends MusicBeatSubState
super.create();
}
function getMusicPath(playerCharacter:Null<PlayableCharacter>, rank:ScoringRank):String
{
return playerCharacter?.getResultsMusicPath(rank) ?? 'resultsNORMAL';
}
var rankTallyTimer:Null<FlxTimer> = null;
var clearPercentTarget:Int = 100;
var clearPercentLerp:Int = 0;

View file

@ -556,40 +556,6 @@ enum abstract ScoringRank(String)
}
}
public function getMusicPath():String
{
switch (abstract)
{
case PERFECT_GOLD:
return 'resultsPERFECT';
case PERFECT:
return 'resultsPERFECT';
case EXCELLENT:
return 'resultsEXCELLENT';
case GREAT:
return 'resultsNORMAL';
case GOOD:
return 'resultsNORMAL';
case SHIT:
return 'resultsSHIT';
default:
return 'resultsNORMAL';
}
}
public function hasMusicIntro():Bool
{
switch (abstract)
{
case EXCELLENT:
return true;
case SHIT:
return true;
default:
return false;
}
}
public function getFreeplayRankIconAsset():String
{
switch (abstract)

View file

@ -533,6 +533,28 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
return variation.playData.difficulties.contains(diffId);
}
/**
* Return the list of available alternate instrumentals.
* Scripts can override this, fun.
* @param variationId
* @param difficultyId
*/
public function listAltInstrumentalIds(difficultyId:String, variationId:String):Array<String>
{
var targetDifficulty:Null<SongDifficulty> = getDifficulty(difficultyId, variationId);
if (targetDifficulty == null) return [];
return targetDifficulty?.characters?.altInstrumentals ?? [];
}
public function getBaseInstrumentalId(difficultyId:String, variationId:String):String
{
var targetDifficulty:Null<SongDifficulty> = getDifficulty(difficultyId, variationId);
if (targetDifficulty == null) return '';
return targetDifficulty?.characters?.instrumental ?? '';
}
/**
* Purge the cached chart data for each difficulty of this song.
*/
@ -851,7 +873,7 @@ class SongDifficulty
* @param charId The player ID.
* @return The generated vocal group.
*/
public function buildVocals():VoicesGroup
public function buildVocals(?instId:String = ''):VoicesGroup
{
var result:VoicesGroup = new VoicesGroup();
@ -870,8 +892,8 @@ class SongDifficulty
result.addOpponentVoice(FunkinSound.load(opponentVoice));
}
result.playerVoicesOffset = offsets.getVocalOffset(characters.player);
result.opponentVoicesOffset = offsets.getVocalOffset(characters.opponent);
result.playerVoicesOffset = offsets.getVocalOffset(characters.player, instId);
result.opponentVoicesOffset = offsets.getVocalOffset(characters.opponent, instId);
return result;
}

View file

@ -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

View file

@ -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 {};
}

View file

@ -26,6 +26,12 @@ import funkin.ui.PixelatedIcon;
import funkin.util.MathUtil;
import funkin.vis.dsp.SpectralAnalyzer;
import openfl.display.BlendMode;
import flixel.util.FlxTimer;
import flixel.tweens.FlxEase;
import flixel.sound.FlxSound;
import funkin.audio.FunkinSound;
import funkin.graphics.shaders.BlueFade;
import openfl.filters.ShaderFilter;
class CharSelectSubState extends MusicBeatSubState
{
@ -57,9 +63,16 @@ class CharSelectSubState extends MusicBeatSubState
var gfChill:CharSelectGF;
var gfChillOut:CharSelectGF;
var barthing:FlxAtlasSprite;
var dipshitBacking:FlxSprite;
var chooseDipshit:FlxSprite;
var dipshitBlur:FlxSprite;
var transitionGradient:FlxSprite;
var curChar(default, set):String = "pico";
var nametag:Nametag;
var camFollow:FlxObject;
var autoFollow:Bool = false;
var availableChars:Map<Int, String> = new Map<Int, String>();
var pressedSelect:Bool = false;
@ -100,10 +113,17 @@ class CharSelectSubState extends MusicBeatSubState
}
}
var fadeShader:BlueFade = new BlueFade();
override public function create():Void
{
super.create();
autoFollow = false;
var fadeShaderFilter:ShaderFilter = new ShaderFilter(fadeShader);
FlxG.camera.filters = [fadeShaderFilter];
selectSound = new FunkinSound();
selectSound.loadEmbedded(Paths.sound('CS_select'));
selectSound.pitch = 1;
@ -148,7 +168,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);
@ -157,12 +177,15 @@ class CharSelectSubState extends MusicBeatSubState
curtains.scrollFactor.set(1.4, 1.4);
add(curtains);
var barthing:FlxAtlasSprite = new FlxAtlasSprite(0, 0, Paths.animateAtlas("charSelect/barThing"));
barthing = new FlxAtlasSprite(0, 0, Paths.animateAtlas("charSelect/barThing"));
barthing.anim.play("");
barthing.blend = BlendMode.MULTIPLY;
barthing.scrollFactor.set(0, 0);
add(barthing);
barthing.y += 80;
FlxTween.tween(barthing, {y: barthing.y - 80}, 1.3, {ease: FlxEase.expoOut});
var charLight:FlxSprite = new FlxSprite(800, 250);
charLight.loadGraphic(Paths.image('charSelect/charLight'));
add(charLight);
@ -193,24 +216,33 @@ class CharSelectSubState extends MusicBeatSubState
fgBlur.blend = openfl.display.BlendMode.MULTIPLY;
add(fgBlur);
var dipshitBlur:FlxSprite = new FlxSprite(419, -65);
dipshitBlur = 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 = 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);
var chooseDipshit:FlxSprite = new FlxSprite(426, -13);
dipshitBacking.y += 210;
FlxTween.tween(dipshitBacking, {y: dipshitBacking.y - 210}, 1.1, {ease: FlxEase.expoOut});
chooseDipshit = new FlxSprite(426, -13);
chooseDipshit.loadGraphic(Paths.image('charSelect/chooseDipshit'));
add(chooseDipshit);
chooseDipshit.y += 200;
FlxTween.tween(chooseDipshit, {y: chooseDipshit.y - 200}, 1, {ease: FlxEase.expoOut});
dipshitBlur.y += 220;
FlxTween.tween(dipshitBlur, {y: dipshitBlur.y - 220}, 1.2, {ease: FlxEase.expoOut});
chooseDipshit.scrollFactor.set();
dipshitBacking.scrollFactor.set();
dipshitBlur.scrollFactor.set();
@ -261,14 +293,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);
@ -278,6 +310,12 @@ class CharSelectSubState extends MusicBeatSubState
initLocks();
for (index => member in grpIcons.members)
{
member.y += 300;
FlxTween.tween(member, {y: member.y - 300}, 1, {ease: FlxEase.expoOut});
}
cursor.scrollFactor.set();
cursorBlue.scrollFactor.set();
cursorDarkBlue.scrollFactor.set();
@ -289,13 +327,12 @@ 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();
FlxG.camera.follow(camFollow, LOCKON, 0.01);
// FlxG.camera.follow(camFollow, LOCKON, 0.01);
FlxG.camera.follow(camFollow, LOCKON);
var temp:FlxSprite = new FlxSprite();
temp.loadGraphic(Paths.image('charSelect/placement'));
@ -303,6 +340,25 @@ class CharSelectSubState extends MusicBeatSubState
temp.alpha = 0.0;
Conductor.stepHit.add(spamOnStep);
// FlxG.debugger.track(temp, "tempBG");
transitionGradient = new FlxSprite(0, 0).loadGraphic(Paths.image('freeplay/transitionGradient'));
transitionGradient.scale.set(1280, 1);
transitionGradient.flipY = true;
transitionGradient.updateHitbox();
FlxTween.tween(transitionGradient, {y: -720}, 1, {ease: FlxEase.expoOut});
add(transitionGradient);
camFollow.screenCenter();
camFollow.y -= 150;
fadeShader.fade(0.0, 1.0, 0.8, {ease: FlxEase.quadOut});
FlxTween.tween(camFollow, {y: camFollow.y + 150}, 1.5,
{
ease: FlxEase.expoOut,
onComplete: function(_) {
autoFollow = true;
FlxG.camera.follow(camFollow, LOCKON, 0.01);
}
});
}
var grpIcons:FlxSpriteGroup;
@ -373,6 +429,42 @@ class CharSelectSubState extends MusicBeatSubState
}
}
function goToFreeplay():Void
{
autoFollow = false;
FlxTween.tween(cursor, {alpha: 0}, 0.8, {ease: FlxEase.expoOut});
FlxTween.tween(cursorBlue, {alpha: 0}, 0.8, {ease: FlxEase.expoOut});
FlxTween.tween(cursorDarkBlue, {alpha: 0}, 0.8, {ease: FlxEase.expoOut});
FlxTween.tween(cursorConfirmed, {alpha: 0}, 0.8, {ease: FlxEase.expoOut});
FlxTween.tween(barthing, {y: barthing.y + 80}, 0.8, {ease: FlxEase.backIn});
FlxTween.tween(dipshitBacking, {y: dipshitBacking.y + 210}, 0.8, {ease: FlxEase.backIn});
FlxTween.tween(chooseDipshit, {y: chooseDipshit.y + 200}, 0.8, {ease: FlxEase.backIn});
FlxTween.tween(dipshitBlur, {y: dipshitBlur.y + 220}, 0.8, {ease: FlxEase.backIn});
for (index => member in grpIcons.members)
{
// member.y += 300;
FlxTween.tween(member, {y: member.y + 300}, 0.8, {ease: FlxEase.backIn});
}
FlxG.camera.follow(camFollow, LOCKON);
FlxTween.tween(transitionGradient, {y: -150}, 0.8, {ease: FlxEase.backIn});
fadeShader.fade(1.0, 0, 0.8, {ease: FlxEase.quadIn});
FlxTween.tween(camFollow, {y: camFollow.y - 150}, 0.8,
{
ease: FlxEase.backIn,
onComplete: function(_) {
FlxG.switchState(FreeplayState.build(
{
{
character: curChar,
fromCharSelect: true
}
}));
}
});
}
var holdTmrUp:Float = 0;
var holdTmrDown:Float = 0;
var holdTmrLeft:Float = 0;
@ -494,18 +586,20 @@ class CharSelectSubState extends MusicBeatSubState
FlxG.sound.play(Paths.sound('CS_confirm'));
FlxTween.tween(FlxG.sound.music, {pitch: 0.1}, 1.5, {ease: FlxEase.quadInOut});
FlxTween.tween(FlxG.sound.music, {pitch: 0.1}, 1, {ease: FlxEase.quadInOut});
FlxTween.tween(FlxG.sound.music, {volume: 0.0}, 1.5, {ease: FlxEase.quadInOut});
playerChill.playAnimation("select");
gfChill.playAnimation("confirm");
pressedSelect = true;
selectTimer.start(1.5, (_) -> {
pressedSelect = false;
FlxG.switchState(FreeplayState.build(
{
{
character: curChar
}
}));
// FlxG.switchState(FreeplayState.build(
// {
// {
// character: curChar
// }
// }));
goToFreeplay();
});
}
@ -515,6 +609,7 @@ class CharSelectSubState extends MusicBeatSubState
grpCursors.visible = true;
FlxTween.globalManager.cancelTweensOf(FlxG.sound.music);
FlxTween.tween(FlxG.sound.music, {pitch: 1.0, volume: 1.0}, 1, {ease: FlxEase.quartInOut});
playerChill.playAnimation("deselect");
gfChill.playAnimation("deselect");
FlxTween.tween(FlxG.sound.music, {pitch: 1.0}, 1,
@ -547,9 +642,12 @@ class CharSelectSubState extends MusicBeatSubState
updateLockAnims();
camFollow.screenCenter();
camFollow.x += cursorX * 10;
camFollow.y += cursorY * 10;
if (autoFollow == true)
{
camFollow.screenCenter();
camFollow.x += cursorX * 10;
camFollow.y += cursorY * 10;
}
cursorLocIntended.x = (cursorFactor * cursorX) + (FlxG.width / 2) - cursor.width / 2;
cursorLocIntended.y = (cursorFactor * cursorY) + (FlxG.height / 2) - cursor.height / 2;
@ -567,6 +665,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)

View file

@ -41,6 +41,7 @@ class AlbumRoll extends FlxSpriteGroup
var difficultyStars:DifficultyStars;
var _exitMovers:Null<FreeplayState.ExitMoverData>;
var _exitMoversCharSel:Null<FreeplayState.ExitMoverData>;
var albumData:Album;
@ -128,7 +129,7 @@ class AlbumRoll extends FlxSpriteGroup
* Apply exit movers for the album roll.
* @param exitMovers The exit movers to apply.
*/
public function applyExitMovers(?exitMovers:FreeplayState.ExitMoverData):Void
public function applyExitMovers(?exitMovers:FreeplayState.ExitMoverData, ?exitMoversCharSel:FreeplayState.ExitMoverData):Void
{
if (exitMovers == null)
{
@ -141,12 +142,30 @@ class AlbumRoll extends FlxSpriteGroup
if (exitMovers == null) return;
if (exitMoversCharSel == null)
{
exitMoversCharSel = _exitMoversCharSel;
}
else
{
_exitMoversCharSel = exitMoversCharSel;
}
if (exitMoversCharSel == null) return;
exitMovers.set([newAlbumArt, difficultyStars],
{
x: FlxG.width,
speed: 0.4,
wait: 0
});
exitMoversCharSel.set([newAlbumArt, difficultyStars],
{
y: -175,
speed: 0.8,
wait: 0.1
});
}
var titleTimer:Null<FlxTimer> = null;
@ -207,6 +226,13 @@ class AlbumRoll extends FlxSpriteGroup
speed: 0.4,
wait: 0
});
if (_exitMoversCharSel != null) _exitMoversCharSel.set([albumTitle],
{
y: -190,
speed: 0.8,
wait: 0.1
});
}
public function setDifficultyStars(?difficulty:Int):Void

View file

@ -0,0 +1,176 @@
package funkin.ui.freeplay;
import funkin.graphics.shaders.PureColor;
import funkin.input.Controls;
import flixel.group.FlxSpriteGroup;
import funkin.graphics.FunkinSprite;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import flixel.text.FlxText;
import flixel.text.FlxText.FlxTextAlign;
@:nullSafety
class CapsuleOptionsMenu extends FlxSpriteGroup
{
var capsuleMenuBG:FunkinSprite;
var parent:FreeplayState;
var queueDestroy:Bool = false;
var instrumentalIds:Array<String> = [''];
var currentInstrumentalIndex:Int = 0;
var currentInstrumental:FlxText;
public function new(parent:FreeplayState, x:Float = 0, y:Float = 0, instIds:Array<String>):Void
{
super(x, y);
this.parent = parent;
this.instrumentalIds = instIds;
capsuleMenuBG = FunkinSprite.createSparrow(0, 0, 'freeplay/instBox/instBox');
capsuleMenuBG.animation.addByPrefix('open', 'open0', 24, false);
capsuleMenuBG.animation.addByPrefix('idle', 'idle0', 24, true);
capsuleMenuBG.animation.addByPrefix('open', 'open0', 24, false);
currentInstrumental = new FlxText(0, 36, capsuleMenuBG.width, '');
currentInstrumental.setFormat('VCR OSD Mono', 40, FlxTextAlign.CENTER, true);
final PAD = 4;
var leftArrow = new InstrumentalSelector(parent, PAD, 30, false, parent.getControls());
var rightArrow = new InstrumentalSelector(parent, capsuleMenuBG.width - leftArrow.width - PAD, 30, true, parent.getControls());
var label:FlxText = new FlxText(0, 5, capsuleMenuBG.width, 'INSTRUMENTAL');
label.setFormat('VCR OSD Mono', 24, FlxTextAlign.CENTER, true);
add(capsuleMenuBG);
add(leftArrow);
add(rightArrow);
add(label);
add(currentInstrumental);
capsuleMenuBG.animation.finishCallback = function(_) {
capsuleMenuBG.animation.play('idle', true);
};
capsuleMenuBG.animation.play('open', true);
}
public override function update(elapsed:Float):Void
{
super.update(elapsed);
if (queueDestroy)
{
destroy();
return;
}
@:privateAccess
if (parent.controls.BACK)
{
close();
return;
}
var changedInst = false;
if (parent.getControls().UI_LEFT_P)
{
currentInstrumentalIndex = (currentInstrumentalIndex + 1) % instrumentalIds.length;
changedInst = true;
}
if (parent.getControls().UI_RIGHT_P)
{
currentInstrumentalIndex = (currentInstrumentalIndex - 1 + instrumentalIds.length) % instrumentalIds.length;
changedInst = true;
}
if (!changedInst && currentInstrumental.text == '') changedInst = true;
if (changedInst)
{
currentInstrumental.text = instrumentalIds[currentInstrumentalIndex].toTitleCase() ?? '';
if (currentInstrumental.text == '') currentInstrumental.text = 'Default';
}
if (parent.getControls().ACCEPT)
{
onConfirm(instrumentalIds[currentInstrumentalIndex] ?? '');
}
}
public function close():Void
{
// Play in reverse.
capsuleMenuBG.animation.play('open', true, true);
capsuleMenuBG.animation.finishCallback = function(_) {
parent.cleanupCapsuleOptionsMenu();
queueDestroy = true;
};
}
/**
* Override this with `capsuleOptionsMenu.onConfirm = myFunction;`
*/
public dynamic function onConfirm(targetInstId:String):Void
{
throw 'onConfirm not implemented!';
}
}
/**
* The difficulty selector arrows to the left and right of the difficulty.
*/
class InstrumentalSelector extends FunkinSprite
{
var controls:Controls;
var whiteShader:PureColor;
var parent:FreeplayState;
var baseScale:Float = 0.6;
public function new(parent:FreeplayState, x:Float, y:Float, flipped:Bool, controls:Controls)
{
super(x, y);
this.parent = parent;
this.controls = controls;
frames = Paths.getSparrowAtlas('freeplay/freeplaySelector');
animation.addByPrefix('shine', 'arrow pointer loop', 24);
animation.play('shine');
whiteShader = new PureColor(FlxColor.WHITE);
shader = whiteShader;
flipX = flipped;
scale.x = scale.y = 1 * baseScale;
updateHitbox();
}
override function update(elapsed:Float):Void
{
if (flipX && controls.UI_RIGHT_P) moveShitDown();
if (!flipX && controls.UI_LEFT_P) moveShitDown();
super.update(elapsed);
}
function moveShitDown():Void
{
offset.y -= 5;
whiteShader.colorSet = true;
scale.x = scale.y = 0.5 * baseScale;
new FlxTimer().start(2 / 24, function(tmr) {
scale.x = scale.y = 1 * baseScale;
whiteShader.colorSet = false;
updateHitbox();
});
}
}

View file

@ -10,6 +10,7 @@ import flixel.tweens.FlxEase;
import flixel.util.FlxTimer;
import flixel.tweens.FlxTween;
import openfl.display.BlendMode;
import flixel.util.FlxColor;
class CapsuleText extends FlxSpriteGroup
{
@ -25,6 +26,8 @@ class CapsuleText extends FlxSpriteGroup
public var tooLong:Bool = false;
var glowColor:FlxColor = 0xFF00ccff;
// 255, 27 normal
// 220, 27 favourited
@ -38,7 +41,7 @@ class CapsuleText extends FlxSpriteGroup
// whiteText.shader = new GaussianBlurShader(0.3);
text = songTitle;
blurredText.color = 0xFF00ccff;
blurredText.color = glowColor;
whiteText.color = 0xFFFFFFFF;
add(blurredText);
add(whiteText);
@ -51,6 +54,16 @@ class CapsuleText extends FlxSpriteGroup
return text;
}
public function applyStyle(styleData:FreeplayStyle):Void
{
glowColor = styleData.getCapsuleSelCol();
blurredText.color = glowColor;
whiteText.textField.filters = [
new openfl.filters.GlowFilter(glowColor, 1, 5, 5, 210, BitmapFilterQuality.MEDIUM),
// new openfl.filters.BlurFilter(5, 5, BitmapFilterQuality.LOW)
];
}
// ???? none
// 255, 27 normal
// 220, 27 favourited
@ -99,7 +112,7 @@ class CapsuleText extends FlxSpriteGroup
whiteText.text = value;
checkClipWidth();
whiteText.textField.filters = [
new openfl.filters.GlowFilter(0x00ccff, 1, 5, 5, 210, BitmapFilterQuality.MEDIUM),
new openfl.filters.GlowFilter(glowColor, 1, 5, 5, 210, BitmapFilterQuality.MEDIUM),
// new openfl.filters.BlurFilter(5, 5, BitmapFilterQuality.LOW)
];
@ -186,7 +199,7 @@ class CapsuleText extends FlxSpriteGroup
}
else
{
blurredText.color = 0xFF00aadd;
blurredText.color = glowColor;
whiteText.color = 0xFFDDDDDD;
whiteText.textField.filters = [
new openfl.filters.GlowFilter(0xDDDDDD, 1, 5, 5, 210, BitmapFilterQuality.MEDIUM),

View file

@ -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');
@ -386,6 +388,7 @@ class FreeplayDJ extends FlxAtlasSprite
}
else
{
FlxG.log.warn("Freeplay character does not have 'charSelect' animation!");
currentState = Confirm;
// Call this immediately; otherwise, we get locked out of Character Select.
onCharSelectComplete();

View file

@ -42,13 +42,20 @@ class FreeplayScore extends FlxTypedSpriteGroup<ScoreNum>
return val;
}
public function new(x:Float, y:Float, digitCount:Int, scoreShit:Int = 100)
public function new(x:Float, y:Float, digitCount:Int, scoreShit:Int = 100, ?styleData:FreeplayStyle)
{
super(x, y);
for (i in 0...digitCount)
{
add(new ScoreNum(x + (45 * i), y, 0));
if (styleData == null)
{
add(new ScoreNum(x + (45 * i), y, 0));
}
else
{
add(new ScoreNum(x + (45 * i), y, 0, styleData));
}
}
this.scoreShit = scoreShit;
@ -76,16 +83,16 @@ class ScoreNum extends FlxSprite
case 1:
offset.x -= 15;
case 5:
// set offsets
// offset.x += 0;
// offset.y += 10;
// set offsets
// offset.x += 0;
// offset.y += 10;
case 7:
// offset.y += 6;
// offset.y += 6;
case 4:
// offset.y += 5;
// offset.y += 5;
case 9:
// offset.y += 5;
// offset.y += 5;
default:
centerOffsets(false);
}
@ -99,14 +106,21 @@ class ScoreNum extends FlxSprite
var numToString:Array<String> = ["ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE"];
public function new(x:Float, y:Float, ?initDigit:Int = 0)
public function new(x:Float, y:Float, ?initDigit:Int = 0, ?styleData:FreeplayStyle)
{
super(x, y);
baseY = y;
baseX = x;
frames = Paths.getSparrowAtlas('digital_numbers');
if (styleData == null)
{
frames = Paths.getSparrowAtlas('digital_numbers');
}
else
{
frames = Paths.getSparrowAtlas(styleData.getNumbersAssetKey());
}
for (i in 0...10)
{

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,121 @@
package funkin.ui.freeplay;
import funkin.data.freeplay.style.FreeplayStyleData;
import funkin.data.freeplay.style.FreeplayStyleRegistry;
import funkin.data.animation.AnimationData;
import funkin.data.IRegistryEntry;
import flixel.graphics.FlxGraphic;
import flixel.util.FlxColor;
/**
* A class representing the data for a style of the Freeplay menu.
*/
class FreeplayStyle implements IRegistryEntry<FreeplayStyleData>
{
/**
* The internal ID for this freeplay style.
*/
public final id:String;
/**
* The full data for a freeplay style.
*/
public final _data:FreeplayStyleData;
public function new(id:String)
{
this.id = id;
this._data = _fetchData(id);
if (_data == null)
{
throw 'Could not parse album data for id: $id';
}
}
/**
* Get the background art as a graphic, ready to apply to a sprite.
* @return The built graphic
*/
public function getBgAssetGraphic():FlxGraphic
{
return FlxG.bitmap.add(Paths.image(getBgAssetKey()));
}
/**
* Get the asset key for the background.
* @return The asset key
*/
public function getBgAssetKey():String
{
return _data.bgAsset;
}
/**
* Get the asset key for the background.
* @return The asset key
*/
public function getSelectorAssetKey():String
{
return _data.selectorAsset;
}
/**
* Get the asset key for the number assets.
* @return The asset key
*/
public function getCapsuleAssetKey():String
{
return _data.capsuleAsset;
}
/**
* Get the asset key for the capsule art.
* @return The asset key
*/
public function getNumbersAssetKey():String
{
return _data.numbersAsset;
}
/**
* Return the deselected color of the text outline
* for freeplay capsules.
* @return The deselected color
*/
public function getCapsuleDeselCol():FlxColor
{
return FlxColor.fromString(_data.capsuleTextColors[0]);
}
/**
* Return the song selection transition delay.
* @return The start delay
*/
public function getStartDelay():Float
{
return _data.startDelay;
}
public function toString():String
{
return 'Style($id)';
}
/**
* Return the selected color of the text outline
* for freeplay capsules.
* @return The selected color
*/
public function getCapsuleSelCol():FlxColor
{
return FlxColor.fromString(_data.capsuleTextColors[1]);
}
public function destroy():Void {}
static function _fetchData(id:String):Null<FreeplayStyleData>
{
return FreeplayStyleRegistry.instance.parseEntryDataWithMigration(id, FreeplayStyleRegistry.instance.fetchEntryVersion(id));
}
}

View file

@ -0,0 +1,9 @@
package funkin.ui.freeplay;
/**
* A script that can be tied to a Freeplay style.
* Create a scripted class that extends FreeplayStyle to use this.
* This allows you to customize how a specific style works.
*/
@:hscriptClass
class ScriptedFreeplayStyle extends funkin.ui.freeplay.FreeplayStyle implements polymod.hscript.HScriptedClass {}

View file

@ -88,7 +88,7 @@ class SongMenuItem extends FlxSpriteGroup
super(x, y);
capsule = new FlxSprite();
capsule.frames = Paths.getSparrowAtlas('freeplay/freeplayCapsule');
capsule.frames = Paths.getSparrowAtlas('freeplay/freeplayCapsule/capsule/freeplayCapsule');
capsule.animation.addByPrefix('selected', 'mp3 capsule w backing0', 24);
capsule.animation.addByPrefix('unselected', 'mp3 capsule w backing NOT SELECTED', 24);
// capsule.animation
@ -500,12 +500,23 @@ class SongMenuItem extends FlxSpriteGroup
updateSelected();
}
public function init(?x:Float, ?y:Float, songData:Null<FreeplaySongData>):Void
public function init(?x:Float, ?y:Float, songData:Null<FreeplaySongData>, ?styleData:FreeplayStyle = null):Void
{
if (x != null) this.x = x;
if (y != null) this.y = y;
this.songData = songData;
// im so mad i have to do this but im pretty sure with the capsules recycling i cant call the new function properly :/
// if thats possible someone Please change the new function to be something like
// capsule.frames = Paths.getSparrowAtlas(styleData == null ? 'freeplay/freeplayCapsule/capsule/freeplayCapsule' : styleData.getCapsuleAssetKey()); thank u luv u
if (styleData != null)
{
capsule.frames = Paths.getSparrowAtlas(styleData.getCapsuleAssetKey());
capsule.animation.addByPrefix('selected', 'mp3 capsule w backing0', 24);
capsule.animation.addByPrefix('unselected', 'mp3 capsule w backing NOT SELECTED', 24);
songText.applyStyle(styleData);
}
// Update capsule text.
songText.text = songData?.songName ?? 'Random';
// Update capsule character.
@ -727,7 +738,7 @@ class FreeplayRank extends FlxSprite
switch (val)
{
case SHIT:
// offset.x -= 1;
// offset.x -= 1;
case GOOD:
// offset.x -= 1;
offset.y -= 8;
@ -735,11 +746,11 @@ class FreeplayRank extends FlxSprite
// offset.x -= 1;
offset.y -= 8;
case EXCELLENT:
// offset.y += 5;
// offset.y += 5;
case PERFECT:
// offset.y += 5;
// offset.y += 5;
case PERFECT_GOLD:
// offset.y += 5;
// offset.y += 5;
default:
centerOffsets(false);
this.visible = false;
@ -796,9 +807,9 @@ class CapsuleNumber extends FlxSprite
case 6:
case 4:
// offset.y += 5;
// offset.y += 5;
case 9:
// offset.y += 5;
// offset.y += 5;
default:
centerOffsets(false);
}

View file

@ -0,0 +1,249 @@
package funkin.ui.freeplay.backcards;
import funkin.ui.freeplay.FreeplayState;
import flixel.FlxCamera;
import flixel.FlxSprite;
import flixel.group.FlxGroup;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.math.FlxAngle;
import flixel.math.FlxPoint;
import flixel.text.FlxText;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import flixel.util.FlxSpriteUtil;
import flixel.util.FlxTimer;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinSprite;
import funkin.ui.freeplay.charselect.PlayableCharacter;
import funkin.ui.MusicBeatSubState;
import lime.utils.Assets;
import openfl.display.BlendMode;
import flixel.group.FlxSpriteGroup;
/**
* A class for the backing cards so they dont have to be part of freeplayState......
*/
class BackingCard extends FlxSpriteGroup
{
public var backingTextYeah:FlxAtlasSprite;
public var orangeBackShit:FunkinSprite;
public var alsoOrangeLOL:FunkinSprite;
public var pinkBack:FunkinSprite;
public var confirmGlow:FlxSprite;
public var confirmGlow2:FlxSprite;
public var confirmTextGlow:FlxSprite;
public var cardGlow:FlxSprite;
var _exitMovers:Null<FreeplayState.ExitMoverData>;
var _exitMoversCharSel:Null<FreeplayState.ExitMoverData>;
public var instance:FreeplayState;
public function new(currentCharacter:PlayableCharacter, ?_instance:FreeplayState)
{
super();
if (_instance != null) instance = _instance;
cardGlow = new FlxSprite(-30, -30).loadGraphic(Paths.image('freeplay/cardGlow'));
confirmGlow = new FlxSprite(-30, 240).loadGraphic(Paths.image('freeplay/confirmGlow'));
confirmTextGlow = new FlxSprite(-8, 115).loadGraphic(Paths.image('freeplay/glowingText'));
pinkBack = FunkinSprite.create('freeplay/pinkBack');
orangeBackShit = new FunkinSprite(84, 440).makeSolidColor(Std.int(pinkBack.width), 75, 0xFFFEDA00);
alsoOrangeLOL = new FunkinSprite(0, orangeBackShit.y).makeSolidColor(100, Std.int(orangeBackShit.height), 0xFFFFD400);
confirmGlow2 = new FlxSprite(confirmGlow.x, confirmGlow.y).loadGraphic(Paths.image('freeplay/confirmGlow2'));
backingTextYeah = new FlxAtlasSprite(640, 370, Paths.animateAtlas("freeplay/backing-text-yeah"),
{
FrameRate: 24.0,
Reversed: false,
// ?OnComplete:Void -> Void,
ShowPivot: false,
Antialiasing: true,
ScrollFactor: new FlxPoint(1, 1),
});
pinkBack.color = 0xFFFFD4E9; // sets it to pink!
pinkBack.x -= pinkBack.width;
}
/**
* Apply exit movers for the pieces of the backing card.
* @param exitMovers The exit movers to apply.
*/
public function applyExitMovers(?exitMovers:FreeplayState.ExitMoverData, ?exitMoversCharSel:FreeplayState.ExitMoverData):Void
{
if (exitMovers == null)
{
exitMovers = _exitMovers;
}
else
{
_exitMovers = exitMovers;
}
if (exitMovers == null) return;
if (exitMoversCharSel == null)
{
exitMoversCharSel = _exitMoversCharSel;
}
else
{
_exitMoversCharSel = exitMoversCharSel;
}
if (exitMoversCharSel == null) return;
exitMovers.set([pinkBack, orangeBackShit, alsoOrangeLOL],
{
x: -pinkBack.width,
y: pinkBack.y,
speed: 0.4,
wait: 0
});
exitMoversCharSel.set([pinkBack],
{
y: -100,
speed: 0.8,
wait: 0.1
});
exitMoversCharSel.set([orangeBackShit, alsoOrangeLOL],
{
y: -40,
speed: 0.8,
wait: 0.1
});
}
/**
* Helper function to snap the back of the card to its final position.
* Used when returning from character select, as we dont want to play the full animation of everything sliding in.
*/
public function skipIntroTween():Void
{
FlxTween.cancelTweensOf(pinkBack);
pinkBack.x = 0;
}
/**
* Called in create. Adds sprites and tweens.
*/
public function init():Void
{
FlxTween.tween(pinkBack, {x: 0}, 0.6, {ease: FlxEase.quartOut});
add(pinkBack);
add(orangeBackShit);
add(alsoOrangeLOL);
FlxSpriteUtil.alphaMaskFlxSprite(orangeBackShit, pinkBack, orangeBackShit);
orangeBackShit.visible = false;
alsoOrangeLOL.visible = false;
confirmTextGlow.blend = BlendMode.ADD;
confirmTextGlow.visible = false;
confirmGlow.blend = BlendMode.ADD;
confirmGlow.visible = false;
confirmGlow2.visible = false;
add(confirmGlow2);
add(confirmGlow);
add(confirmTextGlow);
add(backingTextYeah);
cardGlow.blend = BlendMode.ADD;
cardGlow.visible = false;
add(cardGlow);
}
/**
* Called after the dj finishes their start animation.
*/
public function introDone():Void
{
pinkBack.color = 0xFFFFD863;
orangeBackShit.visible = true;
alsoOrangeLOL.visible = true;
cardGlow.visible = true;
FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.45, {ease: FlxEase.sineOut});
}
/**
* Called when selecting a song.
*/
public function confirm():Void
{
FlxTween.color(pinkBack, 0.33, 0xFFFFD0D5, 0xFF171831, {ease: FlxEase.quadOut});
orangeBackShit.visible = false;
alsoOrangeLOL.visible = false;
confirmGlow.visible = true;
confirmGlow2.visible = true;
backingTextYeah.anim.play("");
confirmGlow2.alpha = 0;
confirmGlow.alpha = 0;
FlxTween.color(instance.bgDad, 0.5, 0xFFA8A8A8, 0xFF646464,
{
onUpdate: function(_) {
instance.angleMaskShader.extraColor = instance.bgDad.color;
}
});
FlxTween.tween(confirmGlow2, {alpha: 0.5}, 0.33,
{
ease: FlxEase.quadOut,
onComplete: function(_) {
confirmGlow2.alpha = 0.6;
confirmGlow.alpha = 1;
confirmTextGlow.visible = true;
confirmTextGlow.alpha = 1;
FlxTween.tween(confirmTextGlow, {alpha: 0.4}, 0.5);
FlxTween.tween(confirmGlow, {alpha: 0}, 0.5);
FlxTween.color(instance.bgDad, 2, 0xFFCDCDCD, 0xFF555555,
{
ease: FlxEase.expoOut,
onUpdate: function(_) {
instance.angleMaskShader.extraColor = instance.bgDad.color;
}
});
}
});
}
/**
* Called when entering character select, does nothing by default.
*/
public function enterCharSel():Void {}
/**
* Called on each beat in freeplay state.
*/
public function beatHit():Void {}
/**
* Called when exiting the freeplay menu.
*/
public function disappear():Void
{
FlxTween.color(pinkBack, 0.25, 0xFFFFD863, 0xFFFFD0D5, {ease: FlxEase.quadOut});
cardGlow.visible = true;
cardGlow.alpha = 1;
cardGlow.scale.set(1, 1);
FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.25, {ease: FlxEase.sineOut});
orangeBackShit.visible = false;
alsoOrangeLOL.visible = false;
}
}

View file

@ -0,0 +1,238 @@
package funkin.ui.freeplay.backcards;
import funkin.ui.freeplay.FreeplayState;
import flixel.FlxCamera;
import flixel.FlxSprite;
import flixel.group.FlxGroup;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.math.FlxAngle;
import flixel.math.FlxPoint;
import flixel.text.FlxText;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import flixel.util.FlxSpriteUtil;
import flixel.util.FlxTimer;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinSprite;
import funkin.ui.freeplay.charselect.PlayableCharacter;
import funkin.ui.MusicBeatSubState;
import lime.utils.Assets;
import openfl.display.BlendMode;
import flixel.group.FlxSpriteGroup;
class BoyfriendCard extends BackingCard
{
public var moreWays:BGScrollingText;
public var funnyScroll:BGScrollingText;
public var txtNuts:BGScrollingText;
public var funnyScroll2:BGScrollingText;
public var moreWays2:BGScrollingText;
public var funnyScroll3:BGScrollingText;
var glow:FlxSprite;
var glowDark:FlxSprite;
public override function applyExitMovers(?exitMovers:FreeplayState.ExitMoverData, ?exitMoversCharSel:FreeplayState.ExitMoverData):Void
{
super.applyExitMovers(exitMovers, exitMoversCharSel);
if (exitMovers == null || exitMoversCharSel == null) return;
exitMovers.set([moreWays],
{
x: FlxG.width * 2,
speed: 0.4,
});
exitMovers.set([funnyScroll],
{
x: -funnyScroll.width * 2,
y: funnyScroll.y,
speed: 0.4,
wait: 0
});
exitMovers.set([txtNuts],
{
x: FlxG.width * 2,
speed: 0.4,
});
exitMovers.set([funnyScroll2],
{
x: -funnyScroll2.width * 2,
speed: 0.5,
});
exitMovers.set([moreWays2],
{
x: FlxG.width * 2,
speed: 0.4
});
exitMovers.set([funnyScroll3],
{
x: -funnyScroll3.width * 2,
speed: 0.3
});
exitMoversCharSel.set([moreWays, funnyScroll, txtNuts, funnyScroll2, moreWays2, funnyScroll3],
{
y: -60,
speed: 0.8,
wait: 0.1
});
}
public override function enterCharSel():Void
{
FlxTween.tween(funnyScroll, {speed: 0}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(funnyScroll2, {speed: 0}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(moreWays, {speed: 0}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(moreWays2, {speed: 0}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(txtNuts, {speed: 0}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(funnyScroll3, {speed: 0}, 0.8, {ease: FlxEase.sineIn});
}
public override function new(currentCharacter:PlayableCharacter)
{
super(currentCharacter);
funnyScroll = new BGScrollingText(0, 220, currentCharacter.getFreeplayDJText(1), FlxG.width / 2, false, 60);
funnyScroll2 = new BGScrollingText(0, 335, currentCharacter.getFreeplayDJText(1), FlxG.width / 2, false, 60);
moreWays = new BGScrollingText(0, 160, currentCharacter.getFreeplayDJText(2), FlxG.width, true, 43);
moreWays2 = new BGScrollingText(0, 397, currentCharacter.getFreeplayDJText(2), FlxG.width, true, 43);
txtNuts = new BGScrollingText(0, 285, currentCharacter.getFreeplayDJText(3), FlxG.width / 2, true, 43);
funnyScroll3 = new BGScrollingText(0, orangeBackShit.y + 10, currentCharacter.getFreeplayDJText(1), FlxG.width / 2, 60);
}
public override function init():Void
{
FlxTween.tween(pinkBack, {x: 0}, 0.6, {ease: FlxEase.quartOut});
add(pinkBack);
add(orangeBackShit);
add(alsoOrangeLOL);
FlxSpriteUtil.alphaMaskFlxSprite(orangeBackShit, pinkBack, orangeBackShit);
orangeBackShit.visible = false;
alsoOrangeLOL.visible = false;
confirmTextGlow.blend = BlendMode.ADD;
confirmTextGlow.visible = false;
confirmGlow.blend = BlendMode.ADD;
confirmGlow.visible = false;
confirmGlow2.visible = false;
add(confirmGlow2);
add(confirmGlow);
add(confirmTextGlow);
add(backingTextYeah);
cardGlow.blend = BlendMode.ADD;
cardGlow.visible = false;
moreWays.visible = false;
funnyScroll.visible = false;
txtNuts.visible = false;
funnyScroll2.visible = false;
moreWays2.visible = false;
funnyScroll3.visible = false;
moreWays.funnyColor = 0xFFFFF383;
moreWays.speed = 6.8;
add(moreWays);
funnyScroll.funnyColor = 0xFFFF9963;
funnyScroll.speed = -3.8;
add(funnyScroll);
txtNuts.speed = 3.5;
add(txtNuts);
funnyScroll2.funnyColor = 0xFFFF9963;
funnyScroll2.speed = -3.8;
add(funnyScroll2);
moreWays2.funnyColor = 0xFFFFF383;
moreWays2.speed = 6.8;
add(moreWays2);
funnyScroll3.funnyColor = 0xFFFEA400;
funnyScroll3.speed = -3.8;
add(funnyScroll3);
glowDark = new FlxSprite(-300, 330).loadGraphic(Paths.image('freeplay/beatglow'));
glowDark.blend = BlendMode.MULTIPLY;
add(glowDark);
glow = new FlxSprite(-300, 330).loadGraphic(Paths.image('freeplay/beatglow'));
glow.blend = BlendMode.ADD;
add(glow);
glowDark.visible = false;
glow.visible = false;
add(cardGlow);
}
var beatFreq:Int = 1;
var beatFreqList:Array<Int> = [1,2,4,8];
public override function beatHit():Void {
// increases the amount of beats that need to go by to pulse the glow because itd flash like craazy at high bpms.....
beatFreq = beatFreqList[Math.floor(Conductor.instance.bpm/140)];
if(Conductor.instance.currentBeat % beatFreq != 0) return;
FlxTween.cancelTweensOf(glow);
FlxTween.cancelTweensOf(glowDark);
glow.alpha = 0.8;
FlxTween.tween(glow, {alpha: 0}, 16/24, {ease: FlxEase.quartOut});
glowDark.alpha = 0;
FlxTween.tween(glowDark, {alpha: 0.6}, 18/24, {ease: FlxEase.quartOut});
}
public override function introDone():Void
{
super.introDone();
moreWays.visible = true;
funnyScroll.visible = true;
txtNuts.visible = true;
funnyScroll2.visible = true;
moreWays2.visible = true;
funnyScroll3.visible = true;
// grpTxtScrolls.visible = true;
glowDark.visible = true;
glow.visible = true;
}
public override function confirm():Void
{
super.confirm();
// FlxTween.color(bgDad, 0.33, 0xFFFFFFFF, 0xFF555555, {ease: FlxEase.quadOut});
moreWays.visible = false;
funnyScroll.visible = false;
txtNuts.visible = false;
funnyScroll2.visible = false;
moreWays2.visible = false;
funnyScroll3.visible = false;
glowDark.visible = false;
glow.visible = false;
}
public override function disappear():Void
{
super.disappear();
moreWays.visible = false;
funnyScroll.visible = false;
txtNuts.visible = false;
funnyScroll2.visible = false;
moreWays2.visible = false;
funnyScroll3.visible = false;
glowDark.visible = false;
glow.visible = false;
}
}

View file

@ -0,0 +1,278 @@
package funkin.ui.freeplay.backcards;
import funkin.ui.freeplay.FreeplayState;
import flash.display.BitmapData;
import flixel.FlxCamera;
import flixel.math.FlxMath;
import flixel.FlxSprite;
import flixel.group.FlxGroup;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.math.FlxAngle;
import flixel.math.FlxPoint;
import flixel.text.FlxText;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import flixel.util.FlxSpriteUtil;
import flixel.util.FlxTimer;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinSprite;
import funkin.ui.freeplay.charselect.PlayableCharacter;
import funkin.ui.MusicBeatSubState;
import lime.utils.Assets;
import openfl.display.BlendMode;
import flixel.group.FlxSpriteGroup;
import funkin.graphics.shaders.AdjustColorShader;
import flixel.addons.display.FlxTiledSprite;
import flixel.addons.display.FlxBackdrop;
class NewCharacterCard extends BackingCard
{
var confirmAtlas:FlxAtlasSprite;
var darkBg:FlxSprite;
var lightLayer:FlxSprite;
var multiply1:FlxSprite;
var multiply2:FlxSprite;
var lightLayer2:FlxSprite;
var lightLayer3:FlxSprite;
var yellow:FlxSprite;
var multiplyBar:FlxSprite;
var bruh:FlxSprite;
public var friendFoe:BGScrollingText;
public var newUnlock1:BGScrollingText;
public var waiting:BGScrollingText;
public var newUnlock2:BGScrollingText;
public var friendFoe2:BGScrollingText;
public var newUnlock3:BGScrollingText;
public override function applyExitMovers(?exitMovers:FreeplayState.ExitMoverData, ?exitMoversCharSel:FreeplayState.ExitMoverData):Void
{
super.applyExitMovers(exitMovers, exitMoversCharSel);
if (exitMovers == null || exitMoversCharSel == null) return;
exitMovers.set([friendFoe],
{
x: FlxG.width * 2,
speed: 0.4,
});
exitMovers.set([newUnlock1],
{
x: -newUnlock1.width * 2,
y: newUnlock1.y,
speed: 0.4,
wait: 0
});
exitMovers.set([waiting],
{
x: FlxG.width * 2,
speed: 0.4,
});
exitMovers.set([newUnlock2],
{
x: -newUnlock2.width * 2,
speed: 0.5,
});
exitMovers.set([friendFoe2],
{
x: FlxG.width * 2,
speed: 0.4
});
exitMovers.set([newUnlock3],
{
x: -newUnlock3.width * 2,
speed: 0.3
});
exitMoversCharSel.set([friendFoe, newUnlock1, waiting, newUnlock2, friendFoe2, newUnlock3, multiplyBar], {
y: -60,
speed: 0.8,
wait: 0.1
});
}
public override function introDone():Void
{
// pinkBack.color = 0xFFFFD863;
darkBg.visible = true;
friendFoe.visible = true;
newUnlock1.visible = true;
waiting.visible = true;
newUnlock2.visible = true;
friendFoe2.visible = true;
newUnlock3.visible = true;
multiplyBar.visible = true;
lightLayer.visible = true;
multiply1.visible = true;
multiply2.visible = true;
lightLayer2.visible = true;
yellow.visible = true;
lightLayer3.visible = true;
cardGlow.visible = true;
FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.45, {ease: FlxEase.sineOut});
}
public override function enterCharSel():Void
{
FlxTween.tween(friendFoe, {speed: 0}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(newUnlock1, {speed: 0}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(waiting, {speed: 0}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(newUnlock2, {speed: 0}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(friendFoe2, {speed: 0}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(newUnlock3, {speed: 0}, 0.8, {ease: FlxEase.sineIn});
}
public override function init():Void
{
FlxTween.tween(pinkBack, {x: 0}, 0.6, {ease: FlxEase.quartOut});
add(pinkBack);
confirmTextGlow.blend = BlendMode.ADD;
confirmTextGlow.visible = false;
confirmGlow.blend = BlendMode.ADD;
confirmGlow.visible = false;
confirmGlow2.visible = false;
friendFoe = new BGScrollingText(0, 163, "COULD IT BE A NEW FRIEND? OR FOE??", FlxG.width, true, 43);
newUnlock1 = new BGScrollingText(-440, 215, 'NEW UNLOCK!', FlxG.width / 2, true, 80);
waiting = new BGScrollingText(0, 286, "SOMEONE'S WAITING!", FlxG.width / 2, true, 43);
newUnlock2 = new BGScrollingText(-220, 331, 'NEW UNLOCK!', FlxG.width / 2, true, 80);
friendFoe2 = new BGScrollingText(0, 402, 'COULD IT BE A NEW FRIEND? OR FOE??', FlxG.width, true, 43);
newUnlock3 = new BGScrollingText(0, 458, 'NEW UNLOCK!', FlxG.width / 2, true, 80);
darkBg = new FlxSprite(0, 0).loadGraphic(Paths.image('freeplay/backingCards/newCharacter/darkback'));
add(darkBg);
friendFoe.funnyColor = 0xFF139376;
friendFoe.speed = -4;
add(friendFoe);
newUnlock1.funnyColor = 0xFF99BDF2;
newUnlock1.speed = 2;
add(newUnlock1);
waiting.funnyColor = 0xFF40EA84;
waiting.speed = -2;
add(waiting);
newUnlock2.funnyColor = 0xFF99BDF2;
newUnlock2.speed = 2;
add(newUnlock2);
friendFoe2.funnyColor = 0xFF139376;
friendFoe2.speed = -4;
add(friendFoe2);
newUnlock3.funnyColor = 0xFF99BDF2;
newUnlock3.speed = 2;
add(newUnlock3);
multiplyBar = new FlxSprite(-10, 440).loadGraphic(Paths.image('freeplay/backingCards/newCharacter/multiplyBar'));
multiplyBar.blend = BlendMode.MULTIPLY;
add(multiplyBar);
lightLayer = new FlxSprite(-360, 230).loadGraphic(Paths.image('freeplay/backingCards/newCharacter/orange gradient'));
lightLayer.blend = BlendMode.ADD;
add(lightLayer);
multiply1 = new FlxSprite(-15, -125).loadGraphic(Paths.image('freeplay/backingCards/newCharacter/red'));
multiply1.blend = BlendMode.MULTIPLY;
add(multiply1);
multiply2 = new FlxSprite(-15, -125).loadGraphic(Paths.image('freeplay/backingCards/newCharacter/red'));
multiply2.blend = BlendMode.MULTIPLY;
add(multiply2);
lightLayer2 = new FlxSprite(-360, 230).loadGraphic(Paths.image('freeplay/backingCards/newCharacter/orange gradient'));
lightLayer2.blend = BlendMode.ADD;
add(lightLayer2);
yellow = new FlxSprite(0, 0).loadGraphic(Paths.image('freeplay/backingCards/newCharacter/yellow bg piece'));
yellow.blend = BlendMode.MULTIPLY;
add(yellow);
lightLayer3 = new FlxSprite(-360, 290).loadGraphic(Paths.image('freeplay/backingCards/newCharacter/red gradient'));
lightLayer3.blend = BlendMode.ADD;
add(lightLayer3);
cardGlow.blend = BlendMode.ADD;
cardGlow.visible = false;
add(cardGlow);
darkBg.visible = false;
friendFoe.visible = false;
newUnlock1.visible = false;
waiting.visible = false;
newUnlock2.visible = false;
friendFoe2.visible = false;
newUnlock3.visible = false;
multiplyBar.visible = false;
lightLayer.visible = false;
multiply1.visible = false;
multiply2.visible = false;
lightLayer2.visible = false;
yellow.visible = false;
lightLayer3.visible = false;
}
var _timer:Float = 0;
override public function update(elapsed:Float):Void
{
super.update(elapsed);
_timer += elapsed * 2;
var sinTest:Float = (Math.sin(_timer) + 1) / 2;
lightLayer.alpha = FlxMath.lerp(0.4, 1, sinTest);
lightLayer2.alpha = FlxMath.lerp(0.2, 0.5, sinTest);
lightLayer3.alpha = FlxMath.lerp(0.1, 0.7, sinTest);
multiply1.alpha = FlxMath.lerp(1, 0.21, sinTest);
multiply2.alpha = FlxMath.lerp(1, 0.21, sinTest);
yellow.alpha = FlxMath.lerp(0.2, 0.72, sinTest);
if (instance != null)
{
instance.angleMaskShader.extraColor = FlxColor.interpolate(0xFF2E2E46, 0xFF60607B, sinTest);
}
}
public override function disappear():Void
{
FlxTween.color(pinkBack, 0.25, 0xFF05020E, 0xFFFFD0D5, {ease: FlxEase.quadOut});
darkBg.visible = false;
friendFoe.visible = false;
newUnlock1.visible = false;
waiting.visible = false;
newUnlock2.visible = false;
friendFoe2.visible = false;
newUnlock3.visible = false;
multiplyBar.visible = false;
lightLayer.visible = false;
multiply1.visible = false;
multiply2.visible = false;
lightLayer2.visible = false;
yellow.visible = false;
lightLayer3.visible = false;
cardGlow.visible = true;
cardGlow.alpha = 1;
cardGlow.scale.set(1, 1);
FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.25, {ease: FlxEase.sineOut});
}
override public function confirm():Void
{
// confirmAtlas.visible = true;
// confirmAtlas.anim.play("");
}
}

View file

@ -0,0 +1,314 @@
package funkin.ui.freeplay.backcards;
import funkin.ui.freeplay.FreeplayState;
import flash.display.BitmapData;
import flixel.FlxCamera;
import flixel.math.FlxMath;
import flixel.FlxSprite;
import flixel.group.FlxGroup;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.math.FlxAngle;
import flixel.math.FlxPoint;
import flixel.text.FlxText;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import flixel.util.FlxSpriteUtil;
import flixel.util.FlxTimer;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinSprite;
import funkin.ui.freeplay.charselect.PlayableCharacter;
import funkin.ui.MusicBeatSubState;
import lime.utils.Assets;
import openfl.display.BlendMode;
import flixel.group.FlxSpriteGroup;
import funkin.graphics.shaders.AdjustColorShader;
import flixel.addons.display.FlxTiledSprite;
import flixel.addons.display.FlxBackdrop;
class PicoCard extends BackingCard
{
var scrollBack:FlxBackdrop;
var scrollLower:FlxBackdrop;
var scrollTop:FlxBackdrop;
var scrollMiddle:FlxBackdrop;
var glow:FlxSprite;
var glowDark:FlxSprite;
var blueBar:FlxSprite;
var confirmAtlas:FlxAtlasSprite;
public override function enterCharSel():Void
{
FlxTween.tween(scrollBack.velocity, {x: 0}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(scrollLower.velocity, {x: 0}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(scrollTop.velocity, {x: 0}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(scrollMiddle.velocity, {x: 0}, 0.8, {ease: FlxEase.sineIn});
}
public override function applyExitMovers(?exitMovers:FreeplayState.ExitMoverData, ?exitMoversCharSel:FreeplayState.ExitMoverData):Void
{
super.applyExitMovers(exitMovers, exitMoversCharSel);
if (exitMovers == null || exitMoversCharSel == null) return;
exitMoversCharSel.set([scrollTop],
{
y: -90,
speed: 0.8,
wait: 0.1
});
exitMoversCharSel.set([scrollMiddle],
{
y: -80,
speed: 0.8,
wait: 0.1
});
exitMoversCharSel.set([blueBar],
{
y: -70,
speed: 0.8,
wait: 0.1
});
exitMoversCharSel.set([scrollLower],
{
y: -60,
speed: 0.8,
wait: 0.1
});
exitMoversCharSel.set([scrollBack],
{
y: -50,
speed: 0.8,
wait: 0.1
});
}
public override function init():Void
{
FlxTween.tween(pinkBack, {x: 0}, 0.6, {ease: FlxEase.quartOut});
add(pinkBack);
confirmTextGlow.blend = BlendMode.ADD;
confirmTextGlow.visible = false;
confirmGlow.blend = BlendMode.ADD;
confirmGlow.visible = false;
confirmGlow2.visible = false;
scrollBack = new FlxBackdrop(Paths.image('freeplay/backingCards/pico/lowerLoop'), X, 20);
scrollBack.setPosition(0, 200);
scrollBack.flipX = true;
scrollBack.alpha = 0.39;
scrollBack.velocity.x = 110;
add(scrollBack);
scrollLower = new FlxBackdrop(Paths.image('freeplay/backingCards/pico/lowerLoop'), X, 20);
scrollLower.setPosition(0, 406);
scrollLower.velocity.x = -110;
add(scrollLower);
blueBar = new FlxSprite(0, 239).loadGraphic(Paths.image('freeplay/backingCards/pico/blueBar'));
blueBar.blend = BlendMode.MULTIPLY;
blueBar.alpha = 0.4;
add(blueBar);
scrollTop = new FlxBackdrop(null, X, 20);
scrollTop.setPosition(0, 80);
scrollTop.velocity.x = -220;
scrollTop.frames = Paths.getSparrowAtlas('freeplay/backingCards/pico/topLoop');
scrollTop.animation.addByPrefix('uzi', 'uzi info', 24, false);
scrollTop.animation.addByPrefix('sniper', 'sniper info', 24, false);
scrollTop.animation.addByPrefix('rocket launcher', 'rocket launcher info', 24, false);
scrollTop.animation.addByPrefix('rifle', 'rifle info', 24, false);
scrollTop.animation.addByPrefix('base', 'base', 24, false);
scrollTop.animation.play('base');
add(scrollTop);
scrollMiddle = new FlxBackdrop(Paths.image('freeplay/backingCards/pico/middleLoop'), X, 15);
scrollMiddle.setPosition(0, 346);
add(scrollMiddle);
scrollMiddle.velocity.x = 220;
glowDark = new FlxSprite(-300, 330).loadGraphic(Paths.image('freeplay/backingCards/pico/glow'));
glowDark.blend = BlendMode.MULTIPLY;
add(glowDark);
glow = new FlxSprite(-300, 330).loadGraphic(Paths.image('freeplay/backingCards/pico/glow'));
glow.blend = BlendMode.ADD;
add(glow);
blueBar.visible = false;
scrollBack.visible = false;
scrollLower.visible = false;
scrollTop.visible = false;
scrollMiddle.visible = false;
glow.visible = false;
glowDark.visible = false;
confirmAtlas = new FlxAtlasSprite(5, 55, Paths.animateAtlas("freeplay/backingCards/pico/pico-confirm"));
confirmAtlas.visible = false;
add(confirmAtlas);
cardGlow.blend = BlendMode.ADD;
cardGlow.visible = false;
add(cardGlow);
}
override public function confirm():Void
{
confirmAtlas.visible = true;
confirmAtlas.anim.play("");
FlxTween.color(instance.bgDad, 10 / 24, 0xFFFFFFFF, 0xFF8A8A8A,
{
ease: FlxEase.expoOut,
onUpdate: function(_) {
instance.angleMaskShader.extraColor = instance.bgDad.color;
}
});
new FlxTimer().start(10 / 24, function(_) {
// shoot
FlxTween.color(instance.bgDad, 3 / 24, 0xFF343036, 0xFF696366,
{
ease: FlxEase.expoOut,
onUpdate: function(_) {
instance.angleMaskShader.extraColor = instance.bgDad.color;
}
});
});
new FlxTimer().start(14 / 24, function(_) {
// shoot
FlxTween.color(instance.bgDad, 3 / 24, 0xFF27292D, 0xFF686A6F,
{
ease: FlxEase.expoOut,
onUpdate: function(_) {
instance.angleMaskShader.extraColor = instance.bgDad.color;
}
});
});
new FlxTimer().start(18 / 24, function(_) {
// shoot
FlxTween.color(instance.bgDad, 3 / 24, 0xFF2D282D, 0xFF676164,
{
ease: FlxEase.expoOut,
onUpdate: function(_) {
instance.angleMaskShader.extraColor = instance.bgDad.color;
}
});
});
new FlxTimer().start(21 / 24, function(_) {
// shoot
FlxTween.color(instance.bgDad, 3 / 24, 0xFF29292F, 0xFF62626B,
{
ease: FlxEase.expoOut,
onUpdate: function(_) {
instance.angleMaskShader.extraColor = instance.bgDad.color;
}
});
});
new FlxTimer().start(24 / 24, function(_) {
// shoot
FlxTween.color(instance.bgDad, 3 / 24, 0xFF29232C, 0xFF808080,
{
ease: FlxEase.expoOut,
onUpdate: function(_) {
instance.angleMaskShader.extraColor = instance.bgDad.color;
}
});
});
}
var beatFreq:Int = 1;
var beatFreqList:Array<Int> = [1,2,4,8];
public override function beatHit():Void {
// increases the amount of beats that need to go by to pulse the glow because itd flash like craazy at high bpms.....
beatFreq = beatFreqList[Math.floor(Conductor.instance.bpm/140)];
if(Conductor.instance.currentBeat % beatFreq != 0) return;
FlxTween.cancelTweensOf(glow);
FlxTween.cancelTweensOf(glowDark);
glow.alpha = 1;
FlxTween.tween(glow, {alpha: 0}, 16/24, {ease: FlxEase.quartOut});
glowDark.alpha = 0;
FlxTween.tween(glowDark, {alpha: 1}, 18/24, {ease: FlxEase.quartOut});
}
public override function introDone():Void
{
pinkBack.color = 0xFF98A2F3;
blueBar.visible = true;
scrollBack.visible = true;
scrollLower.visible = true;
scrollTop.visible = true;
scrollMiddle.visible = true;
glowDark.visible = true;
glow.visible = true;
cardGlow.visible = true;
FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.45, {ease: FlxEase.sineOut});
}
public override function disappear():Void
{
FlxTween.color(pinkBack, 0.25, 0xFF98A2F3, 0xFFFFD0D5, {ease: FlxEase.quadOut});
blueBar.visible = false;
scrollBack.visible = false;
scrollLower.visible = false;
scrollTop.visible = false;
scrollMiddle.visible = false;
glowDark.visible = false;
glow.visible = false;
cardGlow.visible = true;
cardGlow.alpha = 1;
cardGlow.scale.set(1, 1);
FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.25, {ease: FlxEase.sineOut});
}
override public function update(elapsed:Float):Void
{
super.update(elapsed);
var scrollProgress:Float = Math.abs(scrollTop.x % (scrollTop.frameWidth + 20));
if (scrollTop.animation.curAnim.finished == true)
{
if (FlxMath.inBounds(scrollProgress, 500, 700) && scrollTop.animation.curAnim.name != 'sniper')
{
scrollTop.animation.play('sniper', true, false);
}
if (FlxMath.inBounds(scrollProgress, 700, 1300) && scrollTop.animation.curAnim.name != 'rifle')
{
scrollTop.animation.play('rifle', true, false);
}
if (FlxMath.inBounds(scrollProgress, 1450, 2000) && scrollTop.animation.curAnim.name != 'rocket launcher')
{
scrollTop.animation.play('rocket launcher', true, false);
}
if (FlxMath.inBounds(scrollProgress, 0, 300) && scrollTop.animation.curAnim.name != 'uzi')
{
scrollTop.animation.play('uzi', true, false);
}
}
}
}

View file

@ -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 [];
}
@ -119,12 +126,33 @@ class PlayableCharacter implements IRegistryEntry<PlayerData>
}
}
public function getResultsMusicPath(rank:ScoringRank):String
{
switch (rank)
{
case PERFECT_GOLD:
return _data?.results?.music?.PERFECT_GOLD ?? "resultsPERFECT";
case PERFECT:
return _data?.results?.music?.PERFECT ?? "resultsPERFECT";
case EXCELLENT:
return _data?.results?.music?.EXCELLENT ?? "resultsEXCELLENT";
case GREAT:
return _data?.results?.music?.GREAT ?? "resultsNORMAL";
case GOOD:
return _data?.results?.music?.GOOD ?? "resultsNORMAL";
case SHIT:
return _data?.results?.music?.SHIT ?? "resultsSHIT";
default:
return _data?.results?.music?.GOOD ?? "resultsNORMAL";
}
}
/**
* Returns whether this character is unlocked.
*/
public function isUnlocked():Bool
{
return _data.unlocked;
return _data?.unlocked ?? true;
}
/**

View file

@ -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

View file

@ -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.
*/

View file

@ -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);
}
}