mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-23 08:07:54 -05:00
Merge branch 'rewrite/master' into bugfix/freeplay-menu-transition
This commit is contained in:
commit
d6723a8212
23 changed files with 723 additions and 84 deletions
4
.github/workflows/build-shit.yml
vendored
4
.github/workflows/build-shit.yml
vendored
|
@ -3,6 +3,10 @@ on:
|
|||
workflow_dispatch:
|
||||
push:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
create-nightly-html5:
|
||||
runs-on: [self-hosted, linux]
|
||||
|
|
35
.github/workflows/cancel-merged-branches.yml
vendored
Normal file
35
.github/workflows/cancel-merged-branches.yml
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
name: cancel-merged-branches
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
cancel_stuff:
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
id: cancel-runs
|
||||
with:
|
||||
result-encoding: string
|
||||
retries: 3
|
||||
script: |
|
||||
let branch_workflows = await github.rest.actions.listWorkflowRuns({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
workflow_id: "build-shit.yml",
|
||||
status: "queued",
|
||||
branch: "${{ github.event.pull_request.head.ref }}"
|
||||
});
|
||||
let runs = branch_workflows.data.workflow_runs;
|
||||
runs.forEach((run) => {
|
||||
github.rest.actions.cancelWorkflowRun({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: run.id
|
||||
});
|
||||
});
|
||||
console.log(runs);
|
|
@ -183,6 +183,7 @@
|
|||
<haxedef name="haxeui_focus_out_on_click" />
|
||||
<!-- Required to use haxe.ui.backend.flixel.UIState with build macros. -->
|
||||
<haxedef name="haxeui_dont_impose_base_class" />
|
||||
<haxedef name="HARDCODED_CREDITS" />
|
||||
|
||||
<!-- Skip the Intro -->
|
||||
<section if="debug">
|
||||
|
|
2
art
2
art
|
@ -1 +1 @@
|
|||
Subproject commit 00463685fa570f0c853d08e250b46ef80f30bc48
|
||||
Subproject commit 03e7c2a2353b184e45955c96d763b7cdf1acbc34
|
|
@ -59,6 +59,7 @@ abstract Tallies(RawTallies)
|
|||
totalNotes: 0,
|
||||
totalNotesHit: 0,
|
||||
maxCombo: 0,
|
||||
score: 0,
|
||||
isNewHighscore: false
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +82,9 @@ typedef RawTallies =
|
|||
var good:Int;
|
||||
var sick:Int;
|
||||
var maxCombo:Int;
|
||||
|
||||
var score:Int;
|
||||
|
||||
var isNewHighscore:Bool;
|
||||
|
||||
/**
|
||||
|
|
|
@ -402,10 +402,16 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
return sound;
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
public override function destroy():Void
|
||||
{
|
||||
// trace('[FunkinSound] Destroying sound "${this._label}"');
|
||||
super.destroy();
|
||||
if (fadeTween != null)
|
||||
{
|
||||
fadeTween.cancel();
|
||||
fadeTween = null;
|
||||
}
|
||||
FlxTween.cancelTweensOf(this);
|
||||
this._label = 'unknown';
|
||||
}
|
||||
|
|
|
@ -325,12 +325,3 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair of a file name and its contents.
|
||||
*/
|
||||
typedef JsonFile =
|
||||
{
|
||||
fileName:String,
|
||||
contents:String
|
||||
};
|
||||
|
|
10
source/funkin/data/JsonFile.hx
Normal file
10
source/funkin/data/JsonFile.hx
Normal file
|
@ -0,0 +1,10 @@
|
|||
package funkin.data;
|
||||
|
||||
/**
|
||||
* A pair of a file name and its contents.
|
||||
*/
|
||||
typedef JsonFile =
|
||||
{
|
||||
fileName:String,
|
||||
contents:String
|
||||
};
|
|
@ -427,7 +427,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
return ScriptedSong.listScriptClasses();
|
||||
}
|
||||
|
||||
function loadEntryMetadataFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
|
||||
function loadEntryMetadataFile(id:String, ?variation:String):Null<JsonFile>
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}');
|
||||
|
@ -442,7 +442,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
return {fileName: entryFilePath, contents: rawJson};
|
||||
}
|
||||
|
||||
function loadMusicDataFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
|
||||
function loadMusicDataFile(id:String, ?variation:String):Null<JsonFile>
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
var entryFilePath:String = Paths.file('music/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}.json');
|
||||
|
@ -460,7 +460,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
return openfl.Assets.exists(entryFilePath);
|
||||
}
|
||||
|
||||
function loadEntryChartFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
|
||||
function loadEntryChartFile(id:String, ?variation:String):Null<JsonFile>
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-chart${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}');
|
||||
|
|
|
@ -231,7 +231,7 @@ class PauseSubState extends MusicBeatSubState
|
|||
*/
|
||||
function startPauseMusic():Void
|
||||
{
|
||||
var pauseMusicPath:String = Paths.music('breakfast$musicSuffix');
|
||||
var pauseMusicPath:String = Paths.music('breakfast$musicSuffix/breakfast$musicSuffix');
|
||||
pauseMusic = FunkinSound.load(pauseMusicPath, true, true);
|
||||
|
||||
if (pauseMusic == null)
|
||||
|
@ -568,6 +568,8 @@ class PauseSubState extends MusicBeatSubState
|
|||
PlayStatePlaylist.campaignDifficulty = difficulty;
|
||||
PlayState.instance.currentDifficulty = PlayStatePlaylist.campaignDifficulty;
|
||||
|
||||
FreeplayState.rememberedDifficulty = difficulty;
|
||||
|
||||
PlayState.instance.needsReset = true;
|
||||
|
||||
state.close();
|
||||
|
|
|
@ -728,6 +728,10 @@ class PlayState extends MusicBeatSubState
|
|||
#end
|
||||
|
||||
initialized = true;
|
||||
|
||||
// This step ensures z-indexes are applied properly,
|
||||
// and it's important to call it last so all elements get affected.
|
||||
refresh();
|
||||
}
|
||||
|
||||
public override function draw():Void
|
||||
|
@ -1720,8 +1724,6 @@ class PlayState extends MusicBeatSubState
|
|||
playerStrumline.fadeInArrows();
|
||||
opponentStrumline.fadeInArrows();
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2441,7 +2443,8 @@ class PlayState extends MusicBeatSubState
|
|||
if (Highscore.tallies.combo != 0)
|
||||
{
|
||||
// Break the combo.
|
||||
Highscore.tallies.combo = comboPopUps.displayCombo(0);
|
||||
if (Highscore.tallies.combo >= 10) comboPopUps.displayCombo(0);
|
||||
Highscore.tallies.combo = 0;
|
||||
}
|
||||
|
||||
if (playSound)
|
||||
|
@ -2568,32 +2571,38 @@ class PlayState extends MusicBeatSubState
|
|||
*/
|
||||
function popUpScore(daNote:NoteSprite, score:Int, daRating:String, healthChange:Float):Void
|
||||
{
|
||||
vocals.playerVolume = 1;
|
||||
|
||||
if (daRating == 'miss')
|
||||
{
|
||||
// If daRating is 'miss', that means we made a mistake and should not continue.
|
||||
trace('[WARNING] popUpScore judged a note as a miss!');
|
||||
FlxG.log.warn('popUpScore judged a note as a miss!');
|
||||
// TODO: Remove this.
|
||||
comboPopUps.displayRating('miss');
|
||||
return;
|
||||
}
|
||||
|
||||
vocals.playerVolume = 1;
|
||||
|
||||
var isComboBreak = false;
|
||||
switch (daRating)
|
||||
{
|
||||
case 'sick':
|
||||
Highscore.tallies.sick += 1;
|
||||
Highscore.tallies.totalNotesHit++;
|
||||
isComboBreak = Constants.JUDGEMENT_SICK_COMBO_BREAK;
|
||||
case 'good':
|
||||
Highscore.tallies.good += 1;
|
||||
Highscore.tallies.totalNotesHit++;
|
||||
isComboBreak = Constants.JUDGEMENT_GOOD_COMBO_BREAK;
|
||||
case 'bad':
|
||||
Highscore.tallies.bad += 1;
|
||||
Highscore.tallies.totalNotesHit++;
|
||||
isComboBreak = Constants.JUDGEMENT_BAD_COMBO_BREAK;
|
||||
case 'shit':
|
||||
Highscore.tallies.shit += 1;
|
||||
Highscore.tallies.totalNotesHit++;
|
||||
isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK;
|
||||
default:
|
||||
FlxG.log.error('Wuh? Buh? Guh? Note hit judgement was $daRating!');
|
||||
}
|
||||
|
||||
health += healthChange;
|
||||
|
@ -2601,18 +2610,18 @@ class PlayState extends MusicBeatSubState
|
|||
if (isComboBreak)
|
||||
{
|
||||
// Break the combo, but don't increment tallies.misses.
|
||||
Highscore.tallies.combo = comboPopUps.displayCombo(0);
|
||||
if (Highscore.tallies.combo >= 10) comboPopUps.displayCombo(0);
|
||||
Highscore.tallies.combo = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Highscore.tallies.combo++;
|
||||
Highscore.tallies.totalNotesHit++;
|
||||
if (Highscore.tallies.combo > Highscore.tallies.maxCombo) Highscore.tallies.maxCombo = Highscore.tallies.combo;
|
||||
}
|
||||
|
||||
playerStrumline.hitNote(daNote, !isComboBreak);
|
||||
|
||||
if (daRating == "sick")
|
||||
if (daRating == 'sick')
|
||||
{
|
||||
playerStrumline.playNoteSplash(daNote.noteData.getDirection());
|
||||
}
|
||||
|
@ -2724,7 +2733,7 @@ class PlayState extends MusicBeatSubState
|
|||
*/
|
||||
public function endSong(rightGoddamnNow:Bool = false):Void
|
||||
{
|
||||
FlxG.sound.music.volume = 0;
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.volume = 0;
|
||||
vocals.volume = 0;
|
||||
mayPauseGame = false;
|
||||
|
||||
|
@ -2742,6 +2751,8 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
deathCounter = 0;
|
||||
|
||||
var isNewHighscore = false;
|
||||
|
||||
if (currentSong != null && currentSong.validScore)
|
||||
{
|
||||
// crackhead double thingie, sets whether was new highscore, AND saves the song!
|
||||
|
@ -2772,11 +2783,14 @@ class PlayState extends MusicBeatSubState
|
|||
#if newgrounds
|
||||
NGio.postScore(score, currentSong.id);
|
||||
#end
|
||||
isNewHighscore = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
isNewHighscore = false;
|
||||
|
||||
PlayStatePlaylist.campaignScore += songScore;
|
||||
|
||||
// Pop the next song ID from the list.
|
||||
|
@ -2814,6 +2828,7 @@ class PlayState extends MusicBeatSubState
|
|||
#if newgrounds
|
||||
NGio.postScore(score, 'Level ${PlayStatePlaylist.campaignId}');
|
||||
#end
|
||||
isNewHighscore = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2825,11 +2840,11 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
if (rightGoddamnNow)
|
||||
{
|
||||
moveToResultsScreen();
|
||||
moveToResultsScreen(isNewHighscore);
|
||||
}
|
||||
else
|
||||
{
|
||||
zoomIntoResultsScreen();
|
||||
zoomIntoResultsScreen(isNewHighscore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2890,11 +2905,11 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
if (rightGoddamnNow)
|
||||
{
|
||||
moveToResultsScreen();
|
||||
moveToResultsScreen(isNewHighscore);
|
||||
}
|
||||
else
|
||||
{
|
||||
zoomIntoResultsScreen();
|
||||
zoomIntoResultsScreen(isNewHighscore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2968,7 +2983,7 @@ class PlayState extends MusicBeatSubState
|
|||
/**
|
||||
* Play the camera zoom animation and then move to the results screen once it's done.
|
||||
*/
|
||||
function zoomIntoResultsScreen():Void
|
||||
function zoomIntoResultsScreen(isNewHighscore:Bool):Void
|
||||
{
|
||||
trace('WENT TO RESULTS SCREEN!');
|
||||
|
||||
|
@ -3025,7 +3040,7 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
ease: FlxEase.expoIn,
|
||||
onComplete: function(_) {
|
||||
moveToResultsScreen();
|
||||
moveToResultsScreen(isNewHighscore);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -3034,7 +3049,7 @@ class PlayState extends MusicBeatSubState
|
|||
/**
|
||||
* Move to the results screen right goddamn now.
|
||||
*/
|
||||
function moveToResultsScreen():Void
|
||||
function moveToResultsScreen(isNewHighscore:Bool):Void
|
||||
{
|
||||
persistentUpdate = false;
|
||||
vocals.stop();
|
||||
|
@ -3046,7 +3061,24 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
storyMode: PlayStatePlaylist.isStoryMode,
|
||||
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
|
||||
tallies: talliesToUse,
|
||||
scoreData:
|
||||
{
|
||||
score: songScore,
|
||||
tallies:
|
||||
{
|
||||
sick: Highscore.tallies.sick,
|
||||
good: Highscore.tallies.good,
|
||||
bad: Highscore.tallies.bad,
|
||||
shit: Highscore.tallies.shit,
|
||||
missed: Highscore.tallies.missed,
|
||||
combo: Highscore.tallies.combo,
|
||||
maxCombo: Highscore.tallies.maxCombo,
|
||||
totalNotesHit: Highscore.tallies.totalNotesHit,
|
||||
totalNotes: Highscore.tallies.totalNotes,
|
||||
},
|
||||
accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
|
||||
},
|
||||
isNewHighscore: isNewHighscore
|
||||
});
|
||||
res.camera = camHUD;
|
||||
openSubState(res);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.play;
|
||||
|
||||
import funkin.util.MathUtil;
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import flixel.FlxSprite;
|
||||
|
@ -16,6 +17,8 @@ import flixel.tweens.FlxTween;
|
|||
import funkin.audio.FunkinSound;
|
||||
import flixel.util.FlxGradient;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.save.Save;
|
||||
import funkin.save.Save.SaveScoreData;
|
||||
import funkin.graphics.shaders.LeftMaskShader;
|
||||
import funkin.play.components.TallyCounter;
|
||||
|
||||
|
@ -42,12 +45,15 @@ class ResultState extends MusicBeatSubState
|
|||
|
||||
override function create():Void
|
||||
{
|
||||
if (params.tallies.sick == params.tallies.totalNotesHit
|
||||
&& params.tallies.maxCombo == params.tallies.totalNotesHit) resultsVariation = PERFECT;
|
||||
else if (params.tallies.missed + params.tallies.bad + params.tallies.shit >= params.tallies.totalNotes * 0.50)
|
||||
resultsVariation = SHIT; // if more than half of your song was missed, bad, or shit notes, you get shit ending!
|
||||
else
|
||||
resultsVariation = NORMAL;
|
||||
/*
|
||||
if (params.scoreData.sick == params.scoreData.totalNotesHit
|
||||
&& params.scoreData.maxCombo == params.scoreData.totalNotesHit) resultsVariation = PERFECT;
|
||||
else if (params.scoreData.missed + params.scoreData.bad + params.scoreData.shit >= params.scoreData.totalNotes * 0.50)
|
||||
resultsVariation = SHIT; // if more than half of your song was missed, bad, or shit notes, you get shit ending!
|
||||
else
|
||||
resultsVariation = NORMAL;
|
||||
*/
|
||||
resultsVariation = NORMAL;
|
||||
|
||||
FunkinSound.playMusic('results$resultsVariation',
|
||||
{
|
||||
|
@ -130,12 +136,16 @@ class ResultState extends MusicBeatSubState
|
|||
|
||||
var diffSpr:String = switch (PlayState.instance.currentDifficulty)
|
||||
{
|
||||
case 'EASY':
|
||||
case 'easy':
|
||||
'difEasy';
|
||||
case 'NORMAL':
|
||||
case 'normal':
|
||||
'difNormal';
|
||||
case 'HARD':
|
||||
case 'hard':
|
||||
'difHard';
|
||||
case 'erect':
|
||||
'difErect';
|
||||
case 'nightmare':
|
||||
'difNightmare';
|
||||
case _:
|
||||
'difNormal';
|
||||
}
|
||||
|
@ -195,29 +205,33 @@ class ResultState extends MusicBeatSubState
|
|||
* NOTE: We display how many notes were HIT, not how many notes there were in total.
|
||||
*
|
||||
*/
|
||||
var totalHit:TallyCounter = new TallyCounter(375, hStuf * 3, params.tallies.totalNotesHit);
|
||||
var totalHit:TallyCounter = new TallyCounter(375, hStuf * 3, params.scoreData.tallies.totalNotesHit);
|
||||
ratingGrp.add(totalHit);
|
||||
|
||||
var maxCombo:TallyCounter = new TallyCounter(375, hStuf * 4, params.tallies.maxCombo);
|
||||
var maxCombo:TallyCounter = new TallyCounter(375, hStuf * 4, params.scoreData.tallies.maxCombo);
|
||||
ratingGrp.add(maxCombo);
|
||||
|
||||
hStuf += 2;
|
||||
var extraYOffset:Float = 5;
|
||||
var tallySick:TallyCounter = new TallyCounter(230, (hStuf * 5) + extraYOffset, params.tallies.sick, 0xFF89E59E);
|
||||
var tallySick:TallyCounter = new TallyCounter(230, (hStuf * 5) + extraYOffset, params.scoreData.tallies.sick, 0xFF89E59E);
|
||||
ratingGrp.add(tallySick);
|
||||
|
||||
var tallyGood:TallyCounter = new TallyCounter(210, (hStuf * 6) + extraYOffset, params.tallies.good, 0xFF89C9E5);
|
||||
var tallyGood:TallyCounter = new TallyCounter(210, (hStuf * 6) + extraYOffset, params.scoreData.tallies.good, 0xFF89C9E5);
|
||||
ratingGrp.add(tallyGood);
|
||||
|
||||
var tallyBad:TallyCounter = new TallyCounter(190, (hStuf * 7) + extraYOffset, params.tallies.bad, 0xFFE6CF8A);
|
||||
var tallyBad:TallyCounter = new TallyCounter(190, (hStuf * 7) + extraYOffset, params.scoreData.tallies.bad, 0xFFE6CF8A);
|
||||
ratingGrp.add(tallyBad);
|
||||
|
||||
var tallyShit:TallyCounter = new TallyCounter(220, (hStuf * 8) + extraYOffset, params.tallies.shit, 0xFFE68C8A);
|
||||
var tallyShit:TallyCounter = new TallyCounter(220, (hStuf * 8) + extraYOffset, params.scoreData.tallies.shit, 0xFFE68C8A);
|
||||
ratingGrp.add(tallyShit);
|
||||
|
||||
var tallyMissed:TallyCounter = new TallyCounter(260, (hStuf * 9) + extraYOffset, params.tallies.missed, 0xFFC68AE6);
|
||||
var tallyMissed:TallyCounter = new TallyCounter(260, (hStuf * 9) + extraYOffset, params.scoreData.tallies.missed, 0xFFC68AE6);
|
||||
ratingGrp.add(tallyMissed);
|
||||
|
||||
var score:TallyCounter = new TallyCounter(825, 630, params.scoreData.score, RIGHT);
|
||||
score.scale.set(2, 2);
|
||||
ratingGrp.add(score);
|
||||
|
||||
for (ind => rating in ratingGrp.members)
|
||||
{
|
||||
rating.visible = false;
|
||||
|
@ -235,9 +249,16 @@ class ResultState extends MusicBeatSubState
|
|||
scorePopin.animation.play("score");
|
||||
scorePopin.visible = true;
|
||||
|
||||
highscoreNew.visible = true;
|
||||
highscoreNew.animation.play("new");
|
||||
FlxTween.tween(highscoreNew, {y: highscoreNew.y + 10}, 0.8, {ease: FlxEase.quartOut});
|
||||
if (params.isNewHighscore)
|
||||
{
|
||||
highscoreNew.visible = true;
|
||||
highscoreNew.animation.play("new");
|
||||
FlxTween.tween(highscoreNew, {y: highscoreNew.y + 10}, 0.8, {ease: FlxEase.quartOut});
|
||||
}
|
||||
else
|
||||
{
|
||||
highscoreNew.visible = false;
|
||||
}
|
||||
};
|
||||
|
||||
switch (resultsVariation)
|
||||
|
@ -276,8 +297,6 @@ class ResultState extends MusicBeatSubState
|
|||
}
|
||||
});
|
||||
|
||||
if (params.tallies.isNewHighscore) trace("ITS A NEW HIGHSCORE!!!");
|
||||
|
||||
super.create();
|
||||
}
|
||||
|
||||
|
@ -393,8 +412,13 @@ typedef ResultsStateParams =
|
|||
*/
|
||||
var title:String;
|
||||
|
||||
/**
|
||||
* Whether the displayed score is a new highscore
|
||||
*/
|
||||
var isNewHighscore:Bool;
|
||||
|
||||
/**
|
||||
* The score, accuracy, and judgements.
|
||||
*/
|
||||
var tallies:Highscore.Tallies;
|
||||
var scoreData:SaveScoreData;
|
||||
};
|
||||
|
|
|
@ -85,7 +85,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
comboSpr.velocity.y -= 150;
|
||||
comboSpr.velocity.x += FlxG.random.int(1, 10);
|
||||
|
||||
add(comboSpr);
|
||||
// add(comboSpr);
|
||||
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
|
|
|
@ -6,6 +6,8 @@ import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
|||
import flixel.math.FlxMath;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.text.FlxText.FlxTextAlign;
|
||||
import funkin.util.MathUtil;
|
||||
|
||||
/**
|
||||
* Numerical counters used next to each judgement in the Results screen.
|
||||
|
@ -13,18 +15,23 @@ import flixel.tweens.FlxTween;
|
|||
class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
||||
{
|
||||
public var curNumber:Float = 0;
|
||||
|
||||
public var neededNumber:Int = 0;
|
||||
|
||||
public var flavour:Int = 0xFFFFFFFF;
|
||||
|
||||
public function new(x:Float, y:Float, neededNumber:Int = 0, ?flavour:Int = 0xFFFFFFFF)
|
||||
public var align:FlxTextAlign = FlxTextAlign.LEFT;
|
||||
|
||||
public function new(x:Float, y:Float, neededNumber:Int = 0, ?flavour:Int = 0xFFFFFFFF, align:FlxTextAlign = FlxTextAlign.LEFT)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
this.align = align;
|
||||
|
||||
this.flavour = flavour;
|
||||
|
||||
this.neededNumber = neededNumber;
|
||||
drawNumbers();
|
||||
|
||||
if (curNumber == neededNumber) drawNumbers();
|
||||
}
|
||||
|
||||
var tmr:Float = 0;
|
||||
|
@ -41,6 +48,8 @@ class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
|||
var seperatedScore:Array<Int> = [];
|
||||
var tempCombo:Int = Math.round(curNumber);
|
||||
|
||||
var fullNumberDigits:Int = Std.int(Math.max(1, Math.ceil(MathUtil.logBase(10, neededNumber))));
|
||||
|
||||
while (tempCombo != 0)
|
||||
{
|
||||
seperatedScore.push(tempCombo % 10);
|
||||
|
@ -55,7 +64,13 @@ class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
|||
{
|
||||
if (ind >= members.length)
|
||||
{
|
||||
var numb:TallyNumber = new TallyNumber(ind * 43, 0, num);
|
||||
var xPos = ind * (43 * this.scale.x);
|
||||
if (this.align == FlxTextAlign.RIGHT)
|
||||
{
|
||||
xPos -= (fullNumberDigits * (43 * this.scale.x));
|
||||
}
|
||||
var numb:TallyNumber = new TallyNumber(xPos, 0, num);
|
||||
numb.scale.set(this.scale.x, this.scale.y);
|
||||
add(numb);
|
||||
numb.color = flavour;
|
||||
}
|
||||
|
|
|
@ -295,6 +295,11 @@ class Strumline extends FlxSpriteGroup
|
|||
{
|
||||
if (noteData.length == 0) return;
|
||||
|
||||
// Ensure note data gets reset if the song happens to loop.
|
||||
// NOTE: I had to remove this line because it was causing notes visible during the countdown to be placed multiple times.
|
||||
// I don't remember what bug I was trying to fix by adding this.
|
||||
// if (conductorInUse.currentStep == 0) nextNoteIndex = 0;
|
||||
|
||||
var songStart:Float = PlayState.instance?.startTimestamp ?? 0.0;
|
||||
var hitWindowStart:Float = Conductor.instance.songPosition - Constants.HIT_WINDOW_MS;
|
||||
var renderWindowStart:Float = Conductor.instance.songPosition + RENDER_DISTANCE_MS;
|
||||
|
@ -822,7 +827,7 @@ class Strumline extends FlxSpriteGroup
|
|||
{
|
||||
// The note sprite pool is full and all note splashes are active.
|
||||
// We have to create a new note.
|
||||
result = new SustainTrail(0, 100, noteStyle);
|
||||
result = new SustainTrail(0, 0, noteStyle);
|
||||
this.holdNotes.add(result);
|
||||
}
|
||||
|
||||
|
|
34
source/funkin/ui/credits/CreditsData.hx
Normal file
34
source/funkin/ui/credits/CreditsData.hx
Normal file
|
@ -0,0 +1,34 @@
|
|||
package funkin.ui.credits;
|
||||
|
||||
/**
|
||||
* The members of the Funkin' Crew, organized by their roles.
|
||||
*/
|
||||
typedef CreditsData =
|
||||
{
|
||||
var entries:Array<CreditsDataRole>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The members of a specific role on the Funkin' Crew.
|
||||
*/
|
||||
typedef CreditsDataRole =
|
||||
{
|
||||
@:optional
|
||||
var header:String;
|
||||
|
||||
@:optional
|
||||
@:default([])
|
||||
var body:Array<CreditsDataMember>;
|
||||
|
||||
@:optional
|
||||
@:default(false)
|
||||
var appendBackers:Bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* A member of a specific person on the Funkin' Crew.
|
||||
*/
|
||||
typedef CreditsDataMember =
|
||||
{
|
||||
var line:String;
|
||||
}
|
134
source/funkin/ui/credits/CreditsDataHandler.hx
Normal file
134
source/funkin/ui/credits/CreditsDataHandler.hx
Normal file
|
@ -0,0 +1,134 @@
|
|||
package funkin.ui.credits;
|
||||
|
||||
import funkin.data.JsonFile;
|
||||
|
||||
using StringTools;
|
||||
|
||||
@:nullSafety
|
||||
class CreditsDataHandler
|
||||
{
|
||||
public static final BACKER_PUBLIC_URL:String = 'https://funkin.me/backers';
|
||||
|
||||
#if HARDCODED_CREDITS
|
||||
static final CREDITS_DATA_PATH:String = "assets/exclude/data/credits.json";
|
||||
#else
|
||||
static final CREDITS_DATA_PATH:String = "assets/data/credits.json";
|
||||
#end
|
||||
|
||||
public static function debugPrint(data:Null<CreditsData>):Void
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
trace('CreditsData(NULL)');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var entryCount = data.entries.length;
|
||||
var lineCount = 0;
|
||||
for (entry in data.entries)
|
||||
{
|
||||
lineCount += entry?.body?.length ?? 0;
|
||||
}
|
||||
|
||||
trace('CreditsData($entryCount entries containing $lineCount lines)');
|
||||
}
|
||||
|
||||
/**
|
||||
* If for some reason the full credits won't load,
|
||||
* use this hardcoded data for the original Funkin' Crew.
|
||||
*
|
||||
* @return `CreditsData`
|
||||
*/
|
||||
public static inline function getFallback():CreditsData
|
||||
{
|
||||
return {
|
||||
entries: [
|
||||
{
|
||||
header: 'Founders',
|
||||
body: [
|
||||
{line: 'ninjamuffin99'},
|
||||
{line: 'PhantomArcade'},
|
||||
{line: 'KawaiSprite'},
|
||||
{line: 'evilsk8r'},
|
||||
]
|
||||
},
|
||||
{
|
||||
header: 'Kickstarter Backers',
|
||||
appendBackers: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
public static function fetchBackerEntries():Array<String>
|
||||
{
|
||||
// TODO: Replace this with a web request.
|
||||
// We can't just grab the current Kickstarter data and include it in builds,
|
||||
// because we don't want to deadname people who haven't logged into the portal yet.
|
||||
// It can be async and paginated for performance!
|
||||
return ['See the list of backers at $BACKER_PUBLIC_URL.'];
|
||||
}
|
||||
|
||||
#if HARDCODED_CREDITS
|
||||
/**
|
||||
* The data for the credits.
|
||||
* Hardcoded into game via a macro at compile time.
|
||||
*/
|
||||
public static final CREDITS_DATA:Null<CreditsData> = #if macro null #else CreditsDataMacro.loadCreditsData() #end;
|
||||
#else
|
||||
|
||||
/**
|
||||
* The data for the credits.
|
||||
* Loaded dynamically from the game folder when needed.
|
||||
* Nullable because data may fail to parse.
|
||||
*/
|
||||
public static var CREDITS_DATA(get, default):Null<CreditsData> = null;
|
||||
|
||||
static function get_CREDITS_DATA():Null<CreditsData>
|
||||
{
|
||||
if (CREDITS_DATA == null) CREDITS_DATA = parseCreditsData(fetchCreditsData());
|
||||
|
||||
return CREDITS_DATA;
|
||||
}
|
||||
|
||||
static function fetchCreditsData():funkin.data.JsonFile
|
||||
{
|
||||
var rawJson:String = openfl.Assets.getText(CREDITS_DATA_PATH).trim();
|
||||
|
||||
return {
|
||||
fileName: CREDITS_DATA_PATH,
|
||||
contents: rawJson
|
||||
};
|
||||
}
|
||||
|
||||
static function parseCreditsData(file:JsonFile):Null<CreditsData>
|
||||
{
|
||||
#if !macro
|
||||
if (file.contents == null) return null;
|
||||
|
||||
var parser = new json2object.JsonParser<CreditsData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
trace('[CREDITS] Parsing credits data from ${CREDITS_DATA_PATH}');
|
||||
parser.fromJson(file.contents, file.fileName);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
printErrors(parser.errors, file.fileName);
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
|
||||
static function printErrors(errors:Array<json2object.Error>, id:String = ''):Void
|
||||
{
|
||||
trace('[CREDITS] Failed to parse credits data: ${id}');
|
||||
|
||||
for (error in errors)
|
||||
funkin.data.DataError.printError(error);
|
||||
}
|
||||
#end
|
||||
}
|
67
source/funkin/ui/credits/CreditsDataMacro.hx
Normal file
67
source/funkin/ui/credits/CreditsDataMacro.hx
Normal file
|
@ -0,0 +1,67 @@
|
|||
package funkin.ui.credits;
|
||||
|
||||
#if macro
|
||||
import haxe.macro.Context;
|
||||
#end
|
||||
|
||||
@:access(funkin.ui.credits.CreditsDataHandler)
|
||||
class CreditsDataMacro
|
||||
{
|
||||
public static macro function loadCreditsData():haxe.macro.Expr.ExprOf<CreditsData>
|
||||
{
|
||||
#if !display
|
||||
trace('Hardcoding credits data...');
|
||||
var json = CreditsDataMacro.fetchJSON();
|
||||
|
||||
if (json == null)
|
||||
{
|
||||
Context.info('[WARN] Could not fetch JSON data for credits.', Context.currentPos());
|
||||
return macro $v{CreditsDataHandler.getFallback()};
|
||||
}
|
||||
|
||||
var creditsData = CreditsDataMacro.parseJSON(json);
|
||||
|
||||
if (creditsData == null)
|
||||
{
|
||||
Context.info('[WARN] Could not parse JSON data for credits.', Context.currentPos());
|
||||
return macro $v{CreditsDataHandler.getFallback()};
|
||||
}
|
||||
|
||||
CreditsDataHandler.debugPrint(creditsData);
|
||||
return macro $v{creditsData};
|
||||
// return macro $v{null};
|
||||
#else
|
||||
// `#if display` is used for code completion. In this case we return
|
||||
// a minimal value to keep code completion fast.
|
||||
return macro $v{CreditsDataHandler.getFallback()};
|
||||
#end
|
||||
}
|
||||
|
||||
#if macro
|
||||
static function fetchJSON():Null<String>
|
||||
{
|
||||
return sys.io.File.getContent(CreditsDataHandler.CREDITS_DATA_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the JSON data for the credits.
|
||||
*
|
||||
* @param json The string data to parse.
|
||||
* @return The parsed data.
|
||||
*/
|
||||
static function parseJSON(json:String):Null<CreditsData>
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Use something with better validation but that still works at macro time.
|
||||
return haxe.Json.parse(json);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace('[ERROR] Failed to parse JSON data for credits.');
|
||||
trace(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
213
source/funkin/ui/credits/CreditsState.hx
Normal file
213
source/funkin/ui/credits/CreditsState.hx
Normal file
|
@ -0,0 +1,213 @@
|
|||
package funkin.ui.credits;
|
||||
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.audio.FunkinSound;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
|
||||
/**
|
||||
* The state used to display the credits scroll.
|
||||
* AAA studios often fail to credit properly, and we're better than them!
|
||||
*/
|
||||
class CreditsState extends MusicBeatState
|
||||
{
|
||||
/**
|
||||
* The height the credits should start at.
|
||||
* Make this an instanced variable so it gets set by the constructor.
|
||||
*/
|
||||
final STARTING_HEIGHT = FlxG.height;
|
||||
|
||||
/**
|
||||
* The padding on each side of the screen.
|
||||
*/
|
||||
static final SCREEN_PAD = 24;
|
||||
|
||||
/**
|
||||
* The width of the screen the credits should maximally fill up.
|
||||
* Make this an instanced variable so it gets set by the constructor.
|
||||
*/
|
||||
final FULL_WIDTH = FlxG.width - (SCREEN_PAD * 2);
|
||||
|
||||
/**
|
||||
* The font to use to display the text.
|
||||
* To use a font from the `assets` folder, use `Paths.font(...)`.
|
||||
* Choose something that will render Unicode properly.
|
||||
*/
|
||||
static final CREDITS_FONT = 'Arial';
|
||||
|
||||
/**
|
||||
* The size of the font.
|
||||
*/
|
||||
static final CREDITS_FONT_SIZE = 48;
|
||||
|
||||
static final CREDITS_HEADER_FONT_SIZE = 72;
|
||||
|
||||
/**
|
||||
* The color of the text itself.
|
||||
*/
|
||||
static final CREDITS_FONT_COLOR = FlxColor.WHITE;
|
||||
|
||||
/**
|
||||
* The color of the text's outline.
|
||||
*/
|
||||
static final CREDITS_FONT_STROKE_COLOR = FlxColor.BLACK;
|
||||
|
||||
/**
|
||||
* The speed the credits scroll at, in pixels per second.
|
||||
*/
|
||||
static final CREDITS_SCROLL_BASE_SPEED = 25.0;
|
||||
|
||||
/**
|
||||
* The speed the credits scroll at while the button is held, in pixels per second.
|
||||
*/
|
||||
static final CREDITS_SCROLL_FAST_SPEED = CREDITS_SCROLL_BASE_SPEED * 4.0;
|
||||
|
||||
/**
|
||||
* The actual sprites and text used to display the credits.
|
||||
*/
|
||||
var creditsGroup:FlxSpriteGroup;
|
||||
|
||||
var scrollPaused:Bool = false;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public override function create():Void
|
||||
{
|
||||
super.create();
|
||||
|
||||
// Background
|
||||
var bg = new FlxSprite(Paths.image('menuDesat'));
|
||||
bg.scrollFactor.x = 0;
|
||||
bg.scrollFactor.y = 0;
|
||||
bg.setGraphicSize(Std.int(FlxG.width));
|
||||
bg.updateHitbox();
|
||||
bg.x = 0;
|
||||
bg.y = 0;
|
||||
bg.visible = true;
|
||||
bg.color = 0xFFB57EDC; // Lavender
|
||||
add(bg);
|
||||
|
||||
// TODO: Once we need to display Kickstarter backers,
|
||||
// make this use a recycled pool so we don't kill peformance.
|
||||
creditsGroup = new FlxSpriteGroup();
|
||||
creditsGroup.x = SCREEN_PAD;
|
||||
creditsGroup.y = STARTING_HEIGHT;
|
||||
|
||||
buildCreditsGroup();
|
||||
|
||||
add(creditsGroup);
|
||||
|
||||
// Music
|
||||
FunkinSound.playMusic('freeplayRandom',
|
||||
{
|
||||
startingVolume: 0.0,
|
||||
overrideExisting: true,
|
||||
restartTrack: true,
|
||||
loop: true
|
||||
});
|
||||
FlxG.sound.music.fadeIn(2, 0, 0.8);
|
||||
}
|
||||
|
||||
function buildCreditsGroup():Void
|
||||
{
|
||||
var y = 0;
|
||||
|
||||
for (entry in CreditsDataHandler.CREDITS_DATA.entries)
|
||||
{
|
||||
if (entry.header != null)
|
||||
{
|
||||
creditsGroup.add(buildCreditsLine(entry.header, y, true, CreditsSide.Center));
|
||||
y += CREDITS_HEADER_FONT_SIZE;
|
||||
}
|
||||
|
||||
for (line in entry?.body ?? [])
|
||||
{
|
||||
creditsGroup.add(buildCreditsLine(line.line, y, false, CreditsSide.Center));
|
||||
y += CREDITS_FONT_SIZE;
|
||||
}
|
||||
|
||||
if (entry.appendBackers)
|
||||
{
|
||||
var backers = CreditsDataHandler.fetchBackerEntries();
|
||||
for (backer in backers)
|
||||
{
|
||||
creditsGroup.add(buildCreditsLine(backer, y, false, CreditsSide.Center));
|
||||
y += CREDITS_FONT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// Padding between each role.
|
||||
y += CREDITS_FONT_SIZE * 2;
|
||||
}
|
||||
}
|
||||
|
||||
function buildCreditsLine(text:String, yPos:Float, header:Bool, side:CreditsSide = CreditsSide.Center):FlxText
|
||||
{
|
||||
// CreditsSide.Center: Full screen width
|
||||
// CreditsSide.Left: Left half of screen
|
||||
// CreditsSide.Right: Right half of screen
|
||||
var xPos = (side == CreditsSide.Right) ? (FULL_WIDTH / 2) : 0;
|
||||
var width = (side == CreditsSide.Center) ? FULL_WIDTH : (FULL_WIDTH / 2);
|
||||
var size = header ? CREDITS_HEADER_FONT_SIZE : CREDITS_FONT_SIZE;
|
||||
|
||||
var creditsLine:FlxText = new FlxText(xPos, yPos, width, text);
|
||||
creditsLine.setFormat(CREDITS_FONT, size, CREDITS_FONT_COLOR, FlxTextAlign.CENTER, FlxTextBorderStyle.OUTLINE, CREDITS_FONT_STROKE_COLOR, true);
|
||||
|
||||
return creditsLine;
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (!scrollPaused)
|
||||
{
|
||||
// TODO: Replace with whatever the special note button is.
|
||||
if (controls.ACCEPT || FlxG.keys.pressed.SPACE)
|
||||
{
|
||||
// Move the whole group.
|
||||
creditsGroup.y -= CREDITS_SCROLL_FAST_SPEED * elapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Move the whole group.
|
||||
creditsGroup.y -= CREDITS_SCROLL_BASE_SPEED * elapsed;
|
||||
}
|
||||
}
|
||||
|
||||
if (controls.BACK || hasEnded())
|
||||
{
|
||||
exit();
|
||||
}
|
||||
else if (controls.PAUSE)
|
||||
{
|
||||
scrollPaused = !scrollPaused;
|
||||
}
|
||||
}
|
||||
|
||||
function hasEnded():Bool
|
||||
{
|
||||
return creditsGroup.y < -creditsGroup.height;
|
||||
}
|
||||
|
||||
function exit():Void
|
||||
{
|
||||
FlxG.switchState(new funkin.ui.mainmenu.MainMenuState());
|
||||
}
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
enum CreditsSide
|
||||
{
|
||||
Left;
|
||||
Center;
|
||||
Right;
|
||||
}
|
|
@ -133,8 +133,8 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
var stickerSubState:StickerSubState;
|
||||
|
||||
static var rememberedDifficulty:Null<String> = Constants.DEFAULT_DIFFICULTY;
|
||||
static var rememberedSongId:Null<String> = null;
|
||||
public static var rememberedDifficulty:Null<String> = Constants.DEFAULT_DIFFICULTY;
|
||||
public static var rememberedSongId:Null<String> = null;
|
||||
|
||||
public function new(?params:FreeplayStateParams, ?stickers:StickerSubState)
|
||||
{
|
||||
|
@ -536,21 +536,18 @@ class FreeplayState extends MusicBeatSubState
|
|||
});
|
||||
}
|
||||
|
||||
var currentFilter:SongFilter = null;
|
||||
var currentFilteredSongs:Array<FreeplaySongData> = [];
|
||||
|
||||
/**
|
||||
* Given the current filter, rebuild the current song list.
|
||||
*
|
||||
* @param filterStuff A filter to apply to the song list (regex, startswith, all, favorite)
|
||||
* @param force
|
||||
* @param onlyIfChanged Only apply the filter if the song list has changed
|
||||
*/
|
||||
public function generateSongList(?filterStuff:SongFilter, force:Bool = false):Void
|
||||
public function generateSongList(filterStuff:Null<SongFilter>, force:Bool = false, onlyIfChanged:Bool = true):Void
|
||||
{
|
||||
curSelected = 1;
|
||||
|
||||
for (cap in grpCapsules.members)
|
||||
{
|
||||
cap.kill();
|
||||
}
|
||||
|
||||
var tempSongs:Array<FreeplaySongData> = songs;
|
||||
|
||||
if (filterStuff != null)
|
||||
|
@ -582,6 +579,35 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
// Filter further by current selected difficulty.
|
||||
if (currentDifficulty != null)
|
||||
{
|
||||
tempSongs = tempSongs.filter(song -> {
|
||||
if (song == null) return true; // Random
|
||||
return song.songDifficulties.contains(currentDifficulty);
|
||||
});
|
||||
}
|
||||
|
||||
if (onlyIfChanged)
|
||||
{
|
||||
// == performs equality by reference
|
||||
if (tempSongs.isEqualUnordered(currentFilteredSongs)) return;
|
||||
}
|
||||
|
||||
// Only now do we know that the filter is actually changing.
|
||||
|
||||
rememberedSongId = grpCapsules.members[curSelected]?.songData?.songId;
|
||||
|
||||
for (cap in grpCapsules.members)
|
||||
{
|
||||
cap.kill();
|
||||
}
|
||||
|
||||
currentFilter = filterStuff;
|
||||
|
||||
currentFilteredSongs = tempSongs;
|
||||
curSelected = 0;
|
||||
|
||||
var hsvShader:HSVShader = new HSVShader();
|
||||
|
||||
var randomCapsule:SongMenuItem = grpCapsules.recycle(SongMenuItem);
|
||||
|
@ -658,11 +684,12 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
if (FlxG.keys.justPressed.F)
|
||||
{
|
||||
if (songs[curSelected] != null)
|
||||
var targetSong = grpCapsules.members[curSelected]?.songData;
|
||||
if (targetSong != null)
|
||||
{
|
||||
var realShit:Int = curSelected;
|
||||
songs[curSelected].isFav = !songs[curSelected].isFav;
|
||||
if (songs[curSelected].isFav)
|
||||
targetSong.isFav = !targetSong.isFav;
|
||||
if (targetSong.isFav)
|
||||
{
|
||||
FlxTween.tween(grpCapsules.members[realShit], {angle: 360}, 0.4,
|
||||
{
|
||||
|
@ -854,11 +881,13 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
dj.resetAFKTimer();
|
||||
changeDiff(-1);
|
||||
generateSongList(currentFilter, true);
|
||||
}
|
||||
if (controls.UI_RIGHT_P && !FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
dj.resetAFKTimer();
|
||||
changeDiff(1);
|
||||
generateSongList(currentFilter, true);
|
||||
}
|
||||
|
||||
if (controls.BACK && !typing.hasFocus)
|
||||
|
@ -926,7 +955,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
var daSong:Null<FreeplaySongData> = songs[curSelected];
|
||||
var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected]?.songData;
|
||||
if (daSong != null)
|
||||
{
|
||||
clearDaCache(daSong.songName);
|
||||
|
@ -948,10 +977,10 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
currentDifficulty = diffIdsCurrent[currentDifficultyIndex];
|
||||
|
||||
var daSong:Null<FreeplaySongData> = songs[curSelected];
|
||||
var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected].songData;
|
||||
if (daSong != null)
|
||||
{
|
||||
var songScore:SaveScoreData = Save.instance.getSongScore(songs[curSelected].songId, currentDifficulty);
|
||||
var songScore:SaveScoreData = Save.instance.getSongScore(grpCapsules.members[curSelected].songData.songId, currentDifficulty);
|
||||
intendedScore = songScore?.score ?? 0;
|
||||
intendedCompletion = songScore?.accuracy ?? 0.0;
|
||||
rememberedDifficulty = currentDifficulty;
|
||||
|
@ -1103,6 +1132,12 @@ class FreeplayState extends MusicBeatSubState
|
|||
targetVariation: targetVariation,
|
||||
practiceMode: false,
|
||||
minimalMode: false,
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
botPlayMode: FlxG.keys.pressed.SHIFT,
|
||||
#else
|
||||
botPlayMode: false,
|
||||
#end
|
||||
// TODO: Make these an option! It's currently only accessible via chart editor.
|
||||
// startTimestamp: 0.0,
|
||||
// playbackRate: 0.5,
|
||||
|
@ -1115,10 +1150,12 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
if (rememberedSongId != null)
|
||||
{
|
||||
curSelected = songs.findIndex(function(song) {
|
||||
curSelected = currentFilteredSongs.findIndex(function(song) {
|
||||
if (song == null) return false;
|
||||
return song.songId == rememberedSongId;
|
||||
});
|
||||
|
||||
if (curSelected == -1) curSelected = 0;
|
||||
}
|
||||
|
||||
if (rememberedDifficulty != null)
|
||||
|
@ -1127,7 +1164,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
// Set the difficulty star count on the right.
|
||||
var daSong:Null<FreeplaySongData> = songs[curSelected];
|
||||
var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected]?.songData;
|
||||
albumRoll.setDifficultyStars(daSong?.songRating ?? 0);
|
||||
}
|
||||
|
||||
|
@ -1156,6 +1193,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
intendedScore = 0;
|
||||
intendedCompletion = 0.0;
|
||||
diffIdsCurrent = diffIdsTotal;
|
||||
rememberedSongId = null;
|
||||
rememberedDifficulty = null;
|
||||
}
|
||||
|
|
|
@ -51,10 +51,7 @@ class MainMenuState extends MusicBeatState
|
|||
transIn = FlxTransitionableState.defaultTransIn;
|
||||
transOut = FlxTransitionableState.defaultTransOut;
|
||||
|
||||
if (!(FlxG?.sound?.music?.playing ?? false))
|
||||
{
|
||||
playMenuMusic();
|
||||
}
|
||||
playMenuMusic();
|
||||
|
||||
persistentUpdate = false;
|
||||
persistentDraw = true;
|
||||
|
@ -110,14 +107,21 @@ class MainMenuState extends MusicBeatState
|
|||
});
|
||||
|
||||
#if CAN_OPEN_LINKS
|
||||
// In order to prevent popup blockers from triggering,
|
||||
// we need to open the link as an immediate result of a keypress event,
|
||||
// so we can't wait for the flicker animation to complete.
|
||||
var hasPopupBlocker = #if web true #else false #end;
|
||||
createMenuItem('donate', 'mainmenu/donate', selectDonate, hasPopupBlocker);
|
||||
createMenuItem('merch', 'mainmenu/merch', selectMerch, hasPopupBlocker);
|
||||
#end
|
||||
|
||||
createMenuItem('options', 'mainmenu/options', function() {
|
||||
startExitState(() -> new funkin.ui.options.OptionsState());
|
||||
});
|
||||
|
||||
createMenuItem('credits', 'mainmenu/credits', function() {
|
||||
startExitState(() -> new funkin.ui.credits.CreditsState());
|
||||
});
|
||||
|
||||
// Reset position of menu items.
|
||||
var spacing = 160;
|
||||
var top = (FlxG.height - (spacing * (menuItems.length - 1))) / 2;
|
||||
|
@ -126,6 +130,9 @@ class MainMenuState extends MusicBeatState
|
|||
var menuItem = menuItems.members[i];
|
||||
menuItem.x = FlxG.width / 2;
|
||||
menuItem.y = top + spacing * i;
|
||||
menuItem.scrollFactor.x = 0.0;
|
||||
// This one affects how much the menu items move when you scroll between them.
|
||||
menuItem.scrollFactor.y = 0.4;
|
||||
}
|
||||
|
||||
resetCamStuff();
|
||||
|
@ -213,6 +220,11 @@ class MainMenuState extends MusicBeatState
|
|||
{
|
||||
WindowUtil.openURL(Constants.URL_ITCH);
|
||||
}
|
||||
|
||||
function selectMerch()
|
||||
{
|
||||
WindowUtil.openURL(Constants.URL_MERCH);
|
||||
}
|
||||
#end
|
||||
|
||||
#if newgrounds
|
||||
|
|
|
@ -210,7 +210,8 @@ class LoadingState extends MusicBeatState
|
|||
}
|
||||
|
||||
// Load and cache the song's charts.
|
||||
if (params?.targetSong != null)
|
||||
// Don't do this if we already provided the music and charts.
|
||||
if (params?.targetSong != null && !params.overrideMusic)
|
||||
{
|
||||
params.targetSong.cacheCharts(true);
|
||||
}
|
||||
|
|
|
@ -60,6 +60,11 @@ class Constants
|
|||
*/
|
||||
// ==============================
|
||||
|
||||
/**
|
||||
* Link to buy merch for the game.
|
||||
*/
|
||||
public static final URL_MERCH:String = 'https://needlejuicerecords.com/pages/friday-night-funkin';
|
||||
|
||||
/**
|
||||
* Preloader sitelock.
|
||||
* Matching is done by `FlxStringUtil.getDomain`, so any URL on the domain will work.
|
||||
|
@ -181,6 +186,12 @@ class Constants
|
|||
*/
|
||||
public static final DEFAULT_DIFFICULTY_LIST:Array<String> = ['easy', 'normal', 'hard'];
|
||||
|
||||
/**
|
||||
* List of all difficulties used by the base game.
|
||||
* Includes Erect and Nightmare.
|
||||
*/
|
||||
public static final DEFAULT_DIFFICULTY_LIST_FULL:Array<String> = ['easy', 'normal', 'hard', 'erect', 'nightmare'];
|
||||
|
||||
/**
|
||||
* Default player character for charts.
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue