Merge branch 'rewrite/master' into char-unlock-tinkers

This commit is contained in:
Cameron Taylor 2024-09-12 19:55:35 -04:00
commit d826f2a7bc
21 changed files with 290 additions and 108 deletions

View file

@ -29,8 +29,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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
- Week 1 Erect Stage
- Week 2 Erect Stage
- Week 3 Erect Stage
- Week 4 Erect Stage
- Week 5 Erect Stage
- Weekend 1 Erect Stage
- Implemented alternate animations and music for Pico in the results screen.
- These display on Pico remixes, as well as when playing Weekend 1.
- Implemented support for scripted Note Kinds. You can use HScript define a different note style to display for these notes as well as custom behavior. (community feature by lemz1)
- Implemented a new Strumline Background option, to display a darkened background behind the strumline with your choice of opacity.
- Implemented support for Numeric and Selector options in the Options menu. (community feature by FlooferLand)
## Changed
- Girlfriend and Nene now perform previously unused animations when you achieve a large combo, or drop a large combo.

2
art

@ -1 +1 @@
Subproject commit 0bb988c49788fd25a230b56dd9e4448838bc79c9
Subproject commit 0dee03f11afc01c2883da223fa10405f7011dd33

2
assets

@ -1 +1 @@
Subproject commit f4cf1d3721155e16ab4e547b6baabc1238b66dae
Subproject commit aede71ea384103393273fa72e8c02fa0f364da94

View file

@ -14,7 +14,7 @@
5. Run `haxelib --global install hmm` and then `haxelib --global run hmm setup` to install hmm.json
6. Run `hmm install` to install all haxelibs of the current branch
7. Run `haxelib run lime setup` to set up lime
8. Platform setup
8. Perform additional platform setup
- For Windows, download the [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe)
- When prompted, select "Individual Components" and make sure to download the following:
- MSVC v143 VS 2022 C++ x64/x86 build tools
@ -22,8 +22,24 @@
- Mac: [`lime setup mac` Documentation](https://lime.openfl.org/docs/advanced-setup/macos/)
- Linux: [`lime setup linux` Documentation](https://lime.openfl.org/docs/advanced-setup/linux/)
- HTML5: Compiles without any extra setup
9. If you are targeting for native, you may need to run `lime rebuild PLATFORM` and `lime rebuild PLATFORM -debug`
10. `lime test PLATFORM` ! Add `-debug` to enable several debug features such as time travel (`PgUp`/`PgDn` in Play State).
9. If you are targeting for native, you may need to run `lime rebuild <PLATFORM>` and `lime rebuild <PLATFORM> -debug`
10. `lime test <PLATFORM>` to build and launch the game for your platform (for example, `lime test windows`)
## Build Flags
There are several useful build flags you can add to a build to affect how it works. A full list can be found in `project.hxp`, but here's information on some of them:
- `-debug` to build the game in debug mode. This automatically enables several useful debug features.
- This includes enabling in-game debug functions, disables compile-time optimizations, enabling asset redirection (see below), and enabling the VSCode debug server (which can slow the game on some machines but allows for powerful debugging through breakpoints).
- `-DGITHUB_BUILD` will enable in-game debug functions (such as the ability to time travel in a song by pressing `PgUp`/`PgDn`), without enabling the other stuff
- `-DFEATURE_POLYMOD_MODS` or `-DNO_FEATURE_POLYMOD_MODS` to forcibly enable or disable modding support.
- `-DREDIRECT_ASSETS_FOLDER` or `-DNO_REDIRECT_ASSETS_FOLDER` to forcibly enable or disable asset redirection.
- This feature causes the game to load exported assets from the project's assets folder rather than the exported one. Great for fast iteration, but the game
- `-DFEATURE_DISCORD_RPC` or `-DNO_FEATURE_DISCORD_RPC` to forcibly enable or disable support for Discord Rich Presence.
- `-DFEATURE_VIDEO_PLAYBACK` or `-DNO_FEATURE_VIDEO_PLAYBACK` to forcibly enable or disable video cutscene support.
- `-DFEATURE_CHART_EDITOR` or `-DNO_FEATURE_CHART_EDITOR` to forcibly enable or disable the chart editor in the Debug menu.
- `-DFEATURE_STAGE_EDITOR` to forcibly enable the experimental stage editor.
- `-DFEATURE_GHOST_TAPPING` to forcibly enable an experimental gameplay change to the anti-mash system.
# Troubleshooting - GO THROUGH THESE STEPS BEFORE OPENING ISSUES ON GITHUB!

View file

@ -194,7 +194,7 @@
"name": "polymod",
"type": "git",
"dir": null,
"ref": "96cfc5fa693b017e47f7cb13b765cc68698fa6b6",
"ref": "0fbdf27fe124549730accd540cec8a183f8652c0",
"url": "https://github.com/larsiusprime/polymod"
},
{

View file

@ -67,15 +67,9 @@ class Main extends Sprite
function init(?event:Event):Void
{
#if web
untyped js.Syntax.code("
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = 0;
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
}");
// set this variable (which is a function) from the lime version at lime/_internal/backend/html5/HTML5Application.hx
// The framerate cap will more thoroughly initialize via Preferences in InitState.hx
funkin.Preferences.lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
#end
if (hasEventListener(Event.ADDED_TO_STAGE))

View file

@ -128,6 +128,48 @@ class Preferences
return value;
}
public static var unlockedFramerate(get, set):Bool;
static function get_unlockedFramerate():Bool
{
return Save?.instance?.options?.unlockedFramerate;
}
static function set_unlockedFramerate(value:Bool):Bool
{
if (value != Save.instance.options.unlockedFramerate)
{
#if web
toggleFramerateCap(value);
#end
}
var save:Save = Save.instance;
save.options.unlockedFramerate = value;
save.flush();
return value;
}
#if web
// We create a haxe version of this just for readability.
// We use these to override `window.requestAnimationFrame` in Javascript to uncap the framerate / "animation" request rate
// Javascript is crazy since u can just do stuff like that lol
public static function unlockedFramerateFunction(callback, element)
{
var currTime = Date.now().getTime();
var timeToCall = 0;
var id = js.Browser.window.setTimeout(function() {
callback(currTime + timeToCall);
}, timeToCall);
return id;
}
// Lime already implements their own little framerate cap, so we can just use that
// This also gets set in the init function in Main.hx, since we need to definitely override it
public static var lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
#end
/**
* Loads the user's preferences from the save data and apply them.
*/
@ -137,6 +179,17 @@ class Preferences
FlxG.autoPause = Preferences.autoPause;
// Apply the debugDisplay setting (enables the FPS and RAM display).
toggleDebugDisplay(Preferences.debugDisplay);
#if web
toggleFramerateCap(Preferences.unlockedFramerate);
#end
}
static function toggleFramerateCap(unlocked:Bool):Void
{
#if web
var framerateFunction = unlocked ? unlockedFramerateFunction : lockedFramerateFunction;
untyped js.Syntax.code("window.requestAnimationFrame = framerateFunction;");
#end
}
static function toggleDebugDisplay(show:Bool):Void

View file

@ -340,6 +340,8 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
if (songMusicData != null)
{
Conductor.instance.mapTimeChanges(songMusicData.timeChanges);
if (songMusicData.looped != null && params.loop == null) params.loop = songMusicData.looped;
}
else
{
@ -567,6 +569,14 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
return sound;
}
/**
* Produces a string representation suitable for debugging.
*/
public override function toString():String
{
return 'FunkinSound(${this._label})';
}
}
/**

View file

@ -113,6 +113,11 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
public function play(forceRestart:Bool = false, startTime:Float = 0.0, ?endTime:Float)
{
forEachAlive(function(sound:FunkinSound) {
if (sound.length < startTime)
{
// trace('Queuing sound (${sound.toString()} past its length! Skipping...)');
return;
}
sound.play(forceRestart, startTime, endTime);
});
}

View file

@ -95,7 +95,7 @@ class FlxAtlasSprite extends FlxAnimate
*/
public function hasAnimation(id:String):Bool
{
return getLabelIndex(id) != -1 || anim.symbolDictionary.exists(id) || anim.getByName(id) != null;
return getLabelIndex(id) != -1 || anim.symbolDictionary.exists(id);
}
/**
@ -154,32 +154,27 @@ class FlxAtlasSprite extends FlxAnimate
{
if (!anim.isPlaying)
{
if (fr != null) anim.curFrame = fr.index + startFrame;
else
anim.curFrame = startFrame;
// Resume animation if it's paused.
anim.play('', restart, false, startFrame);
anim.resume();
}
return;
}
else
else if (!hasAnimation(id))
{
// Skip if the animation doesn't exist
if (!hasAnimation(id))
{
trace('Animation ' + id + ' not found');
return;
}
trace('Animation ' + id + ' not found');
return;
}
this.currentAnimation = id;
anim.onComplete.removeAll();
anim.onComplete.add(function() {
if (loop)
{
this.anim.play(id, restart, false, startFrame);
this.currentAnimation = id;
}
else
{
onAnimationComplete.dispatch(id);
}
_onAnimationComplete();
});
looping = loop;
@ -190,6 +185,14 @@ class FlxAtlasSprite extends FlxAnimate
// Move to the first frame of the animation.
// goToFrameLabel(id);
trace('Playing animation $id');
if ((id == null || id == "") || this.anim.symbolDictionary.exists(id) || (this.anim.getByName(id) != null))
{
this.anim.play(id, restart, false, startFrame);
this.currentAnimation = anim.curSymbol.name;
fr = null;
}
// Only call goToFrameLabel if there is a frame label with that name. This prevents annoying warnings!
if (getFrameLabelNames().indexOf(id) != -1)
{
@ -197,13 +200,6 @@ class FlxAtlasSprite extends FlxAnimate
fr = anim.getFrameLabel(id);
anim.curFrame += startFrame;
}
else
{
this.anim.play(id, restart, false, startFrame);
fr = null;
}
this.currentAnimation = id;
}
override public function update(elapsed:Float)
@ -301,12 +297,13 @@ class FlxAtlasSprite extends FlxAnimate
{
anim.pause();
_onAnimationComplete();
if (looping)
{
anim.curFrame = (fr != null) ? fr.index : 0;
anim.resume();
}
else
else if (fr != null && anim.curFrame != anim.length - 1)
{
anim.curFrame--;
}
@ -320,6 +317,10 @@ class FlxAtlasSprite extends FlxAnimate
{
onAnimationComplete.dispatch(currentAnimation);
}
else
{
onAnimationComplete.dispatch('');
}
}
var prevFrames:Map<Int, FlxFrame> = [];

View file

@ -395,20 +395,37 @@ class Controls extends FlxActionSet
return result;
}
public function getDialogueName(action:FlxActionDigital):String
public function getDialogueName(action:FlxActionDigital, ?ignoreSurrounding:Bool = false):String
{
var input = action.inputs[0];
return switch (input.device)
if (ignoreSurrounding == false)
{
case KEYBOARD: return '[${(input.inputID : FlxKey)}]';
case GAMEPAD: return '(${(input.inputID : FlxGamepadInputID)})';
case device: throw 'unhandled device: $device';
return switch (input.device)
{
case KEYBOARD: return '[${(input.inputID : FlxKey)}]';
case GAMEPAD: return '(${(input.inputID : FlxGamepadInputID)})';
case device: throw 'unhandled device: $device';
}
}
else
{
return switch (input.device)
{
case KEYBOARD: return '${(input.inputID : FlxKey)}';
case GAMEPAD: return '${(input.inputID : FlxGamepadInputID)}';
case device: throw 'unhandled device: $device';
}
}
}
public function getDialogueNameFromToken(token:String):String
public function getDialogueNameFromToken(token:String, ?ignoreSurrounding:Bool = false):String
{
return getDialogueName(getActionFromControl(Control.createByName(token.toUpperCase())));
return getDialogueName(getActionFromControl(Control.createByName(token.toUpperCase())), ignoreSurrounding);
}
public function getDialogueNameFromControl(control:Control, ?ignoreSurrounding:Bool = false):String
{
return getDialogueName(getActionFromControl(control), ignoreSurrounding);
}
function getActionFromControl(control:Control):FlxActionDigital

View file

@ -194,6 +194,7 @@ class ResultState extends MusicBeatSubState
{
// Animation is not looped.
animation.onAnimationComplete.add((_name:String) -> {
trace("AHAHAH 2");
if (animation != null)
{
animation.anim.pause();
@ -203,9 +204,10 @@ class ResultState extends MusicBeatSubState
else if (animData.loopFrameLabel != null)
{
animation.onAnimationComplete.add((_name:String) -> {
trace("AHAHAH 2");
if (animation != null)
{
animation.playAnimation(animData.loopFrameLabel ?? ''); // unpauses this anim, since it's on PlayOnce!
animation.playAnimation(animData.loopFrameLabel ?? '', true, false, true); // unpauses this anim, since it's on PlayOnce!
}
});
}
@ -214,6 +216,7 @@ class ResultState extends MusicBeatSubState
animation.onAnimationComplete.add((_name:String) -> {
if (animation != null)
{
trace("AHAHAH");
animation.anim.curFrame = animData.loopFrame ?? 0;
animation.anim.play(); // unpauses this anim, since it's on PlayOnce!
}
@ -415,8 +418,7 @@ class ResultState extends MusicBeatSubState
{
startingVolume: 1.0,
overrideExisting: true,
restartTrack: true,
loop: rank.shouldMusicLoop()
restartTrack: true
});
});
}
@ -426,8 +428,7 @@ class ResultState extends MusicBeatSubState
{
startingVolume: 1.0,
overrideExisting: true,
restartTrack: true,
loop: rank.shouldMusicLoop()
restartTrack: true
});
}
});

View file

@ -577,19 +577,6 @@ enum abstract ScoringRank(String)
}
}
public function shouldMusicLoop():Bool
{
switch (abstract)
{
case PERFECT_GOLD | PERFECT | EXCELLENT | GREAT | GOOD:
return true;
case SHIT:
return false;
default:
return false;
}
}
public function getHorTextAsset()
{
switch (abstract)

View file

@ -97,6 +97,7 @@ class Save
autoPause: true,
inputOffset: 0,
audioVisualOffset: 0,
unlockedFramerate: false,
controls:
{
@ -1180,6 +1181,12 @@ typedef SaveDataOptions =
*/
var audioVisualOffset:Int;
/**
* If we want the framerate to be unlocked on HTML5.
* @default `false
*/
var unlockedFramerate:Bool;
var controls:
{
var p1:

View file

@ -349,6 +349,9 @@ class CharSelectSubState extends MusicBeatSubState
// FlxG.camera.follow(camFollow, LOCKON, 0.01);
FlxG.camera.follow(camFollow, LOCKON);
var fadeShaderFilter:ShaderFilter = new ShaderFilter(fadeShader);
FlxG.camera.filters = [fadeShaderFilter];
var temp:FlxSprite = new FlxSprite();
temp.loadGraphic(Paths.image('charSelect/placement'));
add(temp);
@ -515,7 +518,7 @@ class CharSelectSubState extends MusicBeatSubState
syncLock = lock;
// sync = true;
sync = true;
lock.onAnimationComplete.addOnce(function(_) {
syncLock = null;

View file

@ -8,20 +8,22 @@ import hxcodec.flixel.FlxVideoSprite;
#end
import funkin.ui.MusicBeatSubState;
import funkin.audio.FunkinSound;
import funkin.save.Save;
/**
* After about 2 minutes of inactivity on the title screen,
* the game will enter the Attract state, as a reference to physical arcade machines.
*
* In the current version, this just plays the ~~Kickstarter trailer~~ Erect teaser, but this can be changed to
* gameplay footage, a generic game trailer, or something more elaborate.
* When you first enter the character select state, it will play an introductory video opening up the lights
*/
class IntroSubState extends MusicBeatSubState
{
static final ATTRACT_VIDEO_PATH:String = Paths.stripLibrary(Paths.videos('introSelect'));
static final LIGHTS_VIDEO_PATH:String = Paths.stripLibrary(Paths.videos('introSelect'));
public override function create():Void
{
if (Save.instance.oldChar)
{
onLightsEnd();
return;
}
// Pause existing music.
if (FlxG.sound.music != null)
{
@ -30,14 +32,19 @@ class IntroSubState extends MusicBeatSubState
}
#if html5
trace('Playing web video ${ATTRACT_VIDEO_PATH}');
playVideoHTML5(ATTRACT_VIDEO_PATH);
trace('Playing web video ${LIGHTS_VIDEO_PATH}');
playVideoHTML5(LIGHTS_VIDEO_PATH);
#end
#if hxCodec
trace('Playing native video ${ATTRACT_VIDEO_PATH}');
playVideoNative(ATTRACT_VIDEO_PATH);
trace('Playing native video ${LIGHTS_VIDEO_PATH}');
playVideoNative(LIGHTS_VIDEO_PATH);
#end
// Im TOO lazy to even care, so uh, yep
FlxG.camera.zoom = 0.66666666666666666666666666666667;
vid.x = -(FlxG.width - (FlxG.width * FlxG.camera.zoom));
vid.y = -((FlxG.height - (FlxG.height * FlxG.camera.zoom)) * 0.75);
}
#if html5
@ -53,7 +60,7 @@ class IntroSubState extends MusicBeatSubState
{
vid.zIndex = 0;
vid.finishCallback = onAttractEnd;
vid.finishCallback = onLightsEnd;
add(vid);
}
@ -77,7 +84,7 @@ class IntroSubState extends MusicBeatSubState
if (vid != null)
{
vid.zIndex = 0;
vid.bitmap.onEndReached.add(onAttractEnd);
vid.bitmap.onEndReached.add(onLightsEnd);
add(vid);
vid.play(filePath, false);
@ -93,37 +100,33 @@ class IntroSubState extends MusicBeatSubState
{
super.update(elapsed);
if (controls.ACCEPT)
{
onAttractEnd();
}
// if (!introSound.paused)
// {
// #if html5
// @:privateAccess
// vid.netStream.seek(introSound.time);
// #elseif hxCodec
// vid.bitmap.time = Std.int(introSound.time);
// #end
// }
}
/**
* When the attraction state ends (after the video ends or the user presses any button),
* switch immediately to the title screen.
* When the lights video finishes, it will close the substate
*/
function onAttractEnd():Void
function onLightsEnd():Void
{
#if html5
if (vid != null)
{
remove(vid);
}
#end
#if hxCodec
if (vid != null)
{
#if hxCodec
vid.stop();
#end
remove(vid);
vid.destroy();
vid = null;
}
#end
#if (html5 || hxCodec)
vid.destroy();
vid = null;
#end
FlxG.camera.zoom = 1;
close();
}

View file

@ -141,6 +141,7 @@ class FreeplayDJ extends FlxAtlasSprite
}
else if (getCurrentAnimation() == animPrefixB)
{
trace("Loss Intro");
var endFrame = playableCharData.getFistPumpIntroBadEndFrame();
if (endFrame > -1 && anim.curFrame >= endFrame)
{
@ -166,6 +167,7 @@ class FreeplayDJ extends FlxAtlasSprite
}
else if (getCurrentAnimation() == animPrefixB)
{
trace("Loss GYATT");
var endFrame = playableCharData.getFistPumpLoopBadEndFrame();
if (endFrame > -1 && anim.curFrame >= endFrame)
{
@ -375,6 +377,13 @@ class FreeplayDJ extends FlxAtlasSprite
public function confirm():Void
{
// We really don't want to play anything but the new character animation here.
if (PlayerRegistry.instance.hasNewCharacter())
{
currentState = NewUnlock;
return;
}
currentState = Confirm;
}
@ -397,6 +406,13 @@ class FreeplayDJ extends FlxAtlasSprite
public function fistPumpIntro():Void
{
// We really don't want to play anything but the new character animation here.
if (PlayerRegistry.instance.hasNewCharacter())
{
currentState = NewUnlock;
return;
}
currentState = FistPumpIntro;
var animPrefix = playableCharData.getAnimationPrefix('fistPump');
playFlashAnimation(animPrefix, true, false, false, playableCharData.getFistPumpIntroStartFrame());
@ -404,6 +420,13 @@ class FreeplayDJ extends FlxAtlasSprite
public function fistPump():Void
{
// We really don't want to play anything but the new character animation here.
if (PlayerRegistry.instance.hasNewCharacter())
{
currentState = NewUnlock;
return;
}
currentState = FistPump;
var animPrefix = playableCharData.getAnimationPrefix('fistPump');
playFlashAnimation(animPrefix, true, false, false, playableCharData.getFistPumpLoopStartFrame());
@ -411,6 +434,13 @@ class FreeplayDJ extends FlxAtlasSprite
public function fistPumpLossIntro():Void
{
// We really don't want to play anything but the new character animation here.
if (PlayerRegistry.instance.hasNewCharacter())
{
currentState = NewUnlock;
return;
}
currentState = FistPumpIntro;
var animPrefix = playableCharData.getAnimationPrefix('loss');
playFlashAnimation(animPrefix, true, false, false, playableCharData.getFistPumpIntroBadStartFrame());
@ -418,6 +448,13 @@ class FreeplayDJ extends FlxAtlasSprite
public function fistPumpLoss():Void
{
// We really don't want to play anything but the new character animation here.
if (PlayerRegistry.instance.hasNewCharacter())
{
currentState = NewUnlock;
return;
}
currentState = FistPump;
var animPrefix = playableCharData.getAnimationPrefix('loss');
playFlashAnimation(animPrefix, true, false, false, playableCharData.getFistPumpLoopBadStartFrame());

View file

@ -9,6 +9,7 @@ import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.input.touch.FlxTouch;
import flixel.math.FlxAngle;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.system.debug.watch.Tracker.TrackerProfile;
import flixel.text.FlxText;
@ -180,6 +181,8 @@ class FreeplayState extends MusicBeatSubState
var ostName:FlxText;
var albumRoll:AlbumRoll;
var charSelectHint:FlxText;
var letterSort:LetterSort;
var exitMovers:ExitMoverData = new Map();
@ -279,6 +282,7 @@ class FreeplayState extends MusicBeatSubState
txtCompletion = new AtlasText(1185, 87, '69', AtlasFont.FREEPLAY_CLEAR);
ostName = new FlxText(8, 8, FlxG.width - 8 - 8, 'OFFICIAL OST', 48);
charSelectHint = new FlxText(-40, 18, FlxG.width - 8 - 8, 'Press [ LOL ] to change characters', 32);
bgDad = new FlxSprite(backingCard.pinkBack.width * 0.74, 0).loadGraphic(styleData == null ? 'freeplay/freeplayBGdad' : styleData.getBgAssetGraphic());
}
@ -354,10 +358,6 @@ class FreeplayState extends MusicBeatSubState
if (availableDifficultiesForSong.length == 0) continue;
songs.push(new FreeplaySongData(levelId, songId, song, displayedVariations));
for (difficulty in availableDifficultiesForSong)
{
diffIdsTotal.pushUnique(difficulty);
}
for (difficulty in unsuffixedDifficulties)
{
diffIdsTotal.pushUnique(difficulty);
@ -498,7 +498,14 @@ class FreeplayState extends MusicBeatSubState
ostName.alignment = RIGHT;
ostName.visible = false;
exitMovers.set([overhangStuff, fnfFreeplay, ostName],
charSelectHint.alignment = CENTER;
charSelectHint.font = "5by7";
charSelectHint.color = 0xFF5F5F5F;
charSelectHint.text = 'Press [ ${controls.getDialogueNameFromControl(FREEPLAY_CHAR_SELECT, true)} ] to change characters';
charSelectHint.y -= 100;
FlxTween.tween(charSelectHint, {y: charSelectHint.y + 100}, 0.8, {ease: FlxEase.quartOut});
exitMovers.set([overhangStuff, fnfFreeplay, ostName, charSelectHint],
{
y: -overhangStuff.height,
x: 0,
@ -506,7 +513,7 @@ class FreeplayState extends MusicBeatSubState
wait: 0
});
exitMoversCharSel.set([overhangStuff, fnfFreeplay, ostName],
exitMoversCharSel.set([overhangStuff, fnfFreeplay, ostName, charSelectHint],
{
y: -300,
speed: 0.8,
@ -604,6 +611,11 @@ class FreeplayState extends MusicBeatSubState
add(fnfFreeplay);
add(ostName);
if (PlayerRegistry.instance.hasNewCharacter() == true)
{
add(charSelectHint);
}
// 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() {
@ -1348,10 +1360,19 @@ class FreeplayState extends MusicBeatSubState
var originalPos:FlxPoint = new FlxPoint();
var hintTimer:Float = 0;
override function update(elapsed:Float):Void
{
super.update(elapsed);
if (charSelectHint != null)
{
hintTimer += elapsed * 2;
var targetAmt:Float = (Math.sin(hintTimer) + 1) / 2;
charSelectHint.alpha = FlxMath.lerp(0.3, 0.9, targetAmt);
}
#if FEATURE_DEBUG_FUNCTIONS
if (FlxG.keys.justPressed.P)
{
@ -1764,12 +1785,13 @@ class FreeplayState extends MusicBeatSubState
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSong.songId, suffixedDifficulty);
intendedScore = songScore?.score ?? 0;
intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes);
rememberedDifficulty = currentDifficulty;
rememberedDifficulty = suffixedDifficulty;
}
else
{
intendedScore = 0;
intendedCompletion = 0.0;
rememberedDifficulty = currentDifficulty;
}
if (intendedCompletion == Math.POSITIVE_INFINITY || intendedCompletion == Math.NEGATIVE_INFINITY || Math.isNaN(intendedCompletion))

View file

@ -358,6 +358,7 @@ class MainMenuState extends MusicBeatState
// Ctrl+Alt+Shift+W = Meet requirements for Pico Unlock
// Ctrl+Alt+Shift+L = Revoke requirements for Pico Unlock
// Ctrl+Alt+Shift+R = Score/Rank conflict test
// Ctrl+Alt+Shift+N = Mark all characters as not seen
// Ctrl+Alt+Shift+E = Dump save data
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.P)
@ -433,6 +434,15 @@ class MainMenuState extends MusicBeatState
});
}
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.N)
{
@:privateAccess
{
funkin.save.Save.instance.data.unlocks.charactersSeen = ["bf"];
funkin.save.Save.instance.data.unlocks.oldChar = false;
}
}
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.E)
{
funkin.save.Save.instance.debug_dumpSave();

View file

@ -72,6 +72,12 @@ class PreferencesMenu extends Page
createPrefItemCheckbox('Auto Pause', 'Automatically pause the game when it loses focus', function(value:Bool):Void {
Preferences.autoPause = value;
}, Preferences.autoPause);
#if web
createPrefItemCheckbox('Unlocked Framerate', 'Enable to unlock the framerate', function(value:Bool):Void {
Preferences.unlockedFramerate = value;
}, Preferences.unlockedFramerate);
#end
}
override function update(elapsed:Float):Void

View file

@ -273,8 +273,11 @@ class TitleState extends MusicBeatState
}
#end
if (Save.instance.charactersSeen.contains("pico")) Save.instance.charactersSeen.remove("pico");
if (Save.instance.charactersSeen.contains("pico"))
{
Save.instance.charactersSeen.remove("pico");
Save.instance.oldChar = false;
}
Conductor.instance.update();
/* if (FlxG.onMobile)