mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-27 01:55:52 -05:00
Merge branch 'rewrite/master' into ansi-trace
This commit is contained in:
commit
ae907beab9
41 changed files with 1467 additions and 1053 deletions
2
.github/actions/setup-haxeshit/action.yml
vendored
2
.github/actions/setup-haxeshit/action.yml
vendored
|
@ -3,7 +3,7 @@ description: "sets up haxe shit, using HMM!"
|
|||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: krdlab/setup-haxe@v1.5.1
|
||||
- uses: funkincrew/ci-haxe@v2
|
||||
with:
|
||||
haxe-version: 4.3.1
|
||||
- name: Config haxelib
|
||||
|
|
89
.github/workflows/build-shit.yml
vendored
89
.github/workflows/build-shit.yml
vendored
|
@ -13,11 +13,18 @@ jobs:
|
|||
steps:
|
||||
- name: ensure git cli is installed
|
||||
run: apt update && apt install sudo git -y
|
||||
- uses: actions/checkout@v4
|
||||
- name: get token from gh app
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app_token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PEM }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: checkout repo
|
||||
uses: funkincrew/ci-checkout@v5
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GH_RO_PAT }}
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
- name: check whether submodules exist
|
||||
run: |
|
||||
git config --global --add safe.directory $GITHUB_WORKSPACE
|
||||
|
@ -48,15 +55,24 @@ jobs:
|
|||
apt install sudo git curl unzip -y
|
||||
echo $GITHUB_WORKSPACE
|
||||
git config --global --add safe.directory $GITHUB_WORKSPACE
|
||||
- uses: actions/checkout@v4
|
||||
- name: get token from gh app
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app_token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PEM }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: checkout repo
|
||||
uses: funkincrew/ci-checkout@v5
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GH_RO_PAT }}
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
- uses: ./.github/actions/setup-haxeshit
|
||||
- name: Build game
|
||||
- name: game build dependencies
|
||||
run: |
|
||||
sudo apt-get install -y libx11-dev xorg-dev libgl-dev libxi-dev libxext-dev libasound2-dev libxinerama-dev libxrandr-dev libgl1-mesa-dev
|
||||
- name: build game
|
||||
run: |
|
||||
haxelib run lime build html5 -release --times
|
||||
ls
|
||||
- uses: ./.github/actions/upload-itch
|
||||
|
@ -72,11 +88,18 @@ jobs:
|
|||
contents: write
|
||||
actions: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: get token from gh app
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app_token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PEM }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: checkout repo
|
||||
uses: funkincrew/ci-checkout@v5
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GH_RO_PAT }}
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
- uses: ./.github/actions/setup-haxeshit
|
||||
- name: Make HXCPP cache dir
|
||||
run: |
|
||||
|
@ -101,6 +124,50 @@ jobs:
|
|||
butler-key: ${{ secrets.BUTLER_API_KEY }}
|
||||
build-dir: export/release/windows/bin
|
||||
target: win
|
||||
create-nightly-mac:
|
||||
needs: check_date
|
||||
if: ${{ needs.check_date.outputs.should_run != 'false'}}
|
||||
runs-on: [self-hosted, macos]
|
||||
steps:
|
||||
- name: prepare container
|
||||
run: |
|
||||
git config --global --add safe.directory $GITHUB_WORKSPACE
|
||||
- name: get token from gh app
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app_token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PEM }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: checkout repo
|
||||
uses: funkincrew/ci-checkout@v5
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
- uses: ./.github/actions/setup-haxeshit
|
||||
- name: Make HXCPP cache dir
|
||||
run: |
|
||||
mkdir -p ${{ runner.temp }}/hxcpp_cache
|
||||
- name: Restore build cache
|
||||
id: cache-build-win
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
.haxelib
|
||||
export
|
||||
${{ runner.temp }}/hxcpp_cache
|
||||
key: ${{ runner.os }}-build-mac-${{ github.ref_name }}-${{ hashFiles('**/hmm.json') }}
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build macos -release --times
|
||||
ls
|
||||
env:
|
||||
HXCPP_COMPILE_CACHE: "${{ runner.temp }}/hxcpp_cache"
|
||||
- uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||
build-dir: export/release/macos/bin
|
||||
target: macos
|
||||
# test-unit-win:
|
||||
# needs: create-nightly-win
|
||||
# runs-on: windows-latest
|
||||
|
@ -108,7 +175,7 @@ jobs:
|
|||
# contents: write
|
||||
# actions: write
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: funkincrew/ci-checkout@v5
|
||||
# with:
|
||||
# submodules: 'recursive'
|
||||
# fetch-depth: 0
|
||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit d094640f727a670a348b3579d11af5ff6a2ada3a
|
||||
Subproject commit 7e19c4cfa7db57178f03ed4a58a9fd4d2b93dea7
|
4
hmm.json
4
hmm.json
|
@ -11,7 +11,7 @@
|
|||
"name": "flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a83738673e7edbf8acba3a1426af284dfe6719fe",
|
||||
"ref": "07c6018008801972d12275690fc144fcc22e3de6",
|
||||
"url": "https://github.com/FunkinCrew/flixel"
|
||||
},
|
||||
{
|
||||
|
@ -37,7 +37,7 @@
|
|||
"name": "flxanimate",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "d7c5621be742e2c98d523dfe5af7528835eaff1e",
|
||||
"ref": "9bacdd6ea39f5e3a33b0f5dfb7bc583fe76060d4",
|
||||
"url": "https://github.com/FunkinCrew/flxanimate"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -20,11 +20,11 @@ import openfl.display.BitmapData;
|
|||
import funkin.data.level.LevelRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.play.cutscene.dialogue.ConversationDataParser;
|
||||
import funkin.play.cutscene.dialogue.DialogueBoxDataParser;
|
||||
import funkin.play.cutscene.dialogue.SpeakerDataParser;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.ui.title.TitleState;
|
||||
|
@ -217,8 +217,9 @@ class InitState extends FlxState
|
|||
ConversationDataParser.loadConversationCache();
|
||||
DialogueBoxDataParser.loadDialogueBoxCache();
|
||||
SpeakerDataParser.loadSpeakerCache();
|
||||
StageDataParser.loadStageCache();
|
||||
StageRegistry.instance.loadEntries();
|
||||
CharacterDataParser.loadCharacterCache();
|
||||
|
||||
ModuleHandler.buildModuleCallbacks();
|
||||
ModuleHandler.loadModuleCache();
|
||||
|
||||
|
|
0
source/funkin/data/character/TODO.md
Normal file
0
source/funkin/data/character/TODO.md
Normal file
0
source/funkin/data/conversation/TODO.md
Normal file
0
source/funkin/data/conversation/TODO.md
Normal file
0
source/funkin/data/dialogue/TODO.md
Normal file
0
source/funkin/data/dialogue/TODO.md
Normal file
|
@ -15,7 +15,7 @@ abstract SongEventSchema(SongEventSchemaRaw)
|
|||
}
|
||||
|
||||
@:arrayAccess
|
||||
public inline function getByName(name:String):SongEventSchemaField
|
||||
public function getByName(name:String):SongEventSchemaField
|
||||
{
|
||||
for (field in this)
|
||||
{
|
||||
|
@ -41,6 +41,32 @@ abstract SongEventSchema(SongEventSchemaRaw)
|
|||
{
|
||||
return this[k] = v;
|
||||
}
|
||||
|
||||
public function stringifyFieldValue(name:String, value:Dynamic):String
|
||||
{
|
||||
var field:SongEventSchemaField = getByName(name);
|
||||
if (field == null) return 'Unknown';
|
||||
|
||||
switch (field.type)
|
||||
{
|
||||
case SongEventFieldType.STRING:
|
||||
return Std.string(value);
|
||||
case SongEventFieldType.INTEGER:
|
||||
return Std.string(value);
|
||||
case SongEventFieldType.FLOAT:
|
||||
return Std.string(value);
|
||||
case SongEventFieldType.BOOL:
|
||||
return Std.string(value);
|
||||
case SongEventFieldType.ENUM:
|
||||
for (key in field.keys.keys())
|
||||
{
|
||||
if (field.keys.get(key) == value) return key;
|
||||
}
|
||||
return Std.string(value);
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef SongEventSchemaRaw = Array<SongEventSchemaField>;
|
||||
|
|
|
@ -7,9 +7,9 @@ import funkin.ui.story.ScriptedLevel;
|
|||
class LevelRegistry extends BaseRegistry<Level, LevelData>
|
||||
{
|
||||
/**
|
||||
* The current version string for the stage data format.
|
||||
* The current version string for the level data format.
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStageData()` function.
|
||||
* and adding migration to the `migrateLevelData()` function.
|
||||
*/
|
||||
public static final LEVEL_DATA_VERSION:thx.semver.Version = "1.0.0";
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package funkin.data.song;
|
||||
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import thx.semver.Version;
|
||||
|
@ -702,6 +703,11 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
|||
}
|
||||
}
|
||||
|
||||
public inline function getHandler():Null<SongEvent>
|
||||
{
|
||||
return SongEventRegistry.getEvent(this.event);
|
||||
}
|
||||
|
||||
public inline function getSchema():Null<SongEventSchema>
|
||||
{
|
||||
return SongEventRegistry.getEventSchema(this.event);
|
||||
|
@ -752,6 +758,39 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
|||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public function buildTooltip():String
|
||||
{
|
||||
var eventHandler = getHandler();
|
||||
var eventSchema = getSchema();
|
||||
|
||||
if (eventSchema == null) return 'Unknown Event: ${this.event}';
|
||||
|
||||
var result = '${eventHandler.getTitle()}';
|
||||
|
||||
var defaultKey = eventSchema.getFirstField()?.name;
|
||||
var valueStruct:haxe.DynamicAccess<Dynamic> = valueAsStruct(defaultKey);
|
||||
|
||||
for (pair in valueStruct.keyValueIterator())
|
||||
{
|
||||
var key = pair.key;
|
||||
var value = pair.value;
|
||||
|
||||
var title = eventSchema.getByName(key)?.title ?? 'UnknownField';
|
||||
|
||||
if (eventSchema.stringifyFieldValue(key, value) != null) trace(eventSchema.stringifyFieldValue(key, value));
|
||||
var valueStr = eventSchema.stringifyFieldValue(key, value) ?? 'UnknownValue';
|
||||
|
||||
result += '\n- ${title}: ${valueStr}';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public function clone():SongEventData
|
||||
{
|
||||
return new SongEventData(this.time, this.event, this.value);
|
||||
}
|
||||
|
||||
@:op(A == B)
|
||||
public function op_equals(other:SongEventData):Bool
|
||||
{
|
||||
|
|
|
@ -127,7 +127,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
|
||||
var parser = new json2object.JsonParser<SongMetadata>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
parser.ignoreUnknownVariables = true;
|
||||
|
||||
switch (loadEntryMetadataFile(id, variation))
|
||||
{
|
||||
|
@ -150,7 +150,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
|
||||
var parser = new json2object.JsonParser<SongMetadata>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
parser.ignoreUnknownVariables = true;
|
||||
parser.fromJson(contents, fileName);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
|
@ -210,7 +210,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
|
||||
var parser = new json2object.JsonParser<SongMetadata_v2_1_0>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
parser.ignoreUnknownVariables = true;
|
||||
|
||||
switch (loadEntryMetadataFile(id, variation))
|
||||
{
|
||||
|
@ -232,7 +232,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
|
||||
var parser = new json2object.JsonParser<SongMetadata_v2_0_0>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
parser.ignoreUnknownVariables = true;
|
||||
|
||||
switch (loadEntryMetadataFile(id, variation))
|
||||
{
|
||||
|
@ -252,7 +252,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
function parseEntryMetadataRaw_v2_1_0(contents:String, ?fileName:String = 'raw'):Null<SongMetadata>
|
||||
{
|
||||
var parser = new json2object.JsonParser<SongMetadata_v2_1_0>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
parser.ignoreUnknownVariables = true;
|
||||
parser.fromJson(contents, fileName);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
|
@ -266,7 +266,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
function parseEntryMetadataRaw_v2_0_0(contents:String, ?fileName:String = 'raw'):Null<SongMetadata>
|
||||
{
|
||||
var parser = new json2object.JsonParser<SongMetadata_v2_0_0>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
parser.ignoreUnknownVariables = true;
|
||||
parser.fromJson(contents, fileName);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
|
@ -347,7 +347,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
|
||||
var parser = new json2object.JsonParser<SongChartData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
parser.ignoreUnknownVariables = true;
|
||||
|
||||
switch (loadEntryChartFile(id, variation))
|
||||
{
|
||||
|
@ -370,7 +370,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
|
||||
var parser = new json2object.JsonParser<SongChartData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
parser.ignoreUnknownVariables = true;
|
||||
parser.fromJson(contents, fileName);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
|
|
0
source/funkin/data/speaker/TODO.md
Normal file
0
source/funkin/data/speaker/TODO.md
Normal file
199
source/funkin/data/stage/StageData.hx
Normal file
199
source/funkin/data/stage/StageData.hx
Normal file
|
@ -0,0 +1,199 @@
|
|||
package funkin.data.stage;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
|
||||
@:nullSafety
|
||||
class StageData
|
||||
{
|
||||
/**
|
||||
* The sematic version number of the stage data JSON format.
|
||||
* Supports fancy comparisons like NPM does it's neat.
|
||||
*/
|
||||
@:default(funkin.data.stage.StageRegistry.STAGE_DATA_VERSION)
|
||||
public var version:String;
|
||||
|
||||
public var name:String = 'Unknown';
|
||||
public var props:Array<StageDataProp> = [];
|
||||
public var characters:StageDataCharacters;
|
||||
|
||||
@:default(1.0)
|
||||
@:optional
|
||||
public var cameraZoom:Null<Float>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
this.version = StageRegistry.STAGE_DATA_VERSION;
|
||||
this.characters = makeDefaultCharacters();
|
||||
}
|
||||
|
||||
function makeDefaultCharacters():StageDataCharacters
|
||||
{
|
||||
return {
|
||||
bf:
|
||||
{
|
||||
zIndex: 0,
|
||||
position: [0, 0],
|
||||
cameraOffsets: [-100, -100]
|
||||
},
|
||||
dad:
|
||||
{
|
||||
zIndex: 0,
|
||||
position: [0, 0],
|
||||
cameraOffsets: [100, -100]
|
||||
},
|
||||
gf:
|
||||
{
|
||||
zIndex: 0,
|
||||
position: [0, 0],
|
||||
cameraOffsets: [0, 0]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this StageData into a JSON string.
|
||||
*/
|
||||
public function serialize(pretty:Bool = true):String
|
||||
{
|
||||
var writer = new json2object.JsonWriter<StageData>();
|
||||
return writer.write(this, pretty ? ' ' : null);
|
||||
}
|
||||
}
|
||||
|
||||
typedef StageDataCharacters =
|
||||
{
|
||||
var bf:StageDataCharacter;
|
||||
var dad:StageDataCharacter;
|
||||
var gf:StageDataCharacter;
|
||||
};
|
||||
|
||||
typedef StageDataProp =
|
||||
{
|
||||
/**
|
||||
* The name of the prop for later lookup by scripts.
|
||||
* Optional; if unspecified, the prop can't be referenced by scripts.
|
||||
*/
|
||||
@:optional
|
||||
var name:String;
|
||||
|
||||
/**
|
||||
* The asset used to display the prop.
|
||||
* NOTE: As of Stage data v1.0.1, you can also use a color here to create a rectangle, like "#ff0000".
|
||||
* In this case, the `scale` property will be used to determine the size of the prop.
|
||||
*/
|
||||
var assetPath:String;
|
||||
|
||||
/**
|
||||
* The position of the prop as an [x, y] array of two floats.
|
||||
*/
|
||||
var position:Array<Float>;
|
||||
|
||||
/**
|
||||
* A number determining the stack order of the prop, relative to other props and the characters in the stage.
|
||||
* Props with lower numbers render below those with higher numbers.
|
||||
* This is just like CSS, it isn't hard.
|
||||
* @default 0
|
||||
*/
|
||||
@:optional
|
||||
@:default(0)
|
||||
var zIndex:Int;
|
||||
|
||||
/**
|
||||
* If set to true, anti-aliasing will be forcibly disabled on the sprite.
|
||||
* This prevents blurry images on pixel-art levels.
|
||||
* @default false
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
var isPixel:Bool;
|
||||
|
||||
/**
|
||||
* Either the scale of the prop as a float, or the [w, h] scale as an array of two floats.
|
||||
* Pro tip: On pixel-art levels, save the sprite small and set this value to 6 or so to save memory.
|
||||
*/
|
||||
@:jcustomparse(funkin.data.DataParse.eitherFloatOrFloats)
|
||||
@:jcustomwrite(funkin.data.DataWrite.eitherFloatOrFloats)
|
||||
@:optional
|
||||
var scale:haxe.ds.Either<Float, Array<Float>>;
|
||||
|
||||
/**
|
||||
* The alpha of the prop, as a float.
|
||||
* @default 1.0
|
||||
*/
|
||||
@:optional
|
||||
@:default(1.0)
|
||||
var alpha:Float;
|
||||
|
||||
/**
|
||||
* If not zero, this prop will play an animation every X beats of the song.
|
||||
* This requires animations to be defined. If `danceLeft` and `danceRight` are defined,
|
||||
* they will alternated between, otherwise the `idle` animation will be used.
|
||||
*
|
||||
* @default 0
|
||||
*/
|
||||
@:default(0)
|
||||
@:optional
|
||||
var danceEvery:Int;
|
||||
|
||||
/**
|
||||
* How much the prop scrolls relative to the camera. Used to create a parallax effect.
|
||||
* Represented as an [x, y] array of two floats.
|
||||
* [1, 1] means the prop moves 1:1 with the camera.
|
||||
* [0.5, 0.5] means the prop half as much as the camera.
|
||||
* [0, 0] means the prop is not moved.
|
||||
* @default [0, 0]
|
||||
*/
|
||||
@:optional
|
||||
@:default([0, 0])
|
||||
var scroll:Array<Float>;
|
||||
|
||||
/**
|
||||
* An optional array of animations which the prop can play.
|
||||
* @default Prop has no animations.
|
||||
*/
|
||||
@:optional
|
||||
@:default([])
|
||||
var animations:Array<AnimationData>;
|
||||
|
||||
/**
|
||||
* If animations are used, this is the name of the animation to play first.
|
||||
* @default Don't play an animation.
|
||||
*/
|
||||
@:optional
|
||||
var startingAnimation:Null<String>;
|
||||
|
||||
/**
|
||||
* The animation type to use.
|
||||
* Options: "sparrow", "packer"
|
||||
* @default "sparrow"
|
||||
*/
|
||||
@:default("sparrow")
|
||||
@:optional
|
||||
var animType:String;
|
||||
};
|
||||
|
||||
typedef StageDataCharacter =
|
||||
{
|
||||
/**
|
||||
* A number determining the stack order of the character, relative to props and other characters in the stage.
|
||||
* Again, just like CSS.
|
||||
* @default 0
|
||||
*/
|
||||
@:optional
|
||||
@:default(0)
|
||||
var zIndex:Int;
|
||||
|
||||
/**
|
||||
* The position to render the character at.
|
||||
*/
|
||||
@:optional
|
||||
@:default([0, 0])
|
||||
var position:Array<Float>;
|
||||
|
||||
/**
|
||||
* The camera offsets to apply when focusing on the character on this stage.
|
||||
* @default [-100, -100] for BF, [100, -100] for DAD/OPPONENT, [0, 0] for GF
|
||||
*/
|
||||
@:optional
|
||||
var cameraOffsets:Array<Float>;
|
||||
};
|
103
source/funkin/data/stage/StageRegistry.hx
Normal file
103
source/funkin/data/stage/StageRegistry.hx
Normal file
|
@ -0,0 +1,103 @@
|
|||
package funkin.data.stage;
|
||||
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.play.stage.Stage;
|
||||
import funkin.play.stage.ScriptedStage;
|
||||
|
||||
class StageRegistry extends BaseRegistry<Stage, StageData>
|
||||
{
|
||||
/**
|
||||
* The current version string for the stage data format.
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStageData()` function.
|
||||
*/
|
||||
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.1";
|
||||
|
||||
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||
|
||||
public static final instance:StageRegistry = new StageRegistry();
|
||||
|
||||
public function new()
|
||||
{
|
||||
super('STAGE', 'stages', STAGE_DATA_VERSION_RULE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||
*/
|
||||
public function parseEntryData(id:String):Null<StageData>
|
||||
{
|
||||
// JsonParser does not take type parameters,
|
||||
// otherwise this function would be in BaseRegistry.
|
||||
var parser = new json2object.JsonParser<StageData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
|
||||
switch (loadEntryFile(id))
|
||||
{
|
||||
case {fileName: fileName, contents: contents}:
|
||||
parser.fromJson(contents, fileName);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
printErrors(parser.errors, id);
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and validate the JSON data and produce the corresponding data object.
|
||||
*
|
||||
* NOTE: Must be implemented on the implementation class.
|
||||
* @param contents The JSON as a string.
|
||||
* @param fileName An optional file name for error reporting.
|
||||
*/
|
||||
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<StageData>
|
||||
{
|
||||
var parser = new json2object.JsonParser<StageData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
parser.fromJson(contents, fileName);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
printErrors(parser.errors, fileName);
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
}
|
||||
|
||||
function createScriptedEntry(clsName:String):Stage
|
||||
{
|
||||
return ScriptedStage.init(clsName, "unknown");
|
||||
}
|
||||
|
||||
function getScriptedClassNames():Array<String>
|
||||
{
|
||||
return ScriptedStage.listScriptClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of all the stages from the base game, in order.
|
||||
* TODO: Should this be hardcoded?
|
||||
*/
|
||||
public function listBaseGameStageIds():Array<String>
|
||||
{
|
||||
return [
|
||||
"mainStage", "spookyMansion", "phillyTrain", "limoRide", "mallXmas", "mallEvil", "school", "schoolEvil", "tankmanBattlefield", "phillyStreets",
|
||||
"phillyBlazin",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of all installed story weeks that are not from the base game.
|
||||
*/
|
||||
public function listModdedStageIds():Array<String>
|
||||
{
|
||||
return listEntryIds().filter(function(id:String):Bool {
|
||||
return listBaseGameStageIds().indexOf(id) == -1;
|
||||
});
|
||||
}
|
||||
}
|
53
source/funkin/graphics/FunkinSprite.hx
Normal file
53
source/funkin/graphics/FunkinSprite.hx
Normal file
|
@ -0,0 +1,53 @@
|
|||
package funkin.graphics;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
|
||||
/**
|
||||
* An FlxSprite with additional functionality.
|
||||
*/
|
||||
class FunkinSprite extends FlxSprite
|
||||
{
|
||||
/**
|
||||
* @param x Starting X position
|
||||
* @param y Starting Y position
|
||||
*/
|
||||
public function new(?x:Float = 0, ?y:Float = 0)
|
||||
{
|
||||
super(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts similarly to `makeGraphic`, but with improved memory usage,
|
||||
* at the expense of not being able to paint onto the sprite.
|
||||
*
|
||||
* @param width The target width of the sprite.
|
||||
* @param height The target height of the sprite.
|
||||
* @param color The color to fill the sprite with.
|
||||
*/
|
||||
public function makeSolidColor(width:Int, height:Int, color:FlxColor = FlxColor.WHITE):FunkinSprite
|
||||
{
|
||||
var graphic:FlxGraphic = FlxG.bitmap.create(2, 2, color, false, 'solid#${color.toHexString(true, false)}');
|
||||
frames = graphic.imageFrame;
|
||||
scale.set(width / 2, height / 2);
|
||||
updateHitbox();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure scale is applied when cloning a sprite.
|
||||
* The default `clone()` method acts kinda weird TBH.
|
||||
* @return A clone of this sprite.
|
||||
*/
|
||||
public override function clone():FunkinSprite
|
||||
{
|
||||
var result = new FunkinSprite(this.x, this.y);
|
||||
result.frames = this.frames;
|
||||
result.scale.set(this.scale.x, this.scale.y);
|
||||
result.updateHitbox();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -4,11 +4,12 @@ import funkin.util.macro.ClassMacro;
|
|||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.play.stage.StageData;
|
||||
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;
|
||||
|
@ -275,7 +276,7 @@ class PolymodHandler
|
|||
ConversationDataParser.loadConversationCache();
|
||||
DialogueBoxDataParser.loadDialogueBoxCache();
|
||||
SpeakerDataParser.loadSpeakerCache();
|
||||
StageDataParser.loadStageCache();
|
||||
StageRegistry.instance.loadEntries();
|
||||
CharacterDataParser.loadCharacterCache();
|
||||
ModuleHandler.loadModuleCache();
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import flixel.sound.FlxSound;
|
|||
import funkin.ui.story.StoryMenuState;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
|
@ -22,6 +23,12 @@ import funkin.play.character.BaseCharacter;
|
|||
*/
|
||||
class GameOverSubState extends MusicBeatSubState
|
||||
{
|
||||
/**
|
||||
* The currently active GameOverSubState.
|
||||
* There should be only one GameOverSubState in existance at a time, we can use a singleton.
|
||||
*/
|
||||
public static var instance:GameOverSubState = null;
|
||||
|
||||
/**
|
||||
* Which alternate animation on the character to use.
|
||||
* You can set this via script.
|
||||
|
@ -87,6 +94,13 @@ class GameOverSubState extends MusicBeatSubState
|
|||
|
||||
override public function create()
|
||||
{
|
||||
if (instance != null)
|
||||
{
|
||||
// TODO: Do something in this case? IDK.
|
||||
trace('WARNING: GameOverSubState instance already exists. This should not happen.');
|
||||
}
|
||||
instance = this;
|
||||
|
||||
super.create();
|
||||
|
||||
//
|
||||
|
@ -94,7 +108,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
//
|
||||
|
||||
// Add a black background to the screen.
|
||||
var bg = new FlxSprite().makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
||||
var bg = new FunkinSprite().makeSolidColor(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
||||
// We make this transparent so that we can see the stage underneath during debugging,
|
||||
// but it's normally opaque.
|
||||
bg.alpha = transparent ? 0.25 : 1.0;
|
||||
|
@ -282,10 +296,10 @@ class GameOverSubState extends MusicBeatSubState
|
|||
*/
|
||||
function startDeathMusic(?startingVolume:Float = 1, force:Bool = false):Void
|
||||
{
|
||||
var musicPath = Paths.music('gameOver' + musicSuffix);
|
||||
var musicPath = Paths.music('gameplay/gameover/gameOver' + musicSuffix);
|
||||
if (isEnding)
|
||||
{
|
||||
musicPath = Paths.music('gameOverEnd' + musicSuffix);
|
||||
musicPath = Paths.music('gameplay/gameover/gameOverEnd' + musicSuffix);
|
||||
}
|
||||
if (!gameOverMusic.playing || force)
|
||||
{
|
||||
|
@ -305,7 +319,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
public static function playBlueBalledSFX()
|
||||
{
|
||||
blueballed = true;
|
||||
FlxG.sound.play(Paths.sound('fnf_loss_sfx' + blueBallSuffix));
|
||||
FlxG.sound.play(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix));
|
||||
}
|
||||
|
||||
var playingJeffQuote:Bool = false;
|
||||
|
@ -328,6 +342,11 @@ class GameOverSubState extends MusicBeatSubState
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return "GameOverSubState";
|
||||
}
|
||||
}
|
||||
|
||||
typedef GameOverParams =
|
||||
|
|
|
@ -50,11 +50,11 @@ import funkin.play.notes.SustainTrail;
|
|||
import funkin.play.scoring.Scoring;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongCharacterData;
|
||||
import funkin.play.stage.Stage;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import funkin.play.components.PopUpStuff;
|
||||
import funkin.ui.options.PreferencesMenu;
|
||||
|
@ -1353,7 +1353,8 @@ class PlayState extends MusicBeatSubState
|
|||
*/
|
||||
function loadStage(id:String):Void
|
||||
{
|
||||
currentStage = StageDataParser.fetchStage(id);
|
||||
currentStage = StageRegistry.instance.fetchEntry(id);
|
||||
currentStage.revive(); // Stages are killed and props destroyed when the PlayState is destroyed to save memory.
|
||||
|
||||
if (currentStage != null)
|
||||
{
|
||||
|
|
|
@ -9,6 +9,7 @@ import flixel.math.FlxMath;
|
|||
import flixel.math.FlxPoint.FlxCallbackPoint;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.math.FlxRect;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import flixel.system.FlxAssets.FlxGraphicAsset;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxDestroyUtil;
|
||||
|
@ -621,7 +622,7 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
* This functionality isn't supported in SpriteGroup
|
||||
* @return this sprite group
|
||||
*/
|
||||
public override function loadGraphicFromSprite(Sprite:FlxSprite):FlxSprite
|
||||
public override function loadGraphicFromSprite(Sprite:FlxSprite):FunkinSprite
|
||||
{
|
||||
#if FLX_DEBUG
|
||||
throw "This function is not supported in FlxSpriteGroup";
|
||||
|
|
|
@ -5,13 +5,16 @@ import flixel.group.FlxSpriteGroup;
|
|||
import flixel.math.FlxPoint;
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import flixel.util.FlxSort;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.modding.IScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventType;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.stage.StageData.StageDataCharacter;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.data.IRegistryEntry;
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.data.stage.StageData.StageDataCharacter;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.play.stage.StageProp;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.assets.FlxAnimationUtil;
|
||||
|
@ -23,14 +26,25 @@ typedef StagePropGroup = FlxTypedSpriteGroup<StageProp>;
|
|||
*
|
||||
* A Stage is comprised of one or more props, each of which is a FlxSprite.
|
||||
*/
|
||||
class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
||||
class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements IRegistryEntry<StageData>
|
||||
{
|
||||
public final stageId:String;
|
||||
public final stageName:String;
|
||||
public final id:String;
|
||||
|
||||
final _data:StageData;
|
||||
public final _data:StageData;
|
||||
|
||||
public var camZoom:Float = 1.0;
|
||||
public var stageName(get, never):String;
|
||||
|
||||
function get_stageName():String
|
||||
{
|
||||
return _data?.name ?? 'Unknown';
|
||||
}
|
||||
|
||||
public var camZoom(get, never):Float;
|
||||
|
||||
function get_camZoom():Float
|
||||
{
|
||||
return _data?.cameraZoom ?? 1.0;
|
||||
}
|
||||
|
||||
var namedProps:Map<String, StageProp> = new Map<String, StageProp>();
|
||||
var characters:Map<String, BaseCharacter> = new Map<String, BaseCharacter>();
|
||||
|
@ -41,21 +55,18 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
* They're used to cache the data needed to build the stage,
|
||||
* then accessed and fleshed out when the stage needs to be built.
|
||||
*
|
||||
* @param stageId
|
||||
* @param id
|
||||
*/
|
||||
public function new(stageId:String)
|
||||
public function new(id:String)
|
||||
{
|
||||
super();
|
||||
|
||||
this.stageId = stageId;
|
||||
_data = StageDataParser.parseStageData(this.stageId);
|
||||
this.id = id;
|
||||
_data = _fetchData(id);
|
||||
|
||||
if (_data == null)
|
||||
{
|
||||
throw 'Could not find stage data for stageId: $stageId';
|
||||
}
|
||||
else
|
||||
{
|
||||
this.stageName = _data.name;
|
||||
throw 'Could not find stage data for stage id: $id';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,9 +140,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
*/
|
||||
function buildStage():Void
|
||||
{
|
||||
trace('Building stage for display: ${this.stageId}');
|
||||
|
||||
this.camZoom = _data.cameraZoom;
|
||||
trace('Building stage for display: ${this.id}');
|
||||
|
||||
this.debugIconGroup = new FlxSpriteGroup();
|
||||
|
||||
|
@ -139,6 +148,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
{
|
||||
trace(' Placing prop: ${dataProp.name} (${dataProp.assetPath})');
|
||||
|
||||
var isSolidColor = dataProp.assetPath.startsWith('#');
|
||||
var isAnimated = dataProp.animations.length > 0;
|
||||
|
||||
var propSprite:StageProp;
|
||||
|
@ -162,6 +172,22 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
propSprite.frames = Paths.getSparrowAtlas(dataProp.assetPath);
|
||||
}
|
||||
}
|
||||
else if (isSolidColor)
|
||||
{
|
||||
var width:Int = 1;
|
||||
var height:Int = 1;
|
||||
switch (dataProp.scale)
|
||||
{
|
||||
case Left(value):
|
||||
width = Std.int(value);
|
||||
height = Std.int(value);
|
||||
|
||||
case Right(values):
|
||||
width = Std.int(values[0]);
|
||||
height = Std.int(values[1]);
|
||||
}
|
||||
propSprite.makeSolidColor(width, height, FlxColor.fromString(dataProp.assetPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Initalize static sprite.
|
||||
|
@ -177,6 +203,8 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!isSolidColor)
|
||||
{
|
||||
switch (dataProp.scale)
|
||||
{
|
||||
case Left(value):
|
||||
|
@ -185,6 +213,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
case Right(values):
|
||||
propSprite.scale.set(values[0], values[1]);
|
||||
}
|
||||
}
|
||||
propSprite.updateHitbox();
|
||||
|
||||
propSprite.x = dataProp.position[0];
|
||||
|
@ -195,15 +224,8 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
// If pixel, disable antialiasing.
|
||||
propSprite.antialiasing = !dataProp.isPixel;
|
||||
|
||||
switch (dataProp.scroll)
|
||||
{
|
||||
case Left(value):
|
||||
propSprite.scrollFactor.x = value;
|
||||
propSprite.scrollFactor.y = value;
|
||||
case Right(values):
|
||||
propSprite.scrollFactor.x = values[0];
|
||||
propSprite.scrollFactor.y = values[1];
|
||||
}
|
||||
propSprite.scrollFactor.x = dataProp.scroll[0];
|
||||
propSprite.scrollFactor.y = dataProp.scroll[1];
|
||||
|
||||
propSprite.zIndex = dataProp.zIndex;
|
||||
|
||||
|
@ -731,6 +753,11 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
|
|||
return Sprite;
|
||||
}
|
||||
|
||||
static function _fetchData(id:String):Null<StageData>
|
||||
{
|
||||
return StageRegistry.instance.parseEntryDataWithMigration(id, StageRegistry.instance.fetchEntryVersion(id));
|
||||
}
|
||||
|
||||
public function onScriptEvent(event:ScriptEvent) {}
|
||||
|
||||
public function onPause(event:PauseScriptEvent) {}
|
||||
|
|
|
@ -1,548 +0,0 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
import funkin.play.stage.ScriptedStage;
|
||||
import funkin.play.stage.Stage;
|
||||
import funkin.util.VersionUtil;
|
||||
import funkin.util.assets.DataAssets;
|
||||
import haxe.Json;
|
||||
import openfl.Assets;
|
||||
|
||||
/**
|
||||
* Contains utilities for loading and parsing stage data.
|
||||
*/
|
||||
class StageDataParser
|
||||
{
|
||||
/**
|
||||
* The current version string for the stage data format.
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStageData()` function.
|
||||
*/
|
||||
public static final STAGE_DATA_VERSION:String = "1.0.0";
|
||||
|
||||
/**
|
||||
* The current version rule check for the stage data format.
|
||||
*/
|
||||
public static final STAGE_DATA_VERSION_RULE:String = "1.0.x";
|
||||
|
||||
static final stageCache:Map<String, Stage> = new Map<String, Stage>();
|
||||
|
||||
static final DEFAULT_STAGE_ID = 'UNKNOWN';
|
||||
|
||||
/**
|
||||
* Parses and preloads the game's stage data and scripts when the game starts.
|
||||
*
|
||||
* If you want to force stages to be reloaded, you can just call this function again.
|
||||
*/
|
||||
public static function loadStageCache():Void
|
||||
{
|
||||
// Clear any stages that are cached if there were any.
|
||||
clearStageCache();
|
||||
trace("Loading stage cache...");
|
||||
|
||||
//
|
||||
// SCRIPTED STAGES
|
||||
//
|
||||
var scriptedStageClassNames:Array<String> = ScriptedStage.listScriptClasses();
|
||||
trace(' Instantiating ${scriptedStageClassNames.length} scripted stages...');
|
||||
for (stageCls in scriptedStageClassNames)
|
||||
{
|
||||
var stage:Stage = ScriptedStage.init(stageCls, DEFAULT_STAGE_ID);
|
||||
if (stage != null)
|
||||
{
|
||||
trace(' Loaded scripted stage: ${stage.stageName}');
|
||||
// Disable the rendering logic for stage until it's loaded.
|
||||
// Note that kill() =/= destroy()
|
||||
stage.kill();
|
||||
|
||||
// Then store it.
|
||||
stageCache.set(stage.stageId, stage);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace(' Failed to instantiate scripted stage class: ${stageCls}');
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// UNSCRIPTED STAGES
|
||||
//
|
||||
var stageIdList:Array<String> = DataAssets.listDataFilesInPath('stages/');
|
||||
var unscriptedStageIds:Array<String> = stageIdList.filter(function(stageId:String):Bool {
|
||||
return !stageCache.exists(stageId);
|
||||
});
|
||||
trace(' Instantiating ${unscriptedStageIds.length} non-scripted stages...');
|
||||
for (stageId in unscriptedStageIds)
|
||||
{
|
||||
var stage:Stage;
|
||||
try
|
||||
{
|
||||
stage = new Stage(stageId);
|
||||
if (stage != null)
|
||||
{
|
||||
trace(' Loaded stage data: ${stage.stageName}');
|
||||
stageCache.set(stageId, stage);
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace(' An error occurred while loading stage data: ${stageId}');
|
||||
// Assume error was already logged.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
trace(' Successfully loaded ${Lambda.count(stageCache)} stages.');
|
||||
}
|
||||
|
||||
public static function fetchStage(stageId:String):Null<Stage>
|
||||
{
|
||||
if (stageCache.exists(stageId))
|
||||
{
|
||||
trace('Successfully fetch stage: ${stageId}');
|
||||
var stage:Stage = stageCache.get(stageId);
|
||||
stage.revive();
|
||||
return stage;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Failed to fetch stage, not found in cache: ${stageId}');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static function clearStageCache():Void
|
||||
{
|
||||
if (stageCache != null)
|
||||
{
|
||||
for (stage in stageCache)
|
||||
{
|
||||
stage.destroy();
|
||||
}
|
||||
stageCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a stage's JSON file, parse its data, and return it.
|
||||
*
|
||||
* @param stageId The stage to load.
|
||||
* @return The stage data, or null if validation failed.
|
||||
*/
|
||||
public static function parseStageData(stageId:String):Null<StageData>
|
||||
{
|
||||
var rawJson:String = loadStageFile(stageId);
|
||||
|
||||
var stageData:StageData = migrateStageData(rawJson, stageId);
|
||||
|
||||
return validateStageData(stageId, stageData);
|
||||
}
|
||||
|
||||
public static function listStageIds():Array<String>
|
||||
{
|
||||
return stageCache.keys().array();
|
||||
}
|
||||
|
||||
static function loadStageFile(stagePath:String):String
|
||||
{
|
||||
var stageFilePath:String = Paths.json('stages/${stagePath}');
|
||||
var rawJson = Assets.getText(stageFilePath).trim();
|
||||
|
||||
while (!rawJson.endsWith("}"))
|
||||
{
|
||||
rawJson = rawJson.substr(0, rawJson.length - 1);
|
||||
}
|
||||
|
||||
return rawJson;
|
||||
}
|
||||
|
||||
static function migrateStageData(rawJson:String, stageId:String):Null<StageData>
|
||||
{
|
||||
// If you update the stage data format in a breaking way,
|
||||
// handle migration here by checking the `version` value.
|
||||
|
||||
try
|
||||
{
|
||||
var parser = new json2object.JsonParser<StageData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
parser.fromJson(rawJson, '$stageId.json');
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
trace('[STAGE] Failed to parse stage data');
|
||||
|
||||
for (error in parser.errors)
|
||||
funkin.data.DataError.printError(error);
|
||||
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace(' Error parsing data for stage: ${stageId}');
|
||||
trace(' ${e}');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static final DEFAULT_ANIMTYPE:String = "sparrow";
|
||||
static final DEFAULT_CAMERAZOOM:Float = 1.0;
|
||||
static final DEFAULT_DANCEEVERY:Int = 0;
|
||||
static final DEFAULT_ISPIXEL:Bool = false;
|
||||
static final DEFAULT_NAME:String = "Untitled Stage";
|
||||
static final DEFAULT_OFFSETS:Array<Float> = [0, 0];
|
||||
static final DEFAULT_CAMERA_OFFSETS_BF:Array<Float> = [-100, -100];
|
||||
static final DEFAULT_CAMERA_OFFSETS_DAD:Array<Float> = [150, -100];
|
||||
static final DEFAULT_POSITION:Array<Float> = [0, 0];
|
||||
static final DEFAULT_SCALE:Float = 1.0;
|
||||
static final DEFAULT_ALPHA:Float = 1.0;
|
||||
static final DEFAULT_SCROLL:Array<Float> = [0, 0];
|
||||
static final DEFAULT_ZINDEX:Int = 0;
|
||||
|
||||
static final DEFAULT_CHARACTER_DATA:StageDataCharacter =
|
||||
{
|
||||
zIndex: DEFAULT_ZINDEX,
|
||||
position: DEFAULT_POSITION,
|
||||
cameraOffsets: DEFAULT_OFFSETS,
|
||||
}
|
||||
|
||||
/**
|
||||
* Set unspecified parameters to their defaults.
|
||||
* If the parameter is mandatory, print an error message.
|
||||
* @param id
|
||||
* @param input
|
||||
* @return The validated stage data
|
||||
*/
|
||||
static function validateStageData(id:String, input:StageData):Null<StageData>
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
trace('ERROR: Could not parse stage data for "${id}".');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (input.version == null)
|
||||
{
|
||||
trace('ERROR: Could not load stage data for "$id": missing version');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!VersionUtil.validateVersionStr(input.version, STAGE_DATA_VERSION_RULE))
|
||||
{
|
||||
trace('ERROR: Could not load stage data for "$id": bad version (got ${input.version}, expected ${STAGE_DATA_VERSION_RULE})');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (input.name == null)
|
||||
{
|
||||
trace('WARN: Stage data for "$id" missing name');
|
||||
input.name = DEFAULT_NAME;
|
||||
}
|
||||
|
||||
if (input.cameraZoom == null)
|
||||
{
|
||||
input.cameraZoom = DEFAULT_CAMERAZOOM;
|
||||
}
|
||||
|
||||
if (input.props == null)
|
||||
{
|
||||
input.props = [];
|
||||
}
|
||||
|
||||
for (inputProp in input.props)
|
||||
{
|
||||
// It's fine for inputProp.name to be null
|
||||
|
||||
if (inputProp.assetPath == null)
|
||||
{
|
||||
trace('ERROR: Could not load stage data for "$id": missing assetPath for prop "${inputProp.name}"');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (inputProp.position == null)
|
||||
{
|
||||
inputProp.position = DEFAULT_POSITION;
|
||||
}
|
||||
|
||||
if (inputProp.zIndex == null)
|
||||
{
|
||||
inputProp.zIndex = DEFAULT_ZINDEX;
|
||||
}
|
||||
|
||||
if (inputProp.isPixel == null)
|
||||
{
|
||||
inputProp.isPixel = DEFAULT_ISPIXEL;
|
||||
}
|
||||
|
||||
if (inputProp.danceEvery == null)
|
||||
{
|
||||
inputProp.danceEvery = DEFAULT_DANCEEVERY;
|
||||
}
|
||||
|
||||
if (inputProp.animType == null)
|
||||
{
|
||||
inputProp.animType = DEFAULT_ANIMTYPE;
|
||||
}
|
||||
|
||||
switch (inputProp.scale)
|
||||
{
|
||||
case null:
|
||||
inputProp.scale = Right([DEFAULT_SCALE, DEFAULT_SCALE]);
|
||||
case Left(value):
|
||||
inputProp.scale = Right([value, value]);
|
||||
case Right(_):
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
switch (inputProp.scroll)
|
||||
{
|
||||
case null:
|
||||
inputProp.scroll = Right(DEFAULT_SCROLL);
|
||||
case Left(value):
|
||||
inputProp.scroll = Right([value, value]);
|
||||
case Right(_):
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
if (inputProp.alpha == null)
|
||||
{
|
||||
inputProp.alpha = DEFAULT_ALPHA;
|
||||
}
|
||||
|
||||
if (inputProp.animations == null)
|
||||
{
|
||||
inputProp.animations = [];
|
||||
}
|
||||
|
||||
if (inputProp.animations.length == 0 && inputProp.startingAnimation != null)
|
||||
{
|
||||
trace('ERROR: Could not load stage data for "$id": missing animations for prop "${inputProp.name}"');
|
||||
return null;
|
||||
}
|
||||
|
||||
for (inputAnimation in inputProp.animations)
|
||||
{
|
||||
if (inputAnimation.name == null)
|
||||
{
|
||||
trace('ERROR: Could not load stage data for "$id": missing animation name for prop "${inputProp.name}"');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (inputAnimation.frameRate == null)
|
||||
{
|
||||
inputAnimation.frameRate = 24;
|
||||
}
|
||||
|
||||
if (inputAnimation.offsets == null)
|
||||
{
|
||||
inputAnimation.offsets = DEFAULT_OFFSETS;
|
||||
}
|
||||
|
||||
if (inputAnimation.looped == null)
|
||||
{
|
||||
inputAnimation.looped = true;
|
||||
}
|
||||
|
||||
if (inputAnimation.flipX == null)
|
||||
{
|
||||
inputAnimation.flipX = false;
|
||||
}
|
||||
|
||||
if (inputAnimation.flipY == null)
|
||||
{
|
||||
inputAnimation.flipY = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (input.characters == null)
|
||||
{
|
||||
trace('ERROR: Could not load stage data for "$id": missing characters');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (input.characters.bf == null)
|
||||
{
|
||||
input.characters.bf = DEFAULT_CHARACTER_DATA;
|
||||
}
|
||||
if (input.characters.dad == null)
|
||||
{
|
||||
input.characters.dad = DEFAULT_CHARACTER_DATA;
|
||||
}
|
||||
if (input.characters.gf == null)
|
||||
{
|
||||
input.characters.gf = DEFAULT_CHARACTER_DATA;
|
||||
}
|
||||
|
||||
for (inputCharacter in [input.characters.bf, input.characters.dad, input.characters.gf])
|
||||
{
|
||||
if (inputCharacter.position == null || inputCharacter.position.length != 2)
|
||||
{
|
||||
inputCharacter.position = [0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
// All good!
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
class StageData
|
||||
{
|
||||
/**
|
||||
* The sematic version number of the stage data JSON format.
|
||||
* Supports fancy comparisons like NPM does it's neat.
|
||||
*/
|
||||
public var version:String;
|
||||
|
||||
public var name:String;
|
||||
public var cameraZoom:Null<Float>;
|
||||
public var props:Array<StageDataProp>;
|
||||
public var characters:StageDataCharacters;
|
||||
|
||||
public function new()
|
||||
{
|
||||
this.version = StageDataParser.STAGE_DATA_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this StageData into a JSON string.
|
||||
*/
|
||||
public function serialize(pretty:Bool = true):String
|
||||
{
|
||||
var writer = new json2object.JsonWriter<StageData>();
|
||||
return writer.write(this, pretty ? ' ' : null);
|
||||
}
|
||||
}
|
||||
|
||||
typedef StageDataCharacters =
|
||||
{
|
||||
var bf:StageDataCharacter;
|
||||
var dad:StageDataCharacter;
|
||||
var gf:StageDataCharacter;
|
||||
};
|
||||
|
||||
typedef StageDataProp =
|
||||
{
|
||||
/**
|
||||
* The name of the prop for later lookup by scripts.
|
||||
* Optional; if unspecified, the prop can't be referenced by scripts.
|
||||
*/
|
||||
@:optional
|
||||
var name:String;
|
||||
|
||||
/**
|
||||
* The asset used to display the prop.
|
||||
*/
|
||||
var assetPath:String;
|
||||
|
||||
/**
|
||||
* The position of the prop as an [x, y] array of two floats.
|
||||
*/
|
||||
var position:Array<Float>;
|
||||
|
||||
/**
|
||||
* A number determining the stack order of the prop, relative to other props and the characters in the stage.
|
||||
* Props with lower numbers render below those with higher numbers.
|
||||
* This is just like CSS, it isn't hard.
|
||||
* @default 0
|
||||
*/
|
||||
@:optional
|
||||
@:default(0)
|
||||
var zIndex:Int;
|
||||
|
||||
/**
|
||||
* If set to true, anti-aliasing will be forcibly disabled on the sprite.
|
||||
* This prevents blurry images on pixel-art levels.
|
||||
* @default false
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
var isPixel:Bool;
|
||||
|
||||
/**
|
||||
* Either the scale of the prop as a float, or the [w, h] scale as an array of two floats.
|
||||
* Pro tip: On pixel-art levels, save the sprite small and set this value to 6 or so to save memory.
|
||||
*/
|
||||
@:jcustomparse(funkin.data.DataParse.eitherFloatOrFloats)
|
||||
@:jcustomwrite(funkin.data.DataWrite.eitherFloatOrFloats)
|
||||
@:optional
|
||||
var scale:haxe.ds.Either<Float, Array<Float>>;
|
||||
|
||||
/**
|
||||
* The alpha of the prop, as a float.
|
||||
* @default 1.0
|
||||
*/
|
||||
@:optional
|
||||
@:default(1.0)
|
||||
var alpha:Float;
|
||||
|
||||
/**
|
||||
* If not zero, this prop will play an animation every X beats of the song.
|
||||
* This requires animations to be defined. If `danceLeft` and `danceRight` are defined,
|
||||
* they will alternated between, otherwise the `idle` animation will be used.
|
||||
*
|
||||
* @default 0
|
||||
*/
|
||||
@:default(0)
|
||||
@:optional
|
||||
var danceEvery:Int;
|
||||
|
||||
/**
|
||||
* How much the prop scrolls relative to the camera. Used to create a parallax effect.
|
||||
* Represented as a float or as an [x, y] array of two floats.
|
||||
* [1, 1] means the prop moves 1:1 with the camera.
|
||||
* [0.5, 0.5] means the prop half as much as the camera.
|
||||
* [0, 0] means the prop is not moved.
|
||||
* @default [0, 0]
|
||||
*/
|
||||
@:jcustomparse(funkin.data.DataParse.eitherFloatOrFloats)
|
||||
@:jcustomwrite(funkin.data.DataWrite.eitherFloatOrFloats)
|
||||
@:optional
|
||||
var scroll:haxe.ds.Either<Float, Array<Float>>;
|
||||
|
||||
/**
|
||||
* An optional array of animations which the prop can play.
|
||||
* @default Prop has no animations.
|
||||
*/
|
||||
@:optional
|
||||
var animations:Array<AnimationData>;
|
||||
|
||||
/**
|
||||
* If animations are used, this is the name of the animation to play first.
|
||||
* @default Don't play an animation.
|
||||
*/
|
||||
@:optional
|
||||
var startingAnimation:Null<String>;
|
||||
|
||||
/**
|
||||
* The animation type to use.
|
||||
* Options: "sparrow", "packer"
|
||||
* @default "sparrow"
|
||||
*/
|
||||
@:default("sparrow")
|
||||
@:optional
|
||||
var animType:String;
|
||||
};
|
||||
|
||||
typedef StageDataCharacter =
|
||||
{
|
||||
/**
|
||||
* A number determining the stack order of the character, relative to props and other characters in the stage.
|
||||
* Again, just like CSS.
|
||||
* @default 0
|
||||
*/
|
||||
var zIndex:Int;
|
||||
|
||||
/**
|
||||
* The position to render the character at.
|
||||
*/
|
||||
var position:Array<Float>;
|
||||
|
||||
/**
|
||||
* The camera offsets to apply when focusing on the character on this stage.
|
||||
* @default [-100, -100] for BF, [100, -100] for DAD/OPPONENT, [0, 0] for GF
|
||||
*/
|
||||
var cameraOffsets:Array<Float>;
|
||||
};
|
|
@ -1,10 +1,10 @@
|
|||
package funkin.play.stage;
|
||||
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.modding.IScriptedClass.IStateStageProp;
|
||||
|
||||
class StageProp extends FlxSprite implements IStateStageProp
|
||||
class StageProp extends FunkinSprite implements IStateStageProp
|
||||
{
|
||||
/**
|
||||
* An internal name for this prop.
|
||||
|
|
|
@ -12,6 +12,7 @@ import flixel.FlxCamera;
|
|||
import flixel.FlxSprite;
|
||||
import flixel.FlxSubState;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.math.FlxPoint;
|
||||
|
@ -34,6 +35,7 @@ import funkin.data.song.SongData.SongCharacterData;
|
|||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorDifficultyToolbox;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongOffsets;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
@ -56,7 +58,7 @@ import funkin.data.song.SongData.SongNoteData;
|
|||
import funkin.data.song.SongData.SongCharacterData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.save.Save;
|
||||
import funkin.ui.debug.charting.commands.AddEventsCommand;
|
||||
import funkin.ui.debug.charting.commands.AddNotesCommand;
|
||||
|
@ -104,6 +106,7 @@ import haxe.ui.components.Label;
|
|||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.NumberStepper;
|
||||
import haxe.ui.components.Slider;
|
||||
import haxe.ui.components.VerticalSlider;
|
||||
import haxe.ui.components.TextField;
|
||||
import haxe.ui.containers.dialogs.CollapsibleDialog;
|
||||
import haxe.ui.containers.Frame;
|
||||
|
@ -720,6 +723,34 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
return hitsoundsEnabledPlayer || hitsoundsEnabledOpponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sound multiplier for vocals and hitsounds on the player's side.
|
||||
*/
|
||||
var soundMultiplierPlayer(default, set):Float = 1.0;
|
||||
|
||||
function set_soundMultiplierPlayer(value:Float):Float
|
||||
{
|
||||
soundMultiplierPlayer = value;
|
||||
var vocalTargetVolume:Float = (menubarItemVolumeVocals.value ?? 100.0) / 100.0;
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.playerVolume = vocalTargetVolume * soundMultiplierPlayer;
|
||||
|
||||
return soundMultiplierPlayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sound multiplier for vocals and hitsounds on the opponent's side.
|
||||
*/
|
||||
var soundMultiplierOpponent(default, set):Float = 1.0;
|
||||
|
||||
function set_soundMultiplierOpponent(value:Float):Float
|
||||
{
|
||||
soundMultiplierOpponent = value;
|
||||
var vocalTargetVolume:Float = (menubarItemVolumeVocals.value ?? 100.0) / 100.0;
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.opponentVolume = vocalTargetVolume * soundMultiplierOpponent;
|
||||
|
||||
return soundMultiplierOpponent;
|
||||
}
|
||||
|
||||
// Auto-save
|
||||
|
||||
/**
|
||||
|
@ -1749,6 +1780,18 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
*/
|
||||
var buttonSelectEvent:Button;
|
||||
|
||||
/**
|
||||
* The slider above the grid that sets the volume of the player's sounds.
|
||||
* Constructed manually and added to the layout so we can control its position.
|
||||
*/
|
||||
var sliderVolumePlayer:Slider;
|
||||
|
||||
/**
|
||||
* The slider above the grid that sets the volume of the opponent's sounds.
|
||||
* Constructed manually and added to the layout so we can control its position.
|
||||
*/
|
||||
var sliderVolumeOpponent:Slider;
|
||||
|
||||
/**
|
||||
* RENDER OBJECTS
|
||||
*/
|
||||
|
@ -1958,7 +2001,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
buildGrid();
|
||||
buildMeasureTicks();
|
||||
buildNotePreview();
|
||||
buildSelectionBox();
|
||||
|
||||
buildAdditionalUI();
|
||||
populateOpenRecentMenu();
|
||||
|
@ -2214,7 +2256,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
add(gridGhostHoldNote);
|
||||
gridGhostHoldNote.zIndex = 11;
|
||||
|
||||
gridGhostEvent = new ChartEditorEventSprite(this);
|
||||
gridGhostEvent = new ChartEditorEventSprite(this, true);
|
||||
gridGhostEvent.alpha = 0.6;
|
||||
gridGhostEvent.eventData = new SongEventData(-1, '', {});
|
||||
gridGhostEvent.visible = false;
|
||||
|
@ -2230,7 +2272,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
var playheadWidth:Int = GRID_SIZE * (STRUMLINE_SIZE * 2 + 1) + (PLAYHEAD_SCROLL_AREA_WIDTH * 2);
|
||||
var playheadBaseYPos:Float = GRID_INITIAL_Y_POS;
|
||||
gridPlayhead.setPosition(GRID_X_POS, playheadBaseYPos);
|
||||
var playheadSprite:FlxSprite = new FlxSprite().makeGraphic(playheadWidth, PLAYHEAD_HEIGHT, PLAYHEAD_COLOR);
|
||||
var playheadSprite:FunkinSprite = new FunkinSprite().makeSolidColor(playheadWidth, PLAYHEAD_HEIGHT, PLAYHEAD_COLOR);
|
||||
playheadSprite.x = -PLAYHEAD_SCROLL_AREA_WIDTH;
|
||||
playheadSprite.y = 0;
|
||||
gridPlayhead.add(playheadSprite);
|
||||
|
@ -2287,17 +2329,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
setNotePreviewViewportBounds(calculateNotePreviewViewportBounds());
|
||||
}
|
||||
|
||||
function buildSelectionBox():Void
|
||||
{
|
||||
if (selectionBoxSprite == null) throw 'ERROR: Tried to build selection box, but selectionBoxSprite is null! Check ChartEditorThemeHandler.updateTheme().';
|
||||
|
||||
selectionBoxSprite.scrollFactor.set(0, 0);
|
||||
add(selectionBoxSprite);
|
||||
selectionBoxSprite.zIndex = 30;
|
||||
|
||||
setSelectionBoxBounds();
|
||||
}
|
||||
|
||||
function setSelectionBoxBounds(bounds:FlxRect = null):Void
|
||||
{
|
||||
if (selectionBoxSprite == null)
|
||||
|
@ -2319,6 +2350,19 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically goes through and calls render on everything you added.
|
||||
*/
|
||||
override public function draw():Void
|
||||
{
|
||||
if (selectionBoxStartPos != null)
|
||||
{
|
||||
trace('selectionBoxSprite: ${selectionBoxSprite.visible} ${selectionBoxSprite.exists} ${this.members.contains(selectionBoxSprite)}');
|
||||
}
|
||||
|
||||
super.draw();
|
||||
}
|
||||
|
||||
function calculateNotePreviewViewportBounds():FlxRect
|
||||
{
|
||||
var bounds:FlxRect = new FlxRect();
|
||||
|
@ -2557,6 +2601,37 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
performCommand(new SetItemSelectionCommand([], currentSongChartEventData));
|
||||
}
|
||||
}
|
||||
|
||||
function setupSideSlider(x, y):VerticalSlider
|
||||
{
|
||||
var slider = new VerticalSlider();
|
||||
slider.allowFocus = false;
|
||||
slider.x = x;
|
||||
slider.y = y;
|
||||
slider.width = NOTE_SELECT_BUTTON_HEIGHT;
|
||||
slider.height = GRID_SIZE * 4;
|
||||
slider.pos = slider.max;
|
||||
slider.tooltip = "Slide to set the volume of sounds on this side.";
|
||||
slider.zIndex = 110;
|
||||
slider.styleNames = "sideSlider";
|
||||
add(slider);
|
||||
|
||||
return slider;
|
||||
}
|
||||
|
||||
var sliderY = GRID_INITIAL_Y_POS + 34;
|
||||
sliderVolumeOpponent = setupSideSlider(GRID_X_POS - 64, sliderY);
|
||||
sliderVolumePlayer = setupSideSlider(buttonSelectEvent.x + buttonSelectEvent.width, sliderY);
|
||||
|
||||
sliderVolumePlayer.onChange = event -> {
|
||||
var volume:Float = event.value.toFloat() / 100.0;
|
||||
soundMultiplierPlayer = volume;
|
||||
}
|
||||
|
||||
sliderVolumeOpponent.onChange = event -> {
|
||||
var volume:Float = event.value.toFloat() / 100.0;
|
||||
soundMultiplierOpponent = volume;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2797,7 +2872,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
menubarItemVolumeVocals.onChange = event -> {
|
||||
var volume:Float = event.value.toFloat() / 100.0;
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = volume;
|
||||
if (audioVocalTrackGroup != null)
|
||||
{
|
||||
audioVocalTrackGroup.playerVolume = volume * soundMultiplierPlayer;
|
||||
audioVocalTrackGroup.opponentVolume = volume * soundMultiplierOpponent;
|
||||
}
|
||||
menubarLabelVolumeVocals.text = 'Voices - ${Std.int(event.value)}%';
|
||||
}
|
||||
|
||||
|
@ -3366,6 +3445,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
// Setting event data resets position relative to the grid so we fix that.
|
||||
eventSprite.x += renderedEvents.x;
|
||||
eventSprite.y += renderedEvents.y;
|
||||
eventSprite.updateTooltipPosition();
|
||||
}
|
||||
|
||||
// Add hold notes that have been made visible (but not their parents)
|
||||
|
@ -4653,48 +4733,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
{
|
||||
difficultySelectDirty = false;
|
||||
|
||||
// Manage the Select Difficulty tree view.
|
||||
var difficultyToolbox:Null<CollapsibleDialog> = this.getToolbox_OLD(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
||||
var difficultyToolbox:ChartEditorDifficultyToolbox = cast this.getToolbox(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
||||
if (difficultyToolbox == null) return;
|
||||
|
||||
var treeView:Null<TreeView> = difficultyToolbox.findComponent('difficultyToolboxTree');
|
||||
if (treeView == null) return;
|
||||
|
||||
// Clear the tree view so we can rebuild it.
|
||||
treeView.clearNodes();
|
||||
|
||||
// , icon: 'haxeui-core/styles/default/haxeui_tiny.png'
|
||||
var treeSong:TreeViewNode = treeView.addNode({id: 'stv_song', text: 'S: $currentSongName'});
|
||||
treeSong.expanded = true;
|
||||
|
||||
for (curVariation in availableVariations)
|
||||
{
|
||||
trace('DIFFICULTY TOOLBOX: Variation ${curVariation}');
|
||||
var variationMetadata:Null<SongMetadata> = songMetadata.get(curVariation);
|
||||
if (variationMetadata == null) continue;
|
||||
|
||||
var treeVariation:TreeViewNode = treeSong.addNode(
|
||||
{
|
||||
id: 'stv_variation_$curVariation',
|
||||
text: 'V: ${curVariation.toTitleCase()}'
|
||||
});
|
||||
treeVariation.expanded = true;
|
||||
|
||||
var difficultyList:Array<String> = variationMetadata.playData.difficulties;
|
||||
|
||||
for (difficulty in difficultyList)
|
||||
{
|
||||
trace('DIFFICULTY TOOLBOX: Difficulty ${curVariation}_$difficulty');
|
||||
var _treeDifficulty:TreeViewNode = treeVariation.addNode(
|
||||
{
|
||||
id: 'stv_difficulty_${curVariation}_$difficulty',
|
||||
text: 'D: ${difficulty.toTitleCase()}'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
treeView.onChange = onChangeTreeDifficulty;
|
||||
refreshDifficultyTreeSelection(treeView);
|
||||
difficultyToolbox.updateTree();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5196,6 +5238,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
autoSave(true);
|
||||
|
||||
stopWelcomeMusic();
|
||||
stopAudioPlayback();
|
||||
|
||||
var startTimestamp:Float = 0;
|
||||
if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs;
|
||||
|
@ -5439,7 +5482,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
return event != null && currentEventSelection.indexOf(event) != -1;
|
||||
}
|
||||
|
||||
function createDifficulty(variation:String, difficulty:String, scrollSpeed:Float = 1.0)
|
||||
function createDifficulty(variation:String, difficulty:String, scrollSpeed:Float = 1.0):Void
|
||||
{
|
||||
var variationMetadata:Null<SongMetadata> = songMetadata.get(variation);
|
||||
if (variationMetadata == null) return;
|
||||
|
@ -5461,6 +5504,42 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
difficultySelectDirty = true; // Force the Difficulty toolbox to update.
|
||||
}
|
||||
|
||||
function removeDifficulty(variation:String, difficulty:String):Void
|
||||
{
|
||||
var variationMetadata:Null<SongMetadata> = songMetadata.get(variation);
|
||||
if (variationMetadata == null) return;
|
||||
|
||||
variationMetadata.playData.difficulties.remove(difficulty);
|
||||
|
||||
var resultChartData = songChartData.get(variation);
|
||||
if (resultChartData != null)
|
||||
{
|
||||
resultChartData.scrollSpeed.remove(difficulty);
|
||||
resultChartData.notes.remove(difficulty);
|
||||
}
|
||||
|
||||
if (songMetadata.size() > 1)
|
||||
{
|
||||
if (variationMetadata.playData.difficulties.length == 0)
|
||||
{
|
||||
songMetadata.remove(variation);
|
||||
songChartData.remove(variation);
|
||||
}
|
||||
|
||||
if (variation == selectedVariation)
|
||||
{
|
||||
var firstVariation = songMetadata.keyValues()[0];
|
||||
if (firstVariation != null) selectedVariation = firstVariation;
|
||||
variationMetadata = songMetadata.get(selectedVariation);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedDifficulty == difficulty
|
||||
|| !variationMetadata.playData.difficulties.contains(selectedDifficulty)) selectedDifficulty = variationMetadata.playData.difficulties[0];
|
||||
|
||||
difficultySelectDirty = true; // Force the Difficulty toolbox to update.
|
||||
}
|
||||
|
||||
function incrementDifficulty(change:Int):Void
|
||||
{
|
||||
var currentDifficultyIndex:Int = availableDifficulties.indexOf(selectedDifficulty);
|
||||
|
@ -5509,8 +5588,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
Conductor.instance.mapTimeChanges(this.currentSongMetadata.timeChanges);
|
||||
updateTimeSignature();
|
||||
|
||||
refreshDifficultyTreeSelection();
|
||||
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
||||
this.refreshToolbox(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -5518,8 +5597,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
var prevDifficulty = availableDifficulties[currentDifficultyIndex - 1];
|
||||
selectedDifficulty = prevDifficulty;
|
||||
|
||||
refreshDifficultyTreeSelection();
|
||||
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
||||
this.refreshToolbox(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -5537,8 +5616,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
var nextDifficulty = availableDifficulties[0];
|
||||
selectedDifficulty = nextDifficulty;
|
||||
|
||||
refreshDifficultyTreeSelection();
|
||||
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
||||
this.refreshToolbox(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -5546,7 +5625,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
var nextDifficulty = availableDifficulties[currentDifficultyIndex + 1];
|
||||
selectedDifficulty = nextDifficulty;
|
||||
|
||||
refreshDifficultyTreeSelection();
|
||||
this.refreshToolbox(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
||||
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
||||
}
|
||||
}
|
||||
|
@ -5662,7 +5741,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
audioInstTrack.volume = instTargetVolume;
|
||||
audioInstTrack.onComplete = null;
|
||||
}
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = vocalTargetVolume;
|
||||
if (audioVocalTrackGroup != null)
|
||||
{
|
||||
audioVocalTrackGroup.playerVolume = vocalTargetVolume * soundMultiplierPlayer;
|
||||
audioVocalTrackGroup.opponentVolume = vocalTargetVolume * soundMultiplierOpponent;
|
||||
}
|
||||
}
|
||||
|
||||
function updateTimeSignature():Void
|
||||
|
@ -5678,92 +5761,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
*/
|
||||
// ==================
|
||||
|
||||
/**
|
||||
* Set the currently selected item in the Difficulty tree view to the node representing the current difficulty.
|
||||
* @param treeView The tree view to update. If `null`, the tree view will be found.
|
||||
*/
|
||||
function refreshDifficultyTreeSelection(?treeView:TreeView):Void
|
||||
{
|
||||
if (treeView == null)
|
||||
{
|
||||
// Manage the Select Difficulty tree view.
|
||||
var difficultyToolbox:Null<CollapsibleDialog> = this.getToolbox_OLD(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
||||
if (difficultyToolbox == null) return;
|
||||
|
||||
treeView = difficultyToolbox.findComponent('difficultyToolboxTree');
|
||||
if (treeView == null) return;
|
||||
}
|
||||
|
||||
var currentTreeDifficultyNode = getCurrentTreeDifficultyNode(treeView);
|
||||
if (currentTreeDifficultyNode != null) treeView.selectedNode = currentTreeDifficultyNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the node representing the current difficulty in the Difficulty tree view.
|
||||
* @param treeView The tree view to search. If `null`, the tree view will be found.
|
||||
* @return The node representing the current difficulty, or `null` if not found.
|
||||
*/
|
||||
function getCurrentTreeDifficultyNode(?treeView:TreeView = null):Null<TreeViewNode>
|
||||
{
|
||||
if (treeView == null)
|
||||
{
|
||||
var difficultyToolbox:Null<CollapsibleDialog> = this.getToolbox_OLD(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
||||
if (difficultyToolbox == null) return null;
|
||||
|
||||
treeView = difficultyToolbox.findComponent('difficultyToolboxTree');
|
||||
if (treeView == null) return null;
|
||||
}
|
||||
|
||||
var result:TreeViewNode = treeView.findNodeByPath('stv_song/stv_variation_$selectedVariation/stv_difficulty_${selectedVariation}_$selectedDifficulty',
|
||||
'id');
|
||||
if (result == null) return null;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when selecting a tree element in the Difficulty toolbox.
|
||||
* @param event The click event.
|
||||
*/
|
||||
function onChangeTreeDifficulty(event:UIEvent):Void
|
||||
{
|
||||
// Get the newly selected node.
|
||||
var treeView:TreeView = cast event.target;
|
||||
var targetNode:TreeViewNode = treeView.selectedNode;
|
||||
|
||||
if (targetNode == null)
|
||||
{
|
||||
trace('No target node!');
|
||||
// Reset the user's selection.
|
||||
var currentTreeDifficultyNode = getCurrentTreeDifficultyNode(treeView);
|
||||
if (currentTreeDifficultyNode != null) treeView.selectedNode = currentTreeDifficultyNode;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (targetNode.data.id.split('_')[1])
|
||||
{
|
||||
case 'difficulty':
|
||||
var variation:String = targetNode.data.id.split('_')[2];
|
||||
var difficulty:String = targetNode.data.id.split('_')[3];
|
||||
|
||||
if (variation != null && difficulty != null)
|
||||
{
|
||||
trace('Changing difficulty to "$variation:$difficulty"');
|
||||
selectedVariation = variation;
|
||||
selectedDifficulty = difficulty;
|
||||
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
||||
}
|
||||
// case 'song':
|
||||
// case 'variation':
|
||||
default:
|
||||
// Reset the user's selection.
|
||||
trace('Selected wrong node type, resetting selection.');
|
||||
var currentTreeDifficultyNode = getCurrentTreeDifficultyNode(treeView);
|
||||
if (currentTreeDifficultyNode != null) treeView.selectedNode = currentTreeDifficultyNode;
|
||||
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* STATIC FUNCTIONS
|
||||
*/
|
||||
|
@ -5864,9 +5861,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
switch (noteData.getStrumlineIndex())
|
||||
{
|
||||
case 0: // Player
|
||||
if (hitsoundsEnabledPlayer) this.playSound(Paths.sound('chartingSounds/hitNotePlayer'), hitsoundVolume);
|
||||
if (hitsoundsEnabledPlayer) this.playSound(Paths.sound('chartingSounds/hitNotePlayer'), hitsoundVolume * soundMultiplierPlayer);
|
||||
case 1: // Opponent
|
||||
if (hitsoundsEnabledOpponent) this.playSound(Paths.sound('chartingSounds/hitNoteOpponent'), hitsoundVolume);
|
||||
if (hitsoundsEnabledOpponent) this.playSound(Paths.sound('chartingSounds/hitNoteOpponent'), hitsoundVolume * soundMultiplierOpponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,9 @@ import flixel.graphics.frames.FlxFramesCollection;
|
|||
import flixel.graphics.frames.FlxTileFrames;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import haxe.ui.tooltips.ToolTipRegionOptions;
|
||||
import funkin.util.HaxeUIUtil;
|
||||
import haxe.ui.tooltips.ToolTipManager;
|
||||
|
||||
/**
|
||||
* A sprite that can be used to display a song event in a chart.
|
||||
|
@ -36,6 +39,13 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
|
||||
public var overrideStepTime(default, set):Null<Float> = null;
|
||||
|
||||
public var tooltip:ToolTipRegionOptions;
|
||||
|
||||
/**
|
||||
* Whether this sprite is a "ghost" sprite used when hovering to place a new event.
|
||||
*/
|
||||
public var isGhost:Bool = false;
|
||||
|
||||
function set_overrideStepTime(value:Null<Float>):Null<Float>
|
||||
{
|
||||
if (overrideStepTime == value) return overrideStepTime;
|
||||
|
@ -45,12 +55,14 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
return overrideStepTime;
|
||||
}
|
||||
|
||||
public function new(parent:ChartEditorState)
|
||||
public function new(parent:ChartEditorState, isGhost:Bool = false)
|
||||
{
|
||||
super();
|
||||
|
||||
this.parentState = parent;
|
||||
this.isGhost = isGhost;
|
||||
|
||||
this.tooltip = HaxeUIUtil.buildTooltip('N/A');
|
||||
this.frames = buildFrames();
|
||||
|
||||
buildAnimations();
|
||||
|
@ -142,6 +154,7 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
// Disown parent. MAKE SURE TO REVIVE BEFORE REUSING
|
||||
this.kill();
|
||||
this.visible = false;
|
||||
updateTooltipPosition();
|
||||
return null;
|
||||
}
|
||||
else
|
||||
|
@ -151,6 +164,8 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
this.eventData = value;
|
||||
// Update the position to match the note data.
|
||||
updateEventPosition();
|
||||
// Update the tooltip text.
|
||||
this.tooltip.tipData = {text: this.eventData.buildTooltip()};
|
||||
return this.eventData;
|
||||
}
|
||||
}
|
||||
|
@ -169,6 +184,31 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
this.x += origin.x;
|
||||
this.y += origin.y;
|
||||
}
|
||||
|
||||
this.updateTooltipPosition();
|
||||
}
|
||||
|
||||
public function updateTooltipPosition():Void
|
||||
{
|
||||
// No tooltip for ghost sprites.
|
||||
if (this.isGhost) return;
|
||||
|
||||
if (this.eventData == null)
|
||||
{
|
||||
// Disable the tooltip.
|
||||
ToolTipManager.instance.unregisterTooltipRegion(this.tooltip);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the position.
|
||||
this.tooltip.left = this.x;
|
||||
this.tooltip.top = this.y;
|
||||
this.tooltip.width = this.width;
|
||||
this.tooltip.height = this.height;
|
||||
|
||||
// Enable the tooltip.
|
||||
ToolTipManager.instance.registerTooltipRegion(this.tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,6 +13,7 @@ import haxe.ui.notifications.NotificationType;
|
|||
|
||||
// @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros.
|
||||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/dialogs/upload-chart.xml"))
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class ChartEditorUploadChartDialog extends ChartEditorBaseDialog
|
||||
{
|
||||
var dropHandlers:Array<DialogDropTarget> = [];
|
||||
|
|
|
@ -0,0 +1,311 @@
|
|||
package funkin.ui.debug.charting.dialogs;
|
||||
|
||||
import funkin.input.Cursor;
|
||||
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogDropTarget;
|
||||
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogParams;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.play.character.CharacterData;
|
||||
import haxe.io.Path;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.Label;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogButton;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogEvent;
|
||||
import haxe.ui.containers.Box;
|
||||
import haxe.ui.containers.dialogs.Dialogs;
|
||||
import haxe.ui.core.Component;
|
||||
import haxe.ui.notifications.NotificationManager;
|
||||
import haxe.ui.notifications.NotificationType;
|
||||
|
||||
// @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros.
|
||||
|
||||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/dialogs/upload-vocals.xml"))
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class ChartEditorUploadVocalsDialog extends ChartEditorBaseDialog
|
||||
{
|
||||
var dropHandlers:Array<DialogDropTarget> = [];
|
||||
|
||||
var vocalContainer:Component;
|
||||
var dialogCancel:Button;
|
||||
var dialogNoVocals:Button;
|
||||
var dialogContinue:Button;
|
||||
|
||||
var charIds:Array<String>;
|
||||
var instId:String;
|
||||
var hasClearedVocals:Bool = false;
|
||||
|
||||
public function new(state2:ChartEditorState, charIds:Array<String>, params2:DialogParams)
|
||||
{
|
||||
super(state2, params2);
|
||||
|
||||
this.charIds = charIds;
|
||||
this.instId = chartEditorState.currentInstrumentalId;
|
||||
|
||||
dialogCancel.onClick = function(_) {
|
||||
hideDialog(DialogButton.CANCEL);
|
||||
}
|
||||
|
||||
dialogNoVocals.onClick = function(_) {
|
||||
// Dismiss
|
||||
chartEditorState.wipeVocalData();
|
||||
hideDialog(DialogButton.APPLY);
|
||||
};
|
||||
|
||||
dialogContinue.onClick = function(_) {
|
||||
// Dismiss
|
||||
hideDialog(DialogButton.APPLY);
|
||||
};
|
||||
|
||||
buildDropHandlers();
|
||||
}
|
||||
|
||||
function buildDropHandlers():Void
|
||||
{
|
||||
for (charKey in charIds)
|
||||
{
|
||||
trace('Adding vocal upload for character ${charKey}');
|
||||
|
||||
var charMetadata:Null<CharacterData> = CharacterDataParser.fetchCharacterData(charKey);
|
||||
var charName:String = charMetadata?.name ?? charKey;
|
||||
|
||||
var vocalsEntry = new ChartEditorUploadVocalsEntry(charName);
|
||||
|
||||
var dropHandler:DialogDropTarget = {component: vocalsEntry, handler: null};
|
||||
|
||||
var onDropFile:String->Void = function(pathStr:String) {
|
||||
trace('Selected file: $pathStr');
|
||||
var path:Path = new Path(pathStr);
|
||||
|
||||
if (chartEditorState.loadVocalsFromPath(path, charKey, this.instId, !this.hasClearedVocals))
|
||||
{
|
||||
this.hasClearedVocals = true;
|
||||
// Tell the user the load was successful.
|
||||
chartEditorState.success('Loaded Vocals', 'Loaded vocals for $charName (${path.file}.${path.ext}), variation ${chartEditorState.selectedVariation}');
|
||||
#if FILE_DROP_SUPPORTED
|
||||
vocalsEntry.vocalsEntryLabel.text = 'Voices for $charName (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
||||
#else
|
||||
vocalsEntry.vocalsEntryLabel.text = 'Voices for $charName (click to browse)\n${path.file}.${path.ext}';
|
||||
#end
|
||||
|
||||
dialogNoVocals.hidden = true;
|
||||
chartEditorState.removeDropHandler(dropHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Failed to load vocal track (${path.file}.${path.ext})');
|
||||
|
||||
chartEditorState.error('Failed to Load Vocals',
|
||||
'Failed to load vocal track (${path.file}.${path.ext}) for variation (${chartEditorState.selectedVariation})');
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
vocalsEntry.vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||
#else
|
||||
vocalsEntry.vocalsEntryLabel.text = 'Click to browse for vocals for $charName.';
|
||||
#end
|
||||
}
|
||||
};
|
||||
|
||||
vocalsEntry.onClick = function(_event) {
|
||||
Dialogs.openBinaryFile('Open $charName Vocals', [
|
||||
{label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile) {
|
||||
if (selectedFile != null && selectedFile.bytes != null)
|
||||
{
|
||||
trace('Selected file: ' + selectedFile.name);
|
||||
|
||||
if (chartEditorState.loadVocalsFromBytes(selectedFile.bytes, charKey, this.instId, !this.hasClearedVocals))
|
||||
{
|
||||
hasClearedVocals = true;
|
||||
// Tell the user the load was successful.
|
||||
chartEditorState.success('Loaded Vocals',
|
||||
'Loaded vocals for $charName (${selectedFile.name}), variation ${chartEditorState.selectedVariation}');
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
vocalsEntry.vocalsEntryLabel.text = 'Voices for $charName (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
||||
#else
|
||||
vocalsEntry.vocalsEntryLabel.text = 'Voices for $charName (click to browse)\n${selectedFile.name}';
|
||||
#end
|
||||
|
||||
dialogNoVocals.hidden = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Failed to load vocal track (${selectedFile.fullPath})');
|
||||
|
||||
chartEditorState.error('Failed to Load Vocals',
|
||||
'Failed to load vocal track (${selectedFile.name}) for variation (${chartEditorState.selectedVariation})');
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
vocalsEntry.vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||
#else
|
||||
vocalsEntry.vocalsEntryLabel.text = 'Click to browse for vocals for $charName.';
|
||||
#end
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dropHandler.handler = onDropFile;
|
||||
|
||||
// onDropFile
|
||||
#if FILE_DROP_SUPPORTED
|
||||
dropHandlers.push(dropHandler);
|
||||
#end
|
||||
|
||||
vocalContainer.addComponent(vocalsEntry);
|
||||
}
|
||||
}
|
||||
|
||||
public static function build(state:ChartEditorState, charIds:Array<String>, ?closable:Bool, ?modal:Bool):ChartEditorUploadVocalsDialog
|
||||
{
|
||||
var dialog = new ChartEditorUploadVocalsDialog(state, charIds,
|
||||
{
|
||||
closable: closable ?? false,
|
||||
modal: modal ?? true
|
||||
});
|
||||
|
||||
for (dropTarget in dialog.dropHandlers)
|
||||
{
|
||||
state.addDropHandler(dropTarget);
|
||||
}
|
||||
|
||||
dialog.showDialog(modal ?? true);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public override function onClose(event:DialogEvent):Void
|
||||
{
|
||||
super.onClose(event);
|
||||
|
||||
if (event.button != DialogButton.APPLY && !this.closable)
|
||||
{
|
||||
// User cancelled the wizard! Back to the welcome dialog.
|
||||
chartEditorState.openWelcomeDialog(this.closable);
|
||||
}
|
||||
|
||||
for (dropTarget in dropHandlers)
|
||||
{
|
||||
chartEditorState.removeDropHandler(dropTarget);
|
||||
}
|
||||
}
|
||||
|
||||
public override function lock():Void
|
||||
{
|
||||
super.lock();
|
||||
this.dialogCancel.disabled = true;
|
||||
}
|
||||
|
||||
public override function unlock():Void
|
||||
{
|
||||
super.unlock();
|
||||
this.dialogCancel.disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when clicking the Upload Chart box.
|
||||
*/
|
||||
public function onClickChartBox():Void
|
||||
{
|
||||
if (this.locked) return;
|
||||
|
||||
this.lock();
|
||||
// TODO / BUG: File filtering not working on mac finder dialog, so we don't use it for now
|
||||
#if !mac
|
||||
FileUtil.browseForBinaryFile('Open Chart', [FileUtil.FILE_EXTENSION_INFO_FNFC], onSelectFile, onCancelBrowse);
|
||||
#else
|
||||
FileUtil.browseForBinaryFile('Open Chart', null, onSelectFile, onCancelBrowse);
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a file is selected by dropping a file onto the Upload Chart box.
|
||||
*/
|
||||
function onDropFileChartBox(pathStr:String):Void
|
||||
{
|
||||
var path:Path = new Path(pathStr);
|
||||
trace('Dropped file (${path})');
|
||||
|
||||
try
|
||||
{
|
||||
var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFCPath(chartEditorState, path.toString());
|
||||
if (result != null)
|
||||
{
|
||||
chartEditorState.success('Loaded Chart',
|
||||
result.length == 0 ? 'Loaded chart (${path.toString()})' : 'Loaded chart (${path.toString()})\n${result.join("\n")}');
|
||||
this.hideDialog(DialogButton.APPLY);
|
||||
}
|
||||
else
|
||||
{
|
||||
chartEditorState.failure('Failed to Load Chart', 'Failed to load chart (${path.toString()})');
|
||||
}
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
chartEditorState.failure('Failed to Load Chart', 'Failed to load chart (${path.toString()}): ${err}');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a file is selected by the dialog displayed when clicking the Upload Chart box.
|
||||
*/
|
||||
function onSelectFile(selectedFile:SelectedFileInfo):Void
|
||||
{
|
||||
this.unlock();
|
||||
|
||||
if (selectedFile != null && selectedFile.bytes != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFC(chartEditorState, selectedFile.bytes);
|
||||
if (result != null)
|
||||
{
|
||||
chartEditorState.success('Loaded Chart',
|
||||
result.length == 0 ? 'Loaded chart (${selectedFile.name})' : 'Loaded chart (${selectedFile.name})\n${result.join("\n")}');
|
||||
|
||||
if (selectedFile.fullPath != null) chartEditorState.currentWorkingFilePath = selectedFile.fullPath;
|
||||
this.hideDialog(DialogButton.APPLY);
|
||||
}
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
chartEditorState.failure('Failed to Load Chart', 'Failed to load chart (${selectedFile.name}): ${err}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onCancelBrowse():Void
|
||||
{
|
||||
this.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/dialogs/upload-vocals-entry.xml"))
|
||||
class ChartEditorUploadVocalsEntry extends Box
|
||||
{
|
||||
public var vocalsEntryLabel:Label;
|
||||
|
||||
var charName:String;
|
||||
|
||||
public function new(charName:String)
|
||||
{
|
||||
super();
|
||||
|
||||
this.charName = charName;
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||
#else
|
||||
vocalsEntryLabel.text = 'Click to browse for vocals for $charName.';
|
||||
#end
|
||||
|
||||
this.onMouseOver = function(_event) {
|
||||
// if (this.locked) return;
|
||||
this.swapClass('upload-bg', 'upload-bg-hover');
|
||||
Cursor.cursorMode = Pointer;
|
||||
}
|
||||
|
||||
this.onMouseOut = function(_event) {
|
||||
this.swapClass('upload-bg-hover', 'upload-bg');
|
||||
Cursor.cursorMode = Default;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,12 +13,13 @@ import funkin.play.character.BaseCharacter;
|
|||
import funkin.play.character.CharacterData;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.ui.debug.charting.dialogs.ChartEditorAboutDialog;
|
||||
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogDropTarget;
|
||||
import funkin.ui.debug.charting.dialogs.ChartEditorCharacterIconSelectorMenu;
|
||||
import funkin.ui.debug.charting.dialogs.ChartEditorUploadChartDialog;
|
||||
import funkin.ui.debug.charting.dialogs.ChartEditorWelcomeDialog;
|
||||
import funkin.ui.debug.charting.dialogs.ChartEditorUploadVocalsDialog;
|
||||
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
||||
import funkin.util.Constants;
|
||||
import funkin.util.DateUtil;
|
||||
|
@ -59,11 +60,8 @@ using Lambda;
|
|||
class ChartEditorDialogHandler
|
||||
{
|
||||
// Paths to HaxeUI layout files for each dialog.
|
||||
static final CHART_EDITOR_DIALOG_UPLOAD_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-chart');
|
||||
static final CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-inst');
|
||||
static final CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT:String = Paths.ui('chart-editor/dialogs/song-metadata');
|
||||
static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals');
|
||||
static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals-entry');
|
||||
static final CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart-parts');
|
||||
static final CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart-parts-entry');
|
||||
static final CHART_EDITOR_DIALOG_IMPORT_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/import-chart');
|
||||
|
@ -105,6 +103,56 @@ class ChartEditorDialogHandler
|
|||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog letting the user browse for a chart file to open.
|
||||
* @param state The current chart editor state.
|
||||
* @param closable Whether the dialog can be closed by the user.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
public static function openBrowseFNFC(state:ChartEditorState, closable:Bool):Null<Dialog>
|
||||
{
|
||||
var dialog = ChartEditorUploadChartDialog.build(state, closable);
|
||||
|
||||
dialog.zIndex = 1000;
|
||||
state.isHaxeUIDialogOpen = true;
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog where the user uploads vocals for the current song.
|
||||
* @param state The current chart editor state.
|
||||
* @param closable Whether the dialog can be closed by the user.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
public static function openUploadVocalsDialog(state:ChartEditorState, closable:Bool = true):Dialog
|
||||
{
|
||||
var charData:SongCharacterData = state.currentSongMetadata.playData.characters;
|
||||
|
||||
var hasClearedVocals:Bool = false;
|
||||
|
||||
var charIdsForVocals:Array<String> = [charData.player, charData.opponent];
|
||||
|
||||
var dialog = ChartEditorUploadVocalsDialog.build(state, charIdsForVocals, closable);
|
||||
|
||||
dialog.zIndex = 1000;
|
||||
state.isHaxeUIDialogOpen = true;
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens the dialog for selecting a character.
|
||||
*/
|
||||
public static function openCharacterDropdown(state:ChartEditorState, charType:CharacterType, lockPosition:Bool = false):Null<Menu>
|
||||
{
|
||||
var menu = ChartEditorCharacterIconSelectorMenu.build(state, charType, lockPosition);
|
||||
|
||||
menu.zIndex = 1000;
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog letting the user know a backup is available, and prompting them to load it.
|
||||
*/
|
||||
|
@ -186,22 +234,6 @@ class ChartEditorDialogHandler
|
|||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog letting the user browse for a chart file to open.
|
||||
* @param state The current chart editor state.
|
||||
* @param closable Whether the dialog can be closed by the user.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
public static function openBrowseFNFC(state:ChartEditorState, closable:Bool):Null<Dialog>
|
||||
{
|
||||
var dialog = ChartEditorUploadChartDialog.build(state, closable);
|
||||
|
||||
dialog.zIndex = 1000;
|
||||
state.isHaxeUIDialogOpen = true;
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the wizard for opening an existing chart from individual files.
|
||||
* @param state
|
||||
|
@ -288,15 +320,6 @@ class ChartEditorDialogHandler
|
|||
};
|
||||
}
|
||||
|
||||
public static function openCharacterDropdown(state:ChartEditorState, charType:CharacterType, lockPosition:Bool = false):Null<Menu>
|
||||
{
|
||||
var menu = ChartEditorCharacterIconSelectorMenu.build(state, charType, lockPosition);
|
||||
|
||||
menu.zIndex = 1000;
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
public static function openCreateSongWizardBasicOnly(state:ChartEditorState, closable:Bool):Void
|
||||
{
|
||||
// Step 1. Song Metadata
|
||||
|
@ -699,150 +722,6 @@ class ChartEditorDialogHandler
|
|||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog where the user uploads vocals for the current song.
|
||||
* @param state The current chart editor state.
|
||||
* @param closable Whether the dialog can be closed by the user.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
public static function openUploadVocalsDialog(state:ChartEditorState, closable:Bool = true):Dialog
|
||||
{
|
||||
var instId:String = state.currentInstrumentalId;
|
||||
var charIdsForVocals:Array<String> = [];
|
||||
|
||||
var charData:SongCharacterData = state.currentSongMetadata.playData.characters;
|
||||
|
||||
var hasClearedVocals:Bool = false;
|
||||
|
||||
charIdsForVocals.push(charData.player);
|
||||
charIdsForVocals.push(charData.opponent);
|
||||
|
||||
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT, true, closable);
|
||||
if (dialog == null) throw 'Could not locate Upload Vocals dialog';
|
||||
|
||||
var dialogContainer:Null<Component> = dialog.findComponent('vocalContainer');
|
||||
if (dialogContainer == null) throw 'Could not locate vocalContainer in Upload Vocals dialog';
|
||||
|
||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Upload Vocals dialog';
|
||||
buttonCancel.onClick = function(_) {
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
}
|
||||
|
||||
var dialogNoVocals:Null<Button> = dialog.findComponent('dialogNoVocals', Button);
|
||||
if (dialogNoVocals == null) throw 'Could not locate dialogNoVocals button in Upload Vocals dialog';
|
||||
dialogNoVocals.onClick = function(_) {
|
||||
// Dismiss
|
||||
state.wipeVocalData();
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
};
|
||||
|
||||
for (charKey in charIdsForVocals)
|
||||
{
|
||||
trace('Adding vocal upload for character ${charKey}');
|
||||
var charMetadata:Null<CharacterData> = CharacterDataParser.fetchCharacterData(charKey);
|
||||
var charName:String = charMetadata != null ? charMetadata.name : charKey;
|
||||
|
||||
var vocalsEntry:Component = RuntimeComponentBuilder.fromAsset(CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT);
|
||||
|
||||
var vocalsEntryLabel:Null<Label> = vocalsEntry.findComponent('vocalsEntryLabel', Label);
|
||||
if (vocalsEntryLabel == null) throw 'Could not locate vocalsEntryLabel in Upload Vocals dialog';
|
||||
#if FILE_DROP_SUPPORTED
|
||||
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||
#else
|
||||
vocalsEntryLabel.text = 'Click to browse for vocals for $charName.';
|
||||
#end
|
||||
|
||||
var dropHandler:DialogDropTarget = {component: vocalsEntry, handler: null};
|
||||
|
||||
var onDropFile:String->Void = function(pathStr:String) {
|
||||
trace('Selected file: $pathStr');
|
||||
var path:Path = new Path(pathStr);
|
||||
|
||||
if (state.loadVocalsFromPath(path, charKey, instId, !hasClearedVocals))
|
||||
{
|
||||
hasClearedVocals = true;
|
||||
// Tell the user the load was successful.
|
||||
state.success('Loaded Vocals', 'Loaded vocals for $charName (${path.file}.${path.ext}), variation ${state.selectedVariation}');
|
||||
#if FILE_DROP_SUPPORTED
|
||||
vocalsEntryLabel.text = 'Voices for $charName (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
||||
#else
|
||||
vocalsEntryLabel.text = 'Voices for $charName (click to browse)\n${path.file}.${path.ext}';
|
||||
#end
|
||||
|
||||
dialogNoVocals.hidden = true;
|
||||
state.removeDropHandler(dropHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Failed to load vocal track (${path.file}.${path.ext})');
|
||||
|
||||
state.error('Failed to Load Vocals', 'Failed to load vocal track (${path.file}.${path.ext}) for variation (${state.selectedVariation})');
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||
#else
|
||||
vocalsEntryLabel.text = 'Click to browse for vocals for $charName.';
|
||||
#end
|
||||
}
|
||||
};
|
||||
|
||||
dropHandler.handler = onDropFile;
|
||||
|
||||
vocalsEntry.onClick = function(_event) {
|
||||
Dialogs.openBinaryFile('Open $charName Vocals', [
|
||||
{label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile) {
|
||||
if (selectedFile != null && selectedFile.bytes != null)
|
||||
{
|
||||
trace('Selected file: ' + selectedFile.name);
|
||||
|
||||
if (state.loadVocalsFromBytes(selectedFile.bytes, charKey, instId, !hasClearedVocals))
|
||||
{
|
||||
hasClearedVocals = true;
|
||||
// Tell the user the load was successful.
|
||||
state.success('Loaded Vocals', 'Loaded vocals for $charName (${selectedFile.name}), variation ${state.selectedVariation}');
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
vocalsEntryLabel.text = 'Voices for $charName (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
||||
#else
|
||||
vocalsEntryLabel.text = 'Voices for $charName (click to browse)\n${selectedFile.name}';
|
||||
#end
|
||||
|
||||
dialogNoVocals.hidden = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Failed to load vocal track (${selectedFile.fullPath})');
|
||||
|
||||
state.error('Failed to Load Vocals', 'Failed to load vocal track (${selectedFile.name}) for variation (${state.selectedVariation})');
|
||||
|
||||
#if FILE_DROP_SUPPORTED
|
||||
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||
#else
|
||||
vocalsEntryLabel.text = 'Click to browse for vocals for $charName.';
|
||||
#end
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// onDropFile
|
||||
#if FILE_DROP_SUPPORTED
|
||||
addDropHandler(dropHandler);
|
||||
#end
|
||||
dialogContainer.addComponent(vocalsEntry);
|
||||
}
|
||||
|
||||
var dialogContinue:Null<Button> = dialog.findComponent('dialogContinue', Button);
|
||||
if (dialogContinue == null) throw 'Could not locate dialogContinue button in Upload Vocals dialog';
|
||||
dialogContinue.onClick = function(_) {
|
||||
// Dismiss
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
};
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog where the user upload the JSON files for a song.
|
||||
* @param state The current chart editor state.
|
||||
|
|
|
@ -317,6 +317,12 @@ class ChartEditorThemeHandler
|
|||
ChartEditorState.GRID_SIZE
|
||||
- (2 * SELECTION_SQUARE_BORDER_WIDTH + 8)),
|
||||
32, 32);
|
||||
|
||||
state.selectionBoxSprite.scrollFactor.set(0, 0);
|
||||
state.selectionBoxSprite.zIndex = 30;
|
||||
state.add(state.selectionBoxSprite);
|
||||
|
||||
state.setSelectionBoxBounds();
|
||||
}
|
||||
|
||||
static function updateNotePreview(state:ChartEditorState):Void
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package funkin.ui.debug.charting.handlers;
|
||||
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.play.character.CharacterData;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import haxe.ui.components.HorizontalSlider;
|
||||
|
@ -16,8 +15,7 @@ import funkin.play.character.CharacterData;
|
|||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.play.song.SongSerializer;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.data.stage.StageData;
|
||||
import haxe.ui.RuntimeComponentBuilder;
|
||||
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
||||
import funkin.ui.haxeui.components.CharacterPlayer;
|
||||
|
@ -38,6 +36,7 @@ import haxe.ui.containers.dialogs.Dialog.DialogEvent;
|
|||
import funkin.ui.debug.charting.toolboxes.ChartEditorBaseToolbox;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorMetadataToolbox;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorEventDataToolbox;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorDifficultyToolbox;
|
||||
import haxe.ui.containers.Frame;
|
||||
import haxe.ui.containers.Grid;
|
||||
import haxe.ui.containers.TreeView;
|
||||
|
@ -86,7 +85,7 @@ class ChartEditorToolboxHandler
|
|||
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT:
|
||||
onShowToolboxPlaytestProperties(state, toolbox);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:
|
||||
onShowToolboxDifficulty(state, toolbox);
|
||||
cast(toolbox, ChartEditorBaseToolbox).refresh();
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:
|
||||
// TODO: Fix this.
|
||||
cast(toolbox, ChartEditorBaseToolbox).refresh();
|
||||
|
@ -125,8 +124,6 @@ class ChartEditorToolboxHandler
|
|||
onHideToolboxEventData(state, toolbox);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT:
|
||||
onHideToolboxPlaytestProperties(state, toolbox);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:
|
||||
onHideToolboxDifficulty(state, toolbox);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:
|
||||
onHideToolboxMetadata(state, toolbox);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:
|
||||
|
@ -311,8 +308,6 @@ class ChartEditorToolboxHandler
|
|||
|
||||
static function onHideToolboxEventData(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
|
||||
|
||||
static function onHideToolboxDifficulty(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
|
||||
|
||||
static function onShowToolboxPlaytestProperties(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
|
||||
|
||||
static function onHideToolboxPlaytestProperties(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
|
||||
|
@ -360,91 +355,15 @@ class ChartEditorToolboxHandler
|
|||
return toolbox;
|
||||
}
|
||||
|
||||
static function buildToolboxDifficultyLayout(state:ChartEditorState):Null<CollapsibleDialog>
|
||||
static function buildToolboxDifficultyLayout(state:ChartEditorState):Null<ChartEditorBaseToolbox>
|
||||
{
|
||||
var toolbox:CollapsibleDialog = cast RuntimeComponentBuilder.fromAsset(ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
||||
var toolbox:ChartEditorBaseToolbox = ChartEditorDifficultyToolbox.build(state);
|
||||
|
||||
if (toolbox == null) return null;
|
||||
|
||||
// Starting position.
|
||||
toolbox.x = 125;
|
||||
toolbox.y = 200;
|
||||
|
||||
toolbox.onDialogClosed = function(event:UIEvent) {
|
||||
state.menubarItemToggleToolboxDifficulty.selected = false;
|
||||
}
|
||||
|
||||
var difficultyToolboxAddVariation:Null<Button> = toolbox.findComponent('difficultyToolboxAddVariation', Button);
|
||||
if (difficultyToolboxAddVariation == null)
|
||||
throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxAddVariation component.';
|
||||
var difficultyToolboxAddDifficulty:Null<Button> = toolbox.findComponent('difficultyToolboxAddDifficulty', Button);
|
||||
if (difficultyToolboxAddDifficulty == null)
|
||||
throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxAddDifficulty component.';
|
||||
var difficultyToolboxSaveMetadata:Null<Button> = toolbox.findComponent('difficultyToolboxSaveMetadata', Button);
|
||||
if (difficultyToolboxSaveMetadata == null)
|
||||
throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxSaveMetadata component.';
|
||||
var difficultyToolboxSaveChart:Null<Button> = toolbox.findComponent('difficultyToolboxSaveChart', Button);
|
||||
if (difficultyToolboxSaveChart == null)
|
||||
throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxSaveChart component.';
|
||||
// var difficultyToolboxSaveAll:Null<Button> = toolbox.findComponent('difficultyToolboxSaveAll', Button);
|
||||
// if (difficultyToolboxSaveAll == null) throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxSaveAll component.';
|
||||
var difficultyToolboxLoadMetadata:Null<Button> = toolbox.findComponent('difficultyToolboxLoadMetadata', Button);
|
||||
if (difficultyToolboxLoadMetadata == null)
|
||||
throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxLoadMetadata component.';
|
||||
var difficultyToolboxLoadChart:Null<Button> = toolbox.findComponent('difficultyToolboxLoadChart', Button);
|
||||
if (difficultyToolboxLoadChart == null)
|
||||
throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxLoadChart component.';
|
||||
|
||||
difficultyToolboxAddVariation.onClick = function(_:UIEvent) {
|
||||
state.openAddVariationDialog(true);
|
||||
};
|
||||
|
||||
difficultyToolboxAddDifficulty.onClick = function(_:UIEvent) {
|
||||
state.openAddDifficultyDialog(true);
|
||||
};
|
||||
|
||||
difficultyToolboxSaveMetadata.onClick = function(_:UIEvent) {
|
||||
var vari:String = state.selectedVariation != Constants.DEFAULT_VARIATION ? '-${state.selectedVariation}' : '';
|
||||
FileUtil.writeFileReference('${state.currentSongId}$vari-metadata.json', state.currentSongMetadata.serialize());
|
||||
};
|
||||
|
||||
difficultyToolboxSaveChart.onClick = function(_:UIEvent) {
|
||||
var vari:String = state.selectedVariation != Constants.DEFAULT_VARIATION ? '-${state.selectedVariation}' : '';
|
||||
FileUtil.writeFileReference('${state.currentSongId}$vari-chart.json', state.currentSongChartData.serialize());
|
||||
};
|
||||
|
||||
difficultyToolboxLoadMetadata.onClick = function(_:UIEvent) {
|
||||
// Replace metadata for current variation.
|
||||
SongSerializer.importSongMetadataAsync(function(songMetadata) {
|
||||
state.currentSongMetadata = songMetadata;
|
||||
});
|
||||
};
|
||||
|
||||
difficultyToolboxLoadChart.onClick = function(_:UIEvent) {
|
||||
// Replace chart data for current variation.
|
||||
SongSerializer.importSongChartDataAsync(function(songChartData) {
|
||||
state.currentSongChartData = songChartData;
|
||||
state.noteDisplayDirty = true;
|
||||
});
|
||||
};
|
||||
|
||||
state.difficultySelectDirty = true;
|
||||
|
||||
return toolbox;
|
||||
}
|
||||
|
||||
static function onShowToolboxDifficulty(state:ChartEditorState, toolbox:CollapsibleDialog):Void
|
||||
{
|
||||
// Update the selected difficulty when reopening the toolbox.
|
||||
var treeView:Null<TreeView> = toolbox.findComponent('difficultyToolboxTree');
|
||||
if (treeView == null) return;
|
||||
|
||||
var current = state.getCurrentTreeDifficultyNode(treeView);
|
||||
if (current == null) return;
|
||||
treeView.selectedNode = current;
|
||||
trace('selected node: ${treeView.selectedNode}');
|
||||
}
|
||||
|
||||
static function buildToolboxMetadataLayout(state:ChartEditorState):Null<ChartEditorBaseToolbox>
|
||||
{
|
||||
var toolbox:ChartEditorBaseToolbox = ChartEditorMetadataToolbox.build(state);
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
package funkin.ui.debug.charting.toolboxes;
|
||||
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.play.character.CharacterData;
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand;
|
||||
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.CheckBox;
|
||||
import haxe.ui.containers.dialogs.Dialogs;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogButton;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import haxe.ui.components.DropDown;
|
||||
import haxe.ui.components.HorizontalSlider;
|
||||
import funkin.util.FileUtil;
|
||||
import haxe.ui.containers.dialogs.MessageBox.MessageBoxType;
|
||||
import funkin.play.song.SongSerializer;
|
||||
import haxe.ui.components.Label;
|
||||
import haxe.ui.components.NumberStepper;
|
||||
import haxe.ui.components.Slider;
|
||||
import haxe.ui.components.TextField;
|
||||
import funkin.play.stage.Stage;
|
||||
import haxe.ui.containers.Box;
|
||||
import haxe.ui.containers.TreeView;
|
||||
import haxe.ui.containers.TreeViewNode;
|
||||
import haxe.ui.containers.Frame;
|
||||
import haxe.ui.events.UIEvent;
|
||||
|
||||
/**
|
||||
* The toolbox which allows viewing the list of difficulties, switching to a specific one,
|
||||
* and adding/removing variations and difficulties.
|
||||
*/
|
||||
// @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros.
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/toolboxes/difficulty.xml"))
|
||||
class ChartEditorDifficultyToolbox extends ChartEditorBaseToolbox
|
||||
{
|
||||
var difficultyToolboxTree:TreeView;
|
||||
var difficultyToolboxAddVariation:Button;
|
||||
var difficultyToolboxAddDifficulty:Button;
|
||||
var difficultyToolboxRemoveDifficulty:Button;
|
||||
var difficultyToolboxSaveMetadata:Button;
|
||||
var difficultyToolboxSaveChart:Button;
|
||||
var difficultyToolboxLoadMetadata:Button;
|
||||
var difficultyToolboxLoadChart:Button;
|
||||
|
||||
public function new(chartEditorState2:ChartEditorState)
|
||||
{
|
||||
super(chartEditorState2);
|
||||
|
||||
initialize();
|
||||
|
||||
this.onDialogClosed = onClose;
|
||||
}
|
||||
|
||||
function onClose(event:UIEvent)
|
||||
{
|
||||
chartEditorState.menubarItemToggleToolboxDifficulty.selected = false;
|
||||
}
|
||||
|
||||
function initialize():Void
|
||||
{
|
||||
// Starting position.
|
||||
// TODO: Save and load this.
|
||||
this.x = 150;
|
||||
this.y = 250;
|
||||
|
||||
difficultyToolboxAddVariation.onClick = function(_:UIEvent) {
|
||||
chartEditorState.openAddVariationDialog(true);
|
||||
};
|
||||
|
||||
difficultyToolboxAddDifficulty.onClick = function(_:UIEvent) {
|
||||
chartEditorState.openAddDifficultyDialog(true);
|
||||
};
|
||||
|
||||
difficultyToolboxRemoveDifficulty.onClick = function(_:UIEvent) {
|
||||
var currentVariation:String = chartEditorState.selectedVariation;
|
||||
var currentDifficulty:String = chartEditorState.selectedDifficulty;
|
||||
|
||||
trace('Removing difficulty "$currentVariation:$currentDifficulty"');
|
||||
|
||||
var callback = (button) -> {
|
||||
switch (button)
|
||||
{
|
||||
case DialogButton.YES:
|
||||
// Remove the difficulty.
|
||||
chartEditorState.removeDifficulty(currentVariation, currentDifficulty);
|
||||
refresh();
|
||||
case DialogButton.NO: // Do nothing.
|
||||
default: // Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
Dialogs.messageBox("Are you sure? This cannot be undone.", "Remove Difficulty", MessageBoxType.TYPE_YESNO, callback);
|
||||
};
|
||||
|
||||
difficultyToolboxSaveMetadata.onClick = function(_:UIEvent) {
|
||||
var vari:String = chartEditorState.selectedVariation != Constants.DEFAULT_VARIATION ? '-${chartEditorState.selectedVariation}' : '';
|
||||
FileUtil.writeFileReference('${chartEditorState.currentSongId}$vari-metadata.json', chartEditorState.currentSongMetadata.serialize());
|
||||
};
|
||||
|
||||
difficultyToolboxSaveChart.onClick = function(_:UIEvent) {
|
||||
var vari:String = chartEditorState.selectedVariation != Constants.DEFAULT_VARIATION ? '-${chartEditorState.selectedVariation}' : '';
|
||||
FileUtil.writeFileReference('${chartEditorState.currentSongId}$vari-chart.json', chartEditorState.currentSongChartData.serialize());
|
||||
};
|
||||
|
||||
difficultyToolboxLoadMetadata.onClick = function(_:UIEvent) {
|
||||
// Replace metadata for current variation.
|
||||
SongSerializer.importSongMetadataAsync(function(songMetadata) {
|
||||
chartEditorState.currentSongMetadata = songMetadata;
|
||||
});
|
||||
};
|
||||
|
||||
difficultyToolboxLoadChart.onClick = function(_:UIEvent) {
|
||||
// Replace chart data for current variation.
|
||||
SongSerializer.importSongChartDataAsync(function(songChartData) {
|
||||
chartEditorState.currentSongChartData = songChartData;
|
||||
chartEditorState.noteDisplayDirty = true;
|
||||
});
|
||||
};
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the tree view and rebuild it with the current song metadata (variation and difficulty list).
|
||||
*/
|
||||
public function updateTree():Void
|
||||
{
|
||||
// Clear the tree view so we can rebuild it.
|
||||
difficultyToolboxTree.clearNodes();
|
||||
|
||||
// , icon: 'haxeui-core/styles/default/haxeui_tiny.png'
|
||||
var treeSong:TreeViewNode = difficultyToolboxTree.addNode({id: 'stv_song', text: 'S: ${chartEditorState.currentSongName}'});
|
||||
treeSong.expanded = true;
|
||||
|
||||
for (curVariation in chartEditorState.availableVariations)
|
||||
{
|
||||
var variationMetadata:Null<SongMetadata> = chartEditorState.songMetadata.get(curVariation);
|
||||
if (variationMetadata == null) continue;
|
||||
|
||||
var treeVariation:TreeViewNode = treeSong.addNode(
|
||||
{
|
||||
id: 'stv_variation_$curVariation',
|
||||
text: 'V: ${curVariation.toTitleCase()}'
|
||||
});
|
||||
treeVariation.expanded = true;
|
||||
|
||||
var difficultyList:Array<String> = variationMetadata.playData.difficulties;
|
||||
|
||||
for (difficulty in difficultyList)
|
||||
{
|
||||
var _treeDifficulty:TreeViewNode = treeVariation.addNode(
|
||||
{
|
||||
id: 'stv_difficulty_${curVariation}_$difficulty',
|
||||
text: 'D: ${difficulty.toTitleCase()}'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
difficultyToolboxTree.onChange = onTreeChange;
|
||||
refreshTreeSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the selected item in the tree to the current variation/difficulty.
|
||||
*
|
||||
* @param targetNode The node to select. If null, the current variation/difficulty will be used.
|
||||
*/
|
||||
public function refreshTreeSelection():Void
|
||||
{
|
||||
var targetNode = getCurrentTreeNode();
|
||||
if (targetNode != null) difficultyToolboxTree.selectedNode = targetNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node in the tree representing the current variation/difficulty.
|
||||
*/
|
||||
function getCurrentTreeNode():TreeViewNode
|
||||
{
|
||||
return
|
||||
difficultyToolboxTree.findNodeByPath('stv_song/stv_variation_$chartEditorState.selectedVariation/stv_difficulty_${chartEditorState.selectedVariation}_$chartEditorState.selectedDifficulty',
|
||||
'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an item in the tree is selected. Updates the current variation/difficulty.
|
||||
*/
|
||||
function onTreeChange(event:UIEvent):Void
|
||||
{
|
||||
// Get the newly selected node.
|
||||
var treeView:TreeView = cast event.target;
|
||||
var targetNode:TreeViewNode = difficultyToolboxTree.selectedNode;
|
||||
|
||||
if (targetNode == null)
|
||||
{
|
||||
trace('No target node!');
|
||||
// Reset the user's selection.
|
||||
refreshTreeSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (targetNode.data.id.split('_')[1])
|
||||
{
|
||||
case 'difficulty':
|
||||
var variation:String = targetNode.data.id.split('_')[2];
|
||||
var difficulty:String = targetNode.data.id.split('_')[3];
|
||||
|
||||
if (variation != null && difficulty != null)
|
||||
{
|
||||
trace('Changing difficulty to "$variation:$difficulty"');
|
||||
chartEditorState.selectedVariation = variation;
|
||||
chartEditorState.selectedDifficulty = difficulty;
|
||||
chartEditorState.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
||||
refreshTreeSelection();
|
||||
}
|
||||
// case 'song':
|
||||
// case 'variation':
|
||||
default:
|
||||
// Reset the user's selection.
|
||||
trace('Selected wrong node type, resetting selection.');
|
||||
refreshTreeSelection();
|
||||
chartEditorState.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
||||
}
|
||||
}
|
||||
|
||||
public override function refresh():Void
|
||||
{
|
||||
super.refresh();
|
||||
|
||||
refreshTreeSelection();
|
||||
}
|
||||
|
||||
public static function build(chartEditorState:ChartEditorState):ChartEditorDifficultyToolbox
|
||||
{
|
||||
return new ChartEditorDifficultyToolbox(chartEditorState);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ package funkin.ui.debug.charting.toolboxes;
|
|||
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.play.character.CharacterData;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand;
|
||||
|
|
|
@ -2,7 +2,8 @@ package funkin.ui.debug.charting.toolboxes;
|
|||
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.play.character.CharacterData;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand;
|
||||
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
||||
import haxe.ui.components.Button;
|
||||
|
@ -13,6 +14,7 @@ import haxe.ui.components.Label;
|
|||
import haxe.ui.components.NumberStepper;
|
||||
import haxe.ui.components.Slider;
|
||||
import haxe.ui.components.TextField;
|
||||
import funkin.play.stage.Stage;
|
||||
import haxe.ui.containers.Box;
|
||||
import haxe.ui.containers.Frame;
|
||||
import haxe.ui.events.UIEvent;
|
||||
|
@ -199,11 +201,11 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
|
|||
inputTimeSignature.value = {id: currentTimeSignature, text: currentTimeSignature};
|
||||
|
||||
var stageId:String = chartEditorState.currentSongMetadata.playData.stage;
|
||||
var stageData:Null<StageData> = StageDataParser.parseStageData(stageId);
|
||||
var stage:Null<Stage> = StageRegistry.instance.fetchEntry(stageId);
|
||||
if (inputStage != null)
|
||||
{
|
||||
inputStage.value = (stageData != null) ?
|
||||
{id: stageId, text: stageData.name} :
|
||||
inputStage.value = (stage != null) ?
|
||||
{id: stage.id, text: stage.stageName} :
|
||||
{id: "mainStage", text: "Main Stage"};
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,11 @@ package funkin.ui.debug.charting.util;
|
|||
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.play.character.CharacterData;
|
||||
import haxe.ui.components.DropDown;
|
||||
import funkin.play.stage.Stage;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
|
||||
|
@ -60,16 +61,16 @@ class ChartEditorDropdowns
|
|||
{
|
||||
dropDown.dataSource.clear();
|
||||
|
||||
var stageIds:Array<String> = StageDataParser.listStageIds();
|
||||
var stageIds:Array<String> = StageRegistry.instance.listEntryIds();
|
||||
|
||||
var returnValue:DropDownEntry = {id: "mainStage", text: "Main Stage"};
|
||||
|
||||
for (stageId in stageIds)
|
||||
{
|
||||
var stage:Null<StageData> = StageDataParser.parseStageData(stageId);
|
||||
var stage:Null<Stage> = StageRegistry.instance.fetchEntry(stageId);
|
||||
if (stage == null) continue;
|
||||
|
||||
var value = {id: stageId, text: stage.name};
|
||||
var value = {id: stage.id, text: stage.stageName};
|
||||
if (startingStageId == stageId) returnValue = value;
|
||||
|
||||
dropDown.dataSource.add(value);
|
||||
|
|
|
@ -5,15 +5,17 @@ import flixel.input.mouse.FlxMouseEvent;
|
|||
import flixel.math.FlxPoint;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.play.stage.StageProp;
|
||||
import funkin.graphics.shaders.StrokeShader;
|
||||
import funkin.ui.haxeui.HaxeUISubState;
|
||||
import funkin.ui.debug.stage.StageEditorCommand;
|
||||
import funkin.util.SerializerUtil;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.util.MouseUtil;
|
||||
import haxe.ui.containers.ListView;
|
||||
import haxe.ui.core.Component;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.RuntimeComponentBuilder;
|
||||
import openfl.events.Event;
|
||||
|
@ -354,7 +356,13 @@ class StageOffsetSubState extends HaxeUISubState
|
|||
|
||||
function prepStageStuff():String
|
||||
{
|
||||
var stageLol:StageData = StageDataParser.parseStageData(PlayState.instance.currentStageId);
|
||||
var stageLol:StageData = StageRegistry.instance.fetchEntry(PlayState.instance.currentStageId)?._data;
|
||||
|
||||
if (stageLol == null)
|
||||
{
|
||||
FlxG.log.error("Stage not found in registry!");
|
||||
return "";
|
||||
}
|
||||
|
||||
for (prop in stageLol.props)
|
||||
{
|
||||
|
@ -378,6 +386,6 @@ class StageOffsetSubState extends HaxeUISubState
|
|||
stageLol.characters.gf.position[0] = Std.int(GF_FEET_SNIIIIIIIIIIIIIFFFF.x);
|
||||
stageLol.characters.gf.position[1] = Std.int(GF_FEET_SNIIIIIIIIIIIIIFFFF.y);
|
||||
|
||||
return SerializerUtil.toJSON(stageLol);
|
||||
return stageLol.serialize();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import flixel.group.FlxGroup;
|
|||
import flixel.input.actions.FlxActionInput;
|
||||
import flixel.input.gamepad.FlxGamepadInputID;
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.input.Controls;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.ui.MenuList;
|
||||
|
@ -61,8 +62,8 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page
|
|||
|
||||
if (FlxG.gamepads.numActiveGamepads > 0)
|
||||
{
|
||||
var devicesBg:FlxSprite = new FlxSprite();
|
||||
devicesBg.makeGraphic(FlxG.width, 100, 0xFFFAFD6D);
|
||||
var devicesBg:FunkinSprite = new FunkinSprite();
|
||||
devicesBg.makeSolidColor(FlxG.width, 100, 0xFFFAFD6D);
|
||||
add(devicesBg);
|
||||
deviceList = new TextMenuList(Horizontal, None);
|
||||
add(deviceList);
|
||||
|
|
|
@ -10,6 +10,7 @@ import flixel.group.FlxGroup.FlxTypedGroup;
|
|||
import flixel.text.FlxText;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.tweens.FlxEase;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.ui.MusicBeatState;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
|
@ -153,7 +154,7 @@ class StoryMenuState extends MusicBeatState
|
|||
|
||||
updateBackground();
|
||||
|
||||
var black:FlxSprite = new FlxSprite(levelBackground.x, 0).makeGraphic(FlxG.width, Std.int(400 + levelBackground.y), FlxColor.BLACK);
|
||||
var black:FunkinSprite = new FunkinSprite(levelBackground.x, 0).makeSolidColor(FlxG.width, Std.int(400 + levelBackground.y), FlxColor.BLACK);
|
||||
black.zIndex = levelBackground.zIndex - 1;
|
||||
add(black);
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ class OutdatedSubState extends MusicBeatState
|
|||
override function create()
|
||||
{
|
||||
super.create();
|
||||
var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK);
|
||||
var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, FlxColor.BLACK);
|
||||
add(bg);
|
||||
var ver = "v" + Application.current.meta.get('version');
|
||||
var txt:FlxText = new FlxText(0, 0, FlxG.width,
|
||||
|
|
|
@ -13,6 +13,7 @@ import funkin.audio.visualize.SpectogramSprite;
|
|||
import funkin.graphics.shaders.ColorSwap;
|
||||
import funkin.graphics.shaders.LeftMaskShader;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.ui.MusicBeatState;
|
||||
import funkin.data.song.SongData.SongMusicData;
|
||||
import funkin.graphics.shaders.TitleOutline;
|
||||
|
@ -118,7 +119,8 @@ class TitleState extends MusicBeatState
|
|||
|
||||
persistentUpdate = true;
|
||||
|
||||
var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK);
|
||||
var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, FlxColor.BLACK);
|
||||
bg.screenCenter();
|
||||
add(bg);
|
||||
|
||||
logoBl = new FlxSprite(-150, -100);
|
||||
|
|
|
@ -13,6 +13,7 @@ import funkin.play.song.Song.SongDifficulty;
|
|||
import funkin.ui.mainmenu.MainMenuState;
|
||||
import funkin.ui.MusicBeatState;
|
||||
import haxe.io.Path;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import lime.app.Future;
|
||||
import lime.app.Promise;
|
||||
import lime.utils.AssetLibrary;
|
||||
|
@ -42,7 +43,7 @@ class LoadingState extends MusicBeatState
|
|||
|
||||
override function create():Void
|
||||
{
|
||||
var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, 0xFFcaff4d);
|
||||
var bg:FlxSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, 0xFFcaff4d);
|
||||
add(bg);
|
||||
|
||||
funkay = new FlxSprite();
|
||||
|
@ -53,7 +54,7 @@ class LoadingState extends MusicBeatState
|
|||
funkay.scrollFactor.set();
|
||||
funkay.screenCenter();
|
||||
|
||||
loadBar = new FlxSprite(0, FlxG.height - 20).makeGraphic(FlxG.width, 10, 0xFFff16d2);
|
||||
loadBar = new FunkinSprite(0, FlxG.height - 20).makeSolidColor(FlxG.width, 10, 0xFFff16d2);
|
||||
loadBar.screenCenter(X);
|
||||
add(loadBar);
|
||||
|
||||
|
|
17
source/funkin/util/HaxeUIUtil.hx
Normal file
17
source/funkin/util/HaxeUIUtil.hx
Normal file
|
@ -0,0 +1,17 @@
|
|||
package funkin.util;
|
||||
|
||||
import haxe.ui.tooltips.ToolTipRegionOptions;
|
||||
|
||||
class HaxeUIUtil
|
||||
{
|
||||
public static function buildTooltip(text:String, ?left:Float, ?top:Float, ?width:Float, ?height:Float):ToolTipRegionOptions
|
||||
{
|
||||
return {
|
||||
tipData: {text: text},
|
||||
left: left ?? 0.0,
|
||||
top: top ?? 0.0,
|
||||
width: width ?? 0.0,
|
||||
height: height ?? 0.0
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue