mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-27 01:55:52 -05:00
Merge branch 'rewrite/master' of https://github.com/FunkinCrew/Funkin-secret into feature/focusCameraTweening
This commit is contained in:
commit
f9291d6458
32 changed files with 416 additions and 163 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,3 +7,4 @@ export/
|
||||||
RECOVER_*.fla
|
RECOVER_*.fla
|
||||||
shitAudio/
|
shitAudio/
|
||||||
.build_time
|
.build_time
|
||||||
|
.swp
|
||||||
|
|
48
Project.xml
48
Project.xml
|
@ -22,8 +22,8 @@
|
||||||
<set name="BUILD_DIR" value="export/release" unless="debug" />
|
<set name="BUILD_DIR" value="export/release" unless="debug" />
|
||||||
<set name="BUILD_DIR" value="export/32bit" if="32bit" />
|
<set name="BUILD_DIR" value="export/32bit" if="32bit" />
|
||||||
<classpath name="source" />
|
<classpath name="source" />
|
||||||
<assets path="assets/preload" rename="assets" exclude="*.ogg" if="web" />
|
<assets path="assets/preload" rename="assets" exclude="*.ogg|*.wav" if="web" />
|
||||||
<assets path="assets/preload" rename="assets" exclude="*.mp3" unless="web" />
|
<assets path="assets/preload" rename="assets" exclude="*.mp3|*.wav" unless="web" />
|
||||||
<define name="PRELOAD_ALL" unless="web" />
|
<define name="PRELOAD_ALL" unless="web" />
|
||||||
<define name="NO_PRELOAD_ALL" unless="PRELOAD_ALL" />
|
<define name="NO_PRELOAD_ALL" unless="PRELOAD_ALL" />
|
||||||
<section if="PRELOAD_ALL">
|
<section if="PRELOAD_ALL">
|
||||||
|
@ -53,28 +53,28 @@
|
||||||
<library name="weekend1" preload="false" />
|
<library name="weekend1" preload="false" />
|
||||||
</section>
|
</section>
|
||||||
<library name="art" preload="false" />
|
<library name="art" preload="false" />
|
||||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.ogg" if="web" />
|
<assets path="assets/songs" library="songs" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.mp3" unless="web" />
|
<assets path="assets/songs" library="songs" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.ogg" if="web" />
|
<assets path="assets/shared" library="shared" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.mp3" unless="web" />
|
<assets path="assets/shared" library="shared" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.ogg" if="web" />
|
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.mp3" unless="web" />
|
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.ogg" if="web" />
|
<assets path="assets/week1" library="week1" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.mp3" unless="web" />
|
<assets path="assets/week1" library="week1" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.ogg" if="web" />
|
<assets path="assets/week2" library="week2" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.mp3" unless="web" />
|
<assets path="assets/week2" library="week2" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.ogg" if="web" />
|
<assets path="assets/week3" library="week3" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.mp3" unless="web" />
|
<assets path="assets/week3" library="week3" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.ogg" if="web" />
|
<assets path="assets/week4" library="week4" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.mp3" unless="web" />
|
<assets path="assets/week4" library="week4" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.ogg" if="web" />
|
<assets path="assets/week5" library="week5" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.mp3" unless="web" />
|
<assets path="assets/week5" library="week5" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.ogg" if="web" />
|
<assets path="assets/week6" library="week6" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.mp3" unless="web" />
|
<assets path="assets/week6" library="week6" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||||
<assets path="assets/week7" library="week7" exclude="*.fla|*.ogg" if="web" />
|
<assets path="assets/week7" library="week7" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||||
<assets path="assets/week7" library="week7" exclude="*.fla|*.mp3" unless="web" />
|
<assets path="assets/week7" library="week7" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||||
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.ogg" if="web" />
|
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||||
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.mp3" unless="web" />
|
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||||
<!-- <assets path='example_mods' rename='mods' embed='false'/> -->
|
<!-- <assets path='example_mods' rename='mods' embed='false'/> -->
|
||||||
<!--
|
<!--
|
||||||
AUTOMATICALLY MOVING EXAMPLE MODS INTO THE BUILD CAUSES ISSUES
|
AUTOMATICALLY MOVING EXAMPLE MODS INTO THE BUILD CAUSES ISSUES
|
||||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit 49c409b4c8321d8cd317787a78c479aaa64cb517
|
Subproject commit 86248e6c9c64f70349fa7d3055f1df8facab894a
|
2
hmm.json
2
hmm.json
|
@ -146,7 +146,7 @@
|
||||||
"name": "polymod",
|
"name": "polymod",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "d5a3b8995f64d20b95f844454e8c3b38c3d3a9fa",
|
"ref": "be712450e5d3ba446008884921bb56873b299a64",
|
||||||
"url": "https://github.com/larsiusprime/polymod"
|
"url": "https://github.com/larsiusprime/polymod"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,7 +17,7 @@ typedef LevelData =
|
||||||
var version:String;
|
var version:String;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The title of the week, as seen in the top corner.
|
* The title of the level, as seen in the top corner.
|
||||||
*/
|
*/
|
||||||
var name:String;
|
var name:String;
|
||||||
|
|
||||||
|
@ -27,21 +27,35 @@ typedef LevelData =
|
||||||
@:jcustomparse(funkin.data.DataParse.stringNotEmpty)
|
@:jcustomparse(funkin.data.DataParse.stringNotEmpty)
|
||||||
var titleAsset:String;
|
var titleAsset:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The props to display over the colored background.
|
||||||
|
* In the base game this is usually Boyfriend and the opponent.
|
||||||
|
*/
|
||||||
@:default([])
|
@:default([])
|
||||||
var props:Array<LevelPropData>;
|
var props:Array<LevelPropData>;
|
||||||
@:default(["bopeebo"])
|
|
||||||
|
/**
|
||||||
|
* The list of song IDs included in this level.
|
||||||
|
*/
|
||||||
|
@:default(['bopeebo'])
|
||||||
var songs:Array<String>;
|
var songs:Array<String>;
|
||||||
@:default("#F9CF51")
|
|
||||||
|
/**
|
||||||
|
* The background for the level behind the props.
|
||||||
|
*/
|
||||||
|
@:default('#F9CF51')
|
||||||
@:optional
|
@:optional
|
||||||
var background:String;
|
var background:String;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data for a single prop for a story mode level.
|
||||||
|
*/
|
||||||
typedef LevelPropData =
|
typedef LevelPropData =
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The image to use for the prop. May optionally be a sprite sheet.
|
* The image to use for the prop. May optionally be a sprite sheet.
|
||||||
*/
|
*/
|
||||||
// @:jcustomparse(funkin.data.DataParse.stringNotEmpty)
|
|
||||||
var assetPath:String;
|
var assetPath:String;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -13,7 +13,6 @@ using Lambda;
|
||||||
using StringTools;
|
using StringTools;
|
||||||
using funkin.util.tools.ArraySortTools;
|
using funkin.util.tools.ArraySortTools;
|
||||||
using funkin.util.tools.ArrayTools;
|
using funkin.util.tools.ArrayTools;
|
||||||
using funkin.util.tools.DynamicTools;
|
|
||||||
using funkin.util.tools.FloatTools;
|
using funkin.util.tools.FloatTools;
|
||||||
using funkin.util.tools.Int64Tools;
|
using funkin.util.tools.Int64Tools;
|
||||||
using funkin.util.tools.IntTools;
|
using funkin.util.tools.IntTools;
|
||||||
|
|
|
@ -56,7 +56,20 @@ interface IStateStageProp extends IScriptedClass
|
||||||
*/
|
*/
|
||||||
interface INoteScriptedClass extends IScriptedClass
|
interface INoteScriptedClass extends IScriptedClass
|
||||||
{
|
{
|
||||||
public function onNoteHit(event:NoteScriptEvent):Void;
|
/**
|
||||||
|
* Called when a note enters the field of view and approaches the strumline.
|
||||||
|
*/
|
||||||
|
public function onNoteIncoming(event:NoteScriptEvent):Void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when EITHER player hits a note.
|
||||||
|
* Query the note attached to the event to determine if it was hit by the player or CPU.
|
||||||
|
*/
|
||||||
|
public function onNoteHit(event:HitNoteScriptEvent):Void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when EITHER player (usually the player) misses a note.
|
||||||
|
*/
|
||||||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
public function onNoteMiss(event:NoteScriptEvent):Void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +86,7 @@ interface INoteScriptedClass extends IScriptedClass
|
||||||
/**
|
/**
|
||||||
* Defines a set of callbacks available to scripted classes that involve the lifecycle of the Play State.
|
* Defines a set of callbacks available to scripted classes that involve the lifecycle of the Play State.
|
||||||
*/
|
*/
|
||||||
interface IPlayStateScriptedClass extends IScriptedClass
|
interface IPlayStateScriptedClass extends INoteScriptedClass
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Called when the game is paused.
|
* Called when the game is paused.
|
||||||
|
@ -113,17 +126,6 @@ interface IPlayStateScriptedClass extends IScriptedClass
|
||||||
*/
|
*/
|
||||||
public function onSongRetry(event:ScriptEvent):Void;
|
public function onSongRetry(event:ScriptEvent):Void;
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when EITHER player hits a note.
|
|
||||||
* Query the note attached to the event to determine if it was hit by the player or CPU.
|
|
||||||
*/
|
|
||||||
public function onNoteHit(event:NoteScriptEvent):Void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when EITHER player (usually the player) misses a note.
|
|
||||||
*/
|
|
||||||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the player presses a key when no note is on the strumline.
|
* Called when the player presses a key when no note is on the strumline.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
package funkin.modding;
|
package funkin.modding;
|
||||||
|
|
||||||
import funkin.util.macro.ClassMacro;
|
|
||||||
import funkin.modding.module.ModuleHandler;
|
|
||||||
import funkin.data.song.SongData;
|
|
||||||
import funkin.data.stage.StageData;
|
|
||||||
import polymod.Polymod;
|
|
||||||
import polymod.backends.PolymodAssets.PolymodAssetType;
|
|
||||||
import polymod.format.ParseRules.TextFileFormat;
|
|
||||||
import funkin.data.event.SongEventRegistry;
|
|
||||||
import funkin.data.stage.StageRegistry;
|
|
||||||
import funkin.util.FileUtil;
|
|
||||||
import funkin.data.level.LevelRegistry;
|
|
||||||
import funkin.data.notestyle.NoteStyleRegistry;
|
|
||||||
import funkin.data.dialogue.ConversationRegistry;
|
import funkin.data.dialogue.ConversationRegistry;
|
||||||
import funkin.data.dialogue.DialogueBoxRegistry;
|
import funkin.data.dialogue.DialogueBoxRegistry;
|
||||||
import funkin.data.dialogue.SpeakerRegistry;
|
import funkin.data.dialogue.SpeakerRegistry;
|
||||||
|
import funkin.data.event.SongEventRegistry;
|
||||||
|
import funkin.data.level.LevelRegistry;
|
||||||
|
import funkin.data.notestyle.NoteStyleRegistry;
|
||||||
|
import funkin.data.song.SongRegistry;
|
||||||
|
import funkin.data.stage.StageRegistry;
|
||||||
|
import funkin.modding.module.ModuleHandler;
|
||||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
import funkin.save.Save;
|
import funkin.save.Save;
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.util.FileUtil;
|
||||||
|
import funkin.util.macro.ClassMacro;
|
||||||
|
import polymod.backends.PolymodAssets.PolymodAssetType;
|
||||||
|
import polymod.format.ParseRules.TextFileFormat;
|
||||||
|
import polymod.Polymod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for interacting with Polymod, the atomic modding framework for Haxe.
|
||||||
|
*/
|
||||||
class PolymodHandler
|
class PolymodHandler
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -27,16 +28,33 @@ class PolymodHandler
|
||||||
* Bug fixes increment the patch version, new features increment the minor version.
|
* Bug fixes increment the patch version, new features increment the minor version.
|
||||||
* Changes that break old mods increment the major version.
|
* Changes that break old mods increment the major version.
|
||||||
*/
|
*/
|
||||||
static final API_VERSION:String = "0.1.0";
|
static final API_VERSION:String = '0.1.0';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Where relative to the executable that mods are located.
|
* Where relative to the executable that mods are located.
|
||||||
*/
|
*/
|
||||||
static final MOD_FOLDER:String = #if (REDIRECT_ASSETS_FOLDER && macos) "../../../../../../../example_mods" #elseif REDIRECT_ASSETS_FOLDER "../../../../example_mods" #else "mods" #end;
|
static final MOD_FOLDER:String =
|
||||||
|
#if (REDIRECT_ASSETS_FOLDER && macos)
|
||||||
|
'../../../../../../../example_mods'
|
||||||
|
#elseif REDIRECT_ASSETS_FOLDER
|
||||||
|
'../../../../example_mods'
|
||||||
|
#else
|
||||||
|
'mods'
|
||||||
|
#end;
|
||||||
|
|
||||||
static final CORE_FOLDER:Null<String> = #if (REDIRECT_ASSETS_FOLDER && macos) "../../../../../../../assets" #elseif REDIRECT_ASSETS_FOLDER "../../../../assets" #else null #end;
|
static final CORE_FOLDER:Null<String> =
|
||||||
|
#if (REDIRECT_ASSETS_FOLDER && macos)
|
||||||
|
'../../../../../../../assets'
|
||||||
|
#elseif REDIRECT_ASSETS_FOLDER
|
||||||
|
'../../../../assets'
|
||||||
|
#else
|
||||||
|
null
|
||||||
|
#end;
|
||||||
|
|
||||||
public static function createModRoot()
|
/**
|
||||||
|
* If the mods folder doesn't exist, create it.
|
||||||
|
*/
|
||||||
|
public static function createModRoot():Void
|
||||||
{
|
{
|
||||||
FileUtil.createDirIfNotExists(MOD_FOLDER);
|
FileUtil.createDirIfNotExists(MOD_FOLDER);
|
||||||
}
|
}
|
||||||
|
@ -44,40 +62,44 @@ class PolymodHandler
|
||||||
/**
|
/**
|
||||||
* Loads the game with ALL mods enabled with Polymod.
|
* Loads the game with ALL mods enabled with Polymod.
|
||||||
*/
|
*/
|
||||||
public static function loadAllMods()
|
public static function loadAllMods():Void
|
||||||
{
|
{
|
||||||
// Create the mod root if it doesn't exist.
|
// Create the mod root if it doesn't exist.
|
||||||
createModRoot();
|
createModRoot();
|
||||||
trace("Initializing Polymod (using all mods)...");
|
trace('Initializing Polymod (using all mods)...');
|
||||||
loadModsById(getAllModIds());
|
loadModsById(getAllModIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the game with configured mods enabled with Polymod.
|
* Loads the game with configured mods enabled with Polymod.
|
||||||
*/
|
*/
|
||||||
public static function loadEnabledMods()
|
public static function loadEnabledMods():Void
|
||||||
{
|
{
|
||||||
// Create the mod root if it doesn't exist.
|
// Create the mod root if it doesn't exist.
|
||||||
createModRoot();
|
createModRoot();
|
||||||
|
|
||||||
trace("Initializing Polymod (using configured mods)...");
|
trace('Initializing Polymod (using configured mods)...');
|
||||||
loadModsById(Save.instance.enabledModIds);
|
loadModsById(Save.instance.enabledModIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the game without any mods enabled with Polymod.
|
* Loads the game without any mods enabled with Polymod.
|
||||||
*/
|
*/
|
||||||
public static function loadNoMods()
|
public static function loadNoMods():Void
|
||||||
{
|
{
|
||||||
// Create the mod root if it doesn't exist.
|
// Create the mod root if it doesn't exist.
|
||||||
createModRoot();
|
createModRoot();
|
||||||
|
|
||||||
// We still need to configure the debug print calls etc.
|
// We still need to configure the debug print calls etc.
|
||||||
trace("Initializing Polymod (using no mods)...");
|
trace('Initializing Polymod (using no mods)...');
|
||||||
loadModsById([]);
|
loadModsById([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function loadModsById(ids:Array<String>)
|
/**
|
||||||
|
* Load all the mods with the given ids.
|
||||||
|
* @param ids The ORDERED list of mod ids to load.
|
||||||
|
*/
|
||||||
|
public static function loadModsById(ids:Array<String>):Void
|
||||||
{
|
{
|
||||||
if (ids.length == 0)
|
if (ids.length == 0)
|
||||||
{
|
{
|
||||||
|
@ -90,7 +112,7 @@ class PolymodHandler
|
||||||
|
|
||||||
buildImports();
|
buildImports();
|
||||||
|
|
||||||
var loadedModList = polymod.Polymod.init(
|
var loadedModList:Array<ModMetadata> = polymod.Polymod.init(
|
||||||
{
|
{
|
||||||
// Root directory for all mods.
|
// Root directory for all mods.
|
||||||
modRoot: MOD_FOLDER,
|
modRoot: MOD_FOLDER,
|
||||||
|
@ -142,30 +164,40 @@ class PolymodHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
#if debug
|
#if debug
|
||||||
var fileList = Polymod.listModFiles(PolymodAssetType.IMAGE);
|
var fileList:Array<String> = Polymod.listModFiles(PolymodAssetType.IMAGE);
|
||||||
trace('Installed mods have replaced ${fileList.length} images.');
|
trace('Installed mods have replaced ${fileList.length} images.');
|
||||||
for (item in fileList)
|
for (item in fileList)
|
||||||
|
{
|
||||||
trace(' * $item');
|
trace(' * $item');
|
||||||
|
}
|
||||||
|
|
||||||
fileList = Polymod.listModFiles(PolymodAssetType.TEXT);
|
fileList = Polymod.listModFiles(PolymodAssetType.TEXT);
|
||||||
trace('Installed mods have added/replaced ${fileList.length} text files.');
|
trace('Installed mods have added/replaced ${fileList.length} text files.');
|
||||||
for (item in fileList)
|
for (item in fileList)
|
||||||
|
{
|
||||||
trace(' * $item');
|
trace(' * $item');
|
||||||
|
}
|
||||||
|
|
||||||
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_MUSIC);
|
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_MUSIC);
|
||||||
trace('Installed mods have replaced ${fileList.length} music files.');
|
trace('Installed mods have replaced ${fileList.length} music files.');
|
||||||
for (item in fileList)
|
for (item in fileList)
|
||||||
|
{
|
||||||
trace(' * $item');
|
trace(' * $item');
|
||||||
|
}
|
||||||
|
|
||||||
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_SOUND);
|
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_SOUND);
|
||||||
trace('Installed mods have replaced ${fileList.length} sound files.');
|
trace('Installed mods have replaced ${fileList.length} sound files.');
|
||||||
for (item in fileList)
|
for (item in fileList)
|
||||||
|
{
|
||||||
trace(' * $item');
|
trace(' * $item');
|
||||||
|
}
|
||||||
|
|
||||||
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_GENERIC);
|
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_GENERIC);
|
||||||
trace('Installed mods have replaced ${fileList.length} generic audio files.');
|
trace('Installed mods have replaced ${fileList.length} generic audio files.');
|
||||||
for (item in fileList)
|
for (item in fileList)
|
||||||
|
{
|
||||||
trace(' * $item');
|
trace(' * $item');
|
||||||
|
}
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,21 +215,21 @@ class PolymodHandler
|
||||||
for (cls in ClassMacro.listClassesInPackage('polymod'))
|
for (cls in ClassMacro.listClassesInPackage('polymod'))
|
||||||
{
|
{
|
||||||
if (cls == null) continue;
|
if (cls == null) continue;
|
||||||
var className = Type.getClassName(cls);
|
var className:String = Type.getClassName(cls);
|
||||||
Polymod.blacklistImport(className);
|
Polymod.blacklistImport(className);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static function buildParseRules():polymod.format.ParseRules
|
static function buildParseRules():polymod.format.ParseRules
|
||||||
{
|
{
|
||||||
var output = polymod.format.ParseRules.getDefault();
|
var output:polymod.format.ParseRules = polymod.format.ParseRules.getDefault();
|
||||||
// Ensure TXT files have merge support.
|
// Ensure TXT files have merge support.
|
||||||
output.addType("txt", TextFileFormat.LINES);
|
output.addType('txt', TextFileFormat.LINES);
|
||||||
// Ensure script files have merge support.
|
// Ensure script files have merge support.
|
||||||
output.addType("hscript", TextFileFormat.PLAINTEXT);
|
output.addType('hscript', TextFileFormat.PLAINTEXT);
|
||||||
output.addType("hxs", TextFileFormat.PLAINTEXT);
|
output.addType('hxs', TextFileFormat.PLAINTEXT);
|
||||||
output.addType("hxc", TextFileFormat.PLAINTEXT);
|
output.addType('hxc', TextFileFormat.PLAINTEXT);
|
||||||
output.addType("hx", TextFileFormat.PLAINTEXT);
|
output.addType('hx', TextFileFormat.PLAINTEXT);
|
||||||
|
|
||||||
// You can specify the format of a specific file, with file extension.
|
// You can specify the format of a specific file, with file extension.
|
||||||
// output.addFile("data/introText.txt", TextFileFormat.LINES)
|
// output.addFile("data/introText.txt", TextFileFormat.LINES)
|
||||||
|
@ -208,17 +240,21 @@ class PolymodHandler
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
assetLibraryPaths: [
|
assetLibraryPaths: [
|
||||||
"default" => "preload", "shared" => "shared", "songs" => "songs", "tutorial" => "tutorial", "week1" => "week1", "week2" => "week2",
|
'default' => 'preload', 'shared' => 'shared', 'songs' => 'songs', 'tutorial' => 'tutorial', 'week1' => 'week1', 'week2' => 'week2',
|
||||||
"week3" => "week3", "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "weekend1" => "weekend1",
|
'week3' => 'week3', 'week4' => 'week4', 'week5' => 'week5', 'week6' => 'week6', 'week7' => 'week7', 'weekend1' => 'weekend1',
|
||||||
],
|
],
|
||||||
coreAssetRedirect: CORE_FOLDER,
|
coreAssetRedirect: CORE_FOLDER,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a list of metadata for ALL installed mods, including disabled mods.
|
||||||
|
* @return An array of mod metadata
|
||||||
|
*/
|
||||||
public static function getAllMods():Array<ModMetadata>
|
public static function getAllMods():Array<ModMetadata>
|
||||||
{
|
{
|
||||||
trace('Scanning the mods folder...');
|
trace('Scanning the mods folder...');
|
||||||
var modMetadata = Polymod.scan(
|
var modMetadata:Array<ModMetadata> = Polymod.scan(
|
||||||
{
|
{
|
||||||
modRoot: MOD_FOLDER,
|
modRoot: MOD_FOLDER,
|
||||||
apiVersionRule: API_VERSION,
|
apiVersionRule: API_VERSION,
|
||||||
|
@ -228,17 +264,25 @@ class PolymodHandler
|
||||||
return modMetadata;
|
return modMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a list of ALL mod IDs, including disabled mods.
|
||||||
|
* @return An array of mod IDs
|
||||||
|
*/
|
||||||
public static function getAllModIds():Array<String>
|
public static function getAllModIds():Array<String>
|
||||||
{
|
{
|
||||||
var modIds = [for (i in getAllMods()) i.id];
|
var modIds:Array<String> = [for (i in getAllMods()) i.id];
|
||||||
return modIds;
|
return modIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a list of metadata for all enabled mods.
|
||||||
|
* @return An array of mod metadata
|
||||||
|
*/
|
||||||
public static function getEnabledMods():Array<ModMetadata>
|
public static function getEnabledMods():Array<ModMetadata>
|
||||||
{
|
{
|
||||||
var modIds = Save.instance.enabledModIds;
|
var modIds:Array<String> = Save.instance.enabledModIds;
|
||||||
var modMetadata = getAllMods();
|
var modMetadata:Array<ModMetadata> = getAllMods();
|
||||||
var enabledMods = [];
|
var enabledMods:Array<ModMetadata> = [];
|
||||||
for (item in modMetadata)
|
for (item in modMetadata)
|
||||||
{
|
{
|
||||||
if (modIds.indexOf(item.id) != -1)
|
if (modIds.indexOf(item.id) != -1)
|
||||||
|
@ -249,7 +293,11 @@ class PolymodHandler
|
||||||
return enabledMods;
|
return enabledMods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function forceReloadAssets()
|
/**
|
||||||
|
* Clear and reload from disk all data assets.
|
||||||
|
* Useful for "hot reloading" for fast iteration!
|
||||||
|
*/
|
||||||
|
public static function forceReloadAssets():Void
|
||||||
{
|
{
|
||||||
// Forcibly clear scripts so that scripts can be edited.
|
// Forcibly clear scripts so that scripts can be edited.
|
||||||
ModuleHandler.clearModuleCache();
|
ModuleHandler.clearModuleCache();
|
||||||
|
|
|
@ -71,17 +71,29 @@ class ScriptEventDispatcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Std.isOfType(target, IPlayStateScriptedClass))
|
if (Std.isOfType(target, INoteScriptedClass))
|
||||||
{
|
{
|
||||||
var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass);
|
var t:INoteScriptedClass = cast(target, INoteScriptedClass);
|
||||||
switch (event.type)
|
switch (event.type)
|
||||||
{
|
{
|
||||||
|
case NOTE_INCOMING:
|
||||||
|
t.onNoteIncoming(cast event);
|
||||||
|
return;
|
||||||
case NOTE_HIT:
|
case NOTE_HIT:
|
||||||
t.onNoteHit(cast event);
|
t.onNoteHit(cast event);
|
||||||
return;
|
return;
|
||||||
case NOTE_MISS:
|
case NOTE_MISS:
|
||||||
t.onNoteMiss(cast event);
|
t.onNoteMiss(cast event);
|
||||||
return;
|
return;
|
||||||
|
default: // Continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Std.isOfType(target, IPlayStateScriptedClass))
|
||||||
|
{
|
||||||
|
var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass);
|
||||||
|
switch (event.type)
|
||||||
|
{
|
||||||
case NOTE_GHOST_MISS:
|
case NOTE_GHOST_MISS:
|
||||||
t.onNoteGhostMiss(cast event);
|
t.onNoteGhostMiss(cast event);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -63,6 +63,13 @@ enum abstract ScriptEventType(String) from String to String
|
||||||
*/
|
*/
|
||||||
var SONG_STEP_HIT = 'STEP_HIT';
|
var SONG_STEP_HIT = 'STEP_HIT';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a note comes on screen and starts approaching the strumline.
|
||||||
|
*
|
||||||
|
* This event is not cancelable.
|
||||||
|
*/
|
||||||
|
var NOTE_INCOMING = 'NOTE_INCOMING';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a character hits a note.
|
* Called when a character hits a note.
|
||||||
* Important information such as judgement/timing, note data, player/opponent, etc. are all provided.
|
* Important information such as judgement/timing, note data, player/opponent, etc. are all provided.
|
||||||
|
|
|
@ -83,7 +83,9 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
|
||||||
|
|
||||||
public function onGameOver(event:ScriptEvent) {}
|
public function onGameOver(event:ScriptEvent) {}
|
||||||
|
|
||||||
public function onNoteHit(event:NoteScriptEvent) {}
|
public function onNoteIncoming(event:NoteScriptEvent) {}
|
||||||
|
|
||||||
|
public function onNoteHit(event:HitNoteScriptEvent) {}
|
||||||
|
|
||||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,11 @@ typedef PlayStateParams =
|
||||||
* @default `false`
|
* @default `false`
|
||||||
*/
|
*/
|
||||||
?practiceMode:Bool,
|
?practiceMode:Bool,
|
||||||
|
/**
|
||||||
|
* Whether the song should start in Bot Play Mode.
|
||||||
|
* @default `false`
|
||||||
|
*/
|
||||||
|
?botPlayMode:Bool,
|
||||||
/**
|
/**
|
||||||
* Whether the song should be in minimal mode.
|
* Whether the song should be in minimal mode.
|
||||||
* @default `false`
|
* @default `false`
|
||||||
|
@ -288,6 +293,12 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
public var isPracticeMode:Bool = false;
|
public var isPracticeMode:Bool = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the game is currently in Bot Play Mode.
|
||||||
|
* If true, player will not lose gain or lose score from notes.
|
||||||
|
*/
|
||||||
|
public var isBotPlayMode:Bool = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the player has dropped below zero health,
|
* Whether the player has dropped below zero health,
|
||||||
* and we are just waiting for an animation to play out before transitioning.
|
* and we are just waiting for an animation to play out before transitioning.
|
||||||
|
@ -572,6 +583,7 @@ class PlayState extends MusicBeatSubState
|
||||||
if (params.targetDifficulty != null) currentDifficulty = params.targetDifficulty;
|
if (params.targetDifficulty != null) currentDifficulty = params.targetDifficulty;
|
||||||
if (params.targetVariation != null) currentVariation = params.targetVariation;
|
if (params.targetVariation != null) currentVariation = params.targetVariation;
|
||||||
isPracticeMode = params.practiceMode ?? false;
|
isPracticeMode = params.practiceMode ?? false;
|
||||||
|
isBotPlayMode = params.botPlayMode ?? false;
|
||||||
isMinimalMode = params.minimalMode ?? false;
|
isMinimalMode = params.minimalMode ?? false;
|
||||||
startTimestamp = params.startTimestamp ?? 0.0;
|
startTimestamp = params.startTimestamp ?? 0.0;
|
||||||
playbackRate = params.playbackRate ?? 1.0;
|
playbackRate = params.playbackRate ?? 1.0;
|
||||||
|
@ -1026,7 +1038,7 @@ class PlayState extends MusicBeatSubState
|
||||||
if (isInCutscene && !disableKeys) handleCutsceneKeys(elapsed);
|
if (isInCutscene && !disableKeys) handleCutsceneKeys(elapsed);
|
||||||
|
|
||||||
// Moving notes into position is now done by Strumline.update().
|
// Moving notes into position is now done by Strumline.update().
|
||||||
processNotes(elapsed);
|
if (!isInCutscene) processNotes(elapsed);
|
||||||
|
|
||||||
justUnpaused = false;
|
justUnpaused = false;
|
||||||
}
|
}
|
||||||
|
@ -1620,8 +1632,10 @@ class PlayState extends MusicBeatSubState
|
||||||
var noteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId);
|
var noteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId);
|
||||||
if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault();
|
if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault();
|
||||||
|
|
||||||
playerStrumline = new Strumline(noteStyle, true);
|
playerStrumline = new Strumline(noteStyle, !isBotPlayMode);
|
||||||
|
playerStrumline.onNoteIncoming.add(onStrumlineNoteIncoming);
|
||||||
opponentStrumline = new Strumline(noteStyle, false);
|
opponentStrumline = new Strumline(noteStyle, false);
|
||||||
|
opponentStrumline.onNoteIncoming.add(onStrumlineNoteIncoming);
|
||||||
add(playerStrumline);
|
add(playerStrumline);
|
||||||
add(opponentStrumline);
|
add(opponentStrumline);
|
||||||
|
|
||||||
|
@ -1757,6 +1771,13 @@ class PlayState extends MusicBeatSubState
|
||||||
opponentStrumline.applyNoteData(opponentNoteData);
|
opponentStrumline.applyNoteData(opponentNoteData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onStrumlineNoteIncoming(noteSprite:NoteSprite):Void
|
||||||
|
{
|
||||||
|
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_INCOMING, noteSprite, 0, false);
|
||||||
|
|
||||||
|
dispatchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares to start the countdown.
|
* Prepares to start the countdown.
|
||||||
* Ends any running cutscenes, creates the strumlines, and starts the countdown.
|
* Ends any running cutscenes, creates the strumlines, and starts the countdown.
|
||||||
|
@ -1882,7 +1903,14 @@ class PlayState extends MusicBeatSubState
|
||||||
function updateScoreText():Void
|
function updateScoreText():Void
|
||||||
{
|
{
|
||||||
// TODO: Add functionality for modules to update the score text.
|
// TODO: Add functionality for modules to update the score text.
|
||||||
scoreText.text = 'Score:' + songScore;
|
if (isBotPlayMode)
|
||||||
|
{
|
||||||
|
scoreText.text = 'Bot Play Enabled';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
scoreText.text = 'Score:' + songScore;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1890,7 +1918,14 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
function updateHealthBar():Void
|
function updateHealthBar():Void
|
||||||
{
|
{
|
||||||
healthLerp = FlxMath.lerp(healthLerp, health, 0.15);
|
if (isBotPlayMode)
|
||||||
|
{
|
||||||
|
healthLerp = Constants.HEALTH_MAX;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
healthLerp = FlxMath.lerp(healthLerp, health, 0.15);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1934,13 +1969,16 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (Conductor.instance.songPosition > hitWindowEnd)
|
if (Conductor.instance.songPosition > hitWindowEnd)
|
||||||
{
|
{
|
||||||
if (note.hasMissed) continue;
|
if (note.hasMissed || note.hasBeenHit) continue;
|
||||||
|
|
||||||
note.tooEarly = false;
|
note.tooEarly = false;
|
||||||
note.mayHit = false;
|
note.mayHit = false;
|
||||||
note.hasMissed = true;
|
note.hasMissed = true;
|
||||||
|
|
||||||
if (note.holdNoteSprite != null) note.holdNoteSprite.missedNote = true;
|
if (note.holdNoteSprite != null)
|
||||||
|
{
|
||||||
|
note.holdNoteSprite.missedNote = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (Conductor.instance.songPosition > hitWindowCenter)
|
else if (Conductor.instance.songPosition > hitWindowCenter)
|
||||||
{
|
{
|
||||||
|
@ -1948,7 +1986,7 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
// Call an event to allow canceling the note hit.
|
// Call an event to allow canceling the note hit.
|
||||||
// NOTE: This is what handles the character animations!
|
// NOTE: This is what handles the character animations!
|
||||||
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_HIT, note, 0, true);
|
var event:NoteScriptEvent = new HitNoteScriptEvent(note, 0.0, 0, 'perfect', 0);
|
||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
|
|
||||||
// Calling event.cancelEvent() skips all the other logic! Neat!
|
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||||
|
@ -2027,10 +2065,38 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (Conductor.instance.songPosition > hitWindowEnd)
|
if (Conductor.instance.songPosition > hitWindowEnd)
|
||||||
{
|
{
|
||||||
|
if (note.hasMissed || note.hasBeenHit) continue;
|
||||||
note.tooEarly = false;
|
note.tooEarly = false;
|
||||||
note.mayHit = false;
|
note.mayHit = false;
|
||||||
note.hasMissed = true;
|
note.hasMissed = true;
|
||||||
if (note.holdNoteSprite != null) note.holdNoteSprite.missedNote = true;
|
if (note.holdNoteSprite != null)
|
||||||
|
{
|
||||||
|
note.holdNoteSprite.missedNote = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isBotPlayMode && Conductor.instance.songPosition > hitWindowCenter)
|
||||||
|
{
|
||||||
|
if (note.hasBeenHit) continue;
|
||||||
|
|
||||||
|
// We call onHitNote to play the proper animations,
|
||||||
|
// but not goodNoteHit! This means zero score and zero notes hit for the results screen!
|
||||||
|
|
||||||
|
// Call an event to allow canceling the note hit.
|
||||||
|
// NOTE: This is what handles the character animations!
|
||||||
|
var event:NoteScriptEvent = new HitNoteScriptEvent(note, 0.0, 0, 'perfect', 0);
|
||||||
|
dispatchEvent(event);
|
||||||
|
|
||||||
|
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||||
|
if (event.eventCanceled) continue;
|
||||||
|
|
||||||
|
// Command the bot to hit the note on time.
|
||||||
|
// NOTE: This is what handles the strumline and cleaning up the note itself!
|
||||||
|
playerStrumline.hitNote(note);
|
||||||
|
|
||||||
|
if (note.holdNoteSprite != null)
|
||||||
|
{
|
||||||
|
playerStrumline.playNoteHoldCover(note.holdNoteSprite);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (Conductor.instance.songPosition > hitWindowStart)
|
else if (Conductor.instance.songPosition > hitWindowStart)
|
||||||
{
|
{
|
||||||
|
@ -2075,7 +2141,7 @@ class PlayState extends MusicBeatSubState
|
||||||
if (holdNote == null || !holdNote.alive) continue;
|
if (holdNote == null || !holdNote.alive) continue;
|
||||||
|
|
||||||
// While the hold note is being hit, and there is length on the hold note...
|
// While the hold note is being hit, and there is length on the hold note...
|
||||||
if (holdNote.hitNote && !holdNote.missedNote && holdNote.sustainLength > 0)
|
if (!isBotPlayMode && holdNote.hitNote && !holdNote.missedNote && holdNote.sustainLength > 0)
|
||||||
{
|
{
|
||||||
// Grant the player health.
|
// Grant the player health.
|
||||||
health += Constants.HEALTH_HOLD_BONUS_PER_SECOND * elapsed;
|
health += Constants.HEALTH_HOLD_BONUS_PER_SECOND * elapsed;
|
||||||
|
@ -2797,14 +2863,19 @@ class PlayState extends MusicBeatSubState
|
||||||
// TODO: Uncache the song.
|
// TODO: Uncache the song.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!overrideMusic)
|
if (overrideMusic)
|
||||||
{
|
{
|
||||||
// Stop the music.
|
// Stop the music. Do NOT destroy it, something still references it!
|
||||||
FlxG.sound.music.pause();
|
FlxG.sound.music.pause();
|
||||||
if (vocals != null) vocals.stop();
|
if (vocals != null)
|
||||||
|
{
|
||||||
|
vocals.pause();
|
||||||
|
remove(vocals);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Stop and destroy the music.
|
||||||
FlxG.sound.music.pause();
|
FlxG.sound.music.pause();
|
||||||
if (vocals != null)
|
if (vocals != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -347,6 +347,10 @@ class ResultState extends MusicBeatSubState
|
||||||
|
|
||||||
if (controls.PAUSE)
|
if (controls.PAUSE)
|
||||||
{
|
{
|
||||||
|
FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8);
|
||||||
|
FlxTween.tween(FlxG.sound.music, {pitch: 3}, 0.1, {onComplete: _ -> {
|
||||||
|
FlxTween.tween(FlxG.sound.music, {pitch: 0.5}, 0.4);
|
||||||
|
}});
|
||||||
if (params.storyMode)
|
if (params.storyMode)
|
||||||
{
|
{
|
||||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)));
|
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)));
|
||||||
|
|
|
@ -485,7 +485,7 @@ class BaseCharacter extends Bopper
|
||||||
* Every time a note is hit, check if the note is from the same strumline.
|
* Every time a note is hit, check if the note is from the same strumline.
|
||||||
* If it is, then play the sing animation.
|
* If it is, then play the sing animation.
|
||||||
*/
|
*/
|
||||||
public override function onNoteHit(event:NoteScriptEvent)
|
public override function onNoteHit(event:HitNoteScriptEvent)
|
||||||
{
|
{
|
||||||
super.onNoteHit(event);
|
super.onNoteHit(event);
|
||||||
|
|
||||||
|
|
|
@ -378,12 +378,12 @@ class CharacterDataParser
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default time the character should sing for, in beats.
|
* The default time the character should sing for, in steps.
|
||||||
* Values that are too low will cause the character to stop singing between notes.
|
* Values that are too low will cause the character to stop singing between notes.
|
||||||
* Originally, this value was set to 1, but it was changed to 2 because that became
|
* Values that are too high will cause the character to hold their singing pose for too long after they're done.
|
||||||
* too low after some other code changes.
|
* @default `8 steps`
|
||||||
*/
|
*/
|
||||||
static final DEFAULT_SINGTIME:Float = 2.0;
|
static final DEFAULT_SINGTIME:Float = 8.0;
|
||||||
|
|
||||||
static final DEFAULT_DANCEEVERY:Int = 1;
|
static final DEFAULT_DANCEEVERY:Int = 1;
|
||||||
static final DEFAULT_FLIPX:Bool = false;
|
static final DEFAULT_FLIPX:Bool = false;
|
||||||
|
|
|
@ -61,7 +61,7 @@ class VideoCutscene
|
||||||
VideoCutscene.cutsceneType = cutsceneType;
|
VideoCutscene.cutsceneType = cutsceneType;
|
||||||
|
|
||||||
#if html5
|
#if html5
|
||||||
playVideoHTML5(filePath);
|
playVideoHTML5(rawFilePath);
|
||||||
#elseif hxCodec
|
#elseif hxCodec
|
||||||
playVideoNative(rawFilePath);
|
playVideoNative(rawFilePath);
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package funkin.play.notes;
|
package funkin.play.notes;
|
||||||
|
|
||||||
|
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||||
import flixel.FlxG;
|
import flixel.FlxG;
|
||||||
import funkin.play.notes.notestyle.NoteStyle;
|
import funkin.play.notes.notestyle.NoteStyle;
|
||||||
import flixel.group.FlxSpriteGroup;
|
import flixel.group.FlxSpriteGroup;
|
||||||
|
@ -38,6 +39,10 @@ class Strumline extends FlxSpriteGroup
|
||||||
return FlxG.height / 0.45;
|
return FlxG.height / 0.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this strumline is controlled by the player's inputs.
|
||||||
|
* False means it's controlled by the opponent or Bot Play.
|
||||||
|
*/
|
||||||
public var isPlayer:Bool;
|
public var isPlayer:Bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,6 +54,8 @@ class Strumline extends FlxSpriteGroup
|
||||||
|
|
||||||
public var holdNotes:FlxTypedSpriteGroup<SustainTrail>;
|
public var holdNotes:FlxTypedSpriteGroup<SustainTrail>;
|
||||||
|
|
||||||
|
public var onNoteIncoming:FlxTypedSignal<NoteSprite->Void>;
|
||||||
|
|
||||||
var strumlineNotes:FlxTypedSpriteGroup<StrumlineNote>;
|
var strumlineNotes:FlxTypedSpriteGroup<StrumlineNote>;
|
||||||
var noteSplashes:FlxTypedSpriteGroup<NoteSplash>;
|
var noteSplashes:FlxTypedSpriteGroup<NoteSplash>;
|
||||||
var noteHoldCovers:FlxTypedSpriteGroup<NoteHoldCover>;
|
var noteHoldCovers:FlxTypedSpriteGroup<NoteHoldCover>;
|
||||||
|
@ -106,6 +113,8 @@ class Strumline extends FlxSpriteGroup
|
||||||
|
|
||||||
this.refresh();
|
this.refresh();
|
||||||
|
|
||||||
|
this.onNoteIncoming = new FlxTypedSignal<NoteSprite->Void>();
|
||||||
|
|
||||||
for (i in 0...KEY_COUNT)
|
for (i in 0...KEY_COUNT)
|
||||||
{
|
{
|
||||||
var child:StrumlineNote = new StrumlineNote(noteStyle, isPlayer, DIRECTIONS[i]);
|
var child:StrumlineNote = new StrumlineNote(noteStyle, isPlayer, DIRECTIONS[i]);
|
||||||
|
@ -311,6 +320,8 @@ class Strumline extends FlxSpriteGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
nextNoteIndex = noteIndex + 1; // 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.
|
||||||
|
|
||||||
|
onNoteIncoming.dispatch(noteSprite);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update rendering of notes.
|
// Update rendering of notes.
|
||||||
|
|
|
@ -364,7 +364,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
|
|
||||||
public function onSongRetry(event:ScriptEvent):Void {};
|
public function onSongRetry(event:ScriptEvent):Void {};
|
||||||
|
|
||||||
public function onNoteHit(event:NoteScriptEvent):Void {};
|
public function onNoteIncoming(event:NoteScriptEvent) {}
|
||||||
|
|
||||||
|
public function onNoteHit(event:HitNoteScriptEvent) {}
|
||||||
|
|
||||||
public function onNoteMiss(event:NoteScriptEvent):Void {};
|
public function onNoteMiss(event:NoteScriptEvent):Void {};
|
||||||
|
|
||||||
|
|
|
@ -167,10 +167,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
||||||
|
|
||||||
function update_shouldAlternate():Void
|
function update_shouldAlternate():Void
|
||||||
{
|
{
|
||||||
if (hasAnimation('danceLeft'))
|
this.shouldAlternate = hasAnimation('danceLeft');
|
||||||
{
|
|
||||||
this.shouldAlternate = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -228,10 +225,11 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure that a given animation exists before playing it.
|
* Ensure that a given animation exists before playing it.
|
||||||
* Will gracefully check for name, then name with stripped suffixes, then 'idle', then fail to play.
|
* Will gracefully check for name, then name with stripped suffixes, then fail to play.
|
||||||
* @param name
|
* @param name The animation name to attempt to correct.
|
||||||
|
* @param fallback Instead of failing to play, try to play this animation instead.
|
||||||
*/
|
*/
|
||||||
function correctAnimationName(name:String):String
|
function correctAnimationName(name:String, ?fallback:String):String
|
||||||
{
|
{
|
||||||
// If the animation exists, we're good.
|
// If the animation exists, we're good.
|
||||||
if (hasAnimation(name)) return name;
|
if (hasAnimation(name)) return name;
|
||||||
|
@ -247,14 +245,22 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (name != 'idle')
|
if (fallback != null)
|
||||||
{
|
{
|
||||||
FlxG.log.warn('Bopper tried to play animation "$name" that does not exist, fallback to idle...');
|
if (fallback == name)
|
||||||
return correctAnimationName('idle');
|
{
|
||||||
|
FlxG.log.error('Bopper tried to play animation "$name" that does not exist! This is bad!');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FlxG.log.warn('Bopper tried to play animation "$name" that does not exist, fallback to idle...');
|
||||||
|
return correctAnimationName('idle');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FlxG.log.error('Bopper tried to play animation "idle" that does not exist! This is bad!');
|
FlxG.log.error('Bopper tried to play animation "$name" that does not exist! This is bad!');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -353,7 +359,9 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
||||||
|
|
||||||
public function onGameOver(event:ScriptEvent) {}
|
public function onGameOver(event:ScriptEvent) {}
|
||||||
|
|
||||||
public function onNoteHit(event:NoteScriptEvent) {}
|
public function onNoteIncoming(event:NoteScriptEvent) {}
|
||||||
|
|
||||||
|
public function onNoteHit(event:HitNoteScriptEvent) {}
|
||||||
|
|
||||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||||
|
|
||||||
|
|
|
@ -870,7 +870,9 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||||
|
|
||||||
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
||||||
|
|
||||||
public function onNoteHit(event:NoteScriptEvent) {}
|
public function onNoteIncoming(event:NoteScriptEvent) {}
|
||||||
|
|
||||||
|
public function onNoteHit(event:HitNoteScriptEvent) {}
|
||||||
|
|
||||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||||
|
|
||||||
|
|
|
@ -592,6 +592,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
*/
|
*/
|
||||||
var playtestPracticeMode:Bool = false;
|
var playtestPracticeMode:Bool = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, playtesting a chart will make the computer do it for you!
|
||||||
|
*/
|
||||||
|
var playtestBotPlayMode:Bool = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables or disables the "debugger" popup that appears when you run into a flixel error.
|
* Enables or disables the "debugger" popup that appears when you run into a flixel error.
|
||||||
*/
|
*/
|
||||||
|
@ -4525,14 +4530,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
{
|
{
|
||||||
// Create an event and place it in the chart.
|
// Create an event and place it in the chart.
|
||||||
// TODO: Figure out configuring event data.
|
// TODO: Figure out configuring event data.
|
||||||
var newEventData:SongEventData = new SongEventData(cursorSnappedMs, eventKindToPlace, eventDataToPlace.clone());
|
var newEventData:SongEventData = new SongEventData(cursorSnappedMs, eventKindToPlace, eventDataToPlace.copy());
|
||||||
|
|
||||||
performCommand(new AddEventsCommand([newEventData], FlxG.keys.pressed.CONTROL));
|
performCommand(new AddEventsCommand([newEventData], FlxG.keys.pressed.CONTROL));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Create a note and place it in the chart.
|
// Create a note and place it in the chart.
|
||||||
var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace.clone());
|
var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace);
|
||||||
|
|
||||||
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
|
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
|
||||||
|
|
||||||
|
@ -5359,6 +5364,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
targetDifficulty: selectedDifficulty,
|
targetDifficulty: selectedDifficulty,
|
||||||
targetVariation: selectedVariation,
|
targetVariation: selectedVariation,
|
||||||
practiceMode: playtestPracticeMode,
|
practiceMode: playtestPracticeMode,
|
||||||
|
botPlayMode: playtestBotPlayMode,
|
||||||
minimalMode: minimal,
|
minimalMode: minimal,
|
||||||
startTimestamp: startTimestamp,
|
startTimestamp: startTimestamp,
|
||||||
playbackRate: playbackRate,
|
playbackRate: playbackRate,
|
||||||
|
@ -5928,7 +5934,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
var tempNote:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault());
|
var tempNote:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault());
|
||||||
tempNote.noteData = noteData;
|
tempNote.noteData = noteData;
|
||||||
tempNote.scrollFactor.set(0, 0);
|
tempNote.scrollFactor.set(0, 0);
|
||||||
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_HIT, tempNote, 1, true);
|
var event:NoteScriptEvent = new HitNoteScriptEvent(tempNote, 0.0, 0, 'perfect', 0);
|
||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
|
|
||||||
// Calling event.cancelEvent() skips all the other logic! Neat!
|
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||||
|
|
|
@ -299,6 +299,15 @@ class ChartEditorToolboxHandler
|
||||||
state.playtestStartTime = checkboxStartTime.selected;
|
state.playtestStartTime = checkboxStartTime.selected;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var checkboxBotPlay:Null<CheckBox> = toolbox.findComponent('playtestBotPlayCheckbox', CheckBox);
|
||||||
|
if (checkboxBotPlay == null) throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find playtestBotPlayCheckbox component.';
|
||||||
|
|
||||||
|
checkboxBotPlay.selected = state.playtestBotPlayMode;
|
||||||
|
|
||||||
|
checkboxBotPlay.onClick = _ -> {
|
||||||
|
state.playtestBotPlayMode = checkboxBotPlay.selected;
|
||||||
|
};
|
||||||
|
|
||||||
var checkboxDebugger:Null<CheckBox> = toolbox.findComponent('playtestDebuggerCheckbox', CheckBox);
|
var checkboxDebugger:Null<CheckBox> = toolbox.findComponent('playtestDebuggerCheckbox', CheckBox);
|
||||||
|
|
||||||
if (checkboxDebugger == null) throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find playtestDebuggerCheckbox component.';
|
if (checkboxDebugger == null) throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find playtestDebuggerCheckbox component.';
|
||||||
|
|
|
@ -272,19 +272,19 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
// waveformPlayer.waveform.forceUpdate = true;
|
// waveformPlayer.waveform.forceUpdate = true;
|
||||||
waveformPlayer.waveform.waveformData = playerVoice?.waveformData;
|
waveformPlayer.waveform.waveformData = playerVoice?.waveformData;
|
||||||
// Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it.
|
// Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it.
|
||||||
waveformPlayer.waveform.duration = (playerVoice?.length ?? 1000) / Constants.MS_PER_SEC;
|
waveformPlayer.waveform.duration = (playerVoice?.length ?? 1000.0) / Constants.MS_PER_SEC;
|
||||||
|
|
||||||
// Build opponent waveform.
|
// Build opponent waveform.
|
||||||
// waveformOpponent.waveform.forceUpdate = true;
|
// waveformOpponent.waveform.forceUpdate = true;
|
||||||
// note: if song only has one set of vocals (Vocals.ogg/mp3) then this is null and crashes charting editor
|
// note: if song only has one set of vocals (Vocals.ogg/mp3) then this is null and crashes charting editor
|
||||||
// so we null check
|
// so we null check
|
||||||
waveformOpponent.waveform.waveformData = opponentVoice?.waveformData;
|
waveformOpponent.waveform.waveformData = opponentVoice?.waveformData;
|
||||||
waveformOpponent.waveform.duration = (opponentVoice?.length ?? 1000) / Constants.MS_PER_SEC;
|
waveformOpponent.waveform.duration = (opponentVoice?.length ?? 1000.0) / Constants.MS_PER_SEC;
|
||||||
|
|
||||||
// Build instrumental waveform.
|
// Build instrumental waveform.
|
||||||
// waveformInstrumental.waveform.forceUpdate = true;
|
// waveformInstrumental.waveform.forceUpdate = true;
|
||||||
waveformInstrumental.waveform.waveformData = chartEditorState.audioInstTrack.waveformData;
|
waveformInstrumental.waveform.waveformData = chartEditorState.audioInstTrack.waveformData;
|
||||||
waveformInstrumental.waveform.duration = (instTrack?.length ?? 1000) / Constants.MS_PER_SEC;
|
waveformInstrumental.waveform.duration = (instTrack?.length ?? 1000.0) / Constants.MS_PER_SEC;
|
||||||
|
|
||||||
addOffsetsToAudioPreview();
|
addOffsetsToAudioPreview();
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,9 +128,9 @@ class ChartEditorDropdowns
|
||||||
"weekend-1-picouppercutprep" => "Pico Uppercut (Prep) (Blazin')",
|
"weekend-1-picouppercutprep" => "Pico Uppercut (Prep) (Blazin')",
|
||||||
"weekend-1-picouppercut" => "Pico Uppercut (Blazin')",
|
"weekend-1-picouppercut" => "Pico Uppercut (Blazin')",
|
||||||
"weekend-1-blockhigh" => "Block High (Blazin')",
|
"weekend-1-blockhigh" => "Block High (Blazin')",
|
||||||
"weekend-1-blocklow" => "Dodge High (Blazin')",
|
"weekend-1-blocklow" => "Block Low (Blazin')",
|
||||||
"weekend-1-blockspin" => "Block High (Spin) (Blazin')",
|
"weekend-1-blockspin" => "Block High (Spin) (Blazin')",
|
||||||
"weekend-1-dodgehigh" => "Block Low (Blazin')",
|
"weekend-1-dodgehigh" => "Dodge High (Blazin')",
|
||||||
"weekend-1-dodgelow" => "Dodge Low (Blazin')",
|
"weekend-1-dodgelow" => "Dodge Low (Blazin')",
|
||||||
"weekend-1-dodgespin" => "Dodge High (Spin) (Blazin')",
|
"weekend-1-dodgespin" => "Dodge High (Spin) (Blazin')",
|
||||||
"weekend-1-hithigh" => "Hit High (Blazin')",
|
"weekend-1-hithigh" => "Hit High (Blazin')",
|
||||||
|
|
|
@ -1143,12 +1143,12 @@ class FreeplayState extends MusicBeatSubState
|
||||||
targetSong: targetSong,
|
targetSong: targetSong,
|
||||||
targetDifficulty: targetDifficulty,
|
targetDifficulty: targetDifficulty,
|
||||||
targetVariation: targetVariation,
|
targetVariation: targetVariation,
|
||||||
// TODO: Make this an option!
|
|
||||||
// startTimestamp: 0.0,
|
|
||||||
// TODO: Make this an option!
|
|
||||||
// playbackRate: 0.5,
|
|
||||||
practiceMode: false,
|
practiceMode: false,
|
||||||
minimalMode: false,
|
minimalMode: false,
|
||||||
|
// TODO: Make these an option! It's currently only accessible via chart editor.
|
||||||
|
// startTimestamp: 0.0,
|
||||||
|
// playbackRate: 0.5,
|
||||||
|
// botPlayMode: true,
|
||||||
}, true);
|
}, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package funkin.ui.haxeui.components;
|
||||||
|
|
||||||
import funkin.modding.events.ScriptEvent.GhostMissNoteScriptEvent;
|
import funkin.modding.events.ScriptEvent.GhostMissNoteScriptEvent;
|
||||||
import funkin.modding.events.ScriptEvent.NoteScriptEvent;
|
import funkin.modding.events.ScriptEvent.NoteScriptEvent;
|
||||||
|
import funkin.modding.events.ScriptEvent.HitNoteScriptEvent;
|
||||||
import funkin.modding.events.ScriptEvent.SongTimeScriptEvent;
|
import funkin.modding.events.ScriptEvent.SongTimeScriptEvent;
|
||||||
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||||
import haxe.ui.core.IDataComponent;
|
import haxe.ui.core.IDataComponent;
|
||||||
|
@ -216,12 +217,17 @@ class CharacterPlayer extends Box
|
||||||
if (character != null) character.onStepHit(event);
|
if (character != null) character.onStepHit(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onNoteIncoming(event:NoteScriptEvent)
|
||||||
|
{
|
||||||
|
if (character != null) character.onNoteIncoming(event);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a note is hit in the song
|
* Called when a note is hit in the song
|
||||||
* Used to play character animations.
|
* Used to play character animations.
|
||||||
* @param event The event.
|
* @param event The event.
|
||||||
*/
|
*/
|
||||||
public function onNoteHit(event:NoteScriptEvent):Void
|
public function onNoteHit(event:HitNoteScriptEvent):Void
|
||||||
{
|
{
|
||||||
if (character != null) character.onNoteHit(event);
|
if (character != null) character.onNoteHit(event);
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ class Level implements IRegistryEntry<LevelData>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the title of the level for display on the menu.
|
* Retrieve the title of the level for display on the menu.
|
||||||
|
* @return Title of the level as a string
|
||||||
*/
|
*/
|
||||||
public function getTitle():String
|
public function getTitle():String
|
||||||
{
|
{
|
||||||
|
@ -58,16 +59,21 @@ class Level implements IRegistryEntry<LevelData>
|
||||||
return _data.name;
|
return _data.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the title graphic for the level.
|
||||||
|
* @return The constructed graphic as a sprite.
|
||||||
|
*/
|
||||||
public function buildTitleGraphic():FlxSprite
|
public function buildTitleGraphic():FlxSprite
|
||||||
{
|
{
|
||||||
var result = new FlxSprite().loadGraphic(Paths.image(_data.titleAsset));
|
var result:FlxSprite = new FlxSprite().loadGraphic(Paths.image(_data.titleAsset));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of songs in this level, as an array of names, for display on the menu.
|
* Get the list of songs in this level, as an array of names, for display on the menu.
|
||||||
* @return Array<String>
|
* @param difficulty The difficulty of the level being displayed
|
||||||
|
* @return The display names of the songs in this level
|
||||||
*/
|
*/
|
||||||
public function getSongDisplayNames(difficulty:String):Array<String>
|
public function getSongDisplayNames(difficulty:String):Array<String>
|
||||||
{
|
{
|
||||||
|
@ -88,7 +94,9 @@ class Level implements IRegistryEntry<LevelData>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this level is unlocked. If not, it will be greyed out on the menu and have a lock icon.
|
* Whether this level is unlocked. If not, it will be greyed out on the menu and have a lock icon.
|
||||||
* TODO: Change this behavior in a later release.
|
* Override this in a script.
|
||||||
|
* @default `true`
|
||||||
|
* @return Whether this level is unlocked
|
||||||
*/
|
*/
|
||||||
public function isUnlocked():Bool
|
public function isUnlocked():Bool
|
||||||
{
|
{
|
||||||
|
@ -97,6 +105,9 @@ class Level implements IRegistryEntry<LevelData>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this level is visible. If not, it will not be shown on the menu at all.
|
* Whether this level is visible. If not, it will not be shown on the menu at all.
|
||||||
|
* Override this in a script.
|
||||||
|
* @default `true`
|
||||||
|
* @return Whether this level is visible in the menu
|
||||||
*/
|
*/
|
||||||
public function isVisible():Bool
|
public function isVisible():Bool
|
||||||
{
|
{
|
||||||
|
@ -106,6 +117,7 @@ class Level implements IRegistryEntry<LevelData>
|
||||||
/**
|
/**
|
||||||
* Build a sprite for the background of the level.
|
* Build a sprite for the background of the level.
|
||||||
* Can be overriden by ScriptedLevel. Not used if `isBackgroundSimple` returns true.
|
* Can be overriden by ScriptedLevel. Not used if `isBackgroundSimple` returns true.
|
||||||
|
* @return The constructed sprite
|
||||||
*/
|
*/
|
||||||
public function buildBackground():FlxSprite
|
public function buildBackground():FlxSprite
|
||||||
{
|
{
|
||||||
|
@ -124,6 +136,7 @@ class Level implements IRegistryEntry<LevelData>
|
||||||
/**
|
/**
|
||||||
* Returns true if the background is a solid color.
|
* Returns true if the background is a solid color.
|
||||||
* If you have a ScriptedLevel with a fancy background, you may want to override this to false.
|
* If you have a ScriptedLevel with a fancy background, you may want to override this to false.
|
||||||
|
* @return Whether the background is a simple color
|
||||||
*/
|
*/
|
||||||
public function isBackgroundSimple():Bool
|
public function isBackgroundSimple():Bool
|
||||||
{
|
{
|
||||||
|
@ -133,30 +146,36 @@ class Level implements IRegistryEntry<LevelData>
|
||||||
/**
|
/**
|
||||||
* Returns true if the background is a solid color.
|
* Returns true if the background is a solid color.
|
||||||
* If you have a ScriptedLevel with a fancy background, you may want to override this to false.
|
* If you have a ScriptedLevel with a fancy background, you may want to override this to false.
|
||||||
|
* @return The background as a simple color. May not be valid if `isBackgroundSimple` returns false.
|
||||||
*/
|
*/
|
||||||
public function getBackgroundColor():FlxColor
|
public function getBackgroundColor():FlxColor
|
||||||
{
|
{
|
||||||
return FlxColor.fromString(_data.background);
|
return FlxColor.fromString(_data.background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of difficulties the player can select from for this level.
|
||||||
|
* @return The difficulty IDs.
|
||||||
|
*/
|
||||||
public function getDifficulties():Array<String>
|
public function getDifficulties():Array<String>
|
||||||
{
|
{
|
||||||
var difficulties:Array<String> = [];
|
var difficulties:Array<String> = [];
|
||||||
|
|
||||||
var songList = getSongs();
|
var songList:Array<String> = getSongs();
|
||||||
|
|
||||||
var firstSongId:String = songList[0];
|
var firstSongId:String = songList[0];
|
||||||
var firstSong:Song = SongRegistry.instance.fetchEntry(firstSongId);
|
var firstSong:Song = SongRegistry.instance.fetchEntry(firstSongId);
|
||||||
|
|
||||||
if (firstSong != null)
|
if (firstSong != null)
|
||||||
{
|
{
|
||||||
// Don't display alternate characters in Story Mode.
|
// Don't display alternate characters in Story Mode. Only show `default` and `erect` variations.
|
||||||
for (difficulty in firstSong.listDifficulties([Constants.DEFAULT_VARIATION, "erect"]))
|
for (difficulty in firstSong.listDifficulties([Constants.DEFAULT_VARIATION, 'erect']))
|
||||||
{
|
{
|
||||||
difficulties.push(difficulty);
|
difficulties.push(difficulty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort in a specific order! Fall back to alphabetical.
|
||||||
difficulties.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST));
|
difficulties.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST));
|
||||||
|
|
||||||
// Filter to only include difficulties that are present in all songs
|
// Filter to only include difficulties that are present in all songs
|
||||||
|
@ -169,7 +188,7 @@ class Level implements IRegistryEntry<LevelData>
|
||||||
|
|
||||||
for (difficulty in difficulties)
|
for (difficulty in difficulties)
|
||||||
{
|
{
|
||||||
if (!song.hasDifficulty(difficulty, [Constants.DEFAULT_VARIATION, "erect"]))
|
if (!song.hasDifficulty(difficulty, [Constants.DEFAULT_VARIATION, 'erect']))
|
||||||
{
|
{
|
||||||
difficulties.remove(difficulty);
|
difficulties.remove(difficulty);
|
||||||
}
|
}
|
||||||
|
@ -181,6 +200,11 @@ class Level implements IRegistryEntry<LevelData>
|
||||||
return difficulties;
|
return difficulties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the props for display over the colored background.
|
||||||
|
* @param existingProps The existing prop sprites, if any.
|
||||||
|
* @return The constructed prop sprites
|
||||||
|
*/
|
||||||
public function buildProps(?existingProps:Array<LevelProp>):Array<LevelProp>
|
public function buildProps(?existingProps:Array<LevelProp>):Array<LevelProp>
|
||||||
{
|
{
|
||||||
var props:Array<LevelProp> = existingProps == null ? [] : [for (x in existingProps) x];
|
var props:Array<LevelProp> = existingProps == null ? [] : [for (x in existingProps) x];
|
||||||
|
@ -189,11 +213,13 @@ class Level implements IRegistryEntry<LevelData>
|
||||||
|
|
||||||
var hiddenProps:Array<LevelProp> = props.splice(_data.props.length - 1, props.length - 1);
|
var hiddenProps:Array<LevelProp> = props.splice(_data.props.length - 1, props.length - 1);
|
||||||
for (hiddenProp in hiddenProps)
|
for (hiddenProp in hiddenProps)
|
||||||
|
{
|
||||||
hiddenProp.visible = false;
|
hiddenProp.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
for (propIndex in 0..._data.props.length)
|
for (propIndex in 0..._data.props.length)
|
||||||
{
|
{
|
||||||
var propData = _data.props[propIndex];
|
var propData:LevelPropData = _data.props[propIndex];
|
||||||
|
|
||||||
// Attempt to reuse the `LevelProp` object.
|
// Attempt to reuse the `LevelProp` object.
|
||||||
// This prevents animations from resetting.
|
// This prevents animations from resetting.
|
||||||
|
@ -201,8 +227,15 @@ class Level implements IRegistryEntry<LevelData>
|
||||||
if (existingProp != null)
|
if (existingProp != null)
|
||||||
{
|
{
|
||||||
existingProp.propData = propData;
|
existingProp.propData = propData;
|
||||||
existingProp.x = propData.offsets[0] + FlxG.width * 0.25 * propIndex;
|
if (existingProp.propData == null)
|
||||||
existingProp.visible = true;
|
{
|
||||||
|
existingProp.visible = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existingProp.visible = true;
|
||||||
|
existingProp.x = propData.offsets[0] + FlxG.width * 0.25 * propIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -217,6 +250,10 @@ class Level implements IRegistryEntry<LevelData>
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the level is destroyed.
|
||||||
|
* TODO: Document when this gets called
|
||||||
|
*/
|
||||||
public function destroy():Void {}
|
public function destroy():Void {}
|
||||||
|
|
||||||
public function toString():String
|
public function toString():String
|
||||||
|
@ -224,6 +261,11 @@ class Level implements IRegistryEntry<LevelData>
|
||||||
return 'Level($id)';
|
return 'Level($id)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve and parse the JSON data for a level by ID.
|
||||||
|
* @param id The ID of the level
|
||||||
|
* @return The parsed level data, or null if not found or invalid
|
||||||
|
*/
|
||||||
static function _fetchData(id:String):Null<LevelData>
|
static function _fetchData(id:String):Null<LevelData>
|
||||||
{
|
{
|
||||||
return LevelRegistry.instance.parseEntryDataWithMigration(id, LevelRegistry.instance.fetchEntryVersion(id));
|
return LevelRegistry.instance.parseEntryDataWithMigration(id, LevelRegistry.instance.fetchEntryVersion(id));
|
||||||
|
|
|
@ -11,11 +11,11 @@ class LevelProp extends Bopper
|
||||||
function set_propData(value:LevelPropData):LevelPropData
|
function set_propData(value:LevelPropData):LevelPropData
|
||||||
{
|
{
|
||||||
// Only reset the prop if the asset path has changed.
|
// Only reset the prop if the asset path has changed.
|
||||||
if (propData == null || value.assetPath != this.propData.assetPath)
|
if (propData == null || value?.assetPath != propData?.assetPath)
|
||||||
{
|
{
|
||||||
this.visible = (value != null);
|
this.visible = (value != null);
|
||||||
this.propData = value;
|
this.propData = value;
|
||||||
danceEvery = this.propData.danceEvery;
|
danceEvery = this.propData?.danceEvery ?? 0;
|
||||||
applyData();
|
applyData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,19 @@ class LevelProp extends Bopper
|
||||||
|
|
||||||
function applyData():Void
|
function applyData():Void
|
||||||
{
|
{
|
||||||
|
if (propData == null)
|
||||||
|
{
|
||||||
|
this.visible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset animation state.
|
||||||
|
this.shouldAlternate = null;
|
||||||
|
|
||||||
var isAnimated:Bool = propData.animations.length > 0;
|
var isAnimated:Bool = propData.animations.length > 0;
|
||||||
if (isAnimated)
|
if (isAnimated)
|
||||||
{
|
{
|
||||||
|
|
|
@ -141,10 +141,10 @@ class StoryMenuState extends MusicBeatState
|
||||||
|
|
||||||
persistentUpdate = persistentDraw = true;
|
persistentUpdate = persistentDraw = true;
|
||||||
|
|
||||||
updateData();
|
|
||||||
|
|
||||||
rememberSelection();
|
rememberSelection();
|
||||||
|
|
||||||
|
updateData();
|
||||||
|
|
||||||
// Explicitly define the background color.
|
// Explicitly define the background color.
|
||||||
this.bgColor = FlxColor.BLACK;
|
this.bgColor = FlxColor.BLACK;
|
||||||
|
|
||||||
|
@ -403,7 +403,8 @@ class StoryMenuState extends MusicBeatState
|
||||||
|
|
||||||
function hasModdedLevels():Bool
|
function hasModdedLevels():Bool
|
||||||
{
|
{
|
||||||
return LevelRegistry.instance.listModdedLevelIds().length > 0;
|
return false;
|
||||||
|
// return LevelRegistry.instance.listModdedLevelIds().length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,7 +17,9 @@ class TrackerUtil
|
||||||
*/
|
*/
|
||||||
public static function initTrackers():Void
|
public static function initTrackers():Void
|
||||||
{
|
{
|
||||||
|
#if FLX_DEBUG
|
||||||
Tracker.addProfile(new TrackerProfile(Highscore, ["tallies"]));
|
Tracker.addProfile(new TrackerProfile(Highscore, ["tallies"]));
|
||||||
FlxG.console.registerClass(Highscore);
|
FlxG.console.registerClass(Highscore);
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,12 @@ class AnsiTrace
|
||||||
public static function traceBF()
|
public static function traceBF()
|
||||||
{
|
{
|
||||||
#if sys
|
#if sys
|
||||||
if (colorSupported) Sys.println(ansiBF.join("\n"));
|
if (colorSupported)
|
||||||
|
{
|
||||||
|
for (line in ansiBF)
|
||||||
|
Sys.stdout().writeString(line + "\n");
|
||||||
|
Sys.stdout().flush();
|
||||||
|
}
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
package funkin.util.tools;
|
|
||||||
|
|
||||||
class DynamicTools
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Creates a full clone of the input `Dynamic`. Only guaranteed to work on anonymous structures.
|
|
||||||
* @param input The `Dynamic` to clone.
|
|
||||||
* @return A clone of the input `Dynamic`.
|
|
||||||
*/
|
|
||||||
public static function clone(input:Dynamic):Dynamic
|
|
||||||
{
|
|
||||||
return Reflect.copy(input);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue