Merge branch 'rewrite/master' into rewrite/bugfix/chart-editor-various-fixes

This commit is contained in:
EliteMasterEric 2023-10-18 00:04:04 -04:00
commit 6bf6b8ef11
14 changed files with 191 additions and 75 deletions

View file

@ -16,17 +16,18 @@ class PauseSubState extends MusicBeatSubState
{
var grpMenuShit:FlxTypedGroup<Alphabet>;
var pauseOptionsBase:Array<String> = [
final pauseOptionsBase:Array<String> = [
'Resume',
'Restart Song',
'Change Difficulty',
'Toggle Practice Mode',
'Exit to Menu'
];
final pauseOptionsCharting:Array<String> = ['Resume', 'Restart Song', 'Exit to Chart Editor'];
var pauseOptionsDifficulty:Array<String> = ['EASY', 'NORMAL', 'HARD', 'ERECT', 'BACK'];
final pauseOptionsDifficultyBase:Array<String> = ['BACK'];
var pauseOptionsCharting:Array<String> = ['Resume', 'Restart Song', 'Exit to Chart Editor'];
var pauseOptionsDifficulty:Array<String> = []; // AUTO-POPULATED
var menuItems:Array<String> = [];
var curSelected:Int = 0;
@ -48,6 +49,12 @@ class PauseSubState extends MusicBeatSubState
this.isChartingMode = isChartingMode;
menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase;
var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation);
trace('DIFFICULTIES: ${difficultiesInVariation}');
pauseOptionsDifficulty = difficultiesInVariation.map(function(item:String):String {
return item.toUpperCase();
}).concat(pauseOptionsDifficultyBase);
if (PlayStatePlaylist.campaignId == 'week6')
{
@ -201,18 +208,6 @@ class PauseSubState extends MusicBeatSubState
menuItems = pauseOptionsDifficulty;
regenMenu();
case 'EASY' | 'NORMAL' | 'HARD' | 'ERECT':
PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase());
PlayState.instance.currentDifficulty = daSelected.toLowerCase();
PlayState.instance.needsReset = true;
close();
case 'BACK':
menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase;
regenMenu();
case 'Toggle Practice Mode':
PlayState.instance.isPracticeMode = true;
practiceText.visible = PlayState.instance.isPracticeMode;
@ -247,6 +242,30 @@ class PauseSubState extends MusicBeatSubState
this.close();
if (FlxG.sound.music != null) FlxG.sound.music.stop();
PlayState.instance.close(); // This only works because PlayState is a substate!
case 'BACK':
menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase;
regenMenu();
default:
if (pauseOptionsDifficulty.contains(daSelected))
{
PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase());
// Reset campaign score when changing difficulty
// So if you switch difficulty on the last song of a week you get a really low overall score.
PlayStatePlaylist.campaignScore = 0;
PlayStatePlaylist.campaignDifficulty = daSelected.toLowerCase();
PlayState.instance.currentDifficulty = PlayStatePlaylist.campaignDifficulty;
PlayState.instance.needsReset = true;
close();
}
else
{
trace('[WARN] Unhandled pause menu option: ${daSelected}');
}
}
}

View file

@ -102,6 +102,9 @@ class GameOverSubState extends MusicBeatSubState
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x;
cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y;
var offsets:Array<Float> = boyfriend.getDeathCameraOffsets();
cameraFollowPoint.x += offsets[0];
cameraFollowPoint.y += offsets[1];
add(cameraFollowPoint);
FlxG.camera.target = null;

View file

@ -1641,7 +1641,7 @@ class PlayState extends MusicBeatSubState
*/
function onConversationComplete():Void
{
isInCutscene = true;
isInCutscene = false;
remove(currentConversation);
currentConversation = null;
@ -2463,9 +2463,9 @@ class PlayState extends MusicBeatSubState
accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
};
if (Save.get().isLevelHighScore(PlayStatePlaylist.campaignId, currentDifficulty, data))
if (Save.get().isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data))
{
Save.get().setLevelScore(PlayStatePlaylist.campaignId, currentDifficulty, data);
Save.get().setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data);
#if newgrounds
NGio.postScore(score, 'Level ${PlayStatePlaylist.campaignId}');
#end
@ -2513,7 +2513,7 @@ class PlayState extends MusicBeatSubState
var nextPlayState:PlayState = new PlayState(
{
targetSong: targetSong,
targetDifficulty: currentDifficulty,
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
targetCharacter: currentPlayerId,
});
nextPlayState.previousCameraFollowPoint = new FlxSprite(cameraFollowPoint.x, cameraFollowPoint.y);
@ -2529,7 +2529,7 @@ class PlayState extends MusicBeatSubState
var nextPlayState:PlayState = new PlayState(
{
targetSong: targetSong,
targetDifficulty: currentDifficulty,
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
targetCharacter: currentPlayerId,
});
nextPlayState.previousCameraFollowPoint = new FlxSprite(cameraFollowPoint.x, cameraFollowPoint.y);
@ -2655,7 +2655,12 @@ class PlayState extends MusicBeatSubState
persistentUpdate = false;
vocals.stop();
camHUD.alpha = 1;
var res:ResultState = new ResultState();
var res:ResultState = new ResultState(
{
storyMode: PlayStatePlaylist.isStoryMode,
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
tallies: Highscore.tallies,
});
res.camera = camHUD;
openSubState(res);
}

View file

@ -34,10 +34,7 @@ class PlayStatePlaylist
*/
public static var campaignId:String = 'unknown';
/**
* The current difficulty selected for this level (as a named ID).
*/
public static var currentDifficulty(default, default):String = Constants.DEFAULT_DIFFICULTY;
public static var campaignDifficulty:String = Constants.DEFAULT_DIFFICULTY;
/**
* Resets the playlist to its default state.
@ -49,6 +46,6 @@ class PlayStatePlaylist
campaignScore = 0;
campaignTitle = 'UNKNOWN';
campaignId = 'unknown';
currentDifficulty = Constants.DEFAULT_DIFFICULTY;
campaignDifficulty = Constants.DEFAULT_DIFFICULTY;
}
}

View file

@ -22,6 +22,8 @@ import flxanimate.FlxAnimate.Settings;
class ResultState extends MusicBeatSubState
{
final params:ResultsStateParams;
var resultsVariation:ResultVariations;
var songName:FlxBitmapText;
var difficulty:FlxSprite;
@ -29,13 +31,18 @@ class ResultState extends MusicBeatSubState
var maskShaderSongName = new LeftMaskShader();
var maskShaderDifficulty = new LeftMaskShader();
public function new(params:ResultsStateParams)
{
super();
this.params = params;
}
override function create():Void
{
if (Highscore.tallies.sick == Highscore.tallies.totalNotesHit
&& Highscore.tallies.maxCombo == Highscore.tallies.totalNotesHit) resultsVariation = PERFECT;
else if (Highscore.tallies.missed
+ Highscore.tallies.bad
+ Highscore.tallies.shit >= Highscore.tallies.totalNotes * 0.50)
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;
@ -135,17 +142,7 @@ class ResultState extends MusicBeatSubState
var fontLetters:String = "AaBbCcDdEeFfGgHhiIJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz:1234567890";
songName = new FlxBitmapText(FlxBitmapFont.fromMonospace(Paths.image("resultScreen/tardlingSpritesheet"), fontLetters, FlxPoint.get(49, 62)));
// stole this from PauseSubState, I think eric wrote it!!
if (PlayState.instance.currentChart != null)
{
songName.text += '${PlayState.instance.currentChart.songName}:${PlayState.instance.currentChart.songArtist}';
}
else
{
songName.text += PlayState.instance.currentSong.id;
}
songName.text = params.title;
songName.letterSpacing = -15;
songName.angle = -4.1;
add(songName);
@ -194,27 +191,27 @@ class ResultState extends MusicBeatSubState
var ratingGrp:FlxTypedGroup<TallyCounter> = new FlxTypedGroup<TallyCounter>();
add(ratingGrp);
var totalHit:TallyCounter = new TallyCounter(375, hStuf * 3, Highscore.tallies.totalNotesHit);
var totalHit:TallyCounter = new TallyCounter(375, hStuf * 3, params.tallies.totalNotesHit);
ratingGrp.add(totalHit);
var maxCombo:TallyCounter = new TallyCounter(375, hStuf * 4, Highscore.tallies.maxCombo);
var maxCombo:TallyCounter = new TallyCounter(375, hStuf * 4, params.tallies.maxCombo);
ratingGrp.add(maxCombo);
hStuf += 2;
var extraYOffset:Float = 5;
var tallySick:TallyCounter = new TallyCounter(230, (hStuf * 5) + extraYOffset, Highscore.tallies.sick, 0xFF89E59E);
var tallySick:TallyCounter = new TallyCounter(230, (hStuf * 5) + extraYOffset, params.tallies.sick, 0xFF89E59E);
ratingGrp.add(tallySick);
var tallyGood:TallyCounter = new TallyCounter(210, (hStuf * 6) + extraYOffset, Highscore.tallies.good, 0xFF89C9E5);
var tallyGood:TallyCounter = new TallyCounter(210, (hStuf * 6) + extraYOffset, params.tallies.good, 0xFF89C9E5);
ratingGrp.add(tallyGood);
var tallyBad:TallyCounter = new TallyCounter(190, (hStuf * 7) + extraYOffset, Highscore.tallies.bad, 0xffE6CF8A);
var tallyBad:TallyCounter = new TallyCounter(190, (hStuf * 7) + extraYOffset, params.tallies.bad, 0xffE6CF8A);
ratingGrp.add(tallyBad);
var tallyShit:TallyCounter = new TallyCounter(220, (hStuf * 8) + extraYOffset, Highscore.tallies.shit, 0xFFE68C8A);
var tallyShit:TallyCounter = new TallyCounter(220, (hStuf * 8) + extraYOffset, params.tallies.shit, 0xFFE68C8A);
ratingGrp.add(tallyShit);
var tallyMissed:TallyCounter = new TallyCounter(260, (hStuf * 9) + extraYOffset, Highscore.tallies.missed, 0xFFC68AE6);
var tallyMissed:TallyCounter = new TallyCounter(260, (hStuf * 9) + extraYOffset, params.tallies.missed, 0xFFC68AE6);
ratingGrp.add(tallyMissed);
for (ind => rating in ratingGrp.members)
@ -275,7 +272,7 @@ class ResultState extends MusicBeatSubState
}
});
if (Highscore.tallies.isNewHighscore) trace("ITS A NEW HIGHSCORE!!!");
if (params.tallies.isNewHighscore) trace("ITS A NEW HIGHSCORE!!!");
super.create();
}
@ -351,7 +348,7 @@ class ResultState extends MusicBeatSubState
if (controls.PAUSE)
{
if (PlayStatePlaylist.isStoryMode)
if (params.storyMode)
{
FlxG.switchState(new StoryMenuState());
}
@ -372,3 +369,21 @@ enum abstract ResultVariations(String)
var NORMAL;
var SHIT;
}
typedef ResultsStateParams =
{
/**
* True if results are for a level, false if results are for a single song.
*/
var storyMode:Bool;
/**
* Either "Song Name by Artist Name" or "Week Name"
*/
var title:String;
/**
* The score, accuracy, and judgements.
*/
var tallies:Highscore.Tallies;
};

View file

@ -188,6 +188,11 @@ class BaseCharacter extends Bopper
shouldBop = false;
}
public function getDeathCameraOffsets():Array<Float>
{
return _data.death?.cameraOffsets ?? [0.0, 0.0];
}
/**
* Gets the value of flipX from the character data.
* `!getFlipX()` is the direction Boyfriend should face.
@ -571,8 +576,7 @@ class BaseCharacter extends Bopper
public override function playAnimation(name:String, restart:Bool = false, ignoreOther:Bool = false, reversed:Bool = false):Void
{
FlxG.watch.addQuick('playAnim(${characterName})', name);
// trace('playAnim(${characterName}): ${name}');
// FlxG.watch.addQuick('playAnim(${characterName})', name);
super.playAnimation(name, restart, ignoreOther, reversed);
}
}

View file

@ -19,8 +19,10 @@ class CharacterDataParser
* The current version string for the stage data format.
* Handle breaking changes by incrementing this value
* and adding migration to the `migrateStageData()` function.
*
* - Version 1.0.1 adds `death.cameraOffsets`
*/
public static final CHARACTER_DATA_VERSION:String = '1.0.0';
public static final CHARACTER_DATA_VERSION:String = '1.0.1';
/**
* The current version rule check for the stage data format.
@ -603,6 +605,8 @@ typedef CharacterData =
*/
var healthIcon:Null<HealthIconData>;
var death:Null<DeathData>;
/**
* The global offset to the character's position, in pixels.
* @default [0, 0]
@ -695,3 +699,13 @@ typedef HealthIconData =
*/
var offsets:Null<Array<Float>>;
}
typedef DeathData =
{
/**
* The amount to offset the camera by while focusing on this character as they die.
* Default value focuses on the character's graphic midpoint.
* @default [0, 0]
*/
var ?cameraOffsets:Array<Float>;
}

View file

@ -1,5 +1,6 @@
package funkin.play.song;
import funkin.util.SortUtil;
import flixel.sound.FlxSound;
import openfl.utils.Assets;
import funkin.modding.events.ScriptEvent;
@ -56,8 +57,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
*/
public var validScore:Bool = true;
var difficultyIds:Array<String>;
public var songName(get, never):String;
function get_songName():String
@ -85,7 +84,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
this.id = id;
variations = [];
difficultyIds = [];
difficulties = new Map<String, SongDifficulty>();
_data = _fetchData(id);
@ -127,8 +125,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
for (vari in variations)
result.variations.push(vari);
result.difficultyIds.clear();
result.difficulties.clear();
result.populateDifficulties();
for (variation => chartData in charts)
@ -162,8 +159,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
// but all the difficulties in the metadata must be in the chart file.
for (diffId in metadata.playData.difficulties)
{
difficultyIds.push(diffId);
var difficulty:SongDifficulty = new SongDifficulty(this, diffId, metadata.variation);
variations.push(metadata.variation);
@ -237,19 +232,37 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
*/
public inline function getDifficulty(?diffId:String):Null<SongDifficulty>
{
if (diffId == null) diffId = difficulties.keys().array()[0];
if (diffId == null) diffId = listDifficulties()[0];
return difficulties.get(diffId);
}
public function listDifficulties():Array<String>
/**
* List all the difficulties in this song.
* @param variationId Optionally filter by variation.
* @return The list of difficulties.
*/
public function listDifficulties(?variationId:String):Array<String>
{
return difficultyIds;
if (variationId == '') variationId = null;
var diffFiltered:Array<String> = difficulties.keys().array().filter(function(diffId:String):Bool {
if (variationId == null) return true;
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
if (difficulty == null) return false;
return difficulty.variation == variationId;
});
diffFiltered.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST));
return diffFiltered;
}
public function hasDifficulty(diffId:String):Bool
public function hasDifficulty(diffId:String, ?variationId:String):Bool
{
return difficulties.exists(diffId);
if (variationId == '') variationId = null;
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
return variationId == null ? (difficulty != null) : (difficulty != null && difficulty.variation == variationId);
}
/**

View file

@ -667,8 +667,6 @@ class ChartEditorDialogHandler
timeChanges[0].bpm = event.value;
}
Conductor.forceBPM(event.value);
newSongMetadata.timeChanges = timeChanges;
};
@ -677,6 +675,8 @@ class ChartEditorDialogHandler
dialogContinue.onClick = (_event) -> {
state.songMetadata.set(targetVariation, newSongMetadata);
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
dialog.hideDialog(DialogButton.APPLY);
}
@ -696,6 +696,8 @@ class ChartEditorDialogHandler
var charData:SongCharacterData = state.currentSongMetadata.playData.characters;
var hasClearedVocals:Bool = false;
charIdsForVocals.push(charData.player);
charIdsForVocals.push(charData.opponent);
@ -715,6 +717,7 @@ class ChartEditorDialogHandler
if (dialogNoVocals == null) throw 'Could not locate dialogNoVocals button in Upload Vocals dialog';
dialogNoVocals.onClick = function(_event) {
// Dismiss
ChartEditorAudioHandler.stopExistingVocals(state);
dialog.hideDialog(DialogButton.APPLY);
};
@ -738,6 +741,12 @@ class ChartEditorDialogHandler
trace('Selected file: $pathStr');
var path:Path = new Path(pathStr);
if (!hasClearedVocals)
{
hasClearedVocals = true;
ChartEditorAudioHandler.stopExistingVocals(state);
}
if (ChartEditorAudioHandler.loadVocalsFromPath(state, path, charKey, instId))
{
// Tell the user the load was successful.
@ -788,6 +797,11 @@ class ChartEditorDialogHandler
if (selectedFile != null && selectedFile.bytes != null)
{
trace('Selected file: ' + selectedFile.name);
if (!hasClearedVocals)
{
hasClearedVocals = true;
ChartEditorAudioHandler.stopExistingVocals(state);
}
if (ChartEditorAudioHandler.loadVocalsFromBytes(state, selectedFile.bytes, charKey, instId))
{
// Tell the user the load was successful.

View file

@ -1898,6 +1898,16 @@ class ChartEditorState extends HaxeUIState
handleViewKeybinds();
handleTestKeybinds();
handleHelpKeybinds();
#if debug
handleQuickWatch();
#end
}
function handleQuickWatch():Void
{
FlxG.watch.addQuick('scrollPosInPixels', scrollPositionInPixels);
FlxG.watch.addQuick('playheadPosInPixels', playheadPositionInPixels);
}
/**
@ -3140,6 +3150,7 @@ class ChartEditorState extends HaxeUIState
function quitChartEditor():Void
{
autoSave();
stopWelcomeMusic();
FlxG.switchState(new MainMenuState());
}
@ -3363,6 +3374,7 @@ class ChartEditorState extends HaxeUIState
if (!isHaxeUIDialogOpen && !isCursorOverHaxeUI && FlxG.keys.justPressed.ENTER)
{
var minimal = FlxG.keys.pressed.SHIFT;
ChartEditorToolboxHandler.hideAllToolboxes(this);
testSongInPlayState(minimal);
}
}
@ -4166,14 +4178,13 @@ class ChartEditorState extends HaxeUIState
*/
function moveSongToScrollPosition():Void
{
// Update the songPosition in the Conductor.
var targetPos = scrollPositionInMs;
Conductor.update(targetPos);
// Update the songPosition in the audio tracks.
if (audioInstTrack != null) audioInstTrack.time = scrollPositionInMs + playheadPositionInMs;
if (audioVocalTrackGroup != null) audioVocalTrackGroup.time = scrollPositionInMs + playheadPositionInMs;
// Update the songPosition in the Conductor.
Conductor.update(audioInstTrack.time);
// We need to update the note sprites because we changed the scroll position.
noteDisplayDirty = true;
}

View file

@ -136,6 +136,18 @@ class ChartEditorToolboxHandler
}
}
public static function rememberOpenToolboxes(state:ChartEditorState):Void {}
public static function openRememberedToolboxes(state:ChartEditorState):Void {}
public static function hideAllToolboxes(state:ChartEditorState):Void
{
for (toolbox in state.activeToolboxes.values())
{
toolbox.hideDialog(DialogButton.CANCEL);
}
}
public static function minimizeToolbox(state:ChartEditorState, id:String):Void
{
var toolbox:Null<CollapsibleDialog> = state.activeToolboxes.get(id);
@ -634,9 +646,9 @@ class ChartEditorToolboxHandler
timeChanges[0].bpm = event.value;
}
Conductor.forceBPM(event.value);
state.currentSongMetadata.timeChanges = timeChanges;
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
};
inputBPM.value = state.currentSongMetadata.timeChanges[0].bpm;

View file

@ -1,5 +1,6 @@
package funkin.ui.story;
import funkin.util.SortUtil;
import flixel.FlxSprite;
import flixel.util.FlxColor;
import funkin.play.song.Song;
@ -155,6 +156,8 @@ class Level implements IRegistryEntry<LevelData>
}
}
difficulties.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST));
// Filter to only include difficulties that are present in all songs
for (songIndex in 1...songList.length)
{

View file

@ -523,6 +523,7 @@ class StoryMenuState extends MusicBeatState
PlayStatePlaylist.campaignId = currentLevel.id;
PlayStatePlaylist.campaignTitle = currentLevel.getTitle();
PlayStatePlaylist.campaignDifficulty = currentDifficultyId;
if (targetSong != null)
{
@ -538,7 +539,7 @@ class StoryMenuState extends MusicBeatState
LoadingState.loadAndSwitchState(new PlayState(
{
targetSong: targetSong,
targetDifficulty: currentDifficultyId,
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
}), true);
});
}

View file

@ -121,6 +121,11 @@ class Constants
*/
public static final DEFAULT_DIFFICULTY:String = 'normal';
/**
* Default list of difficulties for charts.
*/
public static final DEFAULT_DIFFICULTY_LIST:Array<String> = ['easy', 'normal', 'hard'];
/**
* Default player character for charts.
*/