1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2024-08-18 14:05:06 +00:00

Merge pull request #109 from FunkinCrew/feature/week-7-cutscenes

Week 7 Cutscenes
This commit is contained in:
Cameron Taylor 2023-06-28 21:15:23 -04:00 committed by GitHub
commit 2ce3228bd5
23 changed files with 664 additions and 306 deletions

View file

@ -5,14 +5,14 @@ import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.util.FlxTimer;
class ComboCounter extends FlxTypedSpriteGroup<FlxSprite>
class ComboMilestone extends FlxTypedSpriteGroup<FlxSprite>
{
var effectStuff:FlxSprite;
var wasComboSetup:Bool = false;
var daCombo:Int = 0;
var grpNumbers:FlxTypedGroup<ComboNumber>;
var grpNumbers:FlxTypedGroup<ComboMilestoneNumber>;
var onScreenTime:Float = 0;
@ -23,7 +23,7 @@ class ComboCounter extends FlxTypedSpriteGroup<FlxSprite>
this.daCombo = daCombo;
effectStuff = new FlxSprite(0, 0);
effectStuff.frames = Paths.getSparrowAtlas('noteCombo');
effectStuff.frames = Paths.getSparrowAtlas('comboMilestone');
effectStuff.animation.addByPrefix('funny', 'NOTE COMBO animation', 24, false);
effectStuff.animation.play('funny');
effectStuff.antialiasing = true;
@ -33,7 +33,7 @@ class ComboCounter extends FlxTypedSpriteGroup<FlxSprite>
effectStuff.setGraphicSize(Std.int(effectStuff.width * 0.7));
add(effectStuff);
grpNumbers = new FlxTypedGroup<ComboNumber>();
grpNumbers = new FlxTypedGroup<ComboMilestoneNumber>();
// add(grpNumbers);
}
@ -41,7 +41,7 @@ class ComboCounter extends FlxTypedSpriteGroup<FlxSprite>
{
if (onScreenTime < 0.9)
{
new FlxTimer().start((Conductor.crochet / 1000) * 0.25, function(tmr) {
new FlxTimer().start((Conductor.beatLengthMs / 1000) * 0.25, function(tmr) {
forceFinish();
});
}
@ -62,14 +62,14 @@ class ComboCounter extends FlxTypedSpriteGroup<FlxSprite>
if (effectStuff.animation.curAnim.curFrame == 18)
{
grpNumbers.forEach(function(spr:ComboNumber) {
grpNumbers.forEach(function(spr:ComboMilestoneNumber) {
spr.animation.reset();
});
}
if (effectStuff.animation.curAnim.curFrame == 20)
{
grpNumbers.forEach(function(spr:ComboNumber) {
grpNumbers.forEach(function(spr:ComboMilestoneNumber) {
spr.kill();
});
}
@ -86,7 +86,7 @@ class ComboCounter extends FlxTypedSpriteGroup<FlxSprite>
while (daCombo > 0)
{
var comboNumber:ComboNumber = new ComboNumber(450 - (100 * loopNum), 20 + 14 * loopNum, daCombo % 10);
var comboNumber:ComboMilestoneNumber = new ComboMilestoneNumber(450 - (100 * loopNum), 20 + 14 * loopNum, daCombo % 10);
comboNumber.setGraphicSize(Std.int(comboNumber.width * 0.7));
grpNumbers.add(comboNumber);
add(comboNumber);
@ -95,27 +95,17 @@ class ComboCounter extends FlxTypedSpriteGroup<FlxSprite>
daCombo = Math.floor(daCombo / 10);
}
// var comboNumber:ComboNumber = new ComboNumber(420, 0, 0);
// add to both, in the group just for ez organize/accessing
// grpNumbers.add(comboNumber);
// add(comboNumber);
// var comboNumber2:ComboNumber = new ComboNumber(420 - 134, 44, 0);
// grpNumbers.add(comboNumber2);
// add(comboNumber2);
}
}
class ComboNumber extends FlxSprite
class ComboMilestoneNumber extends FlxSprite
{
public function new(x:Float, y:Float, digit:Int)
{
super(x - 20, y);
var stringNum:String = Std.string(digit);
frames = Paths.getSparrowAtlas('noteComboNumbers');
frames = Paths.getSparrowAtlas('comboMilestoneNumbers');
animation.addByPrefix(stringNum, stringNum, 24, false);
animation.play(stringNum);
antialiasing = true;

View file

@ -1,9 +1,8 @@
package funkin;
import flixel.util.FlxSignal;
import funkin.SongLoad.SwagSong;
import funkin.play.song.Song.SongDifficulty;
import funkin.play.song.SongData.SongTimeChange;
import flixel.util.FlxSignal;
import funkin.play.song.Song.SongDifficulty;
typedef BPMChangeEvent =
{
@ -12,18 +11,27 @@ typedef BPMChangeEvent =
var bpm:Float;
}
/**
* A global source of truth for timing information.
*/
class Conductor
{
/**
* 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.
*/
static var timeChanges:Array<SongTimeChange> = [];
static final STEPS_PER_BEAT:Int = 4;
/**
* The current time change.
*/
static var currentTimeChange:SongTimeChange;
// onBeatHit is called every quarter note
// onStepHit is called every sixteenth note
// 4/4 = 4 beats per measure = 16 steps per measure
// 120 BPM = 120 quarter notes per minute = 2 onBeatHit per second
// 120 BPM = 480 sixteenth notes per minute = 8 onStepHit per second
// 60 BPM = 60 quarter notes per minute = 1 onBeatHit per second
// 60 BPM = 240 sixteenth notes per minute = 4 onStepHit per second
// 3/4 = 3 beats per measure = 12 steps per measure
// (IDENTICAL TO 4/4 but shorter measure length)
// 120 BPM = 120 quarter notes per minute = 2 onBeatHit per second
// 120 BPM = 480 sixteenth notes per minute = 8 onStepHit per second
// 60 BPM = 60 quarter notes per minute = 1 onBeatHit per second
// 60 BPM = 240 sixteenth notes per minute = 4 onStepHit per second
// 7/8 = 3.5 beats per measure = 14 steps per measure
/**
* The current position in the song in milliseconds.
@ -47,8 +55,25 @@ class Conductor
static var bpmOverride:Null<Float> = null;
// OLD, replaced with timeChanges.
public static var bpmChangeMap:Array<BPMChangeEvent> = [];
/**
* Current position in the song, in whole measures.
*/
public static var currentMeasure(default, null):Int;
/**
* Current position in the song, in whole beats.
**/
public static var currentBeat(default, null):Int;
/**
* Current position in the song, in whole steps.
*/
public static var currentStep(default, null):Int;
/**
* Current position in the song, in steps and fractions of a step.
*/
public static var currentStepTime(default, null):Float;
/**
* Duration of a measure in milliseconds. Calculated based on bpm.
@ -57,29 +82,33 @@ class Conductor
static function get_measureLengthMs():Float
{
return crochet * timeSignatureNumerator;
return beatLengthMs * timeSignatureNumerator;
}
/**
* Duration of a beat in millisecond. Calculated based on bpm.
* Duration of a beat (quarter note) in milliseconds. Calculated based on bpm.
*/
public static var crochet(get, null):Float;
public static var beatLengthMs(get, null):Float;
static function get_crochet():Float
static function get_beatLengthMs():Float
{
// Tied directly to BPM.
return ((60 / bpm) * 1000);
}
/**
* Duration of a step (quarter) in milliseconds. Calculated based on bpm.
* Duration of a step (sixteenth) in milliseconds. Calculated based on bpm.
*/
public static var stepCrochet(get, null):Float;
public static var stepLengthMs(get, null):Float;
static function get_stepCrochet():Float
static function get_stepLengthMs():Float
{
return crochet / timeSignatureNumerator;
return beatLengthMs / STEPS_PER_BEAT;
}
/**
* The numerator of the current time signature (number of notes in a measure)
*/
public static var timeSignatureNumerator(get, null):Int;
static function get_timeSignatureNumerator():Int
@ -89,6 +118,9 @@ class Conductor
return currentTimeChange.timeSignatureNum;
}
/**
* The numerator of the current time signature (length of notes in a measure)
*/
public static var timeSignatureDenominator(get, null):Int;
static function get_timeSignatureDenominator():Int
@ -98,30 +130,57 @@ class Conductor
return currentTimeChange.timeSignatureDen;
}
/**
* Current position in the song, in beats.
**/
public static var currentBeat(default, null):Int;
/**
* Current position in the song, in steps.
*/
public static var currentStep(default, null):Int;
/**
* Current position in the song, in steps and fractions of a step.
*/
public static var currentStepTime(default, null):Float;
public static var beatHit(default, null):FlxSignal = new FlxSignal();
public static var stepHit(default, null):FlxSignal = new FlxSignal();
public static var lastSongPos:Float;
public static var visualOffset:Float = 0;
public static var audioOffset:Float = 0;
public static var offset:Float = 0;
// TODO: Add code to update this.
// TODO: What's the difference between visualOffset and audioOffset?
public static var visualOffset:Float = 0;
public static var audioOffset:Float = 0;
//
// Signals
//
/**
* Signal that is dispatched every measure.
* At 120 BPM 4/4, this is dispatched every 2 seconds.
* At 120 BPM 3/4, this is dispatched every 1.5 seconds.
*/
public static var measureHit(default, null):FlxSignal = new FlxSignal();
/**
* Signal that is dispatched every beat.
* At 120 BPM 4/4, this is dispatched every 0.5 seconds.
* At 120 BPM 3/4, this is dispatched every 0.5 seconds.
*/
public static var beatHit(default, null):FlxSignal = new FlxSignal();
/**
* Signal that is dispatched when a step is hit.
* At 120 BPM 4/4, this is dispatched every 0.125 seconds.
* At 120 BPM 3/4, this is dispatched every 0.125 seconds.
*/
public static var stepHit(default, null):FlxSignal = new FlxSignal();
//
// Internal Variables
//
/**
* 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.
*/
static var timeChanges:Array<SongTimeChange> = [];
/**
* The current time change.
*/
static var currentTimeChange:SongTimeChange;
public static var lastSongPos:Float;
/**
* The number of beats (whole notes) in a measure.
*/
public static var beatsPerMeasure(get, null):Int;
static function get_beatsPerMeasure():Int
@ -129,33 +188,17 @@ class Conductor
return timeSignatureNumerator;
}
/**
* The number of steps (quarter-notes) in a measure.
*/
public static var stepsPerMeasure(get, null):Int;
static function get_stepsPerMeasure():Int
{
// Is this always x4?
// This is always 4, b
return timeSignatureNumerator * 4;
}
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;
}
/**
* Forcibly defines the current BPM of the song.
* Useful for things like the chart editor that need to manipulate BPM in real time.
@ -165,11 +208,16 @@ class Conductor
* WARNING: Avoid this for things like setting the BPM of the title screen music,
* you should have a metadata file for it instead.
*/
public static function forceBPM(?bpm:Float = null)
public static function forceBPM(?bpm:Float = null):Void
{
if (bpm != null) trace('[CONDUCTOR] Forcing BPM to ' + bpm);
if (bpm != null)
{
trace('[CONDUCTOR] Forcing BPM to ' + bpm);
}
else
{
trace('[CONDUCTOR] Resetting BPM to default');
}
Conductor.bpmOverride = bpm;
}
@ -180,15 +228,15 @@ class Conductor
* @param songPosition The current position in the song in milliseconds.
* Leave blank to use the FlxG.sound.music position.
*/
public static function update(songPosition:Float = null)
public static function update(songPosition:Float = null):Void
{
if (songPosition == null) songPosition = (FlxG.sound.music != null) ? FlxG.sound.music.time + Conductor.offset : 0.0;
var oldBeat = currentBeat;
var oldStep = currentStep;
var oldMeasure:Int = currentMeasure;
var oldBeat:Int = currentBeat;
var oldStep:Int = currentStep;
Conductor.songPosition = songPosition;
// Conductor.bpm = Conductor.getLastBPMChange().bpm;
currentTimeChange = timeChanges[0];
for (i in 0...timeChanges.length)
@ -204,14 +252,14 @@ class Conductor
}
else if (currentTimeChange != null)
{
currentStepTime = (currentTimeChange.beatTime * 4) + (songPosition - currentTimeChange.timeStamp) / stepCrochet;
currentStepTime = (currentTimeChange.beatTime * 4) + (songPosition - currentTimeChange.timeStamp) / stepLengthMs;
currentStep = Math.floor(currentStepTime);
currentBeat = Math.floor(currentStep / 4);
}
else
{
// Assume a constant BPM equal to the forced value.
currentStepTime = (songPosition / stepCrochet);
currentStepTime = (songPosition / stepLengthMs);
currentStep = Math.floor(currentStepTime);
currentBeat = Math.floor(currentStep / 4);
}
@ -226,37 +274,14 @@ class Conductor
{
beatHit.dispatch();
}
}
@:deprecated // Switch to TimeChanges instead.
public static function mapBPMChanges(song:SwagSong)
{
bpmChangeMap = [];
var curBPM:Float = song.bpm;
var totalSteps:Int = 0;
var totalPos:Float = 0;
for (i in 0...SongLoad.getSong().length)
if (currentMeasure != oldMeasure)
{
if (SongLoad.getSong()[i].changeBPM && SongLoad.getSong()[i].bpm != curBPM)
{
curBPM = SongLoad.getSong()[i].bpm;
var event:BPMChangeEvent =
{
stepTime: totalSteps,
songTime: totalPos,
bpm: curBPM
};
bpmChangeMap.push(event);
}
var deltaSteps:Int = SongLoad.getSong()[i].lengthInSteps;
totalSteps += deltaSteps;
totalPos += ((60 / curBPM) * 1000 / 4) * deltaSteps;
measureHit.dispatch();
}
}
public static function mapTimeChanges(songTimeChanges:Array<SongTimeChange>)
public static function mapTimeChanges(songTimeChanges:Array<SongTimeChange>):Void
{
timeChanges = [];
@ -278,7 +303,7 @@ class Conductor
if (timeChanges.length == 0)
{
// Assume a constant BPM equal to the forced value.
return Math.floor(ms / stepCrochet);
return Math.floor(ms / stepLengthMs);
}
else
{
@ -299,7 +324,7 @@ class Conductor
}
}
resultStep += Math.floor((ms - lastTimeChange.timeStamp) / stepCrochet);
resultStep += Math.floor((ms - lastTimeChange.timeStamp) / stepLengthMs);
return resultStep;
}

View file

@ -41,12 +41,18 @@ enum Control
ACCEPT;
BACK;
PAUSE;
CUTSCENE_ADVANCE;
CUTSCENE_SKIP;
VOLUME_UP;
VOLUME_DOWN;
VOLUME_MUTE;
#if CAN_CHEAT
CHEAT;
#end
}
enum abstract Action(String) to String from String
@:enum
abstract Action(String) to String from String
{
var UI_UP = "ui_up";
var UI_LEFT = "ui_left";
@ -75,6 +81,11 @@ enum abstract Action(String) to String from String
var ACCEPT = "accept";
var BACK = "back";
var PAUSE = "pause";
var CUTSCENE_ADVANCE = "cutscene_advance";
var CUTSCENE_SKIP = "cutscene_skip";
var VOLUME_UP = "volume_up";
var VOLUME_DOWN = "volume_down";
var VOLUME_MUTE = "volume_mute";
var RESET = "reset";
#if CAN_CHEAT
var CHEAT = "cheat";
@ -129,6 +140,11 @@ class Controls extends FlxActionSet
var _back = new FlxActionDigital(Action.BACK);
var _pause = new FlxActionDigital(Action.PAUSE);
var _reset = new FlxActionDigital(Action.RESET);
var _cutscene_advance = new FlxActionDigital(Action.CUTSCENE_ADVANCE);
var _cutscene_skip = new FlxActionDigital(Action.CUTSCENE_SKIP);
var _volume_up = new FlxActionDigital(Action.VOLUME_UP);
var _volume_down = new FlxActionDigital(Action.VOLUME_DOWN);
var _volume_mute = new FlxActionDigital(Action.VOLUME_MUTE);
#if CAN_CHEAT
var _cheat = new FlxActionDigital(Action.CHEAT);
#end
@ -273,6 +289,31 @@ class Controls extends FlxActionSet
inline function get_PAUSE()
return _pause.check();
public var CUTSCENE_ADVANCE(get, never):Bool;
inline function get_CUTSCENE_ADVANCE()
return _cutscene_advance.check();
public var CUTSCENE_SKIP(get, never):Bool;
inline function get_CUTSCENE_SKIP()
return _cutscene_skip.check();
public var VOLUME_UP(get, never):Bool;
inline function get_VOLUME_UP()
return _volume_up.check();
public var VOLUME_DOWN(get, never):Bool;
inline function get_VOLUME_DOWN()
return _volume_down.check();
public var VOLUME_MUTE(get, never):Bool;
inline function get_VOLUME_MUTE()
return _volume_mute.check();
public var RESET(get, never):Bool;
inline function get_RESET()
@ -316,6 +357,11 @@ class Controls extends FlxActionSet
add(_accept);
add(_back);
add(_pause);
add(_cutscene_advance);
add(_cutscene_skip);
add(_volume_up);
add(_volume_down);
add(_volume_mute);
add(_reset);
#if CAN_CHEAT
add(_cheat);
@ -377,6 +423,11 @@ class Controls extends FlxActionSet
case BACK: _back;
case PAUSE: _pause;
case RESET: _reset;
case CUTSCENE_ADVANCE: _cutscene_advance;
case CUTSCENE_SKIP: _cutscene_skip;
case VOLUME_UP: _volume_up;
case VOLUME_DOWN: _volume_down;
case VOLUME_MUTE: _volume_mute;
#if CAN_CHEAT
case CHEAT: _cheat;
#end
@ -437,6 +488,16 @@ class Controls extends FlxActionSet
func(_back, JUST_PRESSED);
case PAUSE:
func(_pause, JUST_PRESSED);
case CUTSCENE_ADVANCE:
func(_cutscene_advance, JUST_PRESSED);
case CUTSCENE_SKIP:
func(_cutscene_skip, PRESSED);
case VOLUME_UP:
func(_volume_up, JUST_PRESSED);
case VOLUME_DOWN:
func(_volume_down, JUST_PRESSED);
case VOLUME_MUTE:
func(_volume_mute, JUST_PRESSED);
case RESET:
func(_reset, JUST_PRESSED);
#if CAN_CHEAT
@ -454,37 +515,70 @@ class Controls extends FlxActionSet
switch(device)
{
case Keys:
forEachBound(control, function(action, _) replaceKey(action, toAdd, toRemove));
forEachBound(control, function(action, state) replaceKey(action, toAdd, toRemove, state));
case Gamepad(id):
forEachBound(control, function(action, _) replaceButton(action, id, toAdd, toRemove));
forEachBound(control, function(action, state) replaceButton(action, id, toAdd, toRemove, state));
}
}
function replaceKey(action:FlxActionDigital, toAdd:Int, toRemove:Int)
function replaceKey(action:FlxActionDigital, toAdd:FlxKey, toRemove:FlxKey, state:FlxInputState)
{
if (action.inputs.length == 0) {
// Add the keybind, don't replace.
addKeys(action, [toAdd], state);
return;
}
var hasReplaced:Bool = false;
for (i in 0...action.inputs.length)
{
var input = action.inputs[i];
if (input == null) continue;
if (input.device == KEYBOARD && input.inputID == toRemove)
{
@:privateAccess
action.inputs[i].inputID = toAdd;
if (toAdd == FlxKey.NONE) {
// Remove the keybind, don't replace.
action.inputs.remove(input);
} else {
// Replace the keybind.
@:privateAccess
action.inputs[i].inputID = toAdd;
}
hasReplaced = true;
}
}
if (!hasReplaced) {
addKeys(action, [toAdd], state);
}
}
function replaceButton(action:FlxActionDigital, deviceID:Int, toAdd:Int, toRemove:Int)
function replaceButton(action:FlxActionDigital, deviceID:Int, toAdd:FlxGamepadInputID, toRemove:FlxGamepadInputID, state:FlxInputState)
{
if (action.inputs.length == 0) {
addButtons(action, [toAdd], state, deviceID);
return;
}
var hasReplaced:Bool = false;
for (i in 0...action.inputs.length)
{
var input = action.inputs[i];
if (input == null) continue;
if (isGamepad(input, deviceID) && input.inputID == toRemove)
{
@:privateAccess
action.inputs[i].inputID = toAdd;
hasReplaced = true;
}
}
if (!hasReplaced) {
addButtons(action, [toAdd], state, deviceID);
}
}
public function copyFrom(controls:Controls, ?device:Device)
@ -558,10 +652,12 @@ class Controls extends FlxActionSet
forEachBound(control, function(action, _) removeKeys(action, keys));
}
inline static function addKeys(action:FlxActionDigital, keys:Array<FlxKey>, state:FlxInputState)
static function addKeys(action:FlxActionDigital, keys:Array<FlxKey>, state:FlxInputState)
{
for (key in keys)
for (key in keys) {
if (key == FlxKey.NONE) continue; // Ignore unbound keys.
action.addKey(key, state);
}
}
static function removeKeys(action:FlxActionDigital, keys:Array<FlxKey>)
@ -582,54 +678,95 @@ class Controls extends FlxActionSet
keyboardScheme = scheme;
switch(scheme)
{
case Solo:
bindKeys(Control.UI_UP, [W, FlxKey.UP]);
bindKeys(Control.UI_DOWN, [S, FlxKey.DOWN]);
bindKeys(Control.UI_LEFT, [A, FlxKey.LEFT]);
bindKeys(Control.UI_RIGHT, [D, FlxKey.RIGHT]);
bindKeys(Control.NOTE_UP, [W, FlxKey.UP]);
bindKeys(Control.NOTE_DOWN, [S, FlxKey.DOWN]);
bindKeys(Control.NOTE_LEFT, [A, FlxKey.LEFT]);
bindKeys(Control.NOTE_RIGHT, [D, FlxKey.RIGHT]);
bindKeys(Control.ACCEPT, [Z, SPACE, ENTER]);
bindKeys(Control.BACK, [X, BACKSPACE, ESCAPE]);
bindKeys(Control.PAUSE, [P, ENTER, ESCAPE]);
bindKeys(Control.RESET, [R]);
case Duo(true):
bindKeys(Control.UI_UP, [W]);
bindKeys(Control.UI_DOWN, [S]);
bindKeys(Control.UI_LEFT, [A]);
bindKeys(Control.UI_RIGHT, [D]);
bindKeys(Control.NOTE_UP, [W]);
bindKeys(Control.NOTE_DOWN, [S]);
bindKeys(Control.NOTE_LEFT, [A]);
bindKeys(Control.NOTE_RIGHT, [D]);
bindKeys(Control.ACCEPT, [G, Z]);
bindKeys(Control.BACK, [H, X]);
bindKeys(Control.PAUSE, [ONE]);
bindKeys(Control.RESET, [R]);
case Duo(false):
bindKeys(Control.UI_UP, [FlxKey.UP]);
bindKeys(Control.UI_DOWN, [FlxKey.DOWN]);
bindKeys(Control.UI_LEFT, [FlxKey.LEFT]);
bindKeys(Control.UI_RIGHT, [FlxKey.RIGHT]);
bindKeys(Control.NOTE_UP, [FlxKey.UP]);
bindKeys(Control.NOTE_DOWN, [FlxKey.DOWN]);
bindKeys(Control.NOTE_LEFT, [FlxKey.LEFT]);
bindKeys(Control.NOTE_RIGHT, [FlxKey.RIGHT]);
bindKeys(Control.ACCEPT, [O]);
bindKeys(Control.BACK, [P]);
bindKeys(Control.PAUSE, [ENTER]);
bindKeys(Control.RESET, [BACKSPACE]);
case None: // nothing
case Custom: // nothing
}
bindKeys(Control.UI_UP, getDefaultKeybinds(scheme, Control.UI_UP));
bindKeys(Control.UI_DOWN, getDefaultKeybinds(scheme, Control.UI_DOWN));
bindKeys(Control.UI_LEFT, getDefaultKeybinds(scheme, Control.UI_LEFT));
bindKeys(Control.UI_RIGHT, getDefaultKeybinds(scheme, Control.UI_RIGHT));
bindKeys(Control.NOTE_UP, getDefaultKeybinds(scheme, Control.NOTE_UP));
bindKeys(Control.NOTE_DOWN, getDefaultKeybinds(scheme, Control.NOTE_DOWN));
bindKeys(Control.NOTE_LEFT, getDefaultKeybinds(scheme, Control.NOTE_LEFT));
bindKeys(Control.NOTE_RIGHT, getDefaultKeybinds(scheme, Control.NOTE_RIGHT));
bindKeys(Control.ACCEPT, getDefaultKeybinds(scheme, Control.ACCEPT));
bindKeys(Control.BACK, getDefaultKeybinds(scheme, Control.BACK));
bindKeys(Control.PAUSE, getDefaultKeybinds(scheme, Control.PAUSE));
bindKeys(Control.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE));
bindKeys(Control.CUTSCENE_SKIP, getDefaultKeybinds(scheme, Control.CUTSCENE_SKIP));
bindKeys(Control.VOLUME_UP, getDefaultKeybinds(scheme, Control.VOLUME_UP));
bindKeys(Control.VOLUME_DOWN, getDefaultKeybinds(scheme, Control.VOLUME_DOWN));
bindKeys(Control.VOLUME_MUTE, getDefaultKeybinds(scheme, Control.VOLUME_MUTE));
bindMobileLol();
}
function getDefaultKeybinds(scheme:KeyboardScheme, control:Control):Array<FlxKey> {
switch (scheme) {
case Solo:
switch (control) {
case Control.UI_UP: return [W, FlxKey.UP];
case Control.UI_DOWN: return [S, FlxKey.DOWN];
case Control.UI_LEFT: return [A, FlxKey.LEFT];
case Control.UI_RIGHT: return [D, FlxKey.RIGHT];
case Control.NOTE_UP: return [W, FlxKey.UP];
case Control.NOTE_DOWN: return [S, FlxKey.DOWN];
case Control.NOTE_LEFT: return [A, FlxKey.LEFT];
case Control.NOTE_RIGHT: return [D, FlxKey.RIGHT];
case Control.ACCEPT: return [Z, SPACE, ENTER];
case Control.BACK: return [X, BACKSPACE, ESCAPE];
case Control.PAUSE: return [P, ENTER, ESCAPE];
case Control.CUTSCENE_ADVANCE: return [Z, ENTER];
case Control.CUTSCENE_SKIP: return [P, ESCAPE];
case Control.VOLUME_UP: return [PLUS, NUMPADPLUS];
case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS];
case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO];
case Control.RESET: return [R];
}
case Duo(true):
switch (control) {
case Control.UI_UP: return [W];
case Control.UI_DOWN: return [S];
case Control.UI_LEFT: return [A];
case Control.UI_RIGHT: return [D];
case Control.NOTE_UP: return [W];
case Control.NOTE_DOWN: return [S];
case Control.NOTE_LEFT: return [A];
case Control.NOTE_RIGHT: return [D];
case Control.ACCEPT: return [G, Z];
case Control.BACK: return [H, X];
case Control.PAUSE: return [ONE];
case Control.CUTSCENE_ADVANCE: return [G, Z];
case Control.CUTSCENE_SKIP: return [ONE];
case Control.VOLUME_UP: return [PLUS];
case Control.VOLUME_DOWN: return [MINUS];
case Control.VOLUME_MUTE: return [ZERO];
case Control.RESET: return [R];
}
case Duo(false):
switch (control) {
case Control.UI_UP: return [FlxKey.UP];
case Control.UI_DOWN: return [FlxKey.DOWN];
case Control.UI_LEFT: return [FlxKey.LEFT];
case Control.UI_RIGHT: return [FlxKey.RIGHT];
case Control.NOTE_UP: return [FlxKey.UP];
case Control.NOTE_DOWN: return [FlxKey.DOWN];
case Control.NOTE_LEFT: return [FlxKey.LEFT];
case Control.NOTE_RIGHT: return [FlxKey.RIGHT];
case Control.ACCEPT: return [ENTER];
case Control.BACK: return [ESCAPE];
case Control.PAUSE: return [ONE];
case Control.CUTSCENE_ADVANCE: return [ENTER];
case Control.CUTSCENE_SKIP: return [ONE];
case Control.VOLUME_UP: return [NUMPADPLUS];
case Control.VOLUME_DOWN: return [NUMPADMINUS];
case Control.VOLUME_MUTE: return [NUMPADZERO];
case Control.RESET: return [R];
}
default:
// Fallthrough.
}
return [];
}
function bindMobileLol()
{
#if FLX_TOUCH
@ -704,23 +841,51 @@ class Controls extends FlxActionSet
{
addGamepadLiteral(id, [
Control.ACCEPT => [#if switch B #else A #end],
Control.BACK => [#if switch A #else B #end, FlxGamepadInputID.BACK],
Control.UI_UP => [DPAD_UP, LEFT_STICK_DIGITAL_UP],
Control.UI_DOWN => [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN],
Control.UI_LEFT => [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT],
Control.UI_RIGHT => [DPAD_RIGHT, LEFT_STICK_DIGITAL_RIGHT],
Control.ACCEPT => getDefaultGamepadBinds(Control.ACCEPT),
Control.BACK => getDefaultGamepadBinds(Control.BACK),
Control.UI_UP => getDefaultGamepadBinds(Control.UI_UP),
Control.UI_DOWN => getDefaultGamepadBinds(Control.UI_DOWN),
Control.UI_LEFT => getDefaultGamepadBinds(Control.UI_LEFT),
Control.UI_RIGHT => getDefaultGamepadBinds(Control.UI_RIGHT),
// don't swap A/B or X/Y for switch on these. A is always the bottom face button
Control.NOTE_UP => [DPAD_UP, Y, LEFT_STICK_DIGITAL_UP, RIGHT_STICK_DIGITAL_UP],
Control.NOTE_DOWN => [DPAD_DOWN, A, LEFT_STICK_DIGITAL_DOWN, RIGHT_STICK_DIGITAL_DOWN],
Control.NOTE_LEFT => [DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT],
Control.NOTE_RIGHT => [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT],
Control.PAUSE => [START],
Control.RESET => [RIGHT_SHOULDER]
#if CAN_CHEAT, Control.CHEAT => [X] #end
Control.NOTE_UP => getDefaultGamepadBinds(Control.NOTE_UP),
Control.NOTE_DOWN => getDefaultGamepadBinds(Control.NOTE_DOWN),
Control.NOTE_LEFT => getDefaultGamepadBinds(Control.NOTE_LEFT),
Control.NOTE_RIGHT => getDefaultGamepadBinds(Control.NOTE_RIGHT),
Control.PAUSE => getDefaultGamepadBinds(Control.PAUSE),
// Control.VOLUME_UP => [RIGHT_SHOULDER],
// Control.VOLUME_DOWN => [LEFT_SHOULDER],
// Control.VOLUME_MUTE => [RIGHT_TRIGGER],
Control.CUTSCENE_ADVANCE => getDefaultGamepadBinds(Control.CUTSCENE_ADVANCE),
Control.CUTSCENE_SKIP => getDefaultGamepadBinds(Control.CUTSCENE_SKIP),
Control.RESET => getDefaultGamepadBinds(Control.RESET),
#if CAN_CHEAT, Control.CHEAT => getDefaultGamepadBinds(Control.CHEAT) #end
]);
}
function getDefaultGamepadBinds(control:Control):Array<FlxGamepadInputID> {
switch(control) {
case Control.ACCEPT: return [#if switch B #else A #end];
case Control.BACK: return [#if switch A #else B #end, FlxGamepadInputID.BACK];
case Control.UI_UP: return [DPAD_UP, LEFT_STICK_DIGITAL_UP];
case Control.UI_DOWN: return [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN];
case Control.UI_LEFT: return [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT];
case Control.UI_RIGHT: return [DPAD_RIGHT, LEFT_STICK_DIGITAL_RIGHT];
case Control.NOTE_UP: return [DPAD_UP, Y, LEFT_STICK_DIGITAL_UP, RIGHT_STICK_DIGITAL_UP];
case Control.NOTE_DOWN: return [DPAD_DOWN, A, LEFT_STICK_DIGITAL_DOWN, RIGHT_STICK_DIGITAL_DOWN];
case Control.NOTE_LEFT: return [DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT];
case Control.NOTE_RIGHT: return [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT];
case Control.PAUSE: return [START];
case Control.CUTSCENE_ADVANCE: return [A];
case Control.CUTSCENE_SKIP: return [START];
case Control.RESET: return [RIGHT_SHOULDER];
#if CAN_CHEAT, Control.CHEAT: return [X]; #end
default:
// Fallthrough.
}
return [];
}
/**
* Sets all actions that pertain to the binder to trigger when the supplied keys are used.
* If binder is a literal you can inline this
@ -749,8 +914,10 @@ class Controls extends FlxActionSet
inline static function addButtons(action:FlxActionDigital, buttons:Array<FlxGamepadInputID>, state, id)
{
for (button in buttons)
for (button in buttons) {
if (button == FlxGamepadInputID.NONE) continue; // Ignore unbound keys.
action.addGamepad(button, state, id);
}
}
static function removeButtons(action:FlxActionDigital, gamepadID:Int, buttons:Array<FlxGamepadInputID>)
@ -798,6 +965,11 @@ class Controls extends FlxActionSet
}
}
/**
* NOTE: When loading controls:
* An EMPTY array means the control is uninitialized and needs to be reset to default.
* An array with a single FlxKey.NONE means the control was intentionally unbound by the user.
*/
public function fromSaveData(data:Dynamic, device:Device)
{
for (control in Control.createAll())
@ -805,17 +977,44 @@ class Controls extends FlxActionSet
var inputs:Array<Int> = Reflect.field(data, control.getName());
if (inputs != null)
{
if (inputs.length == 0) {
trace('Control ${control} is missing bindings, resetting to default.');
switch(device)
{
case Keys:
bindKeys(control, getDefaultKeybinds(Solo, control));
case Gamepad(id):
bindButtons(control, id, getDefaultGamepadBinds(control));
}
} else if (inputs == [FlxKey.NONE]) {
trace('Control ${control} is unbound, leaving it be.');
} else {
switch(device)
{
case Keys:
bindKeys(control, inputs.copy());
case Gamepad(id):
bindButtons(control, id, inputs.copy());
}
}
} else {
trace('Control ${control} is missing bindings, resetting to default.');
switch(device)
{
case Keys:
bindKeys(control, inputs.copy());
bindKeys(control, getDefaultKeybinds(Solo, control));
case Gamepad(id):
bindButtons(control, id, inputs.copy());
bindButtons(control, id, getDefaultGamepadBinds(control));
}
}
}
}
/**
* NOTE: When saving controls:
* An EMPTY array means the control is uninitialized and needs to be reset to default.
* An array with a single FlxKey.NONE means the control was intentionally unbound by the user.
*/
public function createSaveData(device:Device):Dynamic
{
var isEmpty = true;
@ -825,6 +1024,8 @@ class Controls extends FlxActionSet
var inputs = getInputsFor(control, device);
isEmpty = isEmpty && inputs.length == 0;
if (inputs.length == 0) inputs = [FlxKey.NONE];
Reflect.setField(data, control.getName(), inputs);
}

View file

@ -88,14 +88,14 @@ class LatencyState extends MusicBeatSubState
// // musSpec.visType = FREQUENCIES;
// add(musSpec);
for (beat in 0...Math.floor(FlxG.sound.music.length / Conductor.crochet))
for (beat in 0...Math.floor(FlxG.sound.music.length / Conductor.beatLengthMs))
{
var beatTick:FlxSprite = new FlxSprite(songPosToX(beat * Conductor.crochet), FlxG.height - 15);
var beatTick:FlxSprite = new FlxSprite(songPosToX(beat * Conductor.beatLengthMs), FlxG.height - 15);
beatTick.makeGraphic(2, 15);
beatTick.alpha = 0.3;
add(beatTick);
var offsetTxt:FlxText = new FlxText(songPosToX(beat * Conductor.crochet), FlxG.height - 26, 0, "swag");
var offsetTxt:FlxText = new FlxText(songPosToX(beat * Conductor.beatLengthMs), FlxG.height - 26, 0, "swag");
offsetTxt.alpha = 0.5;
diffGrp.add(offsetTxt);
@ -127,7 +127,7 @@ class LatencyState extends MusicBeatSubState
for (i in 0...32)
{
var note:Note = new Note(Conductor.crochet * i, 1);
var note:Note = new Note(Conductor.beatLengthMs * i, 1);
noteGrp.add(note);
}
@ -143,9 +143,9 @@ class LatencyState extends MusicBeatSubState
override function stepHit():Bool
{
if (curStep % 4 == 2)
if (Conductor.currentStep % 4 == 2)
{
blocks.members[((curBeat % 8) + 1) % 8].alpha = 0.5;
blocks.members[((Conductor.currentBeat % 8) + 1) % 8].alpha = 0.5;
}
return super.stepHit();
@ -153,11 +153,11 @@ class LatencyState extends MusicBeatSubState
override function beatHit():Bool
{
if (curBeat % 8 == 0) blocks.forEach(blok -> {
if (Conductor.currentBeat % 8 == 0) blocks.forEach(blok -> {
blok.alpha = 0;
});
blocks.members[curBeat % 8].alpha = 1;
blocks.members[Conductor.currentBeat % 8].alpha = 1;
// block.visible = !block.visible;
return super.beatHit();
@ -198,8 +198,8 @@ class LatencyState extends MusicBeatSubState
offsetText.text = "AUDIO Offset: " + Conductor.audioOffset + "ms";
offsetText.text += "\nVIDOE Offset: " + Conductor.visualOffset + "ms";
offsetText.text += "\ncurStep: " + curStep;
offsetText.text += "\ncurBeat: " + curBeat;
offsetText.text += "\ncurrentStep: " + Conductor.currentStep;
offsetText.text += "\ncurrentBeat: " + Conductor.currentBeat;
var avgOffsetInput:Float = 0;
@ -255,7 +255,7 @@ class LatencyState extends MusicBeatSubState
if (daNote.y < 0 - daNote.height)
{
daNote.alpha = 1;
// daNote.data.strumTime += Conductor.crochet * 8;
// daNote.data.strumTime += Conductor.beatLengthMs * 8;
}
});
@ -266,12 +266,12 @@ class LatencyState extends MusicBeatSubState
{
Conductor.songPosition = swagSong.getTimeWithDiff();
var closestBeat:Int = Math.round(Conductor.songPosition / Conductor.crochet) % diffGrp.members.length;
var getDiff:Float = Conductor.songPosition - (closestBeat * Conductor.crochet);
var closestBeat:Int = Math.round(Conductor.songPosition / Conductor.beatLengthMs) % diffGrp.members.length;
var getDiff:Float = Conductor.songPosition - (closestBeat * Conductor.beatLengthMs);
getDiff -= Conductor.visualOffset;
// lil fix for end of song
if (closestBeat == 0 && getDiff >= Conductor.crochet * 2) getDiff -= FlxG.sound.music.length;
if (closestBeat == 0 && getDiff >= Conductor.beatLengthMs * 2) getDiff -= FlxG.sound.music.length;
trace("\tDISTANCE TO CLOSEST BEAT: " + getDiff + "ms");
trace("\tCLOSEST BEAT: " + closestBeat);

View file

@ -1,5 +1,6 @@
package funkin;
import funkin.ui.debug.DebugMenuSubState;
import flixel.FlxObject;
import flixel.FlxSprite;
import flixel.FlxState;
@ -299,7 +300,14 @@ class MainMenuState extends MusicBeatState
}
}
// FlxG.camera.followLerp = CoolUtil.camLerpShit(0.06);
// ` / ~ to open the debug menu.
if (FlxG.keys.justPressed.GRAVEACCENT)
{
// TODO: Does this break anything?
this.persistentUpdate = false;
this.persistentDraw = false;
FlxG.state.openSubState(new DebugMenuSubState());
}
if (FlxG.sound.music.volume < 0.8)
{

View file

@ -9,7 +9,6 @@ import flixel.util.FlxSort;
import funkin.modding.PolymodHandler;
import funkin.modding.events.ScriptEvent;
import funkin.modding.module.ModuleHandler;
import funkin.ui.debug.DebugMenuSubState;
import funkin.util.SortUtil;
/**
@ -66,15 +65,6 @@ class MusicBeatState extends FlxUIState
// This can now be used in EVERY STATE YAY!
if (FlxG.keys.justPressed.F5) debug_refreshModules();
// ` / ~ to open the debug menu.
if (FlxG.keys.justPressed.GRAVEACCENT)
{
// TODO: Does this break anything?
this.persistentUpdate = false;
this.persistentDraw = false;
FlxG.state.openSubState(new DebugMenuSubState());
}
// Display Conductor info in the watch window.
FlxG.watch.addQuick("songPos", Conductor.songPosition);
FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime);

View file

@ -5,62 +5,96 @@ import flixel.util.FlxColor;
import funkin.Conductor.BPMChangeEvent;
import funkin.modding.events.ScriptEvent;
import funkin.modding.module.ModuleHandler;
import flixel.text.FlxText;
import funkin.modding.PolymodHandler;
/**
* MusicBeatSubState reincorporates the functionality of MusicBeatState into an FlxSubState.
*/
class MusicBeatSubState extends FlxSubState
{
public var leftWatermarkText:FlxText = null;
public var rightWatermarkText:FlxText = null;
public function new(bgColor:FlxColor = FlxColor.TRANSPARENT)
{
super(bgColor);
}
var curStep:Int = 0;
var curBeat:Int = 0;
var controls(get, never):Controls;
inline function get_controls():Controls
return PlayerSettings.player1.controls;
override function update(elapsed:Float)
override function create():Void
{
// everyStep();
var oldStep:Int = curStep;
super.create();
updateCurStep();
curBeat = Math.floor(curStep / 4);
createWatermarkText();
if (oldStep != curStep && curStep >= 0) stepHit();
Conductor.beatHit.add(this.beatHit);
Conductor.stepHit.add(this.stepHit);
}
public override function destroy():Void
{
super.destroy();
Conductor.beatHit.remove(this.beatHit);
Conductor.stepHit.remove(this.stepHit);
}
override function update(elapsed:Float):Void
{
super.update(elapsed);
// Rebindable volume keys.
if (controls.VOLUME_MUTE) FlxG.sound.toggleMuted();
else if (controls.VOLUME_UP) FlxG.sound.changeVolume(0.1);
else if (controls.VOLUME_DOWN) FlxG.sound.changeVolume(-0.1);
// Emergency exit button.
if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState());
// This can now be used in EVERY STATE YAY!
if (FlxG.keys.justPressed.F5) debug_refreshModules();
}
function updateCurStep():Void
function debug_refreshModules()
{
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];
}
PolymodHandler.forceReloadAssets();
curStep = lastChange.stepTime + Math.floor(((Conductor.songPosition - Conductor.audioOffset) - lastChange.songTime) / Conductor.stepCrochet);
// Restart the current state, so old data is cleared.
FlxG.resetState();
}
/**
* Called when a step is hit in the current song.
* Continues outside of PlayState, for things like animations in menus.
* @return Whether the event should continue (not canceled).
*/
public function stepHit():Bool
{
var event = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, curBeat, curStep);
var event:ScriptEvent = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep);
dispatchEvent(event);
if (event.eventCanceled) return false;
if (curStep % 4 == 0) beatHit();
return true;
}
/**
* Called when a beat is hit in the current song.
* Continues outside of PlayState, for things like animations in menus.
* @return Whether the event should continue (not canceled).
*/
public function beatHit():Bool
{
var event:ScriptEvent = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep);
dispatchEvent(event);
if (event.eventCanceled) return false;
return true;
}
@ -70,6 +104,25 @@ class MusicBeatSubState extends FlxSubState
ModuleHandler.callEvent(event);
}
function createWatermarkText():Void
{
// Both have an xPos of 0, but a width equal to the full screen.
// The rightWatermarkText is right aligned, which puts the text in the correct spot.
leftWatermarkText = new FlxText(0, FlxG.height - 18, FlxG.width, '', 12);
rightWatermarkText = new FlxText(0, FlxG.height - 18, FlxG.width, '', 12);
// 100,000 should be good enough.
leftWatermarkText.zIndex = 100000;
rightWatermarkText.zIndex = 100000;
leftWatermarkText.scrollFactor.set(0, 0);
rightWatermarkText.scrollFactor.set(0, 0);
leftWatermarkText.setFormat('VCR OSD Mono', 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
rightWatermarkText.setFormat('VCR OSD Mono', 16, FlxColor.WHITE, RIGHT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
add(leftWatermarkText);
add(rightWatermarkText);
}
/**
* Close this substate and replace it with a different one.
*/
@ -78,15 +131,4 @@ class MusicBeatSubState extends FlxSubState
this.close();
this._parentState.openSubState(substate);
}
public function beatHit():Bool
{
var event = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, curBeat, curStep);
dispatchEvent(event);
if (event.eventCanceled) return false;
return true;
}
}

View file

@ -207,7 +207,7 @@ class Note extends FlxSprite
prevNote.animation.play(prevNote.colorName + 'hold');
prevNote.updateHitbox();
var scaleThing:Float = Math.round((Conductor.stepCrochet) * (0.45 * FlxMath.roundDecimal(SongLoad.getSpeed(), 2)));
var scaleThing:Float = Math.round((Conductor.stepLengthMs) * (0.45 * FlxMath.roundDecimal(PlayState.instance.currentChart.scrollSpeed, 2)));
// get them a LIL closer together cuz the antialiasing blurs the edges
if (antialiasing) scaleThing *= 1.0 + (1.0 / prevNote.frameHeight);
prevNote.scale.y = scaleThing / prevNote.frameHeight;

View file

@ -20,7 +20,7 @@ class FlxVideo extends FlxBasic
/**
* Doesn't actually interact with Flixel shit, only just a pleasant to use class
*/
public function new(vidSrc:String)
public function new(videoPath:String)
{
super();
@ -36,7 +36,7 @@ class FlxVideo extends FlxBasic
netStream = new NetStream(netConnection);
netStream.client = {onMetaData: client_onMetaData};
netConnection.addEventListener(NetStatusEvent.NET_STATUS, netConnection_onNetStatus);
netStream.play(Paths.file(vidSrc));
netStream.play(videoPath);
}
public function finishVideo():Void

View file

@ -8,6 +8,7 @@ import funkin.play.stage.StageData;
import polymod.Polymod;
import polymod.backends.PolymodAssets.PolymodAssetType;
import polymod.format.ParseRules.TextFileFormat;
import funkin.play.event.SongEventData.SongEventParser;
import funkin.util.FileUtil;
class PolymodHandler
@ -279,6 +280,11 @@ class PolymodHandler
// TODO: Reload event callbacks
funkin.data.level.LevelRegistry.instance.loadEntries();
SongEventParser.loadEventCache();
// TODO: Uncomment this once conversation data is implemented.
// ConversationDataParser.loadConversationCache();
// DialogueBoxDataParser.loadDialogueBoxCache();
// SpeakerDataParser.loadSpeakerCache();
SongDataParser.loadSongCache();
StageDataParser.loadStageCache();
CharacterDataParser.loadCharacterCache();

View file

@ -38,7 +38,7 @@ class Countdown
stopCountdown();
PlayState.instance.isInCountdown = true;
Conductor.songPosition = Conductor.crochet * -5;
Conductor.songPosition = Conductor.beatLengthMs * -5;
// Handle onBeatHit events manually
@:privateAccess
PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0));
@ -46,7 +46,7 @@ class Countdown
// The timer function gets called based on the beat of the song.
countdownTimer = new FlxTimer();
countdownTimer.start(Conductor.crochet / 1000, function(tmr:FlxTimer) {
countdownTimer.start(Conductor.beatLengthMs / 1000, function(tmr:FlxTimer) {
countdownStep = decrement(countdownStep);
// Handle onBeatHit events manually
@ -212,7 +212,7 @@ class Countdown
countdownSprite.screenCenter();
// Fade sprite in, then out, then destroy it.
FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.crochet / 1000,
FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.beatLengthMs / 1000,
{
ease: FlxEase.cubeInOut,
onComplete: function(twn:FlxTween) {

View file

@ -148,8 +148,7 @@ class HealthIcon extends FlxSprite
{
if (characterId == 'beta')
{
// characterId = PlayState.instance.currentPlayerId;
characterId = 'bf';
characterId = PlayState.instance.currentPlayerId;
}
else
{

View file

@ -972,10 +972,10 @@ class PlayState extends MusicBeatState
oldNote = newNote;
// Generate X sustain notes.
var sustainSections = Math.round(songNote.length / Conductor.stepCrochet);
var sustainSections = Math.round(songNote.length / Conductor.stepLengthMs);
for (noteIndex in 0...sustainSections)
{
var noteTimeOffset:Float = Conductor.stepCrochet + (Conductor.stepCrochet * noteIndex);
var noteTimeOffset:Float = Conductor.stepLengthMs + (Conductor.stepLengthMs * noteIndex);
var sustainNote:Note = new Note(songNote.time + noteTimeOffset, songNote.data, oldNote, true, strumlineStyle);
sustainNote.mustPress = mustHitNote;
sustainNote.data.noteKind = songNote.kind;
@ -2209,14 +2209,14 @@ class PlayState extends MusicBeatState
if (shouldShowComboText)
{
var animShit:ComboCounter = new ComboCounter(-100, 300, Highscore.tallies.combo);
var animShit:ComboMilestone = new ComboMilestone(-100, 300, Highscore.tallies.combo);
animShit.scrollFactor.set(0.6, 0.6);
animShit.cameras = [camHUD];
add(animShit);
var frameShit:Float = (1 / 24) * 2; // equals 2 frames in the animation
new FlxTimer().start(((Conductor.crochet / 1000) * 1.25) - frameShit, function(tmr) {
new FlxTimer().start(((Conductor.beatLengthMs / 1000) * 1.25) - frameShit, function(tmr) {
animShit.forceFinish();
});
}

View file

@ -1,5 +1,6 @@
package funkin.play;
import funkin.ui.story.StoryMenuState;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import flixel.FlxBasic;
import flixel.FlxSprite;
@ -355,7 +356,17 @@ class ResultState extends MusicBeatSubState
if (FlxG.keys.justPressed.COMMA) songName.angle -= 0.1;
if (controls.PAUSE) FlxG.switchState(new FreeplayState());
if (controls.PAUSE)
{
if (PlayStatePlaylist.isStoryMode)
{
FlxG.switchState(new StoryMenuState());
}
else
{
FlxG.switchState(new FreeplayState());
}
}
super.update(elapsed);
}

View file

@ -367,7 +367,7 @@ class BaseCharacter extends Bopper
// This lets you add frames to the end of the sing animation to ease back into the idle!
holdTimer += event.elapsed;
var singTimeSec:Float = singTimeSec * (Conductor.crochet * 0.001); // x beats, to ms.
var singTimeSec:Float = singTimeSec * (Conductor.beatLengthMs * 0.001); // x beats, to ms.
if (getCurrentAnimation().endsWith('miss')) singTimeSec *= 2; // makes it feel more awkward when you miss

View file

@ -56,10 +56,12 @@ class MultiSparrowCharacter extends BaseCharacter
if (_data.isPixel)
{
this.isPixel = true;
this.antialiasing = false;
}
else
{
this.isPixel = false;
this.antialiasing = true;
}
}

View file

@ -41,10 +41,12 @@ class PackerCharacter extends BaseCharacter
if (_data.isPixel)
{
this.isPixel = true;
this.antialiasing = false;
}
else
{
this.isPixel = false;
this.antialiasing = true;
}

View file

@ -10,7 +10,7 @@ import funkin.play.event.SongEventData.SongEventFieldType;
/**
* This class represents a handler for camera zoom events.
*
*
* Example: Zoom to 1.3x:
* ```
* {
@ -18,8 +18,8 @@ import funkin.play.event.SongEventData.SongEventFieldType;
* 'v': 1.3
* }
* ```
*
* Example: Zoom to 1.3x
*
* Example: Zoom to 1.3x
* ```
* {
* 'e': 'FocusCamera',
@ -29,7 +29,7 @@ import funkin.play.event.SongEventData.SongEventFieldType;
* }
* }
* ```
*
*
* Example: Focus on (100, 100):
* ```
* {
@ -76,7 +76,8 @@ class ZoomCameraSongEvent extends SongEvent
return;
}
FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.stepCrochet * duration / 1000), {ease: easeFunction});
FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.stepLengthMs * duration / 1000),
{ease: easeFunction});
}
}

View file

@ -365,7 +365,7 @@ abstract SongNoteData(RawSongNoteData)
public function get_stepTime():Float
{
// TODO: Account for changes in BPM.
return this.t / Conductor.stepCrochet;
return this.t / Conductor.stepLengthMs;
}
/**
@ -551,7 +551,7 @@ abstract SongEventData(RawSongEventData)
public function get_stepTime():Float
{
// TODO: Account for changes in BPM.
return this.t / Conductor.stepCrochet;
return this.t / Conductor.stepLengthMs;
}
public var event(get, set):String;

View file

@ -658,7 +658,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
public function onBeatHit(event:SongTimeScriptEvent):Void
{
// Override me in your scripted stage to perform custom behavior!
// Make sure to call super.onBeatHit(curBeat) if you want to keep the boppers dancing.
// Make sure to call super.onBeatHit(event) if you want to keep the boppers dancing.
for (bopper in boppers)
{

View file

@ -14,7 +14,7 @@ import funkin.ui.TextMenuList;
class ControlsMenu extends funkin.ui.OptionsState.Page
{
inline static public var COLUMNS = 2;
public static inline final COLUMNS = 2;
static var controlList = Control.createAll();
/*
* Defines groups of controls that cannot share inputs, like left and right. Say, if ACCEPT is Z, Back is X,
@ -23,7 +23,9 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
*/
static var controlGroups:Array<Array<Control>> = [
[NOTE_UP, NOTE_DOWN, NOTE_LEFT, NOTE_RIGHT],
[UI_UP, UI_DOWN, UI_LEFT, UI_RIGHT, ACCEPT, BACK]
[UI_UP, UI_DOWN, UI_LEFT, UI_RIGHT, ACCEPT, BACK],
[CUTSCENE_ADVANCE, CUTSCENE_SKIP],
[VOLUME_UP, VOLUME_DOWN, VOLUME_MUTE]
];
var itemGroups:Array<Array<InputItem>> = [for (i in 0...controlGroups.length) []];
@ -36,7 +38,7 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
var labels:FlxTypedGroup<AtlasText>;
var currentDevice:Device = Keys;
var deviceListSelected = false;
var deviceListSelected:Bool = false;
public function new()
{
@ -48,7 +50,7 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
camera = menuCamera;
labels = new FlxTypedGroup<AtlasText>();
var headers = new FlxTypedGroup<AtlasText>();
var headers:FlxTypedGroup<AtlasText> = new FlxTypedGroup<AtlasText>();
controlGrid = new MenuTypedList(Columns(COLUMNS), Vertical);
add(labels);
@ -57,20 +59,20 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
if (FlxG.gamepads.numActiveGamepads > 0)
{
var devicesBg = new FlxSprite();
devicesBg.makeGraphic(FlxG.width, 100, 0xFFfafd6d);
var devicesBg:FlxSprite = new FlxSprite();
devicesBg.makeGraphic(FlxG.width, 100, 0xFFFAFD6D);
add(devicesBg);
deviceList = new TextMenuList(Horizontal, None);
add(deviceList);
deviceListSelected = true;
var item;
var item:TextMenuItem;
item = deviceList.createItem("Keyboard", AtlasFont.BOLD, selectDevice.bind(Keys));
item = deviceList.createItem('Keyboard', AtlasFont.BOLD, selectDevice.bind(Keys));
item.x = FlxG.width / 2 - item.width - 30;
item.y = (devicesBg.height - item.height) / 2;
item = deviceList.createItem("Gamepad", AtlasFont.BOLD, selectDevice.bind(Gamepad(FlxG.gamepads.firstActive.id)));
item = deviceList.createItem('Gamepad', AtlasFont.BOLD, selectDevice.bind(Gamepad(FlxG.gamepads.firstActive.id)));
item.x = FlxG.width / 2 + 30;
item.y = (devicesBg.height - item.height) / 2;
}
@ -96,6 +98,18 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
headers.add(new AtlasText(0, y, "NOTES", AtlasFont.BOLD)).screenCenter(X);
y += spacer;
}
else if (currentHeader != "CUTSCENE_" && name.indexOf("CUTSCENE_") == 0)
{
currentHeader = "CUTSCENE_";
headers.add(new AtlasText(0, y, "CUTSCENE", AtlasFont.BOLD)).screenCenter(X);
y += spacer;
}
else if (currentHeader != "VOLUME_" && name.indexOf("VOLUME_") == 0)
{
currentHeader = "VOLUME_";
headers.add(new AtlasText(0, y, "VOLUME", AtlasFont.BOLD)).screenCenter(X);
y += spacer;
}
if (currentHeader != null && name.indexOf(currentHeader) == 0) name = name.substr(currentHeader.length);
@ -128,7 +142,7 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
labels.members[Std.int(controlGrid.selectedIndex / COLUMNS)].alpha = 1.0;
});
prompt = new Prompt("\nPress any key to rebind\n\n\n\n Escape to cancel", None);
prompt = new Prompt("\nPress any key to rebind\n\n\nBackspace to unbind\n Escape to cancel", None);
prompt.create();
prompt.createBgFromMargin(100, 0xFFfafd6d);
prompt.back.scrollFactor.set(0, 0);
@ -149,6 +163,8 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
function onSelect():Void
{
keyUsedToEnterPrompt = FlxG.keys.firstJustPressed();
controlGrid.enabled = false;
canExit = false;
prompt.exists = true;
@ -187,7 +203,9 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
canExit = false;
}
override function update(elapsed:Float)
var keyUsedToEnterPrompt:Null<Int> = null;
override function update(elapsed:Float):Void
{
super.update(elapsed);
@ -200,18 +218,35 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
{
case Keys:
{
// check released otherwise bugs can happen when you change the BACK key
// Um?
// Checking pressed causes problems when you change the BACK key,
// but checking released causes problems when the prompt is instant.
// keyUsedToEnterPrompt is my weird workaround.
var key = FlxG.keys.firstJustReleased();
if (key != NONE)
if (key != NONE && key != keyUsedToEnterPrompt)
{
if (key != ESCAPE) onInputSelect(key);
closePrompt();
if (key == ESCAPE)
{
closePrompt();
}
else if (key == BACKSPACE)
{
onInputSelect(NONE);
closePrompt();
}
else
{
onInputSelect(key);
closePrompt();
}
}
}
case Gamepad(id):
{
var button = FlxG.gamepads.getByID(id).firstJustReleasedID();
if (button != NONE)
if (button != NONE && button != keyUsedToEnterPrompt)
{
if (button != BACK) onInputSelect(button);
closePrompt();
@ -219,23 +254,32 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
}
}
}
var keyJustReleased:Int = FlxG.keys.firstJustReleased();
if (keyJustReleased != NONE && keyJustReleased == keyUsedToEnterPrompt)
{
keyUsedToEnterPrompt = null;
}
}
function onInputSelect(input:Int)
function onInputSelect(input:Int):Void
{
var item = controlGrid.selectedItem;
// check if that key is already set for this
var column0 = Math.floor(controlGrid.selectedIndex / 2) * 2;
for (i in 0...COLUMNS)
if (input != FlxKey.NONE)
{
if (controlGrid.members[column0 + i].input == input) return;
var column0 = Math.floor(controlGrid.selectedIndex / 2) * 2;
for (i in 0...COLUMNS)
{
if (controlGrid.members[column0 + i].input == input) return;
}
}
// Check if items in the same group already have the new input
for (group in itemGroups)
{
if (group.contains(item))
if (input != FlxKey.NONE && group.contains(item))
{
for (otherItem in group)
{
@ -252,10 +296,45 @@ class ControlsMenu extends funkin.ui.OptionsState.Page
}
PlayerSettings.player1.controls.replaceBinding(item.control, currentDevice, input, item.input);
// Don't use resetItem() since items share names/labels
item.input = input;
item.label.text = item.getLabel(input);
// Shift left on the grid if the item on the right is bound and the item on the left is unbound.
if (controlGrid.selectedIndex % 2 == 1)
{
trace('Modified item on right side of grid');
var leftItem = controlGrid.members[controlGrid.selectedIndex - 1];
if (leftItem != null && input != FlxKey.NONE && leftItem.input == FlxKey.NONE)
{
trace('Left item is unbound and right item is not!');
// Swap them.
var temp = leftItem.input;
leftItem.input = item.input;
item.input = temp;
leftItem.label.text = leftItem.getLabel(leftItem.input);
item.label.text = item.getLabel(item.input);
}
}
else
{
trace('Modified item on left side of grid');
var rightItem = controlGrid.members[controlGrid.selectedIndex + 1];
if (rightItem != null && input == FlxKey.NONE && rightItem.input != FlxKey.NONE)
{
trace('Left item is unbound and right item is not!');
// Swap them.
var temp = item.input;
item.input = rightItem.input;
rightItem.input = temp;
item.label.text = item.getLabel(item.input);
rightItem.label.text = rightItem.getLabel(rightItem.input);
}
}
PlayerSettings.player1.saveControls();
}
@ -306,6 +385,8 @@ class InputItem extends TextMenuItem
this.input = getInput();
super(x, y, getLabel(input), DEFAULT, callback);
this.fireInstantly = true;
}
public function updateDevice(device:Device)
@ -334,6 +415,6 @@ class InputItem extends TextMenuItem
public function getLabel(input:Int)
{
return input == -1 ? "---" : InputFormatter.format(input, device);
return input == FlxKey.NONE ? "---" : InputFormatter.format(input, device);
}
}

View file

@ -59,7 +59,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
remove(rating, true);
rating.destroy();
},
startDelay: Conductor.crochet * 0.001
startDelay: Conductor.beatLengthMs * 0.001
});
}
@ -109,7 +109,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
remove(comboSpr, true);
comboSpr.destroy();
},
startDelay: Conductor.crochet * 0.001
startDelay: Conductor.beatLengthMs * 0.001
});
var seperatedScore:Array<Int> = [];
@ -155,7 +155,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
remove(numScore, true);
numScore.destroy();
},
startDelay: Conductor.crochet * 0.002
startDelay: Conductor.beatLengthMs * 0.002
});
daLoop++;

View file

@ -189,12 +189,12 @@ class ChartEditorState extends HaxeUIState
function get_scrollPositionInMs():Float
{
return scrollPositionInSteps * Conductor.stepCrochet;
return scrollPositionInSteps * Conductor.stepLengthMs;
}
function set_scrollPositionInMs(value:Float):Float
{
scrollPositionInPixels = value / Conductor.stepCrochet;
scrollPositionInPixels = value / Conductor.stepLengthMs;
return value;
}
@ -223,7 +223,7 @@ class ChartEditorState extends HaxeUIState
function get_playheadPositionInMs():Float
{
return playheadPositionInSteps * Conductor.stepCrochet;
return playheadPositionInSteps * Conductor.stepLengthMs;
}
/**
@ -263,7 +263,7 @@ class ChartEditorState extends HaxeUIState
function get_songLengthInMs():Float
{
return songLengthInSteps * Conductor.stepCrochet;
return songLengthInSteps * Conductor.stepLengthMs;
}
function set_songLengthInMs(value:Float):Float
@ -1667,7 +1667,7 @@ class ChartEditorState extends HaxeUIState
// The song position of the cursor, in steps.
var cursorFractionalStep:Float = cursorY / GRID_SIZE / (16 / noteSnapQuant);
var cursorStep:Int = Std.int(Math.floor(cursorFractionalStep));
var cursorMs:Float = cursorStep * Conductor.stepCrochet * (16 / noteSnapQuant);
var cursorMs:Float = cursorStep * Conductor.stepLengthMs * (16 / noteSnapQuant);
// The direction value for the column at the cursor.
var cursorColumn:Int = Math.floor(cursorX / GRID_SIZE);
if (cursorColumn < 0) cursorColumn = 0;
@ -1705,7 +1705,7 @@ class ChartEditorState extends HaxeUIState
// We released the mouse. Select the notes in the box.
var cursorFractionalStepStart:Float = cursorYStart / GRID_SIZE;
var cursorStepStart:Int = Math.floor(cursorFractionalStepStart);
var cursorMsStart:Float = cursorStepStart * Conductor.stepCrochet;
var cursorMsStart:Float = cursorStepStart * Conductor.stepLengthMs;
var cursorColumnBase:Int = Math.floor(cursorX / GRID_SIZE);
var cursorColumnBaseStart:Int = Math.floor(cursorXStart / GRID_SIZE);
@ -1878,11 +1878,11 @@ class ChartEditorState extends HaxeUIState
// Handle extending the note as you drag.
// Since use Math.floor and stepCrochet here, the hold notes will be beat snapped.
var dragLengthSteps:Float = Math.floor((cursorMs - currentPlaceNoteData.time) / Conductor.stepCrochet);
var dragLengthSteps:Float = Math.floor((cursorMs - currentPlaceNoteData.time) / Conductor.stepLengthMs);
// Without this, the newly placed note feels too short compared to the user's input.
var INCREMENT:Float = 1.0;
var dragLengthMs:Float = (dragLengthSteps + INCREMENT) * Conductor.stepCrochet;
var dragLengthMs:Float = (dragLengthSteps + INCREMENT) * Conductor.stepLengthMs;
// TODO: Add and update some sort of preview?
@ -2187,7 +2187,7 @@ class ChartEditorState extends HaxeUIState
}
// Get the position the note should be at.
var noteTimePixels:Float = noteData.time / Conductor.stepCrochet * GRID_SIZE;
var noteTimePixels:Float = noteData.time / Conductor.stepLengthMs * GRID_SIZE;
// Make sure the note appears when scrolling up.
var modifiedViewAreaTop = viewAreaTop - GRID_SIZE;
@ -2213,7 +2213,7 @@ class ChartEditorState extends HaxeUIState
{
// If the note is a hold, we need to make sure it's long enough.
var noteLengthMs:Float = noteSprite.noteData.length;
var noteLengthSteps:Float = (noteLengthMs / Conductor.stepCrochet);
var noteLengthSteps:Float = (noteLengthMs / Conductor.stepLengthMs);
var lastNoteSprite:ChartEditorNoteSprite = noteSprite;
while (noteLengthSteps > 0)
@ -2237,7 +2237,7 @@ class ChartEditorState extends HaxeUIState
// Make sure the last note sprite shows the end cap properly.
lastNoteSprite.childNoteSprite = null;
// var noteLengthPixels:Float = (noteLengthMs / Conductor.stepCrochet + 1) * GRID_SIZE;
// var noteLengthPixels:Float = (noteLengthMs / Conductor.stepLengthMs + 1) * GRID_SIZE;
// add(new FlxSprite(noteSprite.x, noteSprite.y - renderedNotes.y + noteLengthPixels).makeGraphic(40, 2, 0xFFFF0000));
}
}
@ -2252,7 +2252,7 @@ class ChartEditorState extends HaxeUIState
}
// Get the position the event should be at.
var eventTimePixels:Float = eventData.time / Conductor.stepCrochet * GRID_SIZE;
var eventTimePixels:Float = eventData.time / Conductor.stepLengthMs * GRID_SIZE;
// Make sure the event appears when scrolling up.
var modifiedViewAreaTop = viewAreaTop - GRID_SIZE;