mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-03-20 17:09:21 +00:00
WIP on conductor rework
This commit is contained in:
parent
7fbdbcdfad
commit
9ebb566b2a
|
@ -1,11 +1,10 @@
|
|||
package funkin;
|
||||
|
||||
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 =
|
||||
{
|
||||
var stepTime:Int;
|
||||
|
@ -16,12 +15,40 @@ typedef BPMChangeEvent =
|
|||
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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -40,19 +67,62 @@ class Conductor
|
|||
return crochet / 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current position in the song in milliseconds.
|
||||
*/
|
||||
public static var songPosition:Float;
|
||||
public static var currentBeat(get, null):Float;
|
||||
|
||||
static function get_currentBeat():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 visualOffset:Float = 0;
|
||||
public static var audioOffset: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)
|
||||
{
|
||||
|
@ -80,4 +150,34 @@ class Conductor
|
|||
}
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import funkin.freeplayStuff.FreeplayScore;
|
|||
import funkin.freeplayStuff.SongMenuItem;
|
||||
import funkin.play.HealthIcon;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
import funkin.shaderslmfao.AngleMask;
|
||||
import funkin.shaderslmfao.PureColor;
|
||||
import funkin.shaderslmfao.StrokeShader;
|
||||
|
@ -97,7 +98,7 @@ class FreeplayState extends MusicBeatSubstate
|
|||
}
|
||||
|
||||
if (StoryMenuState.weekUnlocked[2] || isDebug)
|
||||
addWeek(['Bopeebo', 'Fresh', 'Dadbattle'], 1, ['dad']);
|
||||
addWeek(['Bopeebo', 'Bopeebo_new', 'Fresh', 'Dadbattle'], 1, ['dad']);
|
||||
|
||||
if (StoryMenuState.weekUnlocked[2] || isDebug)
|
||||
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_NEW = SongDataParser.fetchSong(songs[curSelected].songName.toLowerCase());
|
||||
PlayState.isStoryMode = false;
|
||||
PlayState.storyDifficulty = curDifficulty;
|
||||
PlayState.storyDifficulty_NEW = 'easy';
|
||||
// SongLoad.curDiff = Highscore.formatSong()
|
||||
|
||||
SongLoad.curDiff = switch (curDifficulty)
|
||||
|
@ -562,6 +565,7 @@ class FreeplayState extends MusicBeatSubstate
|
|||
intendedScore = FlxG.random.int(0, 100000);
|
||||
|
||||
PlayState.storyDifficulty = curDifficulty;
|
||||
PlayState.storyDifficulty_NEW = 'easy';
|
||||
|
||||
grpDifficulties.group.forEach(function(spr)
|
||||
{
|
||||
|
|
|
@ -191,8 +191,10 @@ class InitState extends FlxTransitionableState
|
|||
var dif = getDif();
|
||||
|
||||
PlayState.currentSong = SongLoad.loadFromJson(song, song);
|
||||
PlayState.currentSong_NEW = SongDataParser.fetchSong(song);
|
||||
PlayState.isStoryMode = isStoryMode;
|
||||
PlayState.storyDifficulty = dif;
|
||||
PlayState.storyDifficulty_NEW = 'easy';
|
||||
SongLoad.curDiff = switch (dif)
|
||||
{
|
||||
case 0: 'easy';
|
||||
|
|
|
@ -70,7 +70,7 @@ class LatencyState extends MusicBeatSubstate
|
|||
|
||||
// funnyStatsGraph.hi
|
||||
|
||||
Conductor.bpm = 60;
|
||||
Conductor.forceBPM(60);
|
||||
|
||||
noteGrp = new FlxTypedGroup<Note>();
|
||||
add(noteGrp);
|
||||
|
|
|
@ -2,6 +2,8 @@ package funkin;
|
|||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.math.FlxMath;
|
||||
import funkin.noteStuff.NoteBasic.NoteData;
|
||||
import funkin.noteStuff.NoteBasic.NoteType;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.Strumline.StrumlineStyle;
|
||||
import funkin.shaderslmfao.ColorSwap;
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
package funkin;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxSubState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import flixel.system.FlxSound;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.Controls.Control;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
|
||||
class PauseSubState extends MusicBeatSubstate
|
||||
{
|
||||
|
@ -61,7 +59,14 @@ class PauseSubState extends MusicBeatSubstate
|
|||
add(metaDataGrp);
|
||||
|
||||
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.setFormat(Paths.font("vcr.ttf"), 32);
|
||||
levelInfo.updateHitbox();
|
||||
|
@ -180,9 +185,11 @@ class PauseSubState extends MusicBeatSubstate
|
|||
close();
|
||||
case "EASY" | 'NORMAL' | "HARD":
|
||||
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();
|
||||
|
||||
PlayState.storyDifficulty = curSelected;
|
||||
PlayState.storyDifficulty_NEW = 'easy';
|
||||
|
||||
PlayState.needsReset = true;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package funkin;
|
|||
|
||||
import funkin.Section.SwagSection;
|
||||
import funkin.noteStuff.NoteBasic.NoteData;
|
||||
import funkin.play.PlayState;
|
||||
import haxe.Json;
|
||||
import lime.utils.Assets;
|
||||
|
||||
|
@ -47,7 +48,21 @@ class SongLoad
|
|||
|
||||
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("}"))
|
||||
{
|
||||
|
@ -112,6 +127,11 @@ class SongLoad
|
|||
|
||||
public static function getSpeed(?diff:String):Float
|
||||
{
|
||||
if (PlayState.instance != null && PlayState.instance.currentChart != null)
|
||||
{
|
||||
return getSpeed_NEW(diff);
|
||||
}
|
||||
|
||||
if (diff == null)
|
||||
diff = SongLoad.curDiff;
|
||||
|
||||
|
@ -137,6 +157,14 @@ class SongLoad
|
|||
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
|
||||
{
|
||||
return {
|
||||
|
|
|
@ -12,6 +12,7 @@ import flixel.util.FlxColor;
|
|||
import flixel.util.FlxTimer;
|
||||
import funkin.MenuItem.WeekType;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
import lime.net.curl.CURLCode;
|
||||
import openfl.Assets;
|
||||
|
||||
|
@ -372,10 +373,12 @@ class StoryMenuState extends MusicBeatState
|
|||
selectedWeek = true;
|
||||
|
||||
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.campaignScore = 0;
|
||||
|
||||
PlayState.storyDifficulty = curDifficulty;
|
||||
PlayState.storyDifficulty_NEW = 'easy';
|
||||
SongLoad.curDiff = switch (curDifficulty)
|
||||
{
|
||||
case 0:
|
||||
|
|
|
@ -140,7 +140,7 @@ class TitleState extends MusicBeatState
|
|||
{
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu'), 0);
|
||||
FlxG.sound.music.fadeIn(4, 0, 0.7);
|
||||
Conductor.bpm = Constants.FREAKY_MENU_BPM;
|
||||
Conductor.forceBPM(Constants.FREAKY_MENU_BPM);
|
||||
}
|
||||
|
||||
persistentUpdate = true;
|
||||
|
@ -474,7 +474,7 @@ class TitleState extends MusicBeatState
|
|||
var spec:SpectogramSprite = new SpectogramSprite(FlxG.sound.music);
|
||||
add(spec);
|
||||
|
||||
Conductor.bpm = 190;
|
||||
Conductor.forceBPM(190);
|
||||
FlxG.camera.flash(FlxColor.WHITE, 1);
|
||||
FlxG.sound.play(Paths.sound('confirmMenu'), 0.7);
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@ class ChartingState extends MusicBeatState
|
|||
updateGrid();
|
||||
|
||||
loadSong(_song.song);
|
||||
Conductor.bpm = _song.bpm;
|
||||
// Conductor.bpm = _song.bpm;
|
||||
Conductor.mapBPMChanges(_song);
|
||||
|
||||
bpmTxt = new FlxText(1000, 50, 0, "", 16);
|
||||
|
@ -549,7 +549,7 @@ class ChartingState extends MusicBeatState
|
|||
{
|
||||
tempBpm = nums.value;
|
||||
Conductor.mapBPMChanges(_song);
|
||||
Conductor.bpm = nums.value;
|
||||
Conductor.forceBPM(nums.value);
|
||||
}
|
||||
else if (wname == 'note_susLength')
|
||||
{
|
||||
|
@ -1223,7 +1223,7 @@ class ChartingState extends MusicBeatState
|
|||
|
||||
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!');
|
||||
}
|
||||
else
|
||||
|
@ -1233,7 +1233,7 @@ class ChartingState extends MusicBeatState
|
|||
for (i in 0...curSection)
|
||||
if (SongLoad.getSong()[i].changeBPM)
|
||||
daBPM = SongLoad.getSong()[i].bpm;
|
||||
Conductor.bpm = daBPM;
|
||||
Conductor.forceBPM(daBPM);
|
||||
}
|
||||
|
||||
/* // PORT BULLSHIT, INCASE THERE'S NO SUSTAIN DATA FOR A NOTE
|
||||
|
|
|
@ -5,6 +5,7 @@ import flixel.addons.effects.FlxTrail;
|
|||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxDirectionFlags;
|
||||
import funkin.audiovis.PolygonSpectogram;
|
||||
import funkin.noteStuff.NoteBasic.NoteData;
|
||||
|
||||
|
@ -35,7 +36,7 @@ class PicoFight extends MusicBeatState
|
|||
FlxG.sound.playMusic(Paths.inst("blazin"));
|
||||
|
||||
SongLoad.loadFromJson('blazin', "blazin");
|
||||
Conductor.bpm = SongLoad.songData.bpm;
|
||||
Conductor.forceBPM(SongLoad.songData.bpm);
|
||||
|
||||
for (dumbassSection in SongLoad.songData.noteMap['hard'])
|
||||
{
|
||||
|
|
|
@ -28,6 +28,10 @@ import funkin.play.Strumline.StrumlineStyle;
|
|||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.character.CharacterData;
|
||||
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.StageData;
|
||||
import funkin.ui.PopUpStuff;
|
||||
|
@ -62,6 +66,8 @@ class PlayState extends MusicBeatState
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -116,6 +122,8 @@ class PlayState extends MusicBeatState
|
|||
*/
|
||||
public var currentStage:Stage = null;
|
||||
|
||||
public var currentChart(get, null):SongDifficulty;
|
||||
|
||||
/**
|
||||
* The internal ID of the currently active Stage.
|
||||
* Used to retrieve the data required to build the `currentStage`.
|
||||
|
@ -166,6 +174,12 @@ class PlayState extends MusicBeatState
|
|||
*/
|
||||
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
|
||||
*/
|
||||
|
@ -244,6 +258,7 @@ class PlayState extends MusicBeatState
|
|||
public static var storyWeek:Int = 0;
|
||||
public static var storyPlaylist:Array<String> = [];
|
||||
public static var storyDifficulty:Int = 1;
|
||||
public static var storyDifficulty_NEW:String = "normal";
|
||||
public static var seenCutscene:Bool = false;
|
||||
public static var campaignScore:Int = 0;
|
||||
|
||||
|
@ -279,8 +294,10 @@ class PlayState extends MusicBeatState
|
|||
{
|
||||
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.",
|
||||
"Error loading PlayState");
|
||||
FlxG.switchState(new MainMenuState());
|
||||
|
@ -308,28 +325,48 @@ class PlayState extends MusicBeatState
|
|||
FlxG.sound.music.stop();
|
||||
|
||||
// Prepare the current song to be played.
|
||||
FlxG.sound.cache(Paths.inst(currentSong.song));
|
||||
FlxG.sound.cache(Paths.voices(currentSong.song));
|
||||
if (currentChart != null)
|
||||
{
|
||||
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.
|
||||
initCameras();
|
||||
|
||||
if (currentSong == null)
|
||||
currentSong = SongLoad.loadFromJson('tutorial');
|
||||
|
||||
Conductor.mapBPMChanges(currentSong);
|
||||
Conductor.bpm = currentSong.bpm;
|
||||
|
||||
switch (currentSong.song.toLowerCase())
|
||||
if (currentSong == null && currentSong_NEW == null)
|
||||
{
|
||||
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'));
|
||||
currentSong = SongLoad.loadFromJson('tutorial');
|
||||
}
|
||||
|
||||
if (currentSong_NEW != null)
|
||||
{
|
||||
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)
|
||||
|
@ -379,7 +416,14 @@ class PlayState extends MusicBeatState
|
|||
|
||||
add(grpNoteSplashes);
|
||||
|
||||
generateSong();
|
||||
if (currentSong_NEW != null)
|
||||
{
|
||||
generateSong_NEW();
|
||||
}
|
||||
else
|
||||
{
|
||||
generateSong();
|
||||
}
|
||||
|
||||
resetCamera();
|
||||
|
||||
|
@ -442,6 +486,13 @@ class PlayState extends MusicBeatState
|
|||
#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.
|
||||
*/
|
||||
|
@ -460,6 +511,12 @@ class PlayState extends MusicBeatState
|
|||
|
||||
function initStage()
|
||||
{
|
||||
if (currentSong_NEW != null)
|
||||
{
|
||||
initStage_NEW();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Move stageId to the song file.
|
||||
switch (currentSong.song.toLowerCase())
|
||||
{
|
||||
|
@ -487,9 +544,6 @@ class PlayState extends MusicBeatState
|
|||
currentStageId = 'schoolEvil';
|
||||
case 'guns' | 'stress' | 'ugh':
|
||||
currentStageId = 'tankmanBattlefield';
|
||||
case 'experimental-phase' | 'perfection':
|
||||
// SERIOUSLY REVAMP THE CHART FORMAT ALREADY
|
||||
currentStageId = "breakout";
|
||||
default:
|
||||
currentStageId = "mainStage";
|
||||
}
|
||||
|
@ -497,8 +551,33 @@ class PlayState extends MusicBeatState
|
|||
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()
|
||||
{
|
||||
if (currentSong_NEW != null)
|
||||
{
|
||||
initCharacters_NEW();
|
||||
return;
|
||||
}
|
||||
|
||||
iconP1 = new HealthIcon(currentSong.player1, 0);
|
||||
iconP1.y = healthBar.y - (iconP1.height / 2);
|
||||
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,
|
||||
* then reloads all the stages.
|
||||
|
@ -794,7 +978,14 @@ class PlayState extends MusicBeatState
|
|||
// if (FlxG.sound.music != null)
|
||||
// FlxG.sound.music.play(true);
|
||||
// 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;
|
||||
|
@ -813,7 +1004,7 @@ class PlayState extends MusicBeatState
|
|||
{
|
||||
// FlxG.log.add(ChartParser.parse());
|
||||
|
||||
Conductor.bpm = currentSong.bpm;
|
||||
Conductor.forceBPM(currentSong.bpm);
|
||||
|
||||
currentSong.song = currentSong.song;
|
||||
|
||||
|
@ -836,6 +1027,32 @@ class PlayState extends MusicBeatState
|
|||
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
|
||||
{
|
||||
// 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
|
||||
{
|
||||
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();
|
||||
FlxG.sound.music.play();
|
||||
Conductor.songPosition = FlxG.sound.music.time + Conductor.offset;
|
||||
Conductor.update(FlxG.sound.music.time + Conductor.offset);
|
||||
|
||||
if (vocalsFinished)
|
||||
return;
|
||||
|
@ -999,6 +1343,9 @@ class PlayState extends MusicBeatState
|
|||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (criticalFailure)
|
||||
return;
|
||||
|
||||
if (FlxG.keys.justPressed.U)
|
||||
{
|
||||
// hack for HaxeUI generation, doesn't work unless persistentUpdate is false at state creation!!
|
||||
|
@ -1027,7 +1374,16 @@ class PlayState extends MusicBeatState
|
|||
|
||||
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;
|
||||
songScore = 0;
|
||||
combo = 0;
|
||||
|
@ -1058,7 +1414,7 @@ class PlayState extends MusicBeatState
|
|||
if (Paths.SOUND_EXT == 'mp3')
|
||||
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)
|
||||
{
|
||||
|
@ -1177,7 +1533,7 @@ class PlayState extends MusicBeatState
|
|||
}
|
||||
FlxG.watch.addQuick("songPos", Conductor.songPosition);
|
||||
|
||||
if (currentSong.song == 'Fresh')
|
||||
if (currentSong != null && currentSong.song == 'Fresh')
|
||||
{
|
||||
switch (curBeat)
|
||||
{
|
||||
|
@ -1307,7 +1663,7 @@ class PlayState extends MusicBeatState
|
|||
|
||||
if (!daNote.mustPress && daNote.wasGoodHit && !daNote.tooLate)
|
||||
{
|
||||
if (currentSong.song != 'Tutorial')
|
||||
if (currentSong != null && currentSong.song != 'Tutorial')
|
||||
camZooming = true;
|
||||
|
||||
var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, daNote, combo, true);
|
||||
|
@ -1324,7 +1680,7 @@ class PlayState extends MusicBeatState
|
|||
else
|
||||
{
|
||||
// Volume of DAD.
|
||||
if (currentSong.needsVoices)
|
||||
if (currentSong != null && currentSong.needsVoices)
|
||||
vocals.volume = 1;
|
||||
}
|
||||
}
|
||||
|
@ -1412,8 +1768,9 @@ class PlayState extends MusicBeatState
|
|||
}
|
||||
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();
|
||||
resyncVocals();
|
||||
}
|
||||
|
@ -1857,7 +2214,7 @@ class PlayState extends MusicBeatState
|
|||
{
|
||||
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!');
|
||||
}
|
||||
}
|
||||
|
@ -2118,8 +2475,14 @@ class PlayState extends MusicBeatState
|
|||
function performCleanup()
|
||||
{
|
||||
// Uncache the song.
|
||||
openfl.utils.Assets.cache.clear(Paths.inst(currentSong.song));
|
||||
openfl.utils.Assets.cache.clear(Paths.voices(currentSong.song));
|
||||
if (currentChart != null)
|
||||
{
|
||||
}
|
||||
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.
|
||||
if (currentStage != null)
|
||||
|
|
|
@ -4,9 +4,11 @@ import flixel.FlxSprite;
|
|||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import funkin.noteStuff.NoteBasic.NoteColor;
|
||||
import funkin.noteStuff.NoteBasic.NoteDir;
|
||||
import funkin.noteStuff.NoteBasic.NoteType;
|
||||
import funkin.ui.PreferencesMenu;
|
||||
import funkin.util.Constants;
|
||||
|
||||
/**
|
||||
|
|
|
@ -273,6 +273,10 @@ class BaseCharacter extends Bopper
|
|||
{
|
||||
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.size.set(_data.healthIcon.scale, _data.healthIcon.scale);
|
||||
PlayState.instance.iconP1.offset.x = _data.healthIcon.offsets[0];
|
||||
|
@ -281,6 +285,10 @@ class BaseCharacter extends Bopper
|
|||
}
|
||||
else
|
||||
{
|
||||
if (PlayState.instance.iconP2 == null)
|
||||
{
|
||||
trace('[WARN] Player 2 health icon not found!');
|
||||
}
|
||||
PlayState.instance.iconP2.characterId = _data.healthIcon.id;
|
||||
PlayState.instance.iconP2.size.set(_data.healthIcon.scale, _data.healthIcon.scale);
|
||||
PlayState.instance.iconP2.offset.x = _data.healthIcon.offsets[0];
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package funkin.play.song;
|
||||
|
||||
import funkin.VoicesGroup;
|
||||
import funkin.play.song.SongData.SongChartData;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
import funkin.play.song.SongData.SongEventData;
|
||||
import funkin.play.song.SongData.SongMetadata;
|
||||
|
@ -45,14 +47,18 @@ class Song // implements IPlayStateScriptedClass
|
|||
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.
|
||||
for (metadata in _metadata)
|
||||
{
|
||||
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);
|
||||
|
||||
|
@ -83,25 +89,27 @@ class Song // implements IPlayStateScriptedClass
|
|||
/**
|
||||
* 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');
|
||||
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');
|
||||
var difficulty = difficulties.get(diffId);
|
||||
// Retrieve the cached difficulty data.
|
||||
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
|
||||
if (difficulty == null)
|
||||
{
|
||||
trace('Could not find difficulty $diffId for song $songId');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add the chart data to the difficulty.
|
||||
difficulty.notes = chartData.notes.get(diffId);
|
||||
difficulty.scrollSpeed = chartData.scrollSpeed.get(diffId);
|
||||
difficulty.scrollSpeed = chartData.getScrollSpeed(diffId);
|
||||
|
||||
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.
|
||||
*/
|
||||
public function getDifficulty(diffId:String):SongDifficulty
|
||||
public inline function getDifficulty(diffId:String):SongDifficulty
|
||||
{
|
||||
return difficulties.get(diffId);
|
||||
}
|
||||
|
@ -119,7 +127,7 @@ class Song // implements IPlayStateScriptedClass
|
|||
/**
|
||||
* Purge the cached chart data for each difficulty of this song.
|
||||
*/
|
||||
public function clearCharts()
|
||||
public function clearCharts():Void
|
||||
{
|
||||
for (diff in difficulties)
|
||||
{
|
||||
|
@ -135,6 +143,11 @@ class Song // implements IPlayStateScriptedClass
|
|||
|
||||
class SongDifficulty
|
||||
{
|
||||
/**
|
||||
* The parent song for this difficulty.
|
||||
*/
|
||||
public final song:Song;
|
||||
|
||||
/**
|
||||
* The difficulty ID, such as `easy` or `hard`.
|
||||
*/
|
||||
|
@ -162,8 +175,9 @@ class SongDifficulty
|
|||
public var notes:Array<SongNoteData>;
|
||||
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.variation = variation;
|
||||
}
|
||||
|
@ -172,4 +186,48 @@ class SongDifficulty
|
|||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -339,6 +339,11 @@ abstract SongNoteData(RawSongNoteData)
|
|||
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 function get_length():Float
|
||||
|
@ -522,7 +527,7 @@ abstract SongPlayableChar(RawSongPlayableChar)
|
|||
}
|
||||
}
|
||||
|
||||
typedef SongChartData =
|
||||
typedef RawSongChartData =
|
||||
{
|
||||
var version:Version;
|
||||
|
||||
|
@ -532,6 +537,32 @@ typedef SongChartData =
|
|||
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 =
|
||||
{
|
||||
/**
|
||||
|
@ -569,6 +600,17 @@ typedef RawSongTimeChange =
|
|||
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,
|
||||
* 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
|
||||
{
|
||||
var TICKS = "ticks";
|
||||
|
|
|
@ -5,6 +5,7 @@ import funkin.play.song.SongData.SongMetadata;
|
|||
import funkin.play.song.SongData.SongPlayData;
|
||||
import funkin.play.song.SongData.SongTimeChange;
|
||||
import funkin.play.song.SongData.SongTimeFormat;
|
||||
import funkin.util.Constants;
|
||||
|
||||
/**
|
||||
* For SongMetadata and SongChartData objects,
|
||||
|
@ -17,10 +18,16 @@ class SongValidator
|
|||
public static final DEFAULT_TIMEFORMAT:SongTimeFormat = SongTimeFormat.MILLISECONDS;
|
||||
public static final DEFAULT_DIVISIONS:Int = -1;
|
||||
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_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).
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue