mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-03-24 19:09:21 +00:00
Merge branch 'master' into bugfix/attract-screen
This commit is contained in:
commit
db9f7f503f
2
.github/actions/setup-haxeshit/action.yml
vendored
2
.github/actions/setup-haxeshit/action.yml
vendored
|
@ -21,7 +21,7 @@ runs:
|
|||
- name: Installing Haxe lol
|
||||
run: |
|
||||
haxe -version
|
||||
haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git
|
||||
haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git development
|
||||
haxelib version
|
||||
haxelib --global install hmm
|
||||
haxelib --global run hmm install --quiet
|
||||
|
|
31
.github/workflows/build-shit.yml
vendored
31
.github/workflows/build-shit.yml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
|||
outputs:
|
||||
should_run: ${{ steps.should_run.outputs.should_run }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: print latest_commit
|
||||
run: echo ${{ github.sha }}
|
||||
- id: should_run
|
||||
|
@ -24,22 +24,12 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- uses: ./.github/actions/setup-haxeshit
|
||||
- name: Build Lime
|
||||
# TODO: Remove the step that builds Lime later.
|
||||
# Bash method
|
||||
run: |
|
||||
LIME_PATH=`haxelib libpath lime`
|
||||
echo "Moving to $LIME_PATH"
|
||||
cd $LIME_PATH
|
||||
git submodule sync --recursive
|
||||
git submodule update --recursive
|
||||
git status
|
||||
sudo apt-get install -y libxinerama-dev
|
||||
haxelib run lime rebuild linux --clean
|
||||
- name: Build game
|
||||
run: |
|
||||
sudo apt-get install -y libx11-dev libxinerama-dev libxrandr-dev libgl1-mesa-dev libgl-dev libxi-dev libxext-dev libasound2-dev
|
||||
sudo apt-get install -y libx11-dev xorg-dev libgl-dev libxi-dev libxext-dev libasound2-dev libxinerama-dev libxrandr-dev libgl1-mesa-dev
|
||||
haxelib run lime build html5 -debug --times
|
||||
ls
|
||||
- uses: ./.github/actions/upload-itch
|
||||
|
@ -56,18 +46,9 @@ jobs:
|
|||
actions: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- uses: ./.github/actions/setup-haxeshit
|
||||
- name: Build Lime
|
||||
# TODO: Remove the step that builds Lime later.
|
||||
# Powershell method
|
||||
run: |
|
||||
$LIME_PATH = haxelib libpath lime
|
||||
echo "Moving to $LIME_PATH"
|
||||
cd $LIME_PATH
|
||||
git submodule sync --recursive
|
||||
git submodule update --recursive
|
||||
git status
|
||||
haxelib run lime rebuild windows --clean
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build windows -debug
|
||||
|
|
13
hmm.json
13
hmm.json
|
@ -49,7 +49,7 @@
|
|||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "999faddf862d8a1584ae3794d932c55e94fc65cc",
|
||||
"ref": "be0b18553189a55fd42821026618a18615b070e3",
|
||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||
},
|
||||
{
|
||||
|
@ -88,9 +88,16 @@
|
|||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "acb0334c59bd4618f3c0277584d524ed0b288b5f",
|
||||
"ref": "558798adc5bf0e82d70fef589a59ce88892e0b5b",
|
||||
"url": "https://github.com/EliteMasterEric/lime"
|
||||
},
|
||||
{
|
||||
"name": "mockatoo",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "master",
|
||||
"url": "https://github.com/EliteMasterEric/mockatoo"
|
||||
},
|
||||
{
|
||||
"name": "openfl",
|
||||
"type": "git",
|
||||
|
@ -102,7 +109,7 @@
|
|||
"name": "polymod",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "631a3637f30997e47cd37bbab3cb6a75636a4b2a",
|
||||
"ref": "4bcd614103469af79a320898b823d1df8a55c3de",
|
||||
"url": "https://github.com/larsiusprime/polymod"
|
||||
},
|
||||
{
|
||||
|
|
234
source/flixel/addons/transition/FlxTransitionableSubState.hx
Normal file
234
source/flixel/addons/transition/FlxTransitionableSubState.hx
Normal file
|
@ -0,0 +1,234 @@
|
|||
package flixel.addons.transition;
|
||||
|
||||
import flixel.FlxSubState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
|
||||
/**
|
||||
* A `FlxSubState` which can perform visual transitions
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* First, extend `FlxTransitionableSubState` as ie, `FooState`.
|
||||
*
|
||||
* Method 1:
|
||||
*
|
||||
* ```haxe
|
||||
* var in:TransitionData = new TransitionData(...); // add your data where "..." is
|
||||
* var out:TransitionData = new TransitionData(...);
|
||||
*
|
||||
* FlxG.switchState(new FooState(in,out));
|
||||
* ```
|
||||
*
|
||||
* Method 2:
|
||||
*
|
||||
* ```haxe
|
||||
* FlxTransitionableSubState.defaultTransIn = new TransitionData(...);
|
||||
* FlxTransitionableSubState.defaultTransOut = new TransitionData(...);
|
||||
*
|
||||
* FlxG.switchState(new FooState());
|
||||
* ```
|
||||
*/
|
||||
class FlxTransitionableSubState extends FlxSubState
|
||||
{
|
||||
// global default transitions for ALL states, used if transIn/transOut are null
|
||||
public static var defaultTransIn(get, set):TransitionData;
|
||||
|
||||
static function get_defaultTransIn():TransitionData
|
||||
{
|
||||
return FlxTransitionableState.defaultTransIn;
|
||||
}
|
||||
|
||||
static function set_defaultTransIn(value:TransitionData):TransitionData
|
||||
{
|
||||
return FlxTransitionableState.defaultTransIn = value;
|
||||
}
|
||||
|
||||
public static var defaultTransOut(get, set):TransitionData;
|
||||
|
||||
static function get_defaultTransOut():TransitionData
|
||||
{
|
||||
return FlxTransitionableState.defaultTransOut;
|
||||
}
|
||||
|
||||
static function set_defaultTransOut(value:TransitionData):TransitionData
|
||||
{
|
||||
return FlxTransitionableState.defaultTransOut = value;
|
||||
}
|
||||
|
||||
public static var skipNextTransIn(get, set):Bool;
|
||||
|
||||
static function get_skipNextTransIn():Bool
|
||||
{
|
||||
return FlxTransitionableState.skipNextTransIn;
|
||||
}
|
||||
|
||||
static function set_skipNextTransIn(value:Bool):Bool
|
||||
{
|
||||
return FlxTransitionableState.skipNextTransIn = value;
|
||||
}
|
||||
|
||||
public static var skipNextTransOut(get, set):Bool;
|
||||
|
||||
static function get_skipNextTransOut():Bool
|
||||
{
|
||||
return FlxTransitionableState.skipNextTransOut;
|
||||
}
|
||||
|
||||
static function set_skipNextTransOut(value:Bool):Bool
|
||||
{
|
||||
return FlxTransitionableState.skipNextTransOut = value;
|
||||
}
|
||||
|
||||
// beginning & ending transitions for THIS state:
|
||||
public var transIn:TransitionData;
|
||||
public var transOut:TransitionData;
|
||||
|
||||
public var hasTransIn(get, never):Bool;
|
||||
public var hasTransOut(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Create a state with the ability to do visual transitions
|
||||
* @param TransIn Plays when the state begins
|
||||
* @param TransOut Plays when the state ends
|
||||
*/
|
||||
public function new(?TransIn:TransitionData, ?TransOut:TransitionData)
|
||||
{
|
||||
transIn = TransIn;
|
||||
transOut = TransOut;
|
||||
|
||||
if (transIn == null && defaultTransIn != null)
|
||||
{
|
||||
transIn = defaultTransIn;
|
||||
}
|
||||
if (transOut == null && defaultTransOut != null)
|
||||
{
|
||||
transOut = defaultTransOut;
|
||||
}
|
||||
super();
|
||||
}
|
||||
|
||||
override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
transIn = null;
|
||||
transOut = null;
|
||||
_onExit = null;
|
||||
}
|
||||
|
||||
override function create():Void
|
||||
{
|
||||
super.create();
|
||||
transitionIn();
|
||||
}
|
||||
|
||||
override function startOutro(onOutroComplete:() -> Void)
|
||||
{
|
||||
if (!hasTransOut) onOutroComplete();
|
||||
else if (!_exiting)
|
||||
{
|
||||
// play the exit transition, and when it's done call FlxG.switchState
|
||||
_exiting = true;
|
||||
transitionOut(onOutroComplete);
|
||||
|
||||
if (skipNextTransOut)
|
||||
{
|
||||
skipNextTransOut = false;
|
||||
finishTransOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the in-transition. Can be called manually at any time.
|
||||
*/
|
||||
public function transitionIn():Void
|
||||
{
|
||||
if (transIn != null && transIn.type != NONE)
|
||||
{
|
||||
if (skipNextTransIn)
|
||||
{
|
||||
skipNextTransIn = false;
|
||||
if (finishTransIn != null)
|
||||
{
|
||||
finishTransIn();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var _trans = createTransition(transIn);
|
||||
|
||||
_trans.setStatus(FULL);
|
||||
openSubState(_trans);
|
||||
|
||||
_trans.finishCallback = finishTransIn;
|
||||
_trans.start(OUT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the out-transition. Can be called manually at any time.
|
||||
*/
|
||||
public function transitionOut(?OnExit:Void->Void):Void
|
||||
{
|
||||
_onExit = OnExit;
|
||||
if (hasTransOut)
|
||||
{
|
||||
var _trans = createTransition(transOut);
|
||||
|
||||
_trans.setStatus(EMPTY);
|
||||
openSubState(_trans);
|
||||
|
||||
_trans.finishCallback = finishTransOut;
|
||||
_trans.start(IN);
|
||||
}
|
||||
else
|
||||
{
|
||||
_onExit();
|
||||
}
|
||||
}
|
||||
|
||||
var transOutFinished:Bool = false;
|
||||
|
||||
var _exiting:Bool = false;
|
||||
var _onExit:Void->Void;
|
||||
|
||||
function get_hasTransIn():Bool
|
||||
{
|
||||
return transIn != null && transIn.type != NONE;
|
||||
}
|
||||
|
||||
function get_hasTransOut():Bool
|
||||
{
|
||||
return transOut != null && transOut.type != NONE;
|
||||
}
|
||||
|
||||
function createTransition(data:TransitionData):Transition
|
||||
{
|
||||
return switch (data.type)
|
||||
{
|
||||
case TILES: new Transition(data);
|
||||
case FADE: new Transition(data);
|
||||
default: null;
|
||||
}
|
||||
}
|
||||
|
||||
function finishTransIn()
|
||||
{
|
||||
closeSubState();
|
||||
}
|
||||
|
||||
function finishTransOut()
|
||||
{
|
||||
transOutFinished = true;
|
||||
|
||||
if (!_exiting)
|
||||
{
|
||||
closeSubState();
|
||||
}
|
||||
|
||||
if (_onExit != null)
|
||||
{
|
||||
_onExit();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,6 @@ import flixel.input.keyboard.FlxKey;
|
|||
import flixel.input.mouse.FlxMouseButton.FlxMouseButtonID;
|
||||
import flixel.math.FlxAngle;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.ui.FlxVirtualPad;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import lime.ui.Haptic;
|
||||
|
|
|
@ -890,6 +890,13 @@ class FreeplayState extends MusicBeatSubState
|
|||
FlxG.sound.play(Paths.sound('confirmMenu'));
|
||||
dj.confirm();
|
||||
|
||||
if (targetSong != null)
|
||||
{
|
||||
// Load and cache the song's charts.
|
||||
// TODO: Do this in the loading state.
|
||||
targetSong.cacheCharts(true);
|
||||
}
|
||||
|
||||
new FlxTimer().start(1, function(tmr:FlxTimer) {
|
||||
LoadingState.loadAndSwitchState(new PlayState(
|
||||
{
|
||||
|
|
|
@ -3,6 +3,7 @@ package funkin;
|
|||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import funkin.play.PlayState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
|
||||
class GitarooPause extends MusicBeatState
|
||||
{
|
||||
|
@ -61,6 +62,8 @@ class GitarooPause extends MusicBeatState
|
|||
{
|
||||
if (replaySelect)
|
||||
{
|
||||
FlxTransitionableState.skipNextTransIn = false;
|
||||
FlxTransitionableState.skipNextTransOut = false;
|
||||
FlxG.switchState(new PlayState(previousParams));
|
||||
}
|
||||
else
|
||||
|
|
|
@ -191,7 +191,7 @@ class LatencyState extends MusicBeatSubState
|
|||
|
||||
if (FlxG.keys.pressed.D) FlxG.sound.music.time += 1000 * FlxG.elapsed;
|
||||
|
||||
Conductor.songPosition = swagSong.getTimeWithDiff() - Conductor.offset;
|
||||
Conductor.update(swagSong.getTimeWithDiff() - Conductor.offset);
|
||||
// Conductor.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp;
|
||||
|
||||
songPosVis.x = songPosToX(Conductor.songPosition);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin;
|
||||
|
||||
import flixel.addons.transition.FlxTransitionableSubState;
|
||||
import funkin.ui.debug.DebugMenuSubState;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
|
@ -12,7 +13,6 @@ import flixel.input.touch.FlxTouch;
|
|||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.ui.FlxButton;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.NGio;
|
||||
|
@ -104,6 +104,9 @@ class MainMenuState extends MusicBeatState
|
|||
createMenuItem('freeplay', 'mainmenu/freeplay', function() {
|
||||
persistentDraw = true;
|
||||
persistentUpdate = false;
|
||||
// Freeplay has its own custom transition
|
||||
FlxTransitionableSubState.skipNextTransIn = true;
|
||||
FlxTransitionableSubState.skipNextTransOut = true;
|
||||
openSubState(new FreeplayState());
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package funkin;
|
|||
import funkin.modding.IScriptedClass.IEventHandler;
|
||||
import flixel.FlxState;
|
||||
import flixel.FlxSubState;
|
||||
import flixel.addons.ui.FlxUIState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSort;
|
||||
|
@ -16,7 +16,7 @@ import funkin.util.SortUtil;
|
|||
* MusicBeatState actually represents the core utility FlxState of the game.
|
||||
* It includes functionality for event handling, as well as maintaining BPM-based update events.
|
||||
*/
|
||||
class MusicBeatState extends FlxUIState implements IEventHandler
|
||||
class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
||||
{
|
||||
var controls(get, never):Controls;
|
||||
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
package funkin;
|
||||
|
||||
import flixel.addons.transition.FlxTransitionableSubState;
|
||||
import flixel.FlxSubState;
|
||||
import funkin.modding.IScriptedClass.IEventHandler;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.IScriptedClass.IEventHandler;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import flixel.text.FlxText;
|
||||
import funkin.modding.PolymodHandler;
|
||||
import funkin.util.SortUtil;
|
||||
import flixel.util.FlxSort;
|
||||
|
||||
/**
|
||||
* MusicBeatSubState reincorporates the functionality of MusicBeatState into an FlxSubState.
|
||||
*/
|
||||
class MusicBeatSubState extends FlxSubState implements IEventHandler
|
||||
class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandler
|
||||
{
|
||||
public var leftWatermarkText:FlxText = null;
|
||||
public var rightWatermarkText:FlxText = null;
|
||||
|
||||
public function new(bgColor:FlxColor = FlxColor.TRANSPARENT)
|
||||
{
|
||||
super(bgColor);
|
||||
super();
|
||||
this.bgColor = bgColor;
|
||||
}
|
||||
|
||||
var controls(get, never):Controls;
|
||||
|
@ -57,6 +61,15 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
|
|||
|
||||
// This can now be used in EVERY STATE YAY!
|
||||
if (FlxG.keys.justPressed.F5) debug_refreshModules();
|
||||
|
||||
// Display Conductor info in the watch window.
|
||||
FlxG.watch.addQuick("songPosition", Conductor.songPosition);
|
||||
FlxG.watch.addQuick("bpm", Conductor.bpm);
|
||||
FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime);
|
||||
FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime);
|
||||
FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime);
|
||||
|
||||
dispatchEvent(new UpdateScriptEvent(elapsed));
|
||||
}
|
||||
|
||||
function debug_refreshModules()
|
||||
|
@ -67,6 +80,15 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
|
|||
FlxG.resetState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the state, by redoing the render order of all sprites.
|
||||
* It does this based on the `zIndex` of each prop.
|
||||
*/
|
||||
public function refresh()
|
||||
{
|
||||
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a step is hit in the current song.
|
||||
* Continues outside of PlayState, for things like animations in menus.
|
||||
|
|
|
@ -16,14 +16,17 @@ class PauseSubState extends MusicBeatSubState
|
|||
{
|
||||
var grpMenuShit:FlxTypedGroup<Alphabet>;
|
||||
|
||||
var pauseOG:Array<String> = [
|
||||
var pauseOptionsBase:Array<String> = [
|
||||
'Resume',
|
||||
'Restart Song',
|
||||
'Change Difficulty',
|
||||
'Toggle Practice Mode',
|
||||
'Exit to Menu'
|
||||
];
|
||||
var difficultyChoices:Array<String> = ['EASY', 'NORMAL', 'HARD', 'ERECT', 'BACK'];
|
||||
|
||||
var pauseOptionsDifficulty:Array<String> = ['EASY', 'NORMAL', 'HARD', 'ERECT', 'BACK'];
|
||||
|
||||
var pauseOptionsCharting:Array<String> = ['Resume', 'Restart Song', 'Exit to Chart Editor'];
|
||||
|
||||
var menuItems:Array<String> = [];
|
||||
var curSelected:Int = 0;
|
||||
|
@ -36,11 +39,15 @@ class PauseSubState extends MusicBeatSubState
|
|||
var bg:FlxSprite;
|
||||
var metaDataGrp:FlxTypedGroup<FlxSprite>;
|
||||
|
||||
public function new()
|
||||
var isChartingMode:Bool;
|
||||
|
||||
public function new(?isChartingMode:Bool = false)
|
||||
{
|
||||
super();
|
||||
|
||||
menuItems = pauseOG;
|
||||
this.isChartingMode = isChartingMode;
|
||||
|
||||
menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase;
|
||||
|
||||
if (PlayStatePlaylist.campaignId == 'week6')
|
||||
{
|
||||
|
@ -180,14 +187,13 @@ class PauseSubState extends MusicBeatSubState
|
|||
{
|
||||
var daSelected:String = menuItems[curSelected];
|
||||
|
||||
// TODO: Why is this based on the menu item's name? Make this an enum or something.
|
||||
switch (daSelected)
|
||||
{
|
||||
case 'Resume':
|
||||
close();
|
||||
|
||||
case 'Change Difficulty':
|
||||
menuItems = difficultyChoices;
|
||||
menuItems = pauseOptionsDifficulty;
|
||||
regenMenu();
|
||||
|
||||
case 'EASY' | 'NORMAL' | 'HARD' | 'ERECT':
|
||||
|
@ -199,7 +205,7 @@ class PauseSubState extends MusicBeatSubState
|
|||
|
||||
close();
|
||||
case 'BACK':
|
||||
menuItems = pauseOG;
|
||||
menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase;
|
||||
regenMenu();
|
||||
|
||||
case 'Toggle Practice Mode':
|
||||
|
@ -226,6 +232,11 @@ class PauseSubState extends MusicBeatSubState
|
|||
if (PlayStatePlaylist.isStoryMode) openSubState(new funkin.ui.StickerSubState(null, STORY));
|
||||
else
|
||||
openSubState(new funkin.ui.StickerSubState(null, FREEPLAY));
|
||||
|
||||
case 'Exit to Chart Editor':
|
||||
this.close();
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.stop();
|
||||
PlayState.instance.close(); // This only works because PlayState is a substate!
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ using Lambda;
|
|||
using StringTools;
|
||||
using funkin.util.tools.ArrayTools;
|
||||
using funkin.util.tools.ArraySortTools;
|
||||
using funkin.util.tools.Int64Tools;
|
||||
using funkin.util.tools.IteratorTools;
|
||||
using funkin.util.tools.MapTools;
|
||||
using funkin.util.tools.StringTools;
|
||||
|
|
|
@ -28,10 +28,6 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||
return instance ?? (instance = new PreciseInputManager());
|
||||
}
|
||||
|
||||
static final MS_TO_US:Int64 = 1000;
|
||||
static final US_TO_NS:Int64 = 1000;
|
||||
static final MS_TO_NS:Int64 = MS_TO_US * US_TO_NS;
|
||||
|
||||
static final DIRECTIONS:Array<NoteDirection> = [NoteDirection.LEFT, NoteDirection.DOWN, NoteDirection.UP, NoteDirection.RIGHT];
|
||||
|
||||
public var onInputPressed:FlxTypedSignal<PreciseInputEvent->Void>;
|
||||
|
@ -88,6 +84,11 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from int to Int64.
|
||||
*/
|
||||
static final NS_PER_MS:Int64 = Constants.NS_PER_MS;
|
||||
|
||||
/**
|
||||
* Returns a precise timestamp, measured in nanoseconds.
|
||||
* Timestamp is only useful for comparing against other timestamps.
|
||||
|
@ -101,11 +102,11 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||
// NOTE: This timestamp isn't that precise on standard HTML5 builds.
|
||||
// This is because of browser safeguards against timing attacks.
|
||||
// See https://web.dev/coop-coep to enable headers which allow for more precise timestamps.
|
||||
return js.Browser.window.performance.now() * MS_TO_NS;
|
||||
return haxe.Int64.fromFloat(js.Browser.window.performance.now()) * NS_PER_MS;
|
||||
#elseif cpp
|
||||
// NOTE: If the game hard crashes on this line, rebuild Lime!
|
||||
// `lime rebuild windows -clean`
|
||||
return lime._internal.backend.native.NativeCFFI.lime_sdl_get_ticks() * MS_TO_NS;
|
||||
return lime._internal.backend.native.NativeCFFI.lime_sdl_get_ticks() * NS_PER_MS;
|
||||
#else
|
||||
throw "Eric didn't implement precise timestamps on this platform!";
|
||||
#end
|
||||
|
@ -176,7 +177,7 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||
|
||||
// TODO: Remove this line with SDL3 when timestamps change meaning.
|
||||
// This is because SDL3's timestamps are measured in nanoseconds, not milliseconds.
|
||||
timestamp *= MS_TO_NS;
|
||||
timestamp *= Constants.NS_PER_MS;
|
||||
|
||||
updateKeyStates(key, true);
|
||||
|
||||
|
@ -198,7 +199,7 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList>
|
|||
|
||||
// TODO: Remove this line with SDL3 when timestamps change meaning.
|
||||
// This is because SDL3's timestamps are measured in nanoseconds, not milliseconds.
|
||||
timestamp *= MS_TO_NS;
|
||||
timestamp *= Constants.NS_PER_MS;
|
||||
|
||||
updateKeyStates(key, false);
|
||||
|
||||
|
|
|
@ -31,26 +31,35 @@ class Countdown
|
|||
{
|
||||
countdownStep = BEFORE;
|
||||
var cancelled:Bool = propagateCountdownEvent(countdownStep);
|
||||
if (cancelled) return false;
|
||||
if (cancelled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stop any existing countdown.
|
||||
stopCountdown();
|
||||
|
||||
PlayState.instance.isInCountdown = true;
|
||||
Conductor.songPosition = Conductor.beatLengthMs * -5;
|
||||
Conductor.update(PlayState.instance.startTimestamp + Conductor.beatLengthMs * -5);
|
||||
// Handle onBeatHit events manually
|
||||
@:privateAccess
|
||||
PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0));
|
||||
// @:privateAccess
|
||||
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0));
|
||||
|
||||
// The timer function gets called based on the beat of the song.
|
||||
countdownTimer = new FlxTimer();
|
||||
|
||||
countdownTimer.start(Conductor.beatLengthMs / 1000, function(tmr:FlxTimer) {
|
||||
if (PlayState.instance == null)
|
||||
{
|
||||
tmr.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
countdownStep = decrement(countdownStep);
|
||||
|
||||
// Handle onBeatHit events manually
|
||||
@:privateAccess
|
||||
PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0));
|
||||
// onBeatHit events are now properly dispatched by the Conductor even at negative timestamps,
|
||||
// so calling this is no longer necessary.
|
||||
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0));
|
||||
|
||||
// Countdown graphic.
|
||||
showCountdownGraphic(countdownStep, isPixelStyle);
|
||||
|
@ -61,7 +70,10 @@ class Countdown
|
|||
// Event handling bullshit.
|
||||
var cancelled:Bool = propagateCountdownEvent(countdownStep);
|
||||
|
||||
if (cancelled) pauseCountdown();
|
||||
if (cancelled)
|
||||
{
|
||||
pauseCountdown();
|
||||
}
|
||||
|
||||
if (countdownStep == AFTER)
|
||||
{
|
||||
|
@ -146,7 +158,7 @@ class Countdown
|
|||
{
|
||||
stopCountdown();
|
||||
// This will trigger PlayState.startSong()
|
||||
Conductor.songPosition = 0;
|
||||
Conductor.update(0);
|
||||
// PlayState.isInCountdown = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -117,15 +117,13 @@ class GameOverSubState extends MusicBeatSubState
|
|||
gameOverMusic.stop();
|
||||
|
||||
// The conductor now represents the BPM of the game over music.
|
||||
Conductor.songPosition = 0;
|
||||
Conductor.update(0);
|
||||
}
|
||||
|
||||
var hasStartedAnimation:Bool = false;
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (!hasStartedAnimation)
|
||||
{
|
||||
hasStartedAnimation = true;
|
||||
|
@ -183,7 +181,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
{
|
||||
// Match the conductor to the music.
|
||||
// This enables the stepHit and beatHit events.
|
||||
Conductor.songPosition = gameOverMusic.time;
|
||||
Conductor.update(gameOverMusic.time);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -205,12 +203,13 @@ class GameOverSubState extends MusicBeatSubState
|
|||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished())
|
||||
{
|
||||
startDeathMusic(1.0, false);
|
||||
boyfriend.playAnimation('deathLoop' + animationSuffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch the onUpdate event.
|
||||
dispatchEvent(new UpdateScriptEvent(elapsed));
|
||||
// Start death music before firstDeath gets replaced
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package funkin.play;
|
||||
|
||||
import flixel.addons.transition.FlxTransitionableSubState;
|
||||
import funkin.ui.debug.charting.ChartEditorState;
|
||||
import haxe.Int64;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import funkin.data.notestyle.NoteStyleData;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import flixel.addons.display.FlxPieDial;
|
||||
import flixel.addons.transition.Transition;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxObject;
|
||||
|
@ -77,12 +80,32 @@ typedef PlayStateParams =
|
|||
* @default `bf`, or the first character in the song's character list.
|
||||
*/
|
||||
?targetCharacter:String,
|
||||
/**
|
||||
* Whether the song should start in Practice Mode.
|
||||
* @default `false`
|
||||
*/
|
||||
?practiceMode:Bool,
|
||||
/**
|
||||
* Whether the song should be in minimal mode.
|
||||
* @default `false`
|
||||
*/
|
||||
?minimalMode:Bool,
|
||||
/**
|
||||
* If specified, the game will jump to the specified timestamp after the countdown ends.
|
||||
*/
|
||||
?startTimestamp:Float,
|
||||
/**
|
||||
* If specified, the game will not load the instrumental or vocal tracks,
|
||||
* and must be loaded externally.
|
||||
*/
|
||||
?overrideMusic:Bool,
|
||||
}
|
||||
|
||||
/**
|
||||
* The gameplay state, where all the rhythm gaming happens.
|
||||
* SubState so it can be loaded as a child of the chart editor.
|
||||
*/
|
||||
class PlayState extends MusicBeatState
|
||||
class PlayState extends MusicBeatSubState
|
||||
{
|
||||
/**
|
||||
* STATIC VARIABLES
|
||||
|
@ -209,6 +232,11 @@ class PlayState extends MusicBeatState
|
|||
*/
|
||||
public var isPracticeMode:Bool = false;
|
||||
|
||||
/**
|
||||
* In Minimal Mode, the stage and characters are not loaded and a standard background is used.
|
||||
*/
|
||||
public var isMinimalMode:Bool = false;
|
||||
|
||||
/**
|
||||
* Whether the game is currently in an animated cutscene, and gameplay should be stopped.
|
||||
*/
|
||||
|
@ -219,6 +247,24 @@ class PlayState extends MusicBeatState
|
|||
*/
|
||||
public var disableKeys:Bool = false;
|
||||
|
||||
public var startTimestamp:Float = 0.0;
|
||||
|
||||
var overrideMusic:Bool = false;
|
||||
|
||||
public var isSubState(get, null):Bool;
|
||||
|
||||
function get_isSubState():Bool
|
||||
{
|
||||
return this._parentState != null;
|
||||
}
|
||||
|
||||
public var isChartingMode(get, null):Bool;
|
||||
|
||||
function get_isChartingMode():Bool
|
||||
{
|
||||
return this._parentState != null && Std.isOfType(this._parentState, ChartEditorState);
|
||||
}
|
||||
|
||||
/**
|
||||
* The current dialogue.
|
||||
*/
|
||||
|
@ -275,10 +321,15 @@ class PlayState extends MusicBeatState
|
|||
*/
|
||||
var startingSong:Bool = false;
|
||||
|
||||
/**
|
||||
* False until `create()` has completed.
|
||||
*/
|
||||
var initialized:Bool = false;
|
||||
|
||||
/**
|
||||
* A group of audio tracks, used to play the song's vocals.
|
||||
*/
|
||||
var vocals:VoicesGroup;
|
||||
public var vocals:VoicesGroup;
|
||||
|
||||
#if discord_rpc
|
||||
// Discord RPC variables
|
||||
|
@ -438,6 +489,10 @@ class PlayState extends MusicBeatState
|
|||
currentSong = params.targetSong;
|
||||
if (params.targetDifficulty != null) currentDifficulty = params.targetDifficulty;
|
||||
if (params.targetCharacter != null) currentPlayerId = params.targetCharacter;
|
||||
isPracticeMode = params.practiceMode ?? false;
|
||||
isMinimalMode = params.minimalMode ?? false;
|
||||
startTimestamp = params.startTimestamp ?? 0.0;
|
||||
overrideMusic = params.overrideMusic ?? false;
|
||||
|
||||
// Don't do anything else here! Wait until create() when we attach to the camera.
|
||||
}
|
||||
|
@ -447,8 +502,6 @@ class PlayState extends MusicBeatState
|
|||
*/
|
||||
public override function create():Void
|
||||
{
|
||||
super.create();
|
||||
|
||||
if (instance != null)
|
||||
{
|
||||
// TODO: Do something in this case? IDK.
|
||||
|
@ -458,13 +511,6 @@ class PlayState extends MusicBeatState
|
|||
|
||||
NoteSplash.buildSplashFrames();
|
||||
|
||||
if (currentSong != null)
|
||||
{
|
||||
// Load and cache the song's charts.
|
||||
// TODO: Do this in the loading state.
|
||||
currentSong.cacheCharts(true);
|
||||
}
|
||||
|
||||
// Returns null if the song failed to load or doesn't have the selected difficulty.
|
||||
if (currentSong == null || currentChart == null)
|
||||
{
|
||||
|
@ -490,7 +536,14 @@ class PlayState extends MusicBeatState
|
|||
lime.app.Application.current.window.alert(message, 'Error loading PlayState');
|
||||
|
||||
// Force the user back to the main menu.
|
||||
FlxG.switchState(new MainMenuState());
|
||||
if (isSubState)
|
||||
{
|
||||
this.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.switchState(new MainMenuState());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -516,24 +569,32 @@ class PlayState extends MusicBeatState
|
|||
this.persistentDraw = true;
|
||||
|
||||
// Stop any pre-existing music.
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.stop();
|
||||
if (!overrideMusic && FlxG.sound.music != null) FlxG.sound.music.stop();
|
||||
|
||||
// Prepare the current song's instrumental and vocals to be played.
|
||||
if (currentChart != null)
|
||||
if (!overrideMusic && currentChart != null)
|
||||
{
|
||||
currentChart.cacheInst(currentPlayerId);
|
||||
currentChart.cacheVocals(currentPlayerId);
|
||||
}
|
||||
|
||||
// Prepare the Conductor.
|
||||
Conductor.forceBPM(null);
|
||||
Conductor.mapTimeChanges(currentChart.timeChanges);
|
||||
Conductor.update(-5000);
|
||||
Conductor.update((Conductor.beatLengthMs * -5) + startTimestamp);
|
||||
|
||||
// The song is now loaded. We can continue to initialize the play state.
|
||||
initCameras();
|
||||
initHealthBar();
|
||||
initStage();
|
||||
initCharacters();
|
||||
if (!isMinimalMode)
|
||||
{
|
||||
initStage();
|
||||
initCharacters();
|
||||
}
|
||||
else
|
||||
{
|
||||
initMinimalMode();
|
||||
}
|
||||
initStrumlines();
|
||||
|
||||
// Initialize the judgements and combo meter.
|
||||
|
@ -582,6 +643,9 @@ class PlayState extends MusicBeatState
|
|||
startCountdown();
|
||||
}
|
||||
|
||||
// Do this last to prevent beatHit from being called before create() is done.
|
||||
super.create();
|
||||
|
||||
leftWatermarkText.cameras = [camHUD];
|
||||
rightWatermarkText.cameras = [camHUD];
|
||||
|
||||
|
@ -592,6 +656,8 @@ class PlayState extends MusicBeatState
|
|||
|
||||
FlxG.console.registerObject('playState', this);
|
||||
#end
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float):Void
|
||||
|
@ -629,7 +695,7 @@ class PlayState extends MusicBeatState
|
|||
|
||||
FlxG.sound.music.pause();
|
||||
vocals.pause();
|
||||
FlxG.sound.music.time = 0;
|
||||
FlxG.sound.music.time = (startTimestamp);
|
||||
vocals.time = 0;
|
||||
|
||||
FlxG.sound.music.volume = 1;
|
||||
|
@ -637,7 +703,7 @@ class PlayState extends MusicBeatState
|
|||
vocals.playerVolume = 1;
|
||||
vocals.opponentVolume = 1;
|
||||
|
||||
currentStage.resetStage();
|
||||
if (currentStage != null) currentStage.resetStage();
|
||||
|
||||
playerStrumline.vwooshNotes();
|
||||
opponentStrumline.vwooshNotes();
|
||||
|
@ -666,8 +732,8 @@ class PlayState extends MusicBeatState
|
|||
{
|
||||
if (isInCountdown)
|
||||
{
|
||||
Conductor.songPosition += elapsed * 1000;
|
||||
if (Conductor.songPosition >= 0) startSong();
|
||||
Conductor.update(Conductor.songPosition + elapsed * 1000);
|
||||
if (Conductor.songPosition >= startTimestamp) startSong();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -712,7 +778,7 @@ class PlayState extends MusicBeatState
|
|||
// There is a 1/1000 change to use a special pause menu.
|
||||
// This prevents the player from resuming, but that's the point.
|
||||
// It's a reference to Gitaroo Man, which doesn't let you pause the game.
|
||||
if (event.gitaroo)
|
||||
if (!isSubState && event.gitaroo)
|
||||
{
|
||||
FlxG.switchState(new GitarooPause(
|
||||
{
|
||||
|
@ -731,8 +797,10 @@ class PlayState extends MusicBeatState
|
|||
boyfriendPos = currentStage.getBoyfriend().getScreenPosition();
|
||||
}
|
||||
|
||||
var pauseSubState:FlxSubState = new PauseSubState();
|
||||
var pauseSubState:FlxSubState = new PauseSubState(isChartingMode);
|
||||
|
||||
FlxTransitionableSubState.skipNextTransIn = true;
|
||||
FlxTransitionableSubState.skipNextTransOut = true;
|
||||
openSubState(pauseSubState);
|
||||
pauseSubState.camera = camHUD;
|
||||
// boyfriendPos.put(); // TODO: Why is this here?
|
||||
|
@ -807,6 +875,8 @@ class PlayState extends MusicBeatState
|
|||
#end
|
||||
|
||||
var gameOverSubState = new GameOverSubState();
|
||||
FlxTransitionableSubState.skipNextTransIn = true;
|
||||
FlxTransitionableSubState.skipNextTransOut = true;
|
||||
openSubState(gameOverSubState);
|
||||
|
||||
#if discord_rpc
|
||||
|
@ -827,6 +897,13 @@ class PlayState extends MusicBeatState
|
|||
trace('Found ${songEventsToActivate.length} event(s) to activate.');
|
||||
for (event in songEventsToActivate)
|
||||
{
|
||||
// If an event is trying to play, but it's over 5 seconds old, skip it.
|
||||
if (event.time - Conductor.songPosition < -5000)
|
||||
{
|
||||
event.activated = true;
|
||||
continue;
|
||||
};
|
||||
|
||||
var eventEvent:SongEventScriptEvent = new SongEventScriptEvent(event);
|
||||
dispatchEvent(eventEvent);
|
||||
// Calling event.cancelEvent() skips the event. Neat!
|
||||
|
@ -846,9 +923,6 @@ class PlayState extends MusicBeatState
|
|||
|
||||
// Moving notes into position is now done by Strumline.update().
|
||||
processNotes(elapsed);
|
||||
|
||||
// Dispatch the onUpdate event to scripted elements.
|
||||
dispatchEvent(new UpdateScriptEvent(elapsed));
|
||||
}
|
||||
|
||||
public override function dispatchEvent(event:ScriptEvent):Void
|
||||
|
@ -882,7 +956,7 @@ class PlayState extends MusicBeatState
|
|||
{
|
||||
// If there is a substate which requires the game to continue,
|
||||
// then make this a condition.
|
||||
var shouldPause = true;
|
||||
var shouldPause = (Std.isOfType(subState, PauseSubState) || Std.isOfType(subState, GameOverSubState));
|
||||
|
||||
if (shouldPause)
|
||||
{
|
||||
|
@ -896,6 +970,7 @@ class PlayState extends MusicBeatState
|
|||
// Pause the countdown.
|
||||
Countdown.pauseCountdown();
|
||||
}
|
||||
else {}
|
||||
|
||||
super.openSubState(subState);
|
||||
}
|
||||
|
@ -906,7 +981,7 @@ class PlayState extends MusicBeatState
|
|||
*/
|
||||
public override function closeSubState():Void
|
||||
{
|
||||
if (isGamePaused)
|
||||
if (Std.isOfType(subState, PauseSubState))
|
||||
{
|
||||
var event:ScriptEvent = new ScriptEvent(ScriptEvent.RESUME, true);
|
||||
|
||||
|
@ -914,6 +989,9 @@ class PlayState extends MusicBeatState
|
|||
|
||||
if (event.eventCanceled) return;
|
||||
|
||||
// Resume
|
||||
FlxG.sound.music.play();
|
||||
|
||||
if (FlxG.sound.music != null && !startingSong && !isInCutscene) resyncVocals();
|
||||
|
||||
// Resume the countdown.
|
||||
|
@ -931,6 +1009,10 @@ class PlayState extends MusicBeatState
|
|||
}
|
||||
#end
|
||||
}
|
||||
else if (Std.isOfType(subState, Transition))
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
super.closeSubState();
|
||||
}
|
||||
|
@ -1029,12 +1111,15 @@ class PlayState extends MusicBeatState
|
|||
|
||||
override function stepHit():Bool
|
||||
{
|
||||
if (criticalFailure) return false;
|
||||
if (criticalFailure || !initialized) return false;
|
||||
|
||||
// super.stepHit() returns false if a module cancelled the event.
|
||||
if (!super.stepHit()) return false;
|
||||
|
||||
if (FlxG.sound.music != null
|
||||
if (isGamePaused) return false;
|
||||
|
||||
if (!startingSong
|
||||
&& FlxG.sound.music != null
|
||||
&& (Math.abs(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)) > 200
|
||||
|| Math.abs(vocals.checkSyncError(Conductor.songPosition - Conductor.offset)) > 200))
|
||||
{
|
||||
|
@ -1052,11 +1137,13 @@ class PlayState extends MusicBeatState
|
|||
|
||||
override function beatHit():Bool
|
||||
{
|
||||
if (criticalFailure) return false;
|
||||
if (criticalFailure || !initialized) return false;
|
||||
|
||||
// super.beatHit() returns false if a module cancelled the event.
|
||||
if (!super.beatHit()) return false;
|
||||
|
||||
if (isGamePaused) return false;
|
||||
|
||||
if (generatedMusic)
|
||||
{
|
||||
// TODO: Sort more efficiently, or less often, to improve performance.
|
||||
|
@ -1209,6 +1296,19 @@ class PlayState extends MusicBeatState
|
|||
loadStage(currentStageId);
|
||||
}
|
||||
|
||||
function initMinimalMode():Void
|
||||
{
|
||||
// Create the green background.
|
||||
var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat'));
|
||||
menuBG.color = 0xFF4CAF50;
|
||||
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
|
||||
menuBG.updateHitbox();
|
||||
menuBG.screenCenter();
|
||||
menuBG.scrollFactor.set(0, 0);
|
||||
menuBG.zIndex = -1000;
|
||||
add(menuBG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads stage data from cache, assembles the props,
|
||||
* and adds it to the state.
|
||||
|
@ -1453,12 +1553,16 @@ class PlayState extends MusicBeatState
|
|||
trace('Song difficulty could not be loaded.');
|
||||
}
|
||||
|
||||
Conductor.forceBPM(currentChart.getStartingBPM());
|
||||
// Conductor.forceBPM(currentChart.getStartingBPM());
|
||||
|
||||
vocals = currentChart.buildVocals(currentPlayerId);
|
||||
if (vocals.members.length == 0)
|
||||
if (!overrideMusic)
|
||||
{
|
||||
trace('WARNING: No vocals found for this song.');
|
||||
vocals = currentChart.buildVocals(currentPlayerId);
|
||||
|
||||
if (vocals.members.length == 0)
|
||||
{
|
||||
trace('WARNING: No vocals found for this song.');
|
||||
}
|
||||
}
|
||||
|
||||
regenNoteData();
|
||||
|
@ -1469,7 +1573,7 @@ class PlayState extends MusicBeatState
|
|||
/**
|
||||
* Read note data from the chart and generate the notes.
|
||||
*/
|
||||
function regenNoteData():Void
|
||||
function regenNoteData(startTime:Float = 0):Void
|
||||
{
|
||||
Highscore.tallies.combo = 0;
|
||||
Highscore.tallies = new Tallies();
|
||||
|
@ -1485,6 +1589,8 @@ class PlayState extends MusicBeatState
|
|||
for (songNote in currentChart.notes)
|
||||
{
|
||||
var strumTime:Float = songNote.time;
|
||||
if (strumTime < startTime) continue; // Skip notes that are before the start time.
|
||||
|
||||
var noteData:Int = songNote.getDirection();
|
||||
|
||||
var playerNote:Bool = true;
|
||||
|
@ -1565,20 +1671,29 @@ class PlayState extends MusicBeatState
|
|||
|
||||
startingSong = false;
|
||||
|
||||
if (!isGamePaused && currentChart != null)
|
||||
if (!overrideMusic && !isGamePaused && currentChart != null)
|
||||
{
|
||||
currentChart.playInst(1.0, false);
|
||||
}
|
||||
|
||||
FlxG.sound.music.onComplete = endSong;
|
||||
FlxG.sound.music.play();
|
||||
FlxG.sound.music.time = startTimestamp;
|
||||
trace('Playing vocals...');
|
||||
add(vocals);
|
||||
vocals.play();
|
||||
resyncVocals();
|
||||
|
||||
#if discord_rpc
|
||||
// Updating Discord Rich Presence (with Time Left)
|
||||
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, currentSongLengthMs);
|
||||
#end
|
||||
|
||||
if (startTimestamp > 0)
|
||||
{
|
||||
FlxG.sound.music.time = startTimestamp;
|
||||
handleSkippedNotes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1588,13 +1703,16 @@ class PlayState extends MusicBeatState
|
|||
{
|
||||
if (_exiting || vocals == null) return;
|
||||
|
||||
// Skip this if the music is paused (GameOver, Pause menu, etc.)
|
||||
if (!FlxG.sound.music.playing) return;
|
||||
|
||||
vocals.pause();
|
||||
|
||||
FlxG.sound.music.play();
|
||||
Conductor.update();
|
||||
|
||||
vocals.time = FlxG.sound.music.time;
|
||||
vocals.play();
|
||||
vocals.play(false, FlxG.sound.music.time);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1619,6 +1737,8 @@ class PlayState extends MusicBeatState
|
|||
*/
|
||||
function onKeyPress(event:PreciseInputEvent):Void
|
||||
{
|
||||
if (isGamePaused) return;
|
||||
|
||||
// Do the minimal possible work here.
|
||||
inputPressQueue.push(event);
|
||||
}
|
||||
|
@ -1628,6 +1748,8 @@ class PlayState extends MusicBeatState
|
|||
*/
|
||||
function onKeyRelease(event:PreciseInputEvent):Void
|
||||
{
|
||||
if (isGamePaused) return;
|
||||
|
||||
// Do the minimal possible work here.
|
||||
inputReleaseQueue.push(event);
|
||||
}
|
||||
|
@ -1761,6 +1883,7 @@ class PlayState extends MusicBeatState
|
|||
|
||||
// Judge the miss.
|
||||
// NOTE: This is what handles the scoring.
|
||||
trace('Missed note! ${note.noteData}');
|
||||
onNoteMiss(note);
|
||||
|
||||
note.handledMiss = true;
|
||||
|
@ -1790,6 +1913,25 @@ class PlayState extends MusicBeatState
|
|||
*/
|
||||
var inputSpitter:Array<ScoreInput> = [];
|
||||
|
||||
function handleSkippedNotes():Void
|
||||
{
|
||||
for (note in playerStrumline.notes.members)
|
||||
{
|
||||
if (note == null || note.hasBeenHit) continue;
|
||||
var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS;
|
||||
|
||||
if (Conductor.songPosition > hitWindowEnd)
|
||||
{
|
||||
// We have passed this note.
|
||||
// Flag the note for deletion without actually penalizing the player.
|
||||
note.handledMiss = true;
|
||||
}
|
||||
}
|
||||
|
||||
playerStrumline.handleSkippedNotes();
|
||||
opponentStrumline.handleSkippedNotes();
|
||||
}
|
||||
|
||||
/**
|
||||
* PreciseInputEvents are put into a queue between update() calls,
|
||||
* and then processed here.
|
||||
|
@ -1851,6 +1993,7 @@ class PlayState extends MusicBeatState
|
|||
if (targetNote == null) continue;
|
||||
|
||||
// Judge and hit the note.
|
||||
trace('Hit note! ${targetNote.noteData}');
|
||||
goodNoteHit(targetNote, input);
|
||||
|
||||
targetNote.visible = false;
|
||||
|
@ -2018,11 +2161,10 @@ class PlayState extends MusicBeatState
|
|||
if (event.eventCanceled) return;
|
||||
|
||||
health -= Constants.HEALTH_MISS_PENALTY;
|
||||
songScore -= 10;
|
||||
|
||||
if (!isPracticeMode)
|
||||
{
|
||||
songScore -= 10;
|
||||
|
||||
// messy copy paste rn lol
|
||||
var pressArray:Array<Bool> = [
|
||||
controls.NOTE_LEFT_P,
|
||||
|
@ -2093,11 +2235,10 @@ class PlayState extends MusicBeatState
|
|||
if (event.eventCanceled) return;
|
||||
|
||||
health += event.healthChange;
|
||||
songScore += event.scoreChange;
|
||||
|
||||
if (!isPracticeMode)
|
||||
{
|
||||
songScore += event.scoreChange;
|
||||
|
||||
var pressArray:Array<Bool> = [
|
||||
controls.NOTE_LEFT_P,
|
||||
controls.NOTE_DOWN_P,
|
||||
|
@ -2139,6 +2280,7 @@ class PlayState extends MusicBeatState
|
|||
if (FlxG.keys.justPressed.H) camHUD.visible = !camHUD.visible;
|
||||
#end
|
||||
|
||||
// Eject button
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState());
|
||||
|
||||
if (FlxG.keys.justPressed.F5) debug_refreshModules();
|
||||
|
@ -2170,18 +2312,21 @@ class PlayState extends MusicBeatState
|
|||
}
|
||||
|
||||
// 8: Move to the offset editor.
|
||||
if (FlxG.keys.justPressed.EIGHT) FlxG.switchState(new funkin.ui.animDebugShit.DebugBoundingState());
|
||||
if (FlxG.keys.justPressed.EIGHT)
|
||||
{
|
||||
lime.app.Application.current.window.alert("Press ~ on the main menu to get to the editor", 'LOL');
|
||||
}
|
||||
|
||||
// 9: Toggle the old icon.
|
||||
if (FlxG.keys.justPressed.NINE) iconP1.toggleOldIcon();
|
||||
|
||||
#if debug
|
||||
// PAGEUP: Skip forward one section.
|
||||
// SHIFT+PAGEUP: Skip forward ten sections.
|
||||
if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 10 : 1);
|
||||
// PAGEDOWN: Skip backward one section. Doesn't replace notes.
|
||||
// SHIFT+PAGEDOWN: Skip backward ten sections.
|
||||
if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -10 : -1);
|
||||
// PAGEUP: Skip forward two sections.
|
||||
// SHIFT+PAGEUP: Skip forward twenty sections.
|
||||
if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 20 : 2);
|
||||
// PAGEDOWN: Skip backward two section. Doesn't replace notes.
|
||||
// SHIFT+PAGEDOWN: Skip backward twenty sections.
|
||||
if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -20 : -2);
|
||||
#end
|
||||
|
||||
if (FlxG.keys.justPressed.B) trace(inputSpitter.join('\n'));
|
||||
|
@ -2232,11 +2377,10 @@ class PlayState extends MusicBeatState
|
|||
playerStrumline.playNoteSplash(daNote.noteData.getDirection());
|
||||
}
|
||||
|
||||
// Only add the score if you're not on practice mode
|
||||
songScore += score;
|
||||
|
||||
if (!isPracticeMode)
|
||||
{
|
||||
songScore += score;
|
||||
|
||||
// TODO: Input splitter uses old input system, make it pull from the precise input queue directly.
|
||||
var pressArray:Array<Bool> = [
|
||||
controls.NOTE_LEFT_P,
|
||||
|
@ -2391,7 +2535,14 @@ class PlayState extends MusicBeatState
|
|||
// FlxG.save.data.weekUnlocked = StoryMenuState.weekUnlocked;
|
||||
FlxG.save.flush();
|
||||
|
||||
moveToResultsScreen();
|
||||
if (isSubState)
|
||||
{
|
||||
this.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
moveToResultsScreen();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2445,10 +2596,24 @@ class PlayState extends MusicBeatState
|
|||
}
|
||||
else
|
||||
{
|
||||
moveToResultsScreen();
|
||||
if (isSubState)
|
||||
{
|
||||
this.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
moveToResultsScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override function close():Void
|
||||
{
|
||||
criticalFailure = true; // Stop game updates.
|
||||
performCleanup();
|
||||
super.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform necessary cleanup before leaving the PlayState.
|
||||
*/
|
||||
|
@ -2459,6 +2624,19 @@ class PlayState extends MusicBeatState
|
|||
// TODO: Uncache the song.
|
||||
}
|
||||
|
||||
if (!overrideMusic)
|
||||
{
|
||||
// Stop the music.
|
||||
FlxG.sound.music.pause();
|
||||
vocals.stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.sound.music.pause();
|
||||
vocals.pause();
|
||||
remove(vocals);
|
||||
}
|
||||
|
||||
// Remove reference to stage and remove sprites from it to save memory.
|
||||
if (currentStage != null)
|
||||
{
|
||||
|
@ -2559,6 +2737,7 @@ class PlayState extends MusicBeatState
|
|||
FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.04);
|
||||
FlxG.camera.targetOffset.set();
|
||||
FlxG.camera.zoom = defaultCameraZoom;
|
||||
// Snap the camera to the follow point immediately.
|
||||
FlxG.camera.focusOn(cameraFollowPoint.getPosition());
|
||||
}
|
||||
|
||||
|
@ -2572,30 +2751,16 @@ class PlayState extends MusicBeatState
|
|||
{
|
||||
FlxG.sound.music.pause();
|
||||
|
||||
FlxG.sound.music.time += sections * Conductor.measureLengthMs;
|
||||
var targetTimeSteps:Float = Conductor.currentStepTime + (Conductor.timeSignatureNumerator * Constants.STEPS_PER_BEAT * sections);
|
||||
var targetTimeMs:Float = Conductor.getStepTimeInMs(targetTimeSteps);
|
||||
|
||||
FlxG.sound.music.time = targetTimeMs;
|
||||
|
||||
handleSkippedNotes();
|
||||
// regenNoteData(FlxG.sound.music.time);
|
||||
|
||||
Conductor.update(FlxG.sound.music.time);
|
||||
|
||||
/**
|
||||
*
|
||||
// TODO: Redo this for the new conductor.
|
||||
var daBPM:Float = Conductor.bpm;
|
||||
var daPos:Float = 0;
|
||||
for (i in 0...(Std.int(Conductor.currentStep / 16 + sec)))
|
||||
{
|
||||
var section = .getSong()[i];
|
||||
if (section == null) continue;
|
||||
if (section.changeBPM)
|
||||
{
|
||||
daBPM = .getSong()[i].bpm;
|
||||
}
|
||||
daPos += 4 * (1000 * 60 / daBPM);
|
||||
}
|
||||
Conductor.songPosition = FlxG.sound.music.time = daPos;
|
||||
Conductor.songPosition += Conductor.offset;
|
||||
|
||||
*/
|
||||
|
||||
resyncVocals();
|
||||
}
|
||||
#end
|
||||
|
|
|
@ -227,13 +227,16 @@ class BaseCharacter extends Bopper
|
|||
public function resetCharacter(resetCamera:Bool = true):Void
|
||||
{
|
||||
// Reset the animation offsets. This will modify x and y to be the absolute position of the character.
|
||||
this.animOffsets = [0, 0];
|
||||
// this.animOffsets = [0, 0];
|
||||
|
||||
// Now we can set the x and y to be their original values without having to account for animOffsets.
|
||||
this.resetPosition();
|
||||
|
||||
// Make sure we are playing the idle animation (to reapply animOffsets)...
|
||||
// Then reapply animOffsets...
|
||||
// applyAnimationOffsets(getCurrentAnimation());
|
||||
|
||||
this.dance(true); // Force to avoid the old animation playing with the wrong offset at the start of the song.
|
||||
// Make sure we are playing the idle animation
|
||||
// ...then update the hitbox so that this.width and this.height are correct.
|
||||
this.updateHitbox();
|
||||
|
||||
|
@ -344,7 +347,7 @@ class BaseCharacter extends Bopper
|
|||
|
||||
if (isDead)
|
||||
{
|
||||
playDeathAnimation();
|
||||
// playDeathAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -392,20 +395,6 @@ class BaseCharacter extends Bopper
|
|||
FlxG.watch.addQuick('holdTimer-${characterId}', holdTimer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since no `onBeatHit` or `dance` calls happen in GameOverSubState,
|
||||
* this regularly gets called instead.
|
||||
*
|
||||
* @param force Force the deathLoop animation to play, even if `firstDeath` is still playing.
|
||||
*/
|
||||
public function playDeathAnimation(force:Bool = false):Void
|
||||
{
|
||||
if (force || (getCurrentAnimation().startsWith('firstDeath') && isAnimationFinished()))
|
||||
{
|
||||
playAnimation('deathLoop' + GameOverSubState.animationSuffix);
|
||||
}
|
||||
}
|
||||
|
||||
public function isSinging():Bool
|
||||
{
|
||||
return getCurrentAnimation().startsWith('sing');
|
||||
|
|
|
@ -58,7 +58,12 @@ class Strumline extends FlxSpriteGroup
|
|||
|
||||
final noteStyle:NoteStyle;
|
||||
|
||||
/**
|
||||
* The note data for the song. Should NOT be altered after the song starts,
|
||||
* so we can easily rewind.
|
||||
*/
|
||||
var noteData:Array<SongNoteData> = [];
|
||||
|
||||
var nextNoteIndex:Int = -1;
|
||||
|
||||
var heldKeys:Array<Bool> = [];
|
||||
|
@ -279,14 +284,22 @@ class Strumline extends FlxSpriteGroup
|
|||
{
|
||||
if (noteData.length == 0) return;
|
||||
|
||||
var songStart:Float = PlayState.instance.startTimestamp ?? 0.0;
|
||||
var hitWindowStart:Float = Conductor.songPosition - Constants.HIT_WINDOW_MS;
|
||||
var renderWindowStart:Float = Conductor.songPosition + RENDER_DISTANCE_MS;
|
||||
|
||||
for (noteIndex in nextNoteIndex...noteData.length)
|
||||
{
|
||||
var note:Null<SongNoteData> = noteData[noteIndex];
|
||||
|
||||
if (note == null) continue;
|
||||
if (note.time > renderWindowStart) break;
|
||||
if (note == null) continue; // Note is blank
|
||||
if (note.time < songStart || note.time < hitWindowStart)
|
||||
{
|
||||
// Note is in the past, skip it.
|
||||
nextNoteIndex = noteIndex + 1;
|
||||
continue;
|
||||
}
|
||||
if (note.time > renderWindowStart) break; // Note is too far ahead to render
|
||||
|
||||
var noteSprite = buildNoteSprite(note);
|
||||
|
||||
|
@ -295,7 +308,7 @@ class Strumline extends FlxSpriteGroup
|
|||
noteSprite.holdNoteSprite = buildHoldNoteSprite(note);
|
||||
}
|
||||
|
||||
nextNoteIndex++; // Increment the nextNoteIndex rather than splicing the array, because splicing is slow.
|
||||
nextNoteIndex = noteIndex + 1; // Increment the nextNoteIndex rather than splicing the array, because splicing is slow.
|
||||
}
|
||||
|
||||
// Update rendering of notes.
|
||||
|
@ -433,6 +446,17 @@ class Strumline extends FlxSpriteGroup
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the PlayState skips a large amount of time forward or backward.
|
||||
*/
|
||||
public function handleSkippedNotes():Void
|
||||
{
|
||||
// By calling clean(), we remove all existing notes so they can be re-added.
|
||||
clean();
|
||||
// By setting noteIndex to 0, the next update will skip past all the notes that are in the past.
|
||||
nextNoteIndex = 0;
|
||||
}
|
||||
|
||||
public function onBeatHit():Void
|
||||
{
|
||||
if (notes.members.length > 1) notes.members.insertionSort(compareNoteSprites.bind(FlxSort.ASCENDING));
|
||||
|
@ -485,6 +509,13 @@ class Strumline extends FlxSpriteGroup
|
|||
if (cover == null) continue;
|
||||
cover.kill();
|
||||
}
|
||||
|
||||
heldKeys = [false, false, false, false];
|
||||
|
||||
for (dir in DIRECTIONS)
|
||||
{
|
||||
playStatic(dir);
|
||||
}
|
||||
}
|
||||
|
||||
public function applyNoteData(data:Array<SongNoteData>):Void
|
||||
|
|
|
@ -39,7 +39,11 @@ class Song implements IPlayStateScriptedClass
|
|||
|
||||
var difficultyIds:Array<String>;
|
||||
|
||||
public function new(id:String)
|
||||
/**
|
||||
* @param id The ID of the song to load.
|
||||
* @param ignoreErrors If false, an exception will be thrown if the song data could not be loaded.
|
||||
*/
|
||||
public function new(id:String, ignoreErrors:Bool = false)
|
||||
{
|
||||
this.songId = id;
|
||||
|
||||
|
@ -47,13 +51,49 @@ class Song implements IPlayStateScriptedClass
|
|||
difficultyIds = [];
|
||||
difficulties = new Map<String, SongDifficulty>();
|
||||
|
||||
_metadata = SongDataParser.loadSongMetadata(songId);
|
||||
if (_metadata == null || _metadata.length == 0)
|
||||
try
|
||||
{
|
||||
_metadata = SongDataParser.loadSongMetadata(songId);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
_metadata = [];
|
||||
}
|
||||
|
||||
if (_metadata.length == 0 && !ignoreErrors)
|
||||
{
|
||||
throw 'Could not find song data for songId: $songId';
|
||||
}
|
||||
else
|
||||
{
|
||||
populateFromMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
populateFromMetadata();
|
||||
@:allow(funkin.play.song.Song)
|
||||
public static function buildRaw(songId:String, metadata:Array<SongMetadata>, variations:Array<String>, charts:Map<String, SongChartData>,
|
||||
?validScore:Bool = false):Song
|
||||
{
|
||||
var result:Song = new Song(songId, true);
|
||||
|
||||
result._metadata.clear();
|
||||
for (meta in metadata)
|
||||
result._metadata.push(meta);
|
||||
|
||||
result.variations.clear();
|
||||
for (vari in variations)
|
||||
result.variations.push(vari);
|
||||
|
||||
result.difficultyIds.clear();
|
||||
|
||||
result.populateFromMetadata();
|
||||
|
||||
for (variation => chartData in charts)
|
||||
result.applyChartData(chartData, variation);
|
||||
|
||||
result.validScore = validScore;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public function getRawMetadata():Array<SongMetadata>
|
||||
|
@ -67,6 +107,8 @@ class Song implements IPlayStateScriptedClass
|
|||
*/
|
||||
function populateFromMetadata():Void
|
||||
{
|
||||
if (_metadata == null || _metadata.length == 0) return;
|
||||
|
||||
// Variations may have different artist, time format, generatedBy, etc.
|
||||
for (metadata in _metadata)
|
||||
{
|
||||
|
@ -119,28 +161,33 @@ class Song implements IPlayStateScriptedClass
|
|||
for (variation in variations)
|
||||
{
|
||||
var chartData:SongChartData = SongDataParser.parseSongChartData(songId, variation);
|
||||
var chartNotes = chartData.notes;
|
||||
|
||||
for (diffId in chartNotes.keys())
|
||||
{
|
||||
// Retrieve the cached difficulty data.
|
||||
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
|
||||
if (difficulty == null)
|
||||
{
|
||||
trace('Fabricated new difficulty for $diffId.');
|
||||
difficulty = new SongDifficulty(this, diffId, variation);
|
||||
difficulties.set(diffId, difficulty);
|
||||
}
|
||||
// Add the chart data to the difficulty.
|
||||
difficulty.notes = chartData.notes.get(diffId);
|
||||
difficulty.scrollSpeed = chartData.getScrollSpeed(diffId);
|
||||
|
||||
difficulty.events = chartData.events;
|
||||
}
|
||||
applyChartData(chartData, variation);
|
||||
}
|
||||
trace('Done caching charts.');
|
||||
}
|
||||
|
||||
function applyChartData(chartData:SongChartData, variation:String):Void
|
||||
{
|
||||
var chartNotes = chartData.notes;
|
||||
|
||||
for (diffId in chartNotes.keys())
|
||||
{
|
||||
// Retrieve the cached difficulty data.
|
||||
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
|
||||
if (difficulty == null)
|
||||
{
|
||||
trace('Fabricated new difficulty for $diffId.');
|
||||
difficulty = new SongDifficulty(this, diffId, variation);
|
||||
difficulties.set(diffId, difficulty);
|
||||
}
|
||||
// Add the chart data to the difficulty.
|
||||
difficulty.notes = chartData.notes.get(diffId);
|
||||
difficulty.scrollSpeed = chartData.getScrollSpeed(diffId);
|
||||
|
||||
difficulty.events = chartData.events;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the metadata for a specific difficulty, including the chart if it is loaded.
|
||||
* @param diffId The difficulty ID, such as `easy` or `hard`.
|
||||
|
|
|
@ -205,7 +205,7 @@ class SongDataParser
|
|||
|
||||
static function loadMusicMetadataFile(musicPath:String, variation:String = ''):String
|
||||
{
|
||||
var musicMetadataFilePath:String = (variation != '') ? Paths.file('$MUSIC_DATA_PATH$musicPath/$musicPath-metadata-$variation.json') : Paths.file('$MUSIC_DATA_PATH$musicPath/$musicPath-metadata.json');
|
||||
var musicMetadataFilePath:String = (variation != '' || variation == "default") ? Paths.file('$MUSIC_DATA_PATH$musicPath/$musicPath-metadata-$variation.json') : Paths.file('$MUSIC_DATA_PATH$musicPath/$musicPath-metadata.json');
|
||||
|
||||
var rawJson:String = Assets.getText(musicMetadataFilePath).trim();
|
||||
|
||||
|
@ -245,7 +245,7 @@ class SongDataParser
|
|||
|
||||
static function loadSongChartDataFile(songPath:String, variation:String = ''):String
|
||||
{
|
||||
var songChartDataFilePath:String = (variation != '') ? Paths.json('$SONG_DATA_PATH$songPath/$songPath-chart-$variation') : Paths.json('$SONG_DATA_PATH$songPath/$songPath-chart');
|
||||
var songChartDataFilePath:String = (variation != '' || variation == 'default') ? Paths.json('$SONG_DATA_PATH$songPath/$songPath-chart-$variation') : Paths.json('$SONG_DATA_PATH$songPath/$songPath-chart');
|
||||
|
||||
var rawJson:String = Assets.getText(songChartDataFilePath).trim();
|
||||
|
||||
|
|
|
@ -158,8 +158,11 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
|||
*/
|
||||
public function resetPosition()
|
||||
{
|
||||
this.x = originalPosition.x + animOffsets[0];
|
||||
this.y = originalPosition.y + animOffsets[1];
|
||||
var oldAnimOffsets = [animOffsets[0], animOffsets[1]];
|
||||
animOffsets = [0, 0];
|
||||
this.x = originalPosition.x;
|
||||
this.y = originalPosition.y;
|
||||
animOffsets = oldAnimOffsets;
|
||||
}
|
||||
|
||||
function update_shouldAlternate():Void
|
||||
|
@ -200,10 +203,12 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
|||
{
|
||||
if (hasDanced)
|
||||
{
|
||||
trace('DanceRight (alternate)');
|
||||
playAnimation('danceRight$idleSuffix', forceRestart);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('DanceLeft (alternate)');
|
||||
playAnimation('danceLeft$idleSuffix', forceRestart);
|
||||
}
|
||||
hasDanced = !hasDanced;
|
||||
|
|
|
@ -78,6 +78,10 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
if (getBoyfriend() != null)
|
||||
{
|
||||
getBoyfriend().resetCharacter(true);
|
||||
// Reapply the camera offsets.
|
||||
var charData = _data.characters.bf;
|
||||
getBoyfriend().cameraFocusPoint.x += charData.cameraOffsets[0];
|
||||
getBoyfriend().cameraFocusPoint.y += charData.cameraOffsets[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -86,10 +90,18 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
if (getGirlfriend() != null)
|
||||
{
|
||||
getGirlfriend().resetCharacter(true);
|
||||
// Reapply the camera offsets.
|
||||
var charData = _data.characters.gf;
|
||||
getGirlfriend().cameraFocusPoint.x += charData.cameraOffsets[0];
|
||||
getGirlfriend().cameraFocusPoint.y += charData.cameraOffsets[1];
|
||||
}
|
||||
if (getDad() != null)
|
||||
{
|
||||
getDad().resetCharacter(true);
|
||||
// Reapply the camera offsets.
|
||||
var charData = _data.characters.dad;
|
||||
getDad().cameraFocusPoint.x += charData.cameraOffsets[0];
|
||||
getDad().cameraFocusPoint.y += charData.cameraOffsets[1];
|
||||
}
|
||||
|
||||
// Reset positions of named props.
|
||||
|
@ -216,8 +228,12 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
{
|
||||
cast(propSprite, Bopper).setAnimationOffsets(propAnim.name, propAnim.offsets[0], propAnim.offsets[1]);
|
||||
}
|
||||
cast(propSprite, Bopper).originalPosition.x = dataProp.position[0];
|
||||
cast(propSprite, Bopper).originalPosition.y = dataProp.position[1];
|
||||
|
||||
if (!Std.isOfType(propSprite, BaseCharacter))
|
||||
{
|
||||
cast(propSprite, Bopper).originalPosition.x = dataProp.position[0];
|
||||
cast(propSprite, Bopper).originalPosition.y = dataProp.position[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (dataProp.startingAnimation != null)
|
||||
|
@ -225,7 +241,11 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
propSprite.animation.play(dataProp.startingAnimation);
|
||||
}
|
||||
|
||||
if (Std.isOfType(propSprite, Bopper))
|
||||
if (Std.isOfType(propSprite, BaseCharacter))
|
||||
{
|
||||
// Character stuff.
|
||||
}
|
||||
else if (Std.isOfType(propSprite, Bopper))
|
||||
{
|
||||
addBopper(cast propSprite, dataProp.name);
|
||||
}
|
||||
|
@ -357,8 +377,12 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
character.x = charData.position[0] - character.characterOrigin.x + character.globalOffsets[0];
|
||||
character.y = charData.position[1] - character.characterOrigin.y + character.globalOffsets[1];
|
||||
|
||||
character.originalPosition.x = character.x;
|
||||
character.originalPosition.y = character.y;
|
||||
@:privateAccess(funkin.play.stage.Bopper)
|
||||
{
|
||||
// Undo animOffsets before saving original position.
|
||||
character.originalPosition.x = character.x + character.animOffsets[0];
|
||||
character.originalPosition.y = character.y + character.animOffsets[1];
|
||||
}
|
||||
|
||||
character.cameraFocusPoint.x += charData.cameraOffsets[0];
|
||||
character.cameraFocusPoint.y += charData.cameraOffsets[1];
|
||||
|
@ -674,6 +698,27 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
}
|
||||
}
|
||||
|
||||
public override function kill()
|
||||
{
|
||||
_skipTransformChildren = true;
|
||||
alive = false;
|
||||
exists = false;
|
||||
_skipTransformChildren = false;
|
||||
if (group != null) group.kill();
|
||||
}
|
||||
|
||||
public override function remove(Sprite:FlxSprite, Splice:Bool = false):FlxSprite
|
||||
{
|
||||
var sprite:FlxSprite = cast Sprite;
|
||||
sprite.x -= x;
|
||||
sprite.y -= y;
|
||||
// alpha
|
||||
sprite.cameras = null;
|
||||
|
||||
if (group != null) group.remove(Sprite, Splice);
|
||||
return Sprite;
|
||||
}
|
||||
|
||||
public function onScriptEvent(event:ScriptEvent) {}
|
||||
|
||||
public function onPause(event:PauseScriptEvent) {}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.ui.debug;
|
||||
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.MusicBeatSubState;
|
||||
|
@ -48,6 +49,9 @@ class DebugMenuSubState extends MusicBeatSubState
|
|||
createItem("ANIMATION EDITOR", openAnimationEditor);
|
||||
createItem("STAGE EDITOR", openStageEditor);
|
||||
createItem("TEST STICKERS", testStickers);
|
||||
|
||||
FlxG.camera.focusOn(new FlxPoint(camFocusPoint.x, camFocusPoint.y));
|
||||
FlxG.camera.focusOn(new FlxPoint(camFocusPoint.x, camFocusPoint.y + 500));
|
||||
}
|
||||
|
||||
function onMenuChange(selected:TextMenuItem)
|
||||
|
|
|
@ -986,6 +986,8 @@ class ChartEditorDialogHandler
|
|||
state.isHaxeUIDialogOpen = false;
|
||||
};
|
||||
|
||||
dialog.zIndex = 1000;
|
||||
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ class ChartEditorNotePreview extends FlxSprite
|
|||
*/
|
||||
function buildBackground():Void
|
||||
{
|
||||
makeGraphic(WIDTH, 0, BG_COLOR);
|
||||
makeGraphic(WIDTH, previewHeight, BG_COLOR);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,8 +2,11 @@ package funkin.ui.debug.charting;
|
|||
|
||||
import flixel.addons.display.FlxSliceSprite;
|
||||
import flixel.addons.display.FlxTiledSprite;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxSubState;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.math.FlxRect;
|
||||
|
@ -25,6 +28,7 @@ import funkin.play.character.BaseCharacter.CharacterType;
|
|||
import funkin.play.HealthIcon;
|
||||
import funkin.play.notes.NoteSprite;
|
||||
import funkin.play.notes.Strumline;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.play.song.SongData.SongChartData;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
|
@ -374,7 +378,7 @@ class ChartEditorState extends HaxeUIState
|
|||
/**
|
||||
* Whether to play a metronome sound while the playhead is moving.
|
||||
*/
|
||||
var shouldPlayMetronome:Bool = true;
|
||||
var isMetronomeEnabled:Bool = true;
|
||||
|
||||
/**
|
||||
* Use the tool window to affect how the user interacts with the program.
|
||||
|
@ -413,6 +417,11 @@ class ChartEditorState extends HaxeUIState
|
|||
return isViewDownscroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, playtesting a chart will skip to the current playhead position.
|
||||
*/
|
||||
var playtestStartTime:Bool = false;
|
||||
|
||||
/**
|
||||
* Whether hitsounds are enabled for at least one character.
|
||||
*/
|
||||
|
@ -894,7 +903,7 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
function get_currentSongId():String
|
||||
{
|
||||
return currentSongName.toLowerKebabCase();
|
||||
return currentSongName.toLowerKebabCase().replace('.', '').replace(' ', '-');
|
||||
}
|
||||
|
||||
var currentSongArtist(get, set):String;
|
||||
|
@ -1074,6 +1083,13 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
override function create():Void
|
||||
{
|
||||
// super.create() must be called first, the HaxeUI components get created here.
|
||||
super.create();
|
||||
// Set the z-index of the HaxeUI.
|
||||
this.component.zIndex = 100;
|
||||
|
||||
fixCamera();
|
||||
|
||||
// Get rid of any music from the previous state.
|
||||
FlxG.sound.music.stop();
|
||||
|
||||
|
@ -1088,8 +1104,6 @@ class ChartEditorState extends HaxeUIState
|
|||
buildNotePreview();
|
||||
buildSelectionBox();
|
||||
|
||||
// Add the HaxeUI components after the grid so they're on top.
|
||||
super.create();
|
||||
buildAdditionalUI();
|
||||
|
||||
// Setup the onClick listeners for the UI after it's been created.
|
||||
|
@ -1098,6 +1112,8 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
setupAutoSave();
|
||||
|
||||
refresh();
|
||||
|
||||
ChartEditorDialogHandler.openWelcomeDialog(this, false);
|
||||
}
|
||||
|
||||
|
@ -1127,6 +1143,7 @@ class ChartEditorState extends HaxeUIState
|
|||
menuBG.updateHitbox();
|
||||
menuBG.screenCenter();
|
||||
menuBG.scrollFactor.set(0, 0);
|
||||
menuBG.zIndex = -100;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1138,28 +1155,33 @@ class ChartEditorState extends HaxeUIState
|
|||
gridTiledSprite.x = FlxG.width / 2 - GRID_SIZE * STRUMLINE_SIZE; // Center the grid.
|
||||
gridTiledSprite.y = MENU_BAR_HEIGHT + GRID_TOP_PAD; // Push down to account for the menu bar.
|
||||
add(gridTiledSprite);
|
||||
gridTiledSprite.zIndex = 10;
|
||||
|
||||
gridGhostNote = new ChartEditorNoteSprite(this);
|
||||
gridGhostNote.alpha = 0.6;
|
||||
gridGhostNote.noteData = new SongNoteData(0, 0, 0, "");
|
||||
gridGhostNote.visible = false;
|
||||
add(gridGhostNote);
|
||||
gridGhostNote.zIndex = 11;
|
||||
|
||||
gridGhostEvent = new ChartEditorEventSprite(this);
|
||||
gridGhostEvent.alpha = 0.6;
|
||||
gridGhostEvent.eventData = new SongEventData(-1, '', {});
|
||||
gridGhostEvent.visible = false;
|
||||
add(gridGhostEvent);
|
||||
gridGhostEvent.zIndex = 12;
|
||||
|
||||
buildNoteGroup();
|
||||
|
||||
gridPlayheadScrollArea = new FlxSprite(gridTiledSprite.x - PLAYHEAD_SCROLL_AREA_WIDTH,
|
||||
MENU_BAR_HEIGHT).makeGraphic(PLAYHEAD_SCROLL_AREA_WIDTH, FlxG.height - MENU_BAR_HEIGHT, PLAYHEAD_SCROLL_AREA_COLOR);
|
||||
add(gridPlayheadScrollArea);
|
||||
gridPlayheadScrollArea.zIndex = 25;
|
||||
|
||||
// The playhead that show the current position in the song.
|
||||
gridPlayhead = new FlxSpriteGroup();
|
||||
add(gridPlayhead);
|
||||
gridPlayhead.zIndex = 30;
|
||||
|
||||
var playheadWidth:Int = GRID_SIZE * (STRUMLINE_SIZE * 2 + 1) + (PLAYHEAD_SCROLL_AREA_WIDTH * 2);
|
||||
var playheadBaseYPos:Float = MENU_BAR_HEIGHT + GRID_TOP_PAD;
|
||||
|
@ -1175,26 +1197,29 @@ class ChartEditorState extends HaxeUIState
|
|||
gridPlayhead.add(playheadBlock);
|
||||
|
||||
// Character icons.
|
||||
healthIconDad = new HealthIcon('dad');
|
||||
healthIconDad = new HealthIcon(currentSongCharacterOpponent);
|
||||
healthIconDad.autoUpdate = false;
|
||||
healthIconDad.size.set(0.5, 0.5);
|
||||
healthIconDad.x = gridTiledSprite.x - 15 - (HealthIcon.HEALTH_ICON_SIZE * 0.5);
|
||||
healthIconDad.y = gridTiledSprite.y + 5;
|
||||
add(healthIconDad);
|
||||
healthIconDad.zIndex = 30;
|
||||
|
||||
healthIconBF = new HealthIcon('bf');
|
||||
healthIconBF = new HealthIcon(currentSongCharacterPlayer);
|
||||
healthIconBF.autoUpdate = false;
|
||||
healthIconBF.size.set(0.5, 0.5);
|
||||
healthIconBF.x = gridTiledSprite.x + GRID_SIZE * (STRUMLINE_SIZE * 2 + 1) + 15;
|
||||
healthIconBF.y = gridTiledSprite.y + 5;
|
||||
healthIconBF.flipX = true;
|
||||
add(healthIconBF);
|
||||
healthIconBF.zIndex = 30;
|
||||
}
|
||||
|
||||
function buildSelectionBox():Void
|
||||
{
|
||||
selectionBoxSprite.scrollFactor.set(0, 0);
|
||||
add(selectionBoxSprite);
|
||||
selectionBoxSprite.zIndex = 30;
|
||||
|
||||
setSelectionBoxBounds();
|
||||
}
|
||||
|
@ -1222,7 +1247,8 @@ class ChartEditorState extends HaxeUIState
|
|||
var height:Int = FlxG.height - MENU_BAR_HEIGHT - GRID_TOP_PAD - 200;
|
||||
notePreview = new ChartEditorNotePreview(height);
|
||||
notePreview.y = MENU_BAR_HEIGHT + GRID_TOP_PAD;
|
||||
add(notePreview);
|
||||
// TODO: Re-enable.
|
||||
// add(notePreview);
|
||||
}
|
||||
|
||||
function buildSpectrogram(target:FlxSound):Void
|
||||
|
@ -1243,18 +1269,22 @@ class ChartEditorState extends HaxeUIState
|
|||
renderedHoldNotes = new FlxTypedSpriteGroup<ChartEditorHoldNoteSprite>();
|
||||
renderedHoldNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||
add(renderedHoldNotes);
|
||||
renderedHoldNotes.zIndex = 24;
|
||||
|
||||
renderedNotes = new FlxTypedSpriteGroup<ChartEditorNoteSprite>();
|
||||
renderedNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||
add(renderedNotes);
|
||||
renderedNotes.zIndex = 25;
|
||||
|
||||
renderedEvents = new FlxTypedSpriteGroup<ChartEditorEventSprite>();
|
||||
renderedEvents.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||
add(renderedEvents);
|
||||
renderedNotes.zIndex = 25;
|
||||
|
||||
renderedSelectionSquares = new FlxTypedSpriteGroup<FlxSprite>();
|
||||
renderedSelectionSquares.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||
add(renderedSelectionSquares);
|
||||
renderedNotes.zIndex = 26;
|
||||
}
|
||||
|
||||
var playbarHeadLayout:Component;
|
||||
|
@ -1262,6 +1292,7 @@ class ChartEditorState extends HaxeUIState
|
|||
function buildAdditionalUI():Void
|
||||
{
|
||||
playbarHeadLayout = buildComponent(CHART_EDITOR_PLAYBARHEAD_LAYOUT);
|
||||
playbarHeadLayout.zIndex = 110;
|
||||
|
||||
playbarHeadLayout.width = FlxG.width - 8;
|
||||
playbarHeadLayout.height = 10;
|
||||
|
@ -1393,6 +1424,9 @@ class ChartEditorState extends HaxeUIState
|
|||
// addUIClickListener('menubarItemSelectBeforeCursor', _ -> doSomething());
|
||||
// addUIClickListener('menubarItemSelectAfterCursor', _ -> doSomething());
|
||||
|
||||
addUIClickListener('menubarItemPlaytestFull', _ -> testSongInPlayState(false));
|
||||
addUIClickListener('menubarItemPlaytestMinimal', _ -> testSongInPlayState(true));
|
||||
|
||||
addUIChangeListener('menubarItemInputStyleGroup', function(event:UIEvent) {
|
||||
trace('Change input style: ${event.target}');
|
||||
});
|
||||
|
@ -1404,6 +1438,9 @@ class ChartEditorState extends HaxeUIState
|
|||
addUIChangeListener('menubarItemDownscroll', event -> isViewDownscroll = event.value);
|
||||
setUICheckboxSelected('menubarItemDownscroll', isViewDownscroll);
|
||||
|
||||
addUIChangeListener('menubarItemPlaytestStartTime', event -> playtestStartTime = event.value);
|
||||
setUICheckboxSelected('menubarItemPlaytestStartTime', playtestStartTime);
|
||||
|
||||
addUIChangeListener('menuBarItemThemeLight', function(event:UIEvent) {
|
||||
if (event.target.value) currentTheme = ChartEditorTheme.Light;
|
||||
});
|
||||
|
@ -1414,8 +1451,8 @@ class ChartEditorState extends HaxeUIState
|
|||
});
|
||||
setUICheckboxSelected('menuBarItemThemeDark', currentTheme == ChartEditorTheme.Dark);
|
||||
|
||||
addUIChangeListener('menubarItemMetronomeEnabled', event -> shouldPlayMetronome = event.value);
|
||||
setUICheckboxSelected('menubarItemMetronomeEnabled', shouldPlayMetronome);
|
||||
addUIChangeListener('menubarItemMetronomeEnabled', event -> isMetronomeEnabled = event.value);
|
||||
setUICheckboxSelected('menubarItemMetronomeEnabled', isMetronomeEnabled);
|
||||
|
||||
addUIChangeListener('menubarItemPlayerHitsounds', event -> hitsoundsEnabledPlayer = event.value);
|
||||
setUICheckboxSelected('menubarItemPlayerHitsounds', hitsoundsEnabledPlayer);
|
||||
|
@ -1551,6 +1588,7 @@ class ChartEditorState extends HaxeUIState
|
|||
handleFileKeybinds();
|
||||
handleEditKeybinds();
|
||||
handleViewKeybinds();
|
||||
handleTestKeybinds();
|
||||
handleHelpKeybinds();
|
||||
|
||||
// DEBUG
|
||||
|
@ -1578,7 +1616,7 @@ class ChartEditorState extends HaxeUIState
|
|||
// dispatchEvent gets called here.
|
||||
if (!super.beatHit()) return false;
|
||||
|
||||
if (shouldPlayMetronome && (audioInstTrack != null && audioInstTrack.playing))
|
||||
if (isMetronomeEnabled && this.subState == null && (audioInstTrack != null && audioInstTrack.playing))
|
||||
{
|
||||
playMetronomeTick(Conductor.currentBeat % 4 == 0);
|
||||
}
|
||||
|
@ -2719,6 +2757,18 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
function handleViewKeybinds():Void {}
|
||||
|
||||
/**
|
||||
* Handle keybinds for the Test menu items.
|
||||
*/
|
||||
function handleTestKeybinds():Void
|
||||
{
|
||||
if (!isHaxeUIDialogOpen && FlxG.keys.justPressed.ENTER)
|
||||
{
|
||||
var minimal = FlxG.keys.pressed.SHIFT;
|
||||
testSongInPlayState(minimal);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle keybinds for Help menu items.
|
||||
*/
|
||||
|
@ -3138,8 +3188,8 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
function startAudioPlayback():Void
|
||||
{
|
||||
if (audioInstTrack != null) audioInstTrack.play();
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.play();
|
||||
if (audioInstTrack != null) audioInstTrack.play(false, audioInstTrack.time);
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.play(false, audioInstTrack.time);
|
||||
|
||||
setComponentText('playbarPlay', '||');
|
||||
}
|
||||
|
@ -3252,6 +3302,77 @@ class ChartEditorState extends HaxeUIState
|
|||
return this.scrollPositionInPixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transitions to the Play State to test the song
|
||||
*/
|
||||
public function testSongInPlayState(?minimal:Bool = false):Void
|
||||
{
|
||||
var startTimestamp:Float = 0;
|
||||
if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs;
|
||||
|
||||
var targetSong:Song = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, false);
|
||||
|
||||
// TODO: Rework asset system so we can remove this.
|
||||
switch (currentSongStage)
|
||||
{
|
||||
case 'mainStage':
|
||||
Paths.setCurrentLevel('week1');
|
||||
case 'spookyMansion':
|
||||
Paths.setCurrentLevel('week2');
|
||||
case 'phillyTrain':
|
||||
Paths.setCurrentLevel('week3');
|
||||
case 'limoRide':
|
||||
Paths.setCurrentLevel('week4');
|
||||
case 'mallXmas' | 'mallEvil':
|
||||
Paths.setCurrentLevel('week5');
|
||||
case 'school' | 'schoolEvil':
|
||||
Paths.setCurrentLevel('week6');
|
||||
case 'tankmanBattlefield':
|
||||
Paths.setCurrentLevel('week7');
|
||||
case 'phillyStreets' | 'phillyBlazin':
|
||||
Paths.setCurrentLevel('weekend1');
|
||||
}
|
||||
|
||||
subStateClosed.add(fixCamera);
|
||||
subStateClosed.add(updateConductor);
|
||||
|
||||
FlxTransitionableState.skipNextTransIn = false;
|
||||
FlxTransitionableState.skipNextTransOut = false;
|
||||
|
||||
var targetState = new PlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: selectedDifficulty,
|
||||
// TODO: Add this.
|
||||
// targetCharacter: targetCharacter,
|
||||
practiceMode: true,
|
||||
minimalMode: minimal,
|
||||
startTimestamp: startTimestamp,
|
||||
overrideMusic: true,
|
||||
});
|
||||
|
||||
// Override music.
|
||||
FlxG.sound.music = audioInstTrack;
|
||||
targetState.vocals = audioVocalTrackGroup;
|
||||
|
||||
openSubState(targetState);
|
||||
}
|
||||
|
||||
function fixCamera(_:FlxSubState = null):Void
|
||||
{
|
||||
FlxG.cameras.reset(new FlxCamera());
|
||||
FlxG.camera.focusOn(new FlxPoint(FlxG.width / 2, FlxG.height / 2));
|
||||
FlxG.camera.zoom = 1.0;
|
||||
|
||||
add(this.component);
|
||||
}
|
||||
|
||||
function updateConductor(_:FlxSubState = null):Void
|
||||
{
|
||||
var targetPos = scrollPositionInMs;
|
||||
Conductor.update(targetPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an instrumental from an absolute file path, replacing the current instrumental.
|
||||
*
|
||||
|
@ -3374,14 +3495,23 @@ class ChartEditorState extends HaxeUIState
|
|||
* @param charKey Character to load the vocal track for.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
public function loadVocalsFromAsset(path:String, charKey:String = 'default'):Bool
|
||||
public function loadVocalsFromAsset(path:String, charType:CharacterType = OTHER):Bool
|
||||
{
|
||||
var vocalTrack:FlxSound = FlxG.sound.load(path, 1.0, false);
|
||||
if (vocalTrack != null)
|
||||
{
|
||||
audioVocalTrackGroup.add(vocalTrack);
|
||||
|
||||
audioVocalTrackData.set(charKey, Assets.getBytes(path));
|
||||
switch (charType)
|
||||
{
|
||||
case CharacterType.BF:
|
||||
audioVocalTrackGroup.addPlayerVoice(vocalTrack);
|
||||
audioVocalTrackData.set(currentSongCharacterPlayer, Assets.getBytes(path));
|
||||
case CharacterType.DAD:
|
||||
audioVocalTrackGroup.addOpponentVoice(vocalTrack);
|
||||
audioVocalTrackData.set(currentSongCharacterOpponent, Assets.getBytes(path));
|
||||
default:
|
||||
audioVocalTrackGroup.add(vocalTrack);
|
||||
audioVocalTrackData.set('default', Assets.getBytes(path));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -3442,10 +3572,18 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
loadInstrumentalFromAsset(Paths.inst(songId));
|
||||
|
||||
var voiceList:Array<String> = song.getDifficulty(selectedDifficulty).buildVoiceList();
|
||||
for (voicePath in voiceList)
|
||||
var voiceList:Array<String> = song.getDifficulty(selectedDifficulty).buildVoiceList(currentSongCharacterPlayer);
|
||||
if (voiceList.length == 2)
|
||||
{
|
||||
loadVocalsFromAsset(voicePath);
|
||||
loadVocalsFromAsset(voiceList[0], BF);
|
||||
loadVocalsFromAsset(voiceList[1], DAD);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (voicePath in voiceList)
|
||||
{
|
||||
loadVocalsFromAsset(voicePath);
|
||||
}
|
||||
}
|
||||
|
||||
NotificationManager.instance.addNotification(
|
||||
|
|
|
@ -71,7 +71,12 @@ class Level implements IRegistryEntry<LevelData>
|
|||
{
|
||||
var songList:Array<String> = getSongs() ?? [];
|
||||
var songNameList:Array<String> = songList.map(function(songId) {
|
||||
return funkin.play.song.SongData.SongDataParser.fetchSong(songId) ?.getDifficulty(difficulty) ?.songName ?? 'Unknown';
|
||||
var song:Song = funkin.play.song.SongData.SongDataParser.fetchSong(songId);
|
||||
if (song == null) return 'Unknown';
|
||||
var songDifficulty:SongDifficulty = song.getDifficulty(difficulty);
|
||||
if (songDifficulty == null) songDifficulty = song.getDifficulty();
|
||||
var songName:String = songDifficulty?.songName;
|
||||
return songName ?? 'Unknown';
|
||||
});
|
||||
return songNameList;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import flixel.addons.transition.FlxTransitionableState;
|
|||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
|
@ -469,7 +470,7 @@ class StoryMenuState extends MusicBeatState
|
|||
// super.dispatchEvent(event) dispatches event to module scripts.
|
||||
super.dispatchEvent(event);
|
||||
|
||||
if ((levelProps?.length ?? 0) > 0)
|
||||
if (levelProps != null && levelProps.length > 0)
|
||||
{
|
||||
// Dispatch event to props.
|
||||
for (prop in levelProps)
|
||||
|
@ -513,7 +514,17 @@ class StoryMenuState extends MusicBeatState
|
|||
PlayStatePlaylist.campaignId = currentLevel.id;
|
||||
PlayStatePlaylist.campaignTitle = currentLevel.getTitle();
|
||||
|
||||
if (targetSong != null)
|
||||
{
|
||||
// Load and cache the song's charts.
|
||||
// TODO: Do this in the loading state.
|
||||
targetSong.cacheCharts(true);
|
||||
}
|
||||
|
||||
new FlxTimer().start(1, function(tmr:FlxTimer) {
|
||||
FlxTransitionableState.skipNextTransIn = false;
|
||||
FlxTransitionableState.skipNextTransOut = false;
|
||||
|
||||
LoadingState.loadAndSwitchState(new PlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
|
|
|
@ -273,7 +273,7 @@ class TitleState extends MusicBeatState
|
|||
FlxTween.tween(FlxG.stage.window, {y: FlxG.stage.window.y + 100}, 0.7, {ease: FlxEase.quadInOut, type: PINGPONG});
|
||||
}
|
||||
|
||||
if (FlxG.sound.music != null) Conductor.songPosition = FlxG.sound.music.time;
|
||||
if (FlxG.sound.music != null) Conductor.update(FlxG.sound.music.time);
|
||||
if (FlxG.keys.justPressed.F) FlxG.fullscreen = !FlxG.fullscreen;
|
||||
|
||||
// do controls.PAUSE | controls.ACCEPT instead?
|
||||
|
|
|
@ -37,4 +37,15 @@ class ArrayTools
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all elements from the array, without creating a new array.
|
||||
* @param array The array to clear.
|
||||
*/
|
||||
public static function clear<T>(array:Array<T>):Void
|
||||
{
|
||||
// This method is faster than array.splice(0, array.length)
|
||||
while (array.length > 0)
|
||||
array.pop();
|
||||
}
|
||||
}
|
||||
|
|
32
source/funkin/util/tools/Int64Tools.hx
Normal file
32
source/funkin/util/tools/Int64Tools.hx
Normal file
|
@ -0,0 +1,32 @@
|
|||
package funkin.util.tools;
|
||||
|
||||
/**
|
||||
* @see https://github.com/fponticelli/thx.core/blob/master/src/thx/Int64s.hx
|
||||
*/
|
||||
class Int64Tools
|
||||
{
|
||||
static var min = haxe.Int64.make(0x80000000, 0);
|
||||
static var one = haxe.Int64.make(0, 1);
|
||||
static var two = haxe.Int64.ofInt(2);
|
||||
static var zero = haxe.Int64.make(0, 0);
|
||||
static var ten = haxe.Int64.ofInt(10);
|
||||
|
||||
public static function toFloat(i:haxe.Int64):Float
|
||||
{
|
||||
var isNegative = false;
|
||||
if (i < 0)
|
||||
{
|
||||
if (i < min) return -9223372036854775808.0; // most -ve value can't be made +ve
|
||||
isNegative = true;
|
||||
i = -i;
|
||||
}
|
||||
var multiplier = 1.0, ret = 0.0;
|
||||
for (_ in 0...64)
|
||||
{
|
||||
if (haxe.Int64.and(i, one) != zero) ret += multiplier;
|
||||
multiplier *= 2.0;
|
||||
i = haxe.Int64.shr(i, 1);
|
||||
}
|
||||
return (isNegative ? -1 : 1) * ret;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue