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

WIP on conductor rework

This commit is contained in:
Eric Myllyoja 2022-09-22 06:34:03 -04:00
parent 7fbdbcdfad
commit 9ebb566b2a
17 changed files with 810 additions and 76 deletions

View file

@ -1,11 +1,10 @@
package funkin; package funkin;
import funkin.SongLoad.SwagSong; import funkin.SongLoad.SwagSong;
import funkin.play.song.Song.SongDifficulty;
import funkin.play.song.SongData.ConductorTimeChange;
import funkin.play.song.SongData.SongTimeChange;
/**
* ...
* @author
*/
typedef BPMChangeEvent = typedef BPMChangeEvent =
{ {
var stepTime:Int; var stepTime:Int;
@ -16,12 +15,40 @@ typedef BPMChangeEvent =
class Conductor class Conductor
{ {
/** /**
* Beats per minute of the song. * The list of time changes in the song.
* There should be at least one time change (at the beginning of the song) to define the BPM.
*/ */
public static var bpm:Float = 100; private static var timeChanges:Array<ConductorTimeChange> = [];
/** /**
* Duration of a beat in millisecond. * The current time change.
*/
private static var currentTimeChange:ConductorTimeChange;
/**
* The current position in the song in milliseconds.
* Updated every frame based on the audio position.
*/
public static var songPosition:Float;
/**
* Beats per minute of the current song at the current time.
*/
public static var bpm(get, null):Float = 100;
static function get_bpm():Float
{
if (currentTimeChange == null)
return 100;
return currentTimeChange.bpm;
}
// OLD, replaced with timeChanges.
public static var bpmChangeMap:Array<BPMChangeEvent> = [];
/**
* Duration of a beat in millisecond. Calculated based on bpm.
*/ */
public static var crochet(get, null):Float; public static var crochet(get, null):Float;
@ -31,7 +58,7 @@ class Conductor
} }
/** /**
* Duration of a step in milliseconds. * Duration of a step in milliseconds. Calculated based on bpm.
*/ */
public static var stepCrochet(get, null):Float; public static var stepCrochet(get, null):Float;
@ -40,19 +67,62 @@ class Conductor
return crochet / 4; return crochet / 4;
} }
/** public static var currentBeat(get, null):Float;
* The current position in the song in milliseconds.
*/ static function get_currentBeat():Float
public static var songPosition:Float; {
return currentBeat;
}
public static var currentStep(get, null):Int;
static function get_currentStep():Int
{
return currentStep;
}
public static var lastSongPos:Float; public static var lastSongPos:Float;
public static var visualOffset:Float = 0; public static var visualOffset:Float = 0;
public static var audioOffset:Float = 0; public static var audioOffset:Float = 0;
public static var offset:Float = 0; public static var offset:Float = 0;
public static var bpmChangeMap:Array<BPMChangeEvent> = []; public function new()
{
}
public function new() {} public static function getLastBPMChange()
{
var lastChange:BPMChangeEvent = {
stepTime: 0,
songTime: 0,
bpm: 0
}
for (i in 0...Conductor.bpmChangeMap.length)
{
if (Conductor.songPosition >= Conductor.bpmChangeMap[i].songTime)
lastChange = Conductor.bpmChangeMap[i];
if (Conductor.songPosition < Conductor.bpmChangeMap[i].songTime)
break;
}
return lastChange;
}
public static function forceBPM(bpm:Float)
{
// TODO: Get rid of this and use song metadata instead.
Conductor.bpm = bpm;
}
/**
* Update the conductor with the current song position.
* BPM, current step, etc. will be re-calculated based on the song position.
*/
public static function update(songPosition:Float)
{
Conductor.songPosition = songPosition;
Conductor.bpm = Conductor.getLastBPMChange().bpm;
}
public static function mapBPMChanges(song:SwagSong) public static function mapBPMChanges(song:SwagSong)
{ {
@ -80,4 +150,34 @@ class Conductor
} }
trace("new BPM map BUDDY " + bpmChangeMap); trace("new BPM map BUDDY " + bpmChangeMap);
} }
public static function mapTimeChanges(currentChart:SongDifficulty)
{
var songTimeChanges:Array<SongTimeChange> = currentChart.timeChanges;
timeChanges = [];
for (songTimeChange in timeChanges)
{
var prevTimeChange:ConductorTimeChange = timeChanges.length == 0 ? null : timeChanges[timeChanges.length - 1];
var currentTimeChange:ConductorTimeChange = cast songTimeChange;
if (prevTimeChange != null)
{
var deltaTime:Float = currentTimeChange.timeStamp - prevTimeChange.timeStamp;
var deltaSteps:Int = Math.round(deltaTime / (60 / prevTimeChange.bpm) * 1000 / 4);
currentTimeChange.stepTime = prevTimeChange.stepTime + deltaSteps;
}
else
{
// We know the time and steps of this time change is 0, since this is the first time change.
currentTimeChange.stepTime = 0;
}
timeChanges.push(currentTimeChange);
}
// Done.
}
} }

View file

@ -27,6 +27,7 @@ import funkin.freeplayStuff.FreeplayScore;
import funkin.freeplayStuff.SongMenuItem; import funkin.freeplayStuff.SongMenuItem;
import funkin.play.HealthIcon; import funkin.play.HealthIcon;
import funkin.play.PlayState; import funkin.play.PlayState;
import funkin.play.song.SongData.SongDataParser;
import funkin.shaderslmfao.AngleMask; import funkin.shaderslmfao.AngleMask;
import funkin.shaderslmfao.PureColor; import funkin.shaderslmfao.PureColor;
import funkin.shaderslmfao.StrokeShader; import funkin.shaderslmfao.StrokeShader;
@ -97,7 +98,7 @@ class FreeplayState extends MusicBeatSubstate
} }
if (StoryMenuState.weekUnlocked[2] || isDebug) if (StoryMenuState.weekUnlocked[2] || isDebug)
addWeek(['Bopeebo', 'Fresh', 'Dadbattle'], 1, ['dad']); addWeek(['Bopeebo', 'Bopeebo_new', 'Fresh', 'Dadbattle'], 1, ['dad']);
if (StoryMenuState.weekUnlocked[2] || isDebug) if (StoryMenuState.weekUnlocked[2] || isDebug)
addWeek(['Spookeez', 'South', 'Monster'], 2, ['spooky', 'spooky', 'monster']); addWeek(['Spookeez', 'South', 'Monster'], 2, ['spooky', 'spooky', 'monster']);
@ -520,8 +521,10 @@ class FreeplayState extends MusicBeatSubstate
}*/ }*/
PlayState.currentSong = SongLoad.loadFromJson(poop, songs[curSelected].songName.toLowerCase()); PlayState.currentSong = SongLoad.loadFromJson(poop, songs[curSelected].songName.toLowerCase());
PlayState.currentSong_NEW = SongDataParser.fetchSong(songs[curSelected].songName.toLowerCase());
PlayState.isStoryMode = false; PlayState.isStoryMode = false;
PlayState.storyDifficulty = curDifficulty; PlayState.storyDifficulty = curDifficulty;
PlayState.storyDifficulty_NEW = 'easy';
// SongLoad.curDiff = Highscore.formatSong() // SongLoad.curDiff = Highscore.formatSong()
SongLoad.curDiff = switch (curDifficulty) SongLoad.curDiff = switch (curDifficulty)
@ -562,6 +565,7 @@ class FreeplayState extends MusicBeatSubstate
intendedScore = FlxG.random.int(0, 100000); intendedScore = FlxG.random.int(0, 100000);
PlayState.storyDifficulty = curDifficulty; PlayState.storyDifficulty = curDifficulty;
PlayState.storyDifficulty_NEW = 'easy';
grpDifficulties.group.forEach(function(spr) grpDifficulties.group.forEach(function(spr)
{ {

View file

@ -191,8 +191,10 @@ class InitState extends FlxTransitionableState
var dif = getDif(); var dif = getDif();
PlayState.currentSong = SongLoad.loadFromJson(song, song); PlayState.currentSong = SongLoad.loadFromJson(song, song);
PlayState.currentSong_NEW = SongDataParser.fetchSong(song);
PlayState.isStoryMode = isStoryMode; PlayState.isStoryMode = isStoryMode;
PlayState.storyDifficulty = dif; PlayState.storyDifficulty = dif;
PlayState.storyDifficulty_NEW = 'easy';
SongLoad.curDiff = switch (dif) SongLoad.curDiff = switch (dif)
{ {
case 0: 'easy'; case 0: 'easy';

View file

@ -70,7 +70,7 @@ class LatencyState extends MusicBeatSubstate
// funnyStatsGraph.hi // funnyStatsGraph.hi
Conductor.bpm = 60; Conductor.forceBPM(60);
noteGrp = new FlxTypedGroup<Note>(); noteGrp = new FlxTypedGroup<Note>();
add(noteGrp); add(noteGrp);

View file

@ -2,6 +2,8 @@ package funkin;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.math.FlxMath; import flixel.math.FlxMath;
import funkin.noteStuff.NoteBasic.NoteData;
import funkin.noteStuff.NoteBasic.NoteType;
import funkin.play.PlayState; import funkin.play.PlayState;
import funkin.play.Strumline.StrumlineStyle; import funkin.play.Strumline.StrumlineStyle;
import funkin.shaderslmfao.ColorSwap; import funkin.shaderslmfao.ColorSwap;

View file

@ -1,17 +1,15 @@
package funkin; package funkin;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.FlxSubState;
import flixel.addons.transition.FlxTransitionableState; import flixel.addons.transition.FlxTransitionableState;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.input.keyboard.FlxKey;
import flixel.system.FlxSound; import flixel.system.FlxSound;
import flixel.text.FlxText; import flixel.text.FlxText;
import flixel.tweens.FlxEase; import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween; import flixel.tweens.FlxTween;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import funkin.Controls.Control;
import funkin.play.PlayState; import funkin.play.PlayState;
import funkin.play.song.SongData.SongDataParser;
class PauseSubState extends MusicBeatSubstate class PauseSubState extends MusicBeatSubstate
{ {
@ -61,7 +59,14 @@ class PauseSubState extends MusicBeatSubstate
add(metaDataGrp); add(metaDataGrp);
var levelInfo:FlxText = new FlxText(20, 15, 0, "", 32); var levelInfo:FlxText = new FlxText(20, 15, 0, "", 32);
levelInfo.text += PlayState.currentSong.song; if (PlayState.instance.currentChart != null)
{
levelInfo.text += '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}';
}
else
{
levelInfo.text += PlayState.currentSong.song;
}
levelInfo.scrollFactor.set(); levelInfo.scrollFactor.set();
levelInfo.setFormat(Paths.font("vcr.ttf"), 32); levelInfo.setFormat(Paths.font("vcr.ttf"), 32);
levelInfo.updateHitbox(); levelInfo.updateHitbox();
@ -180,9 +185,11 @@ class PauseSubState extends MusicBeatSubstate
close(); close();
case "EASY" | 'NORMAL' | "HARD": case "EASY" | 'NORMAL' | "HARD":
PlayState.currentSong = SongLoad.loadFromJson(PlayState.currentSong.song.toLowerCase(), PlayState.currentSong.song.toLowerCase()); PlayState.currentSong = SongLoad.loadFromJson(PlayState.currentSong.song.toLowerCase(), PlayState.currentSong.song.toLowerCase());
PlayState.currentSong_NEW = SongDataParser.fetchSong(PlayState.currentSong.song.toLowerCase());
SongLoad.curDiff = daSelected.toLowerCase(); SongLoad.curDiff = daSelected.toLowerCase();
PlayState.storyDifficulty = curSelected; PlayState.storyDifficulty = curSelected;
PlayState.storyDifficulty_NEW = 'easy';
PlayState.needsReset = true; PlayState.needsReset = true;

View file

@ -2,6 +2,7 @@ package funkin;
import funkin.Section.SwagSection; import funkin.Section.SwagSection;
import funkin.noteStuff.NoteBasic.NoteData; import funkin.noteStuff.NoteBasic.NoteData;
import funkin.play.PlayState;
import haxe.Json; import haxe.Json;
import lime.utils.Assets; import lime.utils.Assets;
@ -47,7 +48,21 @@ class SongLoad
public static function loadFromJson(jsonInput:String, ?folder:String):SwagSong public static function loadFromJson(jsonInput:String, ?folder:String):SwagSong
{ {
var rawJson = Assets.getText(Paths.json('songs/${folder.toLowerCase()}/${jsonInput.toLowerCase()}')).trim(); var rawJson:Dynamic = null;
try
{
rawJson = Assets.getText(Paths.json('songs/${folder.toLowerCase()}/${jsonInput.toLowerCase()}')).trim();
}
catch (e)
{
trace('Failed to load song data: ${e}');
rawJson = null;
}
if (rawJson == null)
{
return null;
}
while (!rawJson.endsWith("}")) while (!rawJson.endsWith("}"))
{ {
@ -112,6 +127,11 @@ class SongLoad
public static function getSpeed(?diff:String):Float public static function getSpeed(?diff:String):Float
{ {
if (PlayState.instance != null && PlayState.instance.currentChart != null)
{
return getSpeed_NEW(diff);
}
if (diff == null) if (diff == null)
diff = SongLoad.curDiff; diff = SongLoad.curDiff;
@ -137,6 +157,14 @@ class SongLoad
return speedShit; return speedShit;
} }
public static function getSpeed_NEW(?diff:String):Float
{
if (PlayState.instance == null || PlayState.instance.currentChart == null || PlayState.instance.currentChart.scrollSpeed == 0.0)
return 1.0;
return PlayState.instance.currentChart.scrollSpeed;
}
public static function getDefaultSwagSong():SwagSong public static function getDefaultSwagSong():SwagSong
{ {
return { return {

View file

@ -12,6 +12,7 @@ import flixel.util.FlxColor;
import flixel.util.FlxTimer; import flixel.util.FlxTimer;
import funkin.MenuItem.WeekType; import funkin.MenuItem.WeekType;
import funkin.play.PlayState; import funkin.play.PlayState;
import funkin.play.song.SongData.SongDataParser;
import lime.net.curl.CURLCode; import lime.net.curl.CURLCode;
import openfl.Assets; import openfl.Assets;
@ -372,10 +373,12 @@ class StoryMenuState extends MusicBeatState
selectedWeek = true; selectedWeek = true;
PlayState.currentSong = SongLoad.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.storyPlaylist[0].toLowerCase()); PlayState.currentSong = SongLoad.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.storyPlaylist[0].toLowerCase());
PlayState.currentSong_NEW = SongDataParser.fetchSong(PlayState.storyPlaylist[0].toLowerCase());
PlayState.storyWeek = curWeek; PlayState.storyWeek = curWeek;
PlayState.campaignScore = 0; PlayState.campaignScore = 0;
PlayState.storyDifficulty = curDifficulty; PlayState.storyDifficulty = curDifficulty;
PlayState.storyDifficulty_NEW = 'easy';
SongLoad.curDiff = switch (curDifficulty) SongLoad.curDiff = switch (curDifficulty)
{ {
case 0: case 0:

View file

@ -140,7 +140,7 @@ class TitleState extends MusicBeatState
{ {
FlxG.sound.playMusic(Paths.music('freakyMenu'), 0); FlxG.sound.playMusic(Paths.music('freakyMenu'), 0);
FlxG.sound.music.fadeIn(4, 0, 0.7); FlxG.sound.music.fadeIn(4, 0, 0.7);
Conductor.bpm = Constants.FREAKY_MENU_BPM; Conductor.forceBPM(Constants.FREAKY_MENU_BPM);
} }
persistentUpdate = true; persistentUpdate = true;
@ -474,7 +474,7 @@ class TitleState extends MusicBeatState
var spec:SpectogramSprite = new SpectogramSprite(FlxG.sound.music); var spec:SpectogramSprite = new SpectogramSprite(FlxG.sound.music);
add(spec); add(spec);
Conductor.bpm = 190; Conductor.forceBPM(190);
FlxG.camera.flash(FlxColor.WHITE, 1); FlxG.camera.flash(FlxColor.WHITE, 1);
FlxG.sound.play(Paths.sound('confirmMenu'), 0.7); FlxG.sound.play(Paths.sound('confirmMenu'), 0.7);
} }

View file

@ -148,7 +148,7 @@ class ChartingState extends MusicBeatState
updateGrid(); updateGrid();
loadSong(_song.song); loadSong(_song.song);
Conductor.bpm = _song.bpm; // Conductor.bpm = _song.bpm;
Conductor.mapBPMChanges(_song); Conductor.mapBPMChanges(_song);
bpmTxt = new FlxText(1000, 50, 0, "", 16); bpmTxt = new FlxText(1000, 50, 0, "", 16);
@ -549,7 +549,7 @@ class ChartingState extends MusicBeatState
{ {
tempBpm = nums.value; tempBpm = nums.value;
Conductor.mapBPMChanges(_song); Conductor.mapBPMChanges(_song);
Conductor.bpm = nums.value; Conductor.forceBPM(nums.value);
} }
else if (wname == 'note_susLength') else if (wname == 'note_susLength')
{ {
@ -1223,7 +1223,7 @@ class ChartingState extends MusicBeatState
if (SongLoad.getSong()[curSection].changeBPM && SongLoad.getSong()[curSection].bpm > 0) if (SongLoad.getSong()[curSection].changeBPM && SongLoad.getSong()[curSection].bpm > 0)
{ {
Conductor.bpm = SongLoad.getSong()[curSection].bpm; Conductor.forceBPM(SongLoad.getSong()[curSection].bpm);
FlxG.log.add('CHANGED BPM!'); FlxG.log.add('CHANGED BPM!');
} }
else else
@ -1233,7 +1233,7 @@ class ChartingState extends MusicBeatState
for (i in 0...curSection) for (i in 0...curSection)
if (SongLoad.getSong()[i].changeBPM) if (SongLoad.getSong()[i].changeBPM)
daBPM = SongLoad.getSong()[i].bpm; daBPM = SongLoad.getSong()[i].bpm;
Conductor.bpm = daBPM; Conductor.forceBPM(daBPM);
} }
/* // PORT BULLSHIT, INCASE THERE'S NO SUSTAIN DATA FOR A NOTE /* // PORT BULLSHIT, INCASE THERE'S NO SUSTAIN DATA FOR A NOTE

View file

@ -5,6 +5,7 @@ import flixel.addons.effects.FlxTrail;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.math.FlxMath; import flixel.math.FlxMath;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import flixel.util.FlxDirectionFlags;
import funkin.audiovis.PolygonSpectogram; import funkin.audiovis.PolygonSpectogram;
import funkin.noteStuff.NoteBasic.NoteData; import funkin.noteStuff.NoteBasic.NoteData;
@ -35,7 +36,7 @@ class PicoFight extends MusicBeatState
FlxG.sound.playMusic(Paths.inst("blazin")); FlxG.sound.playMusic(Paths.inst("blazin"));
SongLoad.loadFromJson('blazin', "blazin"); SongLoad.loadFromJson('blazin', "blazin");
Conductor.bpm = SongLoad.songData.bpm; Conductor.forceBPM(SongLoad.songData.bpm);
for (dumbassSection in SongLoad.songData.noteMap['hard']) for (dumbassSection in SongLoad.songData.noteMap['hard'])
{ {

View file

@ -28,6 +28,10 @@ import funkin.play.Strumline.StrumlineStyle;
import funkin.play.character.BaseCharacter; import funkin.play.character.BaseCharacter;
import funkin.play.character.CharacterData; import funkin.play.character.CharacterData;
import funkin.play.scoring.Scoring; import funkin.play.scoring.Scoring;
import funkin.play.song.Song;
import funkin.play.song.SongData.SongNoteData;
import funkin.play.song.SongData.SongPlayableChar;
import funkin.play.song.SongValidator;
import funkin.play.stage.Stage; import funkin.play.stage.Stage;
import funkin.play.stage.StageData; import funkin.play.stage.StageData;
import funkin.ui.PopUpStuff; import funkin.ui.PopUpStuff;
@ -62,6 +66,8 @@ class PlayState extends MusicBeatState
*/ */
public static var currentSong:SwagSong = null; public static var currentSong:SwagSong = null;
public static var currentSong_NEW:Song = null;
/** /**
* Whether the game is currently in Story Mode. If false, we are in Free Play Mode. * Whether the game is currently in Story Mode. If false, we are in Free Play Mode.
*/ */
@ -116,6 +122,8 @@ class PlayState extends MusicBeatState
*/ */
public var currentStage:Stage = null; public var currentStage:Stage = null;
public var currentChart(get, null):SongDifficulty;
/** /**
* The internal ID of the currently active Stage. * The internal ID of the currently active Stage.
* Used to retrieve the data required to build the `currentStage`. * Used to retrieve the data required to build the `currentStage`.
@ -166,6 +174,12 @@ class PlayState extends MusicBeatState
*/ */
private var healthLerp:Float = 1; private var healthLerp:Float = 1;
/**
* Forcibly disables all update logic while the game moves back to the Menu state.
* This is used only when a critical error occurs and the game cannot continue.
*/
private var criticalFailure:Bool = false;
/** /**
* RENDER OBJECTS * RENDER OBJECTS
*/ */
@ -244,6 +258,7 @@ class PlayState extends MusicBeatState
public static var storyWeek:Int = 0; public static var storyWeek:Int = 0;
public static var storyPlaylist:Array<String> = []; public static var storyPlaylist:Array<String> = [];
public static var storyDifficulty:Int = 1; public static var storyDifficulty:Int = 1;
public static var storyDifficulty_NEW:String = "normal";
public static var seenCutscene:Bool = false; public static var seenCutscene:Bool = false;
public static var campaignScore:Int = 0; public static var campaignScore:Int = 0;
@ -279,8 +294,10 @@ class PlayState extends MusicBeatState
{ {
super.create(); super.create();
if (currentSong == null) if (currentSong == null && currentSong_NEW == null)
{ {
criticalFailure = true;
lime.app.Application.current.window.alert("There was a critical error while accessing the selected song. Click OK to return to the main menu.", lime.app.Application.current.window.alert("There was a critical error while accessing the selected song. Click OK to return to the main menu.",
"Error loading PlayState"); "Error loading PlayState");
FlxG.switchState(new MainMenuState()); FlxG.switchState(new MainMenuState());
@ -308,28 +325,48 @@ class PlayState extends MusicBeatState
FlxG.sound.music.stop(); FlxG.sound.music.stop();
// Prepare the current song to be played. // Prepare the current song to be played.
FlxG.sound.cache(Paths.inst(currentSong.song)); if (currentChart != null)
FlxG.sound.cache(Paths.voices(currentSong.song)); {
currentChart.cacheInst();
currentChart.cacheVocals();
}
else
{
FlxG.sound.cache(Paths.inst(currentSong.song));
FlxG.sound.cache(Paths.voices(currentSong.song));
}
Conductor.songPosition = -5000; Conductor.update(-5000);
// Initialize stage stuff. // Initialize stage stuff.
initCameras(); initCameras();
if (currentSong == null) if (currentSong == null && currentSong_NEW == null)
currentSong = SongLoad.loadFromJson('tutorial');
Conductor.mapBPMChanges(currentSong);
Conductor.bpm = currentSong.bpm;
switch (currentSong.song.toLowerCase())
{ {
case 'senpai': currentSong = SongLoad.loadFromJson('tutorial');
dialogue = CoolUtil.coolTextFile(Paths.txt('songs/senpai/senpaiDialogue')); }
case 'roses':
dialogue = CoolUtil.coolTextFile(Paths.txt('songs/roses/rosesDialogue')); if (currentSong_NEW != null)
case 'thorns': {
dialogue = CoolUtil.coolTextFile(Paths.txt('songs/thorns/thornsDialogue')); Conductor.mapTimeChanges(currentChart);
// Conductor.bpm = currentChart.getStartingBPM();
// TODO: Support for dialog.
}
else
{
Conductor.mapBPMChanges(currentSong);
// Conductor.bpm = currentSong.bpm;
switch (currentSong.song.toLowerCase())
{
case 'senpai':
dialogue = CoolUtil.coolTextFile(Paths.txt('songs/senpai/senpaiDialogue'));
case 'roses':
dialogue = CoolUtil.coolTextFile(Paths.txt('songs/roses/rosesDialogue'));
case 'thorns':
dialogue = CoolUtil.coolTextFile(Paths.txt('songs/thorns/thornsDialogue'));
}
} }
if (dialogue != null) if (dialogue != null)
@ -379,7 +416,14 @@ class PlayState extends MusicBeatState
add(grpNoteSplashes); add(grpNoteSplashes);
generateSong(); if (currentSong_NEW != null)
{
generateSong_NEW();
}
else
{
generateSong();
}
resetCamera(); resetCamera();
@ -442,6 +486,13 @@ class PlayState extends MusicBeatState
#end #end
} }
function get_currentChart():SongDifficulty
{
if (currentSong_NEW == null || storyDifficulty_NEW == null)
return null;
return currentSong_NEW.getDifficulty(storyDifficulty_NEW);
}
/** /**
* Initializes the game and HUD cameras. * Initializes the game and HUD cameras.
*/ */
@ -460,6 +511,12 @@ class PlayState extends MusicBeatState
function initStage() function initStage()
{ {
if (currentSong_NEW != null)
{
initStage_NEW();
return;
}
// TODO: Move stageId to the song file. // TODO: Move stageId to the song file.
switch (currentSong.song.toLowerCase()) switch (currentSong.song.toLowerCase())
{ {
@ -487,9 +544,6 @@ class PlayState extends MusicBeatState
currentStageId = 'schoolEvil'; currentStageId = 'schoolEvil';
case 'guns' | 'stress' | 'ugh': case 'guns' | 'stress' | 'ugh':
currentStageId = 'tankmanBattlefield'; currentStageId = 'tankmanBattlefield';
case 'experimental-phase' | 'perfection':
// SERIOUSLY REVAMP THE CHART FORMAT ALREADY
currentStageId = "breakout";
default: default:
currentStageId = "mainStage"; currentStageId = "mainStage";
} }
@ -497,8 +551,33 @@ class PlayState extends MusicBeatState
loadStage(currentStageId); loadStage(currentStageId);
} }
function initStage_NEW()
{
if (currentChart == null)
{
trace('Song difficulty could not be loaded.');
}
if (currentChart.stage != null && currentChart.stage != '')
{
currentStageId = currentChart.stage;
}
else
{
currentStageId = SongValidator.DEFAULT_STAGE;
}
loadStage(currentStageId);
}
function initCharacters() function initCharacters()
{ {
if (currentSong_NEW != null)
{
initCharacters_NEW();
return;
}
iconP1 = new HealthIcon(currentSong.player1, 0); iconP1 = new HealthIcon(currentSong.player1, 0);
iconP1.y = healthBar.y - (iconP1.height / 2); iconP1.y = healthBar.y - (iconP1.height / 2);
add(iconP1); add(iconP1);
@ -615,6 +694,111 @@ class PlayState extends MusicBeatState
} }
} }
function initCharacters_NEW()
{
if (currentSong_NEW == null || currentChart == null)
{
trace('Song difficulty could not be loaded.');
}
// TODO: Switch playable character by manipulating this value.
// TODO: How to choose which one to use for story mode?
var currentPlayer = 'bf';
var currentCharData:SongPlayableChar = currentChart.getPlayableChar(currentPlayer);
//
// GIRLFRIEND
//
var girlfriend:BaseCharacter = CharacterDataParser.fetchCharacter(currentCharData.girlfriend);
if (girlfriend != null)
{
girlfriend.characterType = CharacterType.GF;
}
else if (currentCharData.girlfriend != '')
{
trace('WARNING: Could not load girlfriend character with ID ${currentCharData.girlfriend}, skipping...');
}
else
{
// Chosen GF was '' so we don't load one.
}
//
// DAD
//
var dad:BaseCharacter = CharacterDataParser.fetchCharacter(currentCharData.opponent);
if (dad != null)
{
dad.characterType = CharacterType.DAD;
}
// TODO: Cut out this code/make it generic.
switch (currentCharData.opponent)
{
case 'gf':
if (isStoryMode)
{
cameraFollowPoint.x += 600;
tweenCamIn();
}
}
//
// OPPONENT HEALTH ICON
//
iconP2 = new HealthIcon(currentCharData.opponent, 1);
iconP2.y = healthBar.y - (iconP2.height / 2);
add(iconP2);
//
// BOYFRIEND
//
var boyfriend:BaseCharacter = CharacterDataParser.fetchCharacter(currentPlayer);
if (boyfriend != null)
{
boyfriend.characterType = CharacterType.BF;
}
//
// PLAYER HEALTH ICON
//
iconP1 = new HealthIcon(currentPlayer, 0);
iconP1.y = healthBar.y - (iconP1.height / 2);
add(iconP1);
//
// ADD CHARACTERS TO SCENE
//
if (currentStage != null)
{
// Characters get added to the stage, not the main scene.
if (girlfriend != null)
{
currentStage.addCharacter(girlfriend, GF);
}
if (boyfriend != null)
{
currentStage.addCharacter(boyfriend, BF);
}
if (dad != null)
{
currentStage.addCharacter(dad, DAD);
// Camera starts at dad.
cameraFollowPoint.setPosition(dad.cameraFocusPoint.x, dad.cameraFocusPoint.y);
}
// Rearrange by z-indexes.
currentStage.refresh();
}
}
/** /**
* Removes any references to the current stage, then clears the stage cache, * Removes any references to the current stage, then clears the stage cache,
* then reloads all the stages. * then reloads all the stages.
@ -794,7 +978,14 @@ class PlayState extends MusicBeatState
// if (FlxG.sound.music != null) // if (FlxG.sound.music != null)
// FlxG.sound.music.play(true); // FlxG.sound.music.play(true);
// else // else
FlxG.sound.playMusic(Paths.inst(currentSong.song), 1, false); if (currentChart != null)
{
currentChart.playInst(1.0, false);
}
else
{
FlxG.sound.playMusic(Paths.inst(currentSong.song), 1, false);
}
} }
FlxG.sound.music.onComplete = endSong; FlxG.sound.music.onComplete = endSong;
@ -813,7 +1004,7 @@ class PlayState extends MusicBeatState
{ {
// FlxG.log.add(ChartParser.parse()); // FlxG.log.add(ChartParser.parse());
Conductor.bpm = currentSong.bpm; Conductor.forceBPM(currentSong.bpm);
currentSong.song = currentSong.song; currentSong.song = currentSong.song;
@ -836,6 +1027,32 @@ class PlayState extends MusicBeatState
generatedMusic = true; generatedMusic = true;
} }
private function generateSong_NEW():Void
{
if (currentChart == null)
{
trace('Song difficulty could not be loaded.');
}
Conductor.forceBPM(currentChart.getStartingBPM());
// TODO: Fix grouped vocals
vocals = currentChart.buildVocals();
vocals.members[0].onComplete = function()
{
vocalsFinished = true;
}
// Create the rendered note group.
activeNotes = new FlxTypedGroup<Note>();
activeNotes.zIndex = 1000;
add(activeNotes);
regenNoteData_NEW();
generatedMusic = true;
}
function regenNoteData():Void function regenNoteData():Void
{ {
// make unspawn notes shit def empty // make unspawn notes shit def empty
@ -950,6 +1167,133 @@ class PlayState extends MusicBeatState
}); });
} }
function regenNoteData_NEW():Void
{
// Destroy inactive notes.
inactiveNotes = [];
// Destroy active notes.
activeNotes.forEach(function(nt)
{
nt.followsTime = false;
FlxTween.tween(nt, {y: FlxG.height + nt.y}, 0.5, {
ease: FlxEase.expoIn,
onComplete: function(twn)
{
nt.kill();
activeNotes.remove(nt, true);
nt.destroy();
}
});
});
var noteData:Array<SongNoteData> = currentChart.notes;
var oldNote:Note = null;
for (songNote in noteData)
{
var mustHitNote:Bool = songNote.getMustHitNote();
// TODO: Put this in the chart or something?
var strumlineStyle:StrumlineStyle = null;
switch (currentStageId)
{
case 'school':
strumlineStyle = PIXEL;
case 'schoolEvil':
strumlineStyle = PIXEL;
default:
strumlineStyle = NORMAL;
}
var newNote:Note = new Note(songNote.time, songNote.data, oldNote, false, strumlineStyle);
newNote.mustPress = mustHitNote;
newNote.data.sustainLength = songNote.length;
newNote.data.noteKind = songNote.kind;
newNote.scrollFactor.set(0, 0);
// Note positioning.
// TODO: Make this more robust.
if (newNote.mustPress)
{
if (playerStrumline != null)
{
// Align with the strumline arrow.
newNote.x = playerStrumline.getArrow(songNote.getDirection()).x;
}
else
{
// Assume strumline position.
newNote.x += FlxG.width / 2;
}
}
else
{
if (enemyStrumline != null)
{
newNote.x = enemyStrumline.getArrow(songNote.getDirection()).x;
}
else
{
// newNote.x += 0;
}
}
inactiveNotes.push(newNote);
oldNote = newNote;
// Generate X sustain notes.
var sustainSections = Math.round(songNote.length / Conductor.stepCrochet);
for (noteIndex in 0...sustainSections)
{
var noteTimeOffset:Float = Conductor.stepCrochet + (Conductor.stepCrochet * noteIndex);
var sustainNote:Note = new Note(songNote.time + noteTimeOffset, songNote.data, oldNote, true, strumlineStyle);
sustainNote.mustPress = mustHitNote;
sustainNote.data.noteKind = songNote.kind;
sustainNote.scrollFactor.set(0, 0);
if (sustainNote.mustPress)
{
if (playerStrumline != null)
{
// Align with the strumline arrow.
sustainNote.x = playerStrumline.getArrow(songNote.getDirection()).x;
}
else
{
// Assume strumline position.
sustainNote.x += FlxG.width / 2;
}
}
else
{
if (enemyStrumline != null)
{
sustainNote.x = enemyStrumline.getArrow(songNote.getDirection()).x;
}
else
{
// newNote.x += 0;
}
}
inactiveNotes.push(sustainNote);
oldNote = sustainNote;
}
}
// Sorting is an expensive operation.
// Assume it was done in the chart file.
/**
inactiveNotes.sort(function(a:Note, b:Note):Int
{
return SortUtil.byStrumtime(FlxSort.ASCENDING, a, b);
});
**/
}
function tweenCamIn():Void function tweenCamIn():Void
{ {
FlxTween.tween(FlxG.camera, {zoom: 1.3 * FlxCamera.defaultZoom}, (Conductor.stepCrochet * 4 / 1000), {ease: FlxEase.elasticInOut}); FlxTween.tween(FlxG.camera, {zoom: 1.3 * FlxCamera.defaultZoom}, (Conductor.stepCrochet * 4 / 1000), {ease: FlxEase.elasticInOut});
@ -986,7 +1330,7 @@ class PlayState extends MusicBeatState
vocals.pause(); vocals.pause();
FlxG.sound.music.play(); FlxG.sound.music.play();
Conductor.songPosition = FlxG.sound.music.time + Conductor.offset; Conductor.update(FlxG.sound.music.time + Conductor.offset);
if (vocalsFinished) if (vocalsFinished)
return; return;
@ -999,6 +1343,9 @@ class PlayState extends MusicBeatState
{ {
super.update(elapsed); super.update(elapsed);
if (criticalFailure)
return;
if (FlxG.keys.justPressed.U) if (FlxG.keys.justPressed.U)
{ {
// hack for HaxeUI generation, doesn't work unless persistentUpdate is false at state creation!! // hack for HaxeUI generation, doesn't work unless persistentUpdate is false at state creation!!
@ -1027,7 +1374,16 @@ class PlayState extends MusicBeatState
currentStage.resetStage(); currentStage.resetStage();
regenNoteData(); // loads the note data from start // Delete all notes and reset the arrays.
if (currentChart != null)
{
regenNoteData_NEW();
}
else
{
regenNoteData();
}
health = 1; health = 1;
songScore = 0; songScore = 0;
combo = 0; combo = 0;
@ -1058,7 +1414,7 @@ class PlayState extends MusicBeatState
if (Paths.SOUND_EXT == 'mp3') if (Paths.SOUND_EXT == 'mp3')
Conductor.offset = -13; // DO NOT FORGET TO REMOVE THE HARDCODE! WHEN I MAKE BETTER OFFSET SYSTEM! Conductor.offset = -13; // DO NOT FORGET TO REMOVE THE HARDCODE! WHEN I MAKE BETTER OFFSET SYSTEM!
Conductor.songPosition = FlxG.sound.music.time + Conductor.offset; // 20 is THE MILLISECONDS?? Conductor.update(FlxG.sound.music.time + Conductor.offset);
if (!isGamePaused) if (!isGamePaused)
{ {
@ -1177,7 +1533,7 @@ class PlayState extends MusicBeatState
} }
FlxG.watch.addQuick("songPos", Conductor.songPosition); FlxG.watch.addQuick("songPos", Conductor.songPosition);
if (currentSong.song == 'Fresh') if (currentSong != null && currentSong.song == 'Fresh')
{ {
switch (curBeat) switch (curBeat)
{ {
@ -1307,7 +1663,7 @@ class PlayState extends MusicBeatState
if (!daNote.mustPress && daNote.wasGoodHit && !daNote.tooLate) if (!daNote.mustPress && daNote.wasGoodHit && !daNote.tooLate)
{ {
if (currentSong.song != 'Tutorial') if (currentSong != null && currentSong.song != 'Tutorial')
camZooming = true; camZooming = true;
var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, daNote, combo, true); var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, daNote, combo, true);
@ -1324,7 +1680,7 @@ class PlayState extends MusicBeatState
else else
{ {
// Volume of DAD. // Volume of DAD.
if (currentSong.needsVoices) if (currentSong != null && currentSong.needsVoices)
vocals.volume = 1; vocals.volume = 1;
} }
} }
@ -1412,8 +1768,9 @@ class PlayState extends MusicBeatState
} }
daPos += 4 * (1000 * 60 / daBPM); daPos += 4 * (1000 * 60 / daBPM);
} }
Conductor.songPosition = FlxG.sound.music.time = daPos;
Conductor.songPosition += Conductor.offset; FlxG.sound.music.time = daPos;
Conductor.update(FlxG.sound.music.time + Conductor.offset);
updateCurStep(); updateCurStep();
resyncVocals(); resyncVocals();
} }
@ -1857,7 +2214,7 @@ class PlayState extends MusicBeatState
{ {
if (SongLoad.getSong()[Math.floor(curStep / 16)].changeBPM) if (SongLoad.getSong()[Math.floor(curStep / 16)].changeBPM)
{ {
Conductor.bpm = SongLoad.getSong()[Math.floor(curStep / 16)].bpm; Conductor.forceBPM(SongLoad.getSong()[Math.floor(curStep / 16)].bpm);
FlxG.log.add('CHANGED BPM!'); FlxG.log.add('CHANGED BPM!');
} }
} }
@ -2118,8 +2475,14 @@ class PlayState extends MusicBeatState
function performCleanup() function performCleanup()
{ {
// Uncache the song. // Uncache the song.
openfl.utils.Assets.cache.clear(Paths.inst(currentSong.song)); if (currentChart != null)
openfl.utils.Assets.cache.clear(Paths.voices(currentSong.song)); {
}
else
{
openfl.utils.Assets.cache.clear(Paths.inst(currentSong.song));
openfl.utils.Assets.cache.clear(Paths.voices(currentSong.song));
}
// Remove reference to stage and remove sprites from it to save memory. // Remove reference to stage and remove sprites from it to save memory.
if (currentStage != null) if (currentStage != null)

View file

@ -4,9 +4,11 @@ import flixel.FlxSprite;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.math.FlxPoint; import flixel.math.FlxPoint;
import flixel.tweens.FlxEase; import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import funkin.noteStuff.NoteBasic.NoteColor; import funkin.noteStuff.NoteBasic.NoteColor;
import funkin.noteStuff.NoteBasic.NoteDir; import funkin.noteStuff.NoteBasic.NoteDir;
import funkin.noteStuff.NoteBasic.NoteType; import funkin.noteStuff.NoteBasic.NoteType;
import funkin.ui.PreferencesMenu;
import funkin.util.Constants; import funkin.util.Constants;
/** /**

View file

@ -273,6 +273,10 @@ class BaseCharacter extends Bopper
{ {
if (!isOpponent) if (!isOpponent)
{ {
if (PlayState.instance.iconP1 == null)
{
trace('[WARN] Player 1 health icon not found!');
}
PlayState.instance.iconP1.characterId = _data.healthIcon.id; PlayState.instance.iconP1.characterId = _data.healthIcon.id;
PlayState.instance.iconP1.size.set(_data.healthIcon.scale, _data.healthIcon.scale); PlayState.instance.iconP1.size.set(_data.healthIcon.scale, _data.healthIcon.scale);
PlayState.instance.iconP1.offset.x = _data.healthIcon.offsets[0]; PlayState.instance.iconP1.offset.x = _data.healthIcon.offsets[0];
@ -281,6 +285,10 @@ class BaseCharacter extends Bopper
} }
else else
{ {
if (PlayState.instance.iconP2 == null)
{
trace('[WARN] Player 2 health icon not found!');
}
PlayState.instance.iconP2.characterId = _data.healthIcon.id; PlayState.instance.iconP2.characterId = _data.healthIcon.id;
PlayState.instance.iconP2.size.set(_data.healthIcon.scale, _data.healthIcon.scale); PlayState.instance.iconP2.size.set(_data.healthIcon.scale, _data.healthIcon.scale);
PlayState.instance.iconP2.offset.x = _data.healthIcon.offsets[0]; PlayState.instance.iconP2.offset.x = _data.healthIcon.offsets[0];

View file

@ -1,5 +1,7 @@
package funkin.play.song; package funkin.play.song;
import funkin.VoicesGroup;
import funkin.play.song.SongData.SongChartData;
import funkin.play.song.SongData.SongDataParser; import funkin.play.song.SongData.SongDataParser;
import funkin.play.song.SongData.SongEventData; import funkin.play.song.SongData.SongEventData;
import funkin.play.song.SongData.SongMetadata; import funkin.play.song.SongData.SongMetadata;
@ -45,14 +47,18 @@ class Song // implements IPlayStateScriptedClass
cacheCharts(); cacheCharts();
} }
function populateFromMetadata() /**
* Populate the song data from the provided metadata,
* including data from individual difficulties. Does not load chart data.
*/
function populateFromMetadata():Void
{ {
// Variations may have different artist, time format, generatedBy, etc. // Variations may have different artist, time format, generatedBy, etc.
for (metadata in _metadata) for (metadata in _metadata)
{ {
for (diffId in metadata.playData.difficulties) for (diffId in metadata.playData.difficulties)
{ {
var difficulty = new SongDifficulty(diffId, metadata.variation); var difficulty:SongDifficulty = new SongDifficulty(this, diffId, metadata.variation);
variations.push(metadata.variation); variations.push(metadata.variation);
@ -83,25 +89,27 @@ class Song // implements IPlayStateScriptedClass
/** /**
* Parse and cache the chart for all difficulties of this song. * Parse and cache the chart for all difficulties of this song.
*/ */
public function cacheCharts() public function cacheCharts():Void
{ {
trace('Caching ${variations.length} chart files for song $songId'); trace('Caching ${variations.length} chart files for song $songId');
for (variation in variations) for (variation in variations)
{ {
var chartData = SongDataParser.parseSongChartData(songId, variation); var chartData:SongChartData = SongDataParser.parseSongChartData(songId, variation);
var chartNotes = chartData.notes;
for (diffId in chartData.notes.keys()) for (diffId in chartNotes.keys())
{ {
trace(' Difficulty $diffId'); // Retrieve the cached difficulty data.
var difficulty = difficulties.get(diffId); var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
if (difficulty == null) if (difficulty == null)
{ {
trace('Could not find difficulty $diffId for song $songId'); trace('Could not find difficulty $diffId for song $songId');
continue; continue;
} }
// Add the chart data to the difficulty.
difficulty.notes = chartData.notes.get(diffId); difficulty.notes = chartData.notes.get(diffId);
difficulty.scrollSpeed = chartData.scrollSpeed.get(diffId); difficulty.scrollSpeed = chartData.getScrollSpeed(diffId);
difficulty.events = chartData.events; difficulty.events = chartData.events;
} }
} }
@ -111,7 +119,7 @@ class Song // implements IPlayStateScriptedClass
/** /**
* Retrieve the metadata for a specific difficulty, including the chart if it is loaded. * Retrieve the metadata for a specific difficulty, including the chart if it is loaded.
*/ */
public function getDifficulty(diffId:String):SongDifficulty public inline function getDifficulty(diffId:String):SongDifficulty
{ {
return difficulties.get(diffId); return difficulties.get(diffId);
} }
@ -119,7 +127,7 @@ class Song // implements IPlayStateScriptedClass
/** /**
* Purge the cached chart data for each difficulty of this song. * Purge the cached chart data for each difficulty of this song.
*/ */
public function clearCharts() public function clearCharts():Void
{ {
for (diff in difficulties) for (diff in difficulties)
{ {
@ -135,6 +143,11 @@ class Song // implements IPlayStateScriptedClass
class SongDifficulty class SongDifficulty
{ {
/**
* The parent song for this difficulty.
*/
public final song:Song;
/** /**
* The difficulty ID, such as `easy` or `hard`. * The difficulty ID, such as `easy` or `hard`.
*/ */
@ -162,8 +175,9 @@ class SongDifficulty
public var notes:Array<SongNoteData>; public var notes:Array<SongNoteData>;
public var events:Array<SongEventData>; public var events:Array<SongEventData>;
public function new(diffId:String, variation:String) public function new(song:Song, diffId:String, variation:String)
{ {
this.song = song;
this.difficulty = diffId; this.difficulty = diffId;
this.variation = variation; this.variation = variation;
} }
@ -172,4 +186,48 @@ class SongDifficulty
{ {
notes = null; notes = null;
} }
public function getStartingBPM():Float
{
if (timeChanges.length == 0)
{
return 0;
}
return timeChanges[0].bpm;
}
public function getPlayableChar(id:String):SongPlayableChar
{
return chars.get(id);
}
public inline function cacheInst()
{
// DEBUG: Remove this.
// FlxG.sound.cache(Paths.inst(this.song.songId));
FlxG.sound.cache(Paths.inst('bopeebo'));
}
public inline function playInst(volume:Float = 1.0, looped:Bool = false)
{
// DEBUG: Remove this.
// FlxG.sound.playMusic(Paths.inst(this.song.songId), volume, looped);
FlxG.sound.playMusic(Paths.inst('bopeebo'), volume, looped);
}
public inline function cacheVocals()
{
// DEBUG: Remove this.
// FlxG.sound.cache(Paths.voices(this.song.songId));
FlxG.sound.cache(Paths.voices('bopeebo'));
}
public inline function buildVocals(charId:String = "bf"):VoicesGroup
{
// DEBUG: Remove this.
// var result:VoicesGroup = new VoicesGroup(this.song.songId, null, false);
var result:VoicesGroup = new VoicesGroup('bopeebo', null, false);
return result;
}
} }

View file

@ -339,6 +339,11 @@ abstract SongNoteData(RawSongNoteData)
return Math.floor(this.d / strumlineSize); return Math.floor(this.d / strumlineSize);
} }
public inline function getMustHitNote(strumlineSize:Int = 4):Bool
{
return getStrumlineIndex(strumlineSize) == 0;
}
public var length(get, set):Float; public var length(get, set):Float;
public function get_length():Float public function get_length():Float
@ -522,7 +527,7 @@ abstract SongPlayableChar(RawSongPlayableChar)
} }
} }
typedef SongChartData = typedef RawSongChartData =
{ {
var version:Version; var version:Version;
@ -532,6 +537,32 @@ typedef SongChartData =
var generatedBy:String; var generatedBy:String;
}; };
@:forward
abstract SongChartData(RawSongChartData)
{
public function new(scrollSpeed:DynamicAccess<Float>, events:Array<SongEventData>, notes:DynamicAccess<Array<SongNoteData>>)
{
this = {
version: SongMigrator.CHART_VERSION,
events: events,
notes: notes,
scrollSpeed: scrollSpeed,
generatedBy: SongValidator.DEFAULT_GENERATEDBY
}
}
public function getScrollSpeed(diff:String = 'default'):Float
{
var result:Float = this.scrollSpeed.get(diff);
if (result == 0.0 && diff != 'default')
return getScrollSpeed('default');
return (result == 0.0) ? 1.0 : result;
}
}
typedef RawSongTimeChange = typedef RawSongTimeChange =
{ {
/** /**
@ -569,6 +600,17 @@ typedef RawSongTimeChange =
var bt:OneOfTwo<Int, Array<Int>>; var bt:OneOfTwo<Int, Array<Int>>;
} }
typedef RawConductorTimeChange =
{
> RawSongTimeChange,
/**
* The time in the song (in steps) that this change occurs at.
* This time is somewhat weird because the rate it increases is dependent on the BPM at that point in the song.
*/
public var st:Float;
}
/** /**
* Add aliases to the minimalized property names of the typedef, * Add aliases to the minimalized property names of the typedef,
* to improve readability. * to improve readability.
@ -667,6 +709,113 @@ abstract SongTimeChange(RawSongTimeChange)
} }
} }
abstract ConductorTimeChange(RawConductorTimeChange)
{
public function new(timeStamp:Float, beatTime:Int, bpm:Float, timeSignatureNum:Int = 4, timeSignatureDen:Int = 4, beatTuplets:Array<Int>)
{
this = {
t: timeStamp,
b: beatTime,
bpm: bpm,
n: timeSignatureNum,
d: timeSignatureDen,
bt: beatTuplets,
st: 0.0
}
}
public var timeStamp(get, set):Float;
public function get_timeStamp():Float
{
return this.t;
}
public function set_timeStamp(value:Float):Float
{
return this.t = value;
}
public var beatTime(get, set):Int;
public function get_beatTime():Int
{
return this.b;
}
public function set_beatTime(value:Int):Int
{
return this.b = value;
}
public var bpm(get, set):Float;
public function get_bpm():Float
{
return this.bpm;
}
public function set_bpm(value:Float):Float
{
return this.bpm = value;
}
public var timeSignatureNum(get, set):Int;
public function get_timeSignatureNum():Int
{
return this.n;
}
public function set_timeSignatureNum(value:Int):Int
{
return this.n = value;
}
public var timeSignatureDen(get, set):Int;
public function get_timeSignatureDen():Int
{
return this.d;
}
public function set_timeSignatureDen(value:Int):Int
{
return this.d = value;
}
public var beatTuplets(get, set):Array<Int>;
public function get_beatTuplets():Array<Int>
{
if (Std.isOfType(this.bt, Int))
{
return [this.bt];
}
else
{
return this.bt;
}
}
public function set_beatTuplets(value:Array<Int>):Array<Int>
{
return this.bt = value;
}
public var stepTime(get, set):Float;
public function get_stepTime():Float
{
return this.st;
}
public function set_stepTime(value:Float):Float
{
return this.st = value;
}
}
enum abstract SongTimeFormat(String) from String to String enum abstract SongTimeFormat(String) from String to String
{ {
var TICKS = "ticks"; var TICKS = "ticks";

View file

@ -5,6 +5,7 @@ import funkin.play.song.SongData.SongMetadata;
import funkin.play.song.SongData.SongPlayData; import funkin.play.song.SongData.SongPlayData;
import funkin.play.song.SongData.SongTimeChange; import funkin.play.song.SongData.SongTimeChange;
import funkin.play.song.SongData.SongTimeFormat; import funkin.play.song.SongData.SongTimeFormat;
import funkin.util.Constants;
/** /**
* For SongMetadata and SongChartData objects, * For SongMetadata and SongChartData objects,
@ -17,10 +18,16 @@ class SongValidator
public static final DEFAULT_TIMEFORMAT:SongTimeFormat = SongTimeFormat.MILLISECONDS; public static final DEFAULT_TIMEFORMAT:SongTimeFormat = SongTimeFormat.MILLISECONDS;
public static final DEFAULT_DIVISIONS:Int = -1; public static final DEFAULT_DIVISIONS:Int = -1;
public static final DEFAULT_LOOP:Bool = false; public static final DEFAULT_LOOP:Bool = false;
public static final DEFAULT_GENERATEDBY:String = "Unknown";
public static final DEFAULT_STAGE:String = "mainStage"; public static final DEFAULT_STAGE:String = "mainStage";
public static final DEFAULT_SCROLLSPEED:Float = 1.0; public static final DEFAULT_SCROLLSPEED:Float = 1.0;
public static var DEFAULT_GENERATEDBY(get, null):String;
static function get_DEFAULT_GENERATEDBY():String
{
return '${Constants.TITLE} - ${Constants.VERSION}';
}
/** /**
* Validates the fields of a SongMetadata object (excluding the version field). * Validates the fields of a SongMetadata object (excluding the version field).
* *