mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2024-11-15 11:22:55 +00:00
Implemented playable character registry, added Freeplay character filtering, added alt instrumental support
This commit is contained in:
parent
6f2f2a9aa4
commit
60e741434c
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 2e1594ee4c04c7148628bae471bdd061c9deb6b7
|
||||
Subproject commit fece99b3b121045fb2f6f02dba485201b32f1c87
|
|
@ -1,5 +1,6 @@
|
|||
package funkin;
|
||||
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.ui.debug.charting.ChartEditorState;
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import flixel.FlxState;
|
||||
|
@ -164,6 +165,7 @@ class InitState extends FlxState
|
|||
SongRegistry.instance.loadEntries();
|
||||
LevelRegistry.instance.loadEntries();
|
||||
NoteStyleRegistry.instance.loadEntries();
|
||||
PlayerRegistry.instance.loadEntries();
|
||||
ConversationRegistry.instance.loadEntries();
|
||||
DialogueBoxRegistry.instance.loadEntries();
|
||||
SpeakerRegistry.instance.loadEntries();
|
||||
|
|
9
source/funkin/data/freeplay/player/CHANGELOG.md
Normal file
9
source/funkin/data/freeplay/player/CHANGELOG.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Freeplay Playable Character Data Schema Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.0.0]
|
||||
Initial release.
|
63
source/funkin/data/freeplay/player/PlayerData.hx
Normal file
63
source/funkin/data/freeplay/player/PlayerData.hx
Normal file
|
@ -0,0 +1,63 @@
|
|||
package funkin.data.freeplay.player;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
|
||||
@:nullSafety
|
||||
class PlayerData
|
||||
{
|
||||
/**
|
||||
* The sematic version number of the player data JSON format.
|
||||
* Supports fancy comparisons like NPM does it's neat.
|
||||
*/
|
||||
@:default(funkin.data.freeplay.player.PlayerRegistry.PLAYER_DATA_VERSION)
|
||||
public var version:String;
|
||||
|
||||
/**
|
||||
* A readable name for this playable character.
|
||||
*/
|
||||
public var name:String = 'Unknown';
|
||||
|
||||
/**
|
||||
* The character IDs this character is associated with.
|
||||
* Only songs that use these characters will show up in Freeplay.
|
||||
*/
|
||||
@:default([])
|
||||
public var ownedChars:Array<String> = [];
|
||||
|
||||
/**
|
||||
* Whether to show songs with character IDs that aren't associated with any specific character.
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
public var showUnownedChars:Bool = false;
|
||||
|
||||
/**
|
||||
* Whether this character is unlocked by default.
|
||||
* Use a ScriptedPlayableCharacter to add custom logic.
|
||||
*/
|
||||
@:optional
|
||||
@:default(true)
|
||||
public var unlocked:Bool = true;
|
||||
|
||||
public function new()
|
||||
{
|
||||
this.version = PlayerRegistry.PLAYER_DATA_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this StageData into a JSON string.
|
||||
*/
|
||||
public function serialize(pretty:Bool = true):String
|
||||
{
|
||||
// Update generatedBy and version before writing.
|
||||
updateVersionToLatest();
|
||||
|
||||
var writer = new json2object.JsonWriter<PlayerData>();
|
||||
return writer.write(this, pretty ? ' ' : null);
|
||||
}
|
||||
|
||||
public function updateVersionToLatest():Void
|
||||
{
|
||||
this.version = PlayerRegistry.PLAYER_DATA_VERSION;
|
||||
}
|
||||
}
|
151
source/funkin/data/freeplay/player/PlayerRegistry.hx
Normal file
151
source/funkin/data/freeplay/player/PlayerRegistry.hx
Normal file
|
@ -0,0 +1,151 @@
|
|||
package funkin.data.freeplay.player;
|
||||
|
||||
import funkin.data.freeplay.player.PlayerData;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter;
|
||||
|
||||
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
||||
{
|
||||
/**
|
||||
* The current version string for the stage data format.
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migratePlayerData()` function.
|
||||
*/
|
||||
public static final PLAYER_DATA_VERSION:thx.semver.Version = "1.0.0";
|
||||
|
||||
public static final PLAYER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||
|
||||
public static var instance(get, never):PlayerRegistry;
|
||||
static var _instance:Null<PlayerRegistry> = null;
|
||||
|
||||
static function get_instance():PlayerRegistry
|
||||
{
|
||||
if (_instance == null) _instance = new PlayerRegistry();
|
||||
return _instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapping between stage character IDs and Freeplay playable character IDs.
|
||||
*/
|
||||
var ownedCharacterIds:Map<String, String> = [];
|
||||
|
||||
public function new()
|
||||
{
|
||||
super('PLAYER', 'players', PLAYER_DATA_VERSION_RULE);
|
||||
}
|
||||
|
||||
public override function loadEntries():Void
|
||||
{
|
||||
super.loadEntries();
|
||||
|
||||
for (playerId in listEntryIds())
|
||||
{
|
||||
var player = fetchEntry(playerId);
|
||||
if (player == null) continue;
|
||||
|
||||
var currentPlayerCharIds = player.getOwnedCharacterIds();
|
||||
for (characterId in currentPlayerCharIds)
|
||||
{
|
||||
ownedCharacterIds.set(characterId, playerId);
|
||||
}
|
||||
}
|
||||
|
||||
log('Loaded ${countEntries()} playable characters with ${ownedCharacterIds.size()} associations.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the playable character associated with a given stage character.
|
||||
* @param characterId The stage character ID.
|
||||
* @return The playable character.
|
||||
*/
|
||||
public function getCharacterOwnerId(characterId:String):String
|
||||
{
|
||||
return ownedCharacterIds[characterId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given stage character is associated with a specific playable character.
|
||||
* If so, the level should only appear if that character is selected in Freeplay.
|
||||
* @param characterId The stage character ID.
|
||||
* @return Whether the character is owned by any one character.
|
||||
*/
|
||||
public function isCharacterOwned(characterId:String):Bool
|
||||
{
|
||||
return ownedCharacterIds.exists(characterId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||
*/
|
||||
public function parseEntryData(id:String):Null<PlayerData>
|
||||
{
|
||||
// JsonParser does not take type parameters,
|
||||
// otherwise this function would be in BaseRegistry.
|
||||
var parser = new json2object.JsonParser<PlayerData>();
|
||||
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<PlayerData>
|
||||
{
|
||||
var parser = new json2object.JsonParser<PlayerData>();
|
||||
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):PlayableCharacter
|
||||
{
|
||||
return ScriptedPlayableCharacter.init(clsName, "unknown");
|
||||
}
|
||||
|
||||
function getScriptedClassNames():Array<String>
|
||||
{
|
||||
return ScriptedPlayableCharacter.listScriptClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of all the playable characters from the base game, in order.
|
||||
*/
|
||||
public function listBaseGamePlayerIds():Array<String>
|
||||
{
|
||||
return ["bf", "pico"];
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of all installed playable characters that are not from the base game.
|
||||
*/
|
||||
public function listModdedPlayerIds():Array<String>
|
||||
{
|
||||
return listEntryIds().filter(function(id:String):Bool {
|
||||
return listBaseGamePlayerIds().indexOf(id) == -1;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import funkin.data.event.SongEventRegistry;
|
|||
import funkin.data.story.level.LevelRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.data.freeplay.album.AlbumRegistry;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
|
@ -369,15 +370,18 @@ class PolymodHandler
|
|||
|
||||
// These MUST be imported at the top of the file and not referred to by fully qualified name,
|
||||
// to ensure build macros work properly.
|
||||
SongEventRegistry.loadEventCache();
|
||||
|
||||
SongRegistry.instance.loadEntries();
|
||||
LevelRegistry.instance.loadEntries();
|
||||
NoteStyleRegistry.instance.loadEntries();
|
||||
SongEventRegistry.loadEventCache();
|
||||
PlayerRegistry.instance.loadEntries();
|
||||
ConversationRegistry.instance.loadEntries();
|
||||
DialogueBoxRegistry.instance.loadEntries();
|
||||
SpeakerRegistry.instance.loadEntries();
|
||||
AlbumRegistry.instance.loadEntries();
|
||||
StageRegistry.instance.loadEntries();
|
||||
|
||||
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
|
||||
ModuleHandler.loadModuleCache();
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import funkin.data.song.SongData.SongTimeFormat;
|
|||
import funkin.data.song.SongRegistry;
|
||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.util.SortUtil;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
|
@ -401,11 +402,11 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
return null;
|
||||
}
|
||||
|
||||
public function getFirstValidVariation(?diffId:String, ?possibleVariations:Array<String>):Null<String>
|
||||
public function getFirstValidVariation(?diffId:String, ?currentCharacter:PlayableCharacter, ?possibleVariations:Array<String>):Null<String>
|
||||
{
|
||||
if (possibleVariations == null)
|
||||
{
|
||||
possibleVariations = variations;
|
||||
possibleVariations = getVariationsByCharacter(currentCharacter);
|
||||
possibleVariations.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_VARIATION_LIST));
|
||||
}
|
||||
if (diffId == null) diffId = listDifficulties(null, possibleVariations)[0];
|
||||
|
@ -422,24 +423,31 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
/**
|
||||
* Given that this character is selected in the Freeplay menu,
|
||||
* which variations should be available?
|
||||
* @param charId The character ID to query.
|
||||
* @param char The playable character to query.
|
||||
* @return An array of available variations.
|
||||
*/
|
||||
public function getVariationsByCharId(?charId:String):Array<String>
|
||||
public function getVariationsByCharacter(?char:PlayableCharacter):Array<String>
|
||||
{
|
||||
if (charId == null) charId = Constants.DEFAULT_CHARACTER;
|
||||
if (char == null) return variations;
|
||||
|
||||
if (variations.contains(charId))
|
||||
var result = [];
|
||||
trace('Evaluating variations for ${this.id} ${char.id}: ${this.variations}');
|
||||
for (variation in variations)
|
||||
{
|
||||
return [charId];
|
||||
}
|
||||
else
|
||||
var metadata = _metadata.get(variation);
|
||||
|
||||
var playerCharId = metadata?.playData?.characters?.player;
|
||||
if (playerCharId == null) continue;
|
||||
|
||||
if (char.shouldShowCharacter(playerCharId))
|
||||
{
|
||||
// TODO: How to exclude character variations while keeping other custom variations?
|
||||
return variations;
|
||||
result.push(variation);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all the difficulties in this song.
|
||||
*
|
||||
|
@ -455,6 +463,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
if (variationIds == null) variationIds = [];
|
||||
if (variationId != null) variationIds.push(variationId);
|
||||
|
||||
if (variationIds.length == 0) return [];
|
||||
|
||||
// The difficulties array contains entries like 'normal', 'nightmare-erect', and 'normal-pico',
|
||||
// so we have to map it to the actual difficulty names.
|
||||
// We also filter out difficulties that don't match the variation or that don't exist.
|
||||
|
|
|
@ -808,8 +808,11 @@ class ChartEditorDialogHandler
|
|||
}
|
||||
songVariationMetadataEntry.onClick = onClickMetadataVariation.bind(variation).bind(songVariationMetadataEntryLabel);
|
||||
#if FILE_DROP_SUPPORTED
|
||||
state.addDropHandler({component: songVariationMetadataEntry, handler: onDropFileMetadataVariation.bind(variation)
|
||||
.bind(songVariationMetadataEntryLabel)});
|
||||
state.addDropHandler(
|
||||
{
|
||||
component: songVariationMetadataEntry,
|
||||
handler: onDropFileMetadataVariation.bind(variation).bind(songVariationMetadataEntryLabel)
|
||||
});
|
||||
#end
|
||||
chartContainerB.addComponent(songVariationMetadataEntry);
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import flixel.util.FlxTimer;
|
|||
import funkin.audio.FunkinSound;
|
||||
import funkin.data.story.level.LevelRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.graphics.FunkinCamera;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.graphics.shaders.AngleMask;
|
||||
|
@ -47,6 +48,7 @@ import lime.utils.Assets;
|
|||
import flixel.tweens.misc.ShakeTween;
|
||||
import funkin.effects.IntervalShake;
|
||||
import funkin.ui.freeplay.SongMenuItem.FreeplayRank;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
|
||||
/**
|
||||
* Parameters used to initialize the FreeplayState.
|
||||
|
@ -102,7 +104,9 @@ class FreeplayState extends MusicBeatSubState
|
|||
* The current character for this FreeplayState.
|
||||
* You can't change this without transitioning to a new FreeplayState.
|
||||
*/
|
||||
final currentCharacter:String;
|
||||
final currentCharacterId:String;
|
||||
|
||||
final currentCharacter:PlayableCharacter;
|
||||
|
||||
/**
|
||||
* For the audio preview, the duration of the fade-in effect.
|
||||
|
@ -205,7 +209,8 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
public function new(?params:FreeplayStateParams, ?stickers:StickerSubState)
|
||||
{
|
||||
currentCharacter = params?.character ?? Constants.DEFAULT_CHARACTER;
|
||||
currentCharacterId = params?.character ?? Constants.DEFAULT_CHARACTER;
|
||||
currentCharacter = PlayerRegistry.instance.fetchEntry(currentCharacterId);
|
||||
|
||||
fromResultsParams = params?.fromResults;
|
||||
|
||||
|
@ -290,11 +295,10 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
// Only display songs which actually have available difficulties for the current character.
|
||||
var displayedVariations = song.getVariationsByCharId(currentCharacter);
|
||||
trace(songId);
|
||||
trace(displayedVariations);
|
||||
var displayedVariations = song.getVariationsByCharacter(currentCharacter);
|
||||
trace('Displayed Variations (${songId}): $displayedVariations');
|
||||
var availableDifficultiesForSong:Array<String> = song.listDifficulties(displayedVariations, false);
|
||||
trace(availableDifficultiesForSong);
|
||||
trace('Available Difficulties: $availableDifficultiesForSong');
|
||||
if (availableDifficultiesForSong.length == 0) continue;
|
||||
|
||||
songs.push(new FreeplaySongData(levelId, songId, song, displayedVariations));
|
||||
|
@ -454,7 +458,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
});
|
||||
|
||||
// TODO: Replace this.
|
||||
if (currentCharacter == 'pico') dj.visible = false;
|
||||
if (currentCharacterId == 'pico') dj.visible = false;
|
||||
|
||||
add(dj);
|
||||
|
||||
|
@ -1195,6 +1199,16 @@ class FreeplayState extends MusicBeatSubState
|
|||
rankAnimStart(fromResultsParams);
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.P)
|
||||
{
|
||||
FlxG.switchState(FreeplayState.build(
|
||||
{
|
||||
{
|
||||
character: currentCharacterId == "pico" ? "bf" : "pico",
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// if (FlxG.keys.justPressed.H)
|
||||
// {
|
||||
// rankDisplayNew(fromResultsParams);
|
||||
|
@ -1302,9 +1316,9 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
if (busy) return;
|
||||
|
||||
var upP:Bool = controls.UI_UP_P && !FlxG.keys.pressed.CONTROL;
|
||||
var downP:Bool = controls.UI_DOWN_P && !FlxG.keys.pressed.CONTROL;
|
||||
var accepted:Bool = controls.ACCEPT && !FlxG.keys.pressed.CONTROL;
|
||||
var upP:Bool = controls.UI_UP_P;
|
||||
var downP:Bool = controls.UI_DOWN_P;
|
||||
var accepted:Bool = controls.ACCEPT;
|
||||
|
||||
if (FlxG.onMobile)
|
||||
{
|
||||
|
@ -1378,7 +1392,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
#end
|
||||
|
||||
if (!FlxG.keys.pressed.CONTROL && (controls.UI_UP || controls.UI_DOWN))
|
||||
if ((controls.UI_UP || controls.UI_DOWN))
|
||||
{
|
||||
if (spamming)
|
||||
{
|
||||
|
@ -1440,13 +1454,13 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
#end
|
||||
|
||||
if (controls.UI_LEFT_P && !FlxG.keys.pressed.CONTROL)
|
||||
if (controls.UI_LEFT_P)
|
||||
{
|
||||
dj.resetAFKTimer();
|
||||
changeDiff(-1);
|
||||
generateSongList(currentFilter, true);
|
||||
}
|
||||
if (controls.UI_RIGHT_P && !FlxG.keys.pressed.CONTROL)
|
||||
if (controls.UI_RIGHT_P)
|
||||
{
|
||||
dj.resetAFKTimer();
|
||||
changeDiff(1);
|
||||
|
@ -1720,7 +1734,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
return;
|
||||
}
|
||||
var targetDifficultyId:String = currentDifficulty;
|
||||
var targetVariation:String = targetSong.getFirstValidVariation(targetDifficultyId);
|
||||
var targetVariation:String = targetSong.getFirstValidVariation(targetDifficultyId, currentCharacter);
|
||||
PlayStatePlaylist.campaignId = cap.songData.levelId;
|
||||
|
||||
var targetDifficulty:SongDifficulty = targetSong.getDifficulty(targetDifficultyId, targetVariation);
|
||||
|
@ -1730,8 +1744,18 @@ class FreeplayState extends MusicBeatSubState
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO: Change this with alternate instrumentals
|
||||
var targetInstId:String = targetDifficulty.characters.instrumental;
|
||||
var baseInstrumentalId:String = targetDifficulty?.characters?.instrumental ?? '';
|
||||
var altInstrumentalIds:Array<String> = targetDifficulty?.characters?.altInstrumentals ?? [];
|
||||
|
||||
var targetInstId:String = baseInstrumentalId;
|
||||
|
||||
// TODO: Make this a UI element.
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
if (altInstrumentalIds.length > 0 && FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
targetInstId = altInstrumentalIds[0];
|
||||
}
|
||||
#end
|
||||
|
||||
// Visual and audio effects.
|
||||
FunkinSound.playOnce(Paths.sound('confirmMenu'));
|
||||
|
@ -1883,9 +1907,23 @@ class FreeplayState extends MusicBeatSubState
|
|||
else
|
||||
{
|
||||
var previewSong:Null<Song> = SongRegistry.instance.fetchEntry(daSongCapsule.songData.songId);
|
||||
var instSuffix:String = previewSong?.getDifficulty(currentDifficulty,
|
||||
previewSong?.getVariationsByCharId(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST)?.characters?.instrumental ?? '';
|
||||
var songDifficulty = previewSong?.getDifficulty(currentDifficulty,
|
||||
previewSong?.getVariationsByCharacter(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST);
|
||||
var baseInstrumentalId:String = songDifficulty?.characters?.instrumental ?? '';
|
||||
var altInstrumentalIds:Array<String> = songDifficulty?.characters?.altInstrumentals ?? [];
|
||||
|
||||
var instSuffix:String = baseInstrumentalId;
|
||||
|
||||
// TODO: Make this a UI element.
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
if (altInstrumentalIds.length > 0 && FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
instSuffix = altInstrumentalIds[0];
|
||||
}
|
||||
#end
|
||||
|
||||
instSuffix = (instSuffix != '') ? '-$instSuffix' : '';
|
||||
|
||||
FunkinSound.playMusic(daSongCapsule.songData.songId,
|
||||
{
|
||||
startingVolume: 0.0,
|
||||
|
@ -1913,7 +1951,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
public static function build(?params:FreeplayStateParams, ?stickers:StickerSubState):MusicBeatState
|
||||
{
|
||||
var result:MainMenuState;
|
||||
if (params?.fromResults.playRankAnim) result = new MainMenuState(true);
|
||||
if (params?.fromResults?.playRankAnim) result = new MainMenuState(true);
|
||||
else
|
||||
result = new MainMenuState(false);
|
||||
|
||||
|
@ -1951,8 +1989,8 @@ class DifficultySelector extends FlxSprite
|
|||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
if (flipX && controls.UI_RIGHT_P && !FlxG.keys.pressed.CONTROL) moveShitDown();
|
||||
if (!flipX && controls.UI_LEFT_P && !FlxG.keys.pressed.CONTROL) moveShitDown();
|
||||
if (flipX && controls.UI_RIGHT_P) moveShitDown();
|
||||
if (!flipX && controls.UI_LEFT_P) moveShitDown();
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
|
108
source/funkin/ui/freeplay/charselect/PlayableCharacter.hx
Normal file
108
source/funkin/ui/freeplay/charselect/PlayableCharacter.hx
Normal file
|
@ -0,0 +1,108 @@
|
|||
package funkin.ui.freeplay.charselect;
|
||||
|
||||
import funkin.data.IRegistryEntry;
|
||||
import funkin.data.freeplay.player.PlayerData;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
|
||||
/**
|
||||
* An object used to retrieve data about a playable character (also known as "weeks").
|
||||
* Can be scripted to override each function, for custom behavior.
|
||||
*/
|
||||
class PlayableCharacter implements IRegistryEntry<PlayerData>
|
||||
{
|
||||
/**
|
||||
* The ID of the playable character.
|
||||
*/
|
||||
public final id:String;
|
||||
|
||||
/**
|
||||
* Playable character data as parsed from the JSON file.
|
||||
*/
|
||||
public final _data:PlayerData;
|
||||
|
||||
/**
|
||||
* @param id The ID of the JSON file to parse.
|
||||
*/
|
||||
public function new(id:String)
|
||||
{
|
||||
this.id = id;
|
||||
_data = _fetchData(id);
|
||||
|
||||
if (_data == null)
|
||||
{
|
||||
throw 'Could not parse playable character data for id: $id';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the readable name of the playable character.
|
||||
*/
|
||||
public function getName():String
|
||||
{
|
||||
// TODO: Maybe add localization support?
|
||||
return _data.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the list of stage character IDs associated with this playable character.
|
||||
* @return The list of associated character IDs
|
||||
*/
|
||||
public function getOwnedCharacterIds():Array<String>
|
||||
{
|
||||
return _data.ownedChars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return `true` if, when this character is selected in Freeplay,
|
||||
* songs unassociated with a specific character should appear.
|
||||
*/
|
||||
public function shouldShowUnownedChars():Bool
|
||||
{
|
||||
return _data.showUnownedChars;
|
||||
}
|
||||
|
||||
public function shouldShowCharacter(id:String):Bool
|
||||
{
|
||||
if (_data.ownedChars.contains(id))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_data.showUnownedChars)
|
||||
{
|
||||
var result = !PlayerRegistry.instance.isCharacterOwned(id);
|
||||
return result;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this character is unlocked.
|
||||
*/
|
||||
public function isUnlocked():Bool
|
||||
{
|
||||
return _data.unlocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the character is destroyed.
|
||||
* TODO: Document when this gets called
|
||||
*/
|
||||
public function destroy():Void {}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'PlayableCharacter($id)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and parse the JSON data for a playable character by ID.
|
||||
* @param id The ID of the character
|
||||
* @return The parsed player data, or null if not found or invalid
|
||||
*/
|
||||
static function _fetchData(id:String):Null<PlayerData>
|
||||
{
|
||||
return PlayerRegistry.instance.parseEntryDataWithMigration(id, PlayerRegistry.instance.fetchEntryVersion(id));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package funkin.ui.freeplay.charselect;
|
||||
|
||||
/**
|
||||
* A script that can be tied to a PlayableCharacter.
|
||||
* Create a scripted class that extends PlayableCharacter to use this.
|
||||
*/
|
||||
@:hscriptClass
|
||||
class ScriptedPlayableCharacter extends funkin.ui.freeplay.charselect.PlayableCharacter implements polymod.hscript.HScriptedClass {}
|
|
@ -24,7 +24,6 @@ class VersionUtil
|
|||
try
|
||||
{
|
||||
var versionRaw:thx.semver.Version.SemVer = version;
|
||||
trace('${versionRaw} satisfies (${versionRule})? ${version.satisfies(versionRule)}');
|
||||
return version.satisfies(versionRule);
|
||||
}
|
||||
catch (e)
|
||||
|
|
Loading…
Reference in a new issue