1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-06-21 18:23:12 +00:00

Add stage data files

This commit is contained in:
Eric Myllyoja 2022-02-23 16:58:23 -05:00
parent af21a56dc0
commit c0c1fb482f
5 changed files with 441 additions and 441 deletions

View file

@ -1,9 +1,9 @@
package play.character; package play.character;
enum CharacterType enum CharacterType
{ {
BF; BF;
GF; GF;
DAD; DAD;
OTHER; OTHER;
} }

View file

@ -1,17 +1,17 @@
package play.stage; package play.stage;
import modding.IHook; import modding.IHook;
/** /**
* NOTE: Turns out one of the few edge case that scripted classes are broken with, * NOTE: Turns out one of the few edge case that scripted classes are broken with,
* that being generic classes with a constrained type argument, applies to FlxSpriteGroup. * that being generic classes with a constrained type argument, applies to FlxSpriteGroup.
* Will have to find a fix for the issue before stages can have scripting enabled. * Will have to find a fix for the issue before stages can have scripting enabled.
* *
* In the meantime though, I want to get stages working just with JSON. * In the meantime though, I want to get stages working just with JSON.
* -Eric * -Eric
*/ */
// @:hscriptClass // @:hscriptClass
// class ScriptedStage extends Stage implements IHook // class ScriptedStage extends Stage implements IHook
// { // {
// // No body needed for this class, it's magic ;) // // No body needed for this class, it's magic ;)
// } // }

View file

@ -1,373 +1,373 @@
package play.stage; package play.stage;
import openfl.Assets; import openfl.Assets;
import util.assets.DataAssets; import util.assets.DataAssets;
import haxe.Json; import haxe.Json;
import flixel.util.typeLimit.OneOfTwo; import flixel.util.typeLimit.OneOfTwo;
using StringTools; using StringTools;
/** /**
* Contains utilities for loading and parsing stage data. * Contains utilities for loading and parsing stage data.
*/ */
class StageDataParser class StageDataParser
{ {
/** /**
* The current version string for the stage data format. * The current version string for the stage data format.
* Handle breaking changes by incrementing this value * Handle breaking changes by incrementing this value
* and adding migration to the `migrateStageData()` function. * and adding migration to the `migrateStageData()` function.
*/ */
public static final STAGE_DATA_VERSION:String = "1.0"; public static final STAGE_DATA_VERSION:String = "1.0";
static final stageCache:Map<String, Stage> = new Map<String, Stage>(); static final stageCache:Map<String, Stage> = new Map<String, Stage>();
public static function loadStageCache():Void public static function loadStageCache():Void
{ {
trace("Loading stage cache..."); trace("Loading stage cache...");
var stageIdList:Array<String> = DataAssets.listDataFilesInPath('stages/'); var stageIdList:Array<String> = DataAssets.listDataFilesInPath('stages/');
for (stageId in stageIdList) for (stageId in stageIdList)
{ {
var stage:Stage = new Stage(stageId); var stage:Stage = new Stage(stageId);
if (stage != null) if (stage != null)
{ {
trace(' Loaded stage data: ${stage.stageName}'); trace(' Loaded stage data: ${stage.stageName}');
stageCache.set(stageId, stage); stageCache.set(stageId, stage);
} }
} }
trace(' Successfully loaded ${Lambda.count(stageCache)} stages.'); trace(' Successfully loaded ${Lambda.count(stageCache)} stages.');
} }
public static function fetchStage(stageId:String):Null<Stage> public static function fetchStage(stageId:String):Null<Stage>
{ {
if (stageCache.exists(stageId)) if (stageCache.exists(stageId))
{ {
trace('Successfully fetch stage: ${stageId}'); trace('Successfully fetch stage: ${stageId}');
return stageCache.get(stageId); return stageCache.get(stageId);
} }
else else
{ {
trace('Failed to fetch stage, not found in cache: ${stageId}'); trace('Failed to fetch stage, not found in cache: ${stageId}');
return null; return null;
} }
} }
/** /**
* Load a stage's JSON file, parse its data, and return it. * Load a stage's JSON file, parse its data, and return it.
* *
* @param stageId The stage to load. * @param stageId The stage to load.
* @return The stage data, or null if validation failed. * @return The stage data, or null if validation failed.
*/ */
public static function parseStageData(stageId:String):Null<StageData> public static function parseStageData(stageId:String):Null<StageData>
{ {
var rawJson:String = loadStageFile(stageId); var rawJson:String = loadStageFile(stageId);
var stageData:StageData = migrateStageData(rawJson); var stageData:StageData = migrateStageData(rawJson);
return validateStageData(stageId, stageData); return validateStageData(stageId, stageData);
} }
static function loadStageFile(stagePath:String):String static function loadStageFile(stagePath:String):String
{ {
var stageFilePath:String = Paths.json('stages/${stagePath}'); var stageFilePath:String = Paths.json('stages/${stagePath}');
var rawJson = Assets.getText(stageFilePath).trim(); var rawJson = Assets.getText(stageFilePath).trim();
while (!rawJson.endsWith("}")) while (!rawJson.endsWith("}"))
{ {
rawJson = rawJson.substr(0, rawJson.length - 1); rawJson = rawJson.substr(0, rawJson.length - 1);
} }
return rawJson; return rawJson;
} }
static function migrateStageData(rawJson:String) static function migrateStageData(rawJson:String)
{ {
// If you update the stage data format in a breaking way, // If you update the stage data format in a breaking way,
// handle migration here by checking the `version` value. // handle migration here by checking the `version` value.
var stageData:StageData = cast Json.parse(rawJson); var stageData:StageData = cast Json.parse(rawJson);
return stageData; return stageData;
} }
static final DEFAULT_NAME:String = "Untitled Stage"; static final DEFAULT_NAME:String = "Untitled Stage";
static final DEFAULT_CAMERAZOOM:Float = 1.0; static final DEFAULT_CAMERAZOOM:Float = 1.0;
static final DEFAULT_ZINDEX:Int = 0; static final DEFAULT_ZINDEX:Int = 0;
static final DEFAULT_SCALE:Float = 1.0; static final DEFAULT_SCALE:Float = 1.0;
static final DEFAULT_POSITION:Array<Float> = [0, 0]; static final DEFAULT_POSITION:Array<Float> = [0, 0];
static final DEFAULT_SCROLL:Array<Float> = [0, 0]; static final DEFAULT_SCROLL:Array<Float> = [0, 0];
static final DEFAULT_CHARACTER_DATA:StageDataCharacter = { static final DEFAULT_CHARACTER_DATA:StageDataCharacter = {
zIndex: DEFAULT_ZINDEX, zIndex: DEFAULT_ZINDEX,
position: DEFAULT_POSITION, position: DEFAULT_POSITION,
} }
/** /**
* Set unspecified parameters to their defaults. * Set unspecified parameters to their defaults.
* If the parameter is mandatory, print an error message. * If the parameter is mandatory, print an error message.
* @param id * @param id
* @param input * @param input
* @return The validated stage data * @return The validated stage data
*/ */
static function validateStageData(id:String, input:StageData):Null<StageData> static function validateStageData(id:String, input:StageData):Null<StageData>
{ {
if (input.version != STAGE_DATA_VERSION) if (input.version != STAGE_DATA_VERSION)
{ {
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing version'); trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing version');
return null; return null;
} }
if (input.name == null) if (input.name == null)
{ {
trace('[STAGEDATA] WARN: Stage data for "$id" missing name'); trace('[STAGEDATA] WARN: Stage data for "$id" missing name');
input.name = DEFAULT_NAME; input.name = DEFAULT_NAME;
} }
if (input.cameraZoom == null) if (input.cameraZoom == null)
{ {
input.cameraZoom = DEFAULT_CAMERAZOOM; input.cameraZoom = DEFAULT_CAMERAZOOM;
} }
if (input.props == null || input.props.length == 0) if (input.props == null || input.props.length == 0)
{ {
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing props'); trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing props');
return null; return null;
} }
for (inputProp in input.props) for (inputProp in input.props)
{ {
// It's fine for inputProp.name to be null // It's fine for inputProp.name to be null
if (inputProp.assetPath == null) if (inputProp.assetPath == null)
{ {
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing assetPath for prop "${inputProp.name}"'); trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing assetPath for prop "${inputProp.name}"');
return null; return null;
} }
if (inputProp.position == null) if (inputProp.position == null)
{ {
inputProp.position = DEFAULT_POSITION; inputProp.position = DEFAULT_POSITION;
} }
if (inputProp.zIndex == null) if (inputProp.zIndex == null)
{ {
inputProp.zIndex = DEFAULT_ZINDEX; inputProp.zIndex = DEFAULT_ZINDEX;
} }
if (inputProp.scale == null) if (inputProp.scale == null)
{ {
inputProp.scale = DEFAULT_SCALE; inputProp.scale = DEFAULT_SCALE;
} }
if (Std.isOfType(inputProp.scale, Float)) if (Std.isOfType(inputProp.scale, Float))
{ {
inputProp.scale = [inputProp.scale, inputProp.scale]; inputProp.scale = [inputProp.scale, inputProp.scale];
} }
if (inputProp.scroll == null) if (inputProp.scroll == null)
{ {
inputProp.scroll = DEFAULT_SCROLL; inputProp.scroll = DEFAULT_SCROLL;
} }
if (Std.isOfType(inputProp.scroll, Float)) if (Std.isOfType(inputProp.scroll, Float))
{ {
inputProp.scroll = [inputProp.scroll, inputProp.scroll]; inputProp.scroll = [inputProp.scroll, inputProp.scroll];
} }
if (inputProp.animations == null) if (inputProp.animations == null)
{ {
inputProp.animations = []; inputProp.animations = [];
} }
if (inputProp.animations.length == 0 && inputProp.startingAnimation != null) if (inputProp.animations.length == 0 && inputProp.startingAnimation != null)
{ {
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing animations for prop "${inputProp.name}"'); trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing animations for prop "${inputProp.name}"');
return null; return null;
} }
for (inputAnimation in inputProp.animations) for (inputAnimation in inputProp.animations)
{ {
if (inputAnimation.name == null) if (inputAnimation.name == null)
{ {
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing animation name for prop "${inputProp.name}"'); trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing animation name for prop "${inputProp.name}"');
return null; return null;
} }
if (inputAnimation.frameRate == null) if (inputAnimation.frameRate == null)
{ {
inputAnimation.frameRate = 24; inputAnimation.frameRate = 24;
} }
if (inputAnimation.loop == null) if (inputAnimation.loop == null)
{ {
inputAnimation.loop = true; inputAnimation.loop = true;
} }
if (inputAnimation.flipX == null) if (inputAnimation.flipX == null)
{ {
inputAnimation.flipX = false; inputAnimation.flipX = false;
} }
if (inputAnimation.flipY == null) if (inputAnimation.flipY == null)
{ {
inputAnimation.flipY = false; inputAnimation.flipY = false;
} }
} }
} }
if (input.characters == null) if (input.characters == null)
{ {
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing characters'); trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing characters');
return null; return null;
} }
if (input.characters.bf == null) if (input.characters.bf == null)
{ {
input.characters.bf = DEFAULT_CHARACTER_DATA; input.characters.bf = DEFAULT_CHARACTER_DATA;
} }
if (input.characters.dad == null) if (input.characters.dad == null)
{ {
input.characters.dad = DEFAULT_CHARACTER_DATA; input.characters.dad = DEFAULT_CHARACTER_DATA;
} }
if (input.characters.gf == null) if (input.characters.gf == null)
{ {
input.characters.gf = DEFAULT_CHARACTER_DATA; input.characters.gf = DEFAULT_CHARACTER_DATA;
} }
for (inputCharacter in [input.characters.bf, input.characters.dad, input.characters.gf]) for (inputCharacter in [input.characters.bf, input.characters.dad, input.characters.gf])
{ {
if (inputCharacter.zIndex == null) if (inputCharacter.zIndex == null)
{ {
inputCharacter.zIndex = 0; inputCharacter.zIndex = 0;
} }
if (inputCharacter.position == null || inputCharacter.position.length != 2) if (inputCharacter.position == null || inputCharacter.position.length != 2)
{ {
inputCharacter.position = [0, 0]; inputCharacter.position = [0, 0];
} }
} }
// All good! // All good!
return input; return input;
} }
} }
typedef StageData = typedef StageData =
{ {
// Uses semantic versioning. // Uses semantic versioning.
var version:String; var version:String;
var name:String; var name:String;
var cameraZoom:Null<Float>; var cameraZoom:Null<Float>;
var props:Array<StageDataProp>; var props:Array<StageDataProp>;
var characters: var characters:
{ {
bf:StageDataCharacter, bf:StageDataCharacter,
dad:StageDataCharacter, dad:StageDataCharacter,
gf:StageDataCharacter, gf:StageDataCharacter,
}; };
}; };
typedef StageDataProp = typedef StageDataProp =
{ {
/** /**
* The name of the prop for later lookup by scripts. * The name of the prop for later lookup by scripts.
* Optional; if unspecified, the prop can't be referenced by scripts. * Optional; if unspecified, the prop can't be referenced by scripts.
*/ */
var name:String; var name:String;
/** /**
* The asset used to display the prop. * The asset used to display the prop.
*/ */
var assetPath:String; var assetPath:String;
/** /**
* The position of the prop as an [x, y] array of two floats. * The position of the prop as an [x, y] array of two floats.
*/ */
var position:Array<Float>; var position:Array<Float>;
/** /**
* A number determining the stack order of the prop, relative to other props and the characters in the stage. * 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. * Props with lower numbers render below those with higher numbers.
* This is just like CSS, it isn't hard. * This is just like CSS, it isn't hard.
* @default 0 * @default 0
*/ */
var zIndex:Null<Int>; var zIndex:Null<Int>;
/** /**
* Either the scale of the prop as a float, or the [w, h] scale as an array of two floats. * Either the scale of the prop as a float, or the [w, h] scale as an array of two floats.
* @default 1 * @default 1
*/ */
var scale:OneOfTwo<Float, Array<Float>>; var scale:OneOfTwo<Float, Array<Float>>;
/** /**
* How much the prop scrolls relative to the camera. Used to create a parallax effect. * 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. * Represented as a float or as an [x, y] array of two floats.
* [1, 1] means the prop moves 1:1 with the camera. * [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.5, 0.5] means the prop half as much as the camera.
* [0, 0] means the prop is not moved. * [0, 0] means the prop is not moved.
* @default [0, 0] * @default [0, 0]
*/ */
var scroll:OneOfTwo<Float, Array<Float>>; var scroll:OneOfTwo<Float, Array<Float>>;
/** /**
* An optional array of animations which the prop can play. * An optional array of animations which the prop can play.
* @default Prop has no animations. * @default Prop has no animations.
*/ */
var animations:Array<StageDataPropAnimation>; var animations:Array<StageDataPropAnimation>;
/** /**
* If animations are used, this is the name of the animation to play first. * If animations are used, this is the name of the animation to play first.
* @default Don't play an animation. * @default Don't play an animation.
*/ */
var startingAnimation:String; var startingAnimation:String;
}; };
typedef StageDataPropAnimation = typedef StageDataPropAnimation =
{ {
/** /**
* The name of the animation. * The name of the animation.
*/ */
var name:String; var name:String;
/** /**
* The common beginning of image names in atlas for this animation's frames. * The common beginning of image names in atlas for this animation's frames.
* For example, if the frames are named "test0001.png", "test0002.png", etc., use "test". * For example, if the frames are named "test0001.png", "test0002.png", etc., use "test".
*/ */
var prefix:String; var prefix:String;
/** /**
* The speed of the animation in frames per second. * The speed of the animation in frames per second.
* @default 24 * @default 24
*/ */
var frameRate:Null<Int>; var frameRate:Null<Int>;
/** /**
* Whether the animation should loop. * Whether the animation should loop.
* @default false * @default false
*/ */
var loop:Null<Bool>; var loop:Null<Bool>;
/** /**
* Whether to flip the sprite horizontally while animating. * Whether to flip the sprite horizontally while animating.
* @default false * @default false
*/ */
var flipX:Null<Bool>; var flipX:Null<Bool>;
/** /**
* Whether to flip the sprite vertically while animating. * Whether to flip the sprite vertically while animating.
* @default false * @default false
*/ */
var flipY:Null<Bool>; var flipY:Null<Bool>;
}; };
typedef StageDataCharacter = typedef StageDataCharacter =
{ {
/** /**
* A number determining the stack order of the character, relative to props and other characters in the stage. * A number determining the stack order of the character, relative to props and other characters in the stage.
* Again, just like CSS. * Again, just like CSS.
* @default 0 * @default 0
*/ */
zIndex:Null<Int>, zIndex:Null<Int>,
/** /**
* The position to render the character at. * The position to render the character at.
*/ position:Array<Float> */ position:Array<Float>
}; };

View file

@ -1,30 +1,30 @@
package util.assets; package util.assets;
using StringTools; using StringTools;
class DataAssets class DataAssets
{ {
static function buildDataPath(path:String):String static function buildDataPath(path:String):String
{ {
return 'assets/data/${path}'; return 'assets/data/${path}';
} }
public static function listDataFilesInPath(path:String, ?ext:String = '.json'):Array<String> public static function listDataFilesInPath(path:String, ?ext:String = '.json'):Array<String>
{ {
var textAssets = openfl.utils.Assets.list(); var textAssets = openfl.utils.Assets.list();
var queryPath = buildDataPath(path); var queryPath = buildDataPath(path);
var results:Array<String> = []; var results:Array<String> = [];
for (textPath in textAssets) for (textPath in textAssets)
{ {
if (textPath.startsWith(queryPath) && textPath.endsWith(ext)) if (textPath.startsWith(queryPath) && textPath.endsWith(ext))
{ {
var pathNoSuffix = textPath.substring(0, textPath.length - ext.length); var pathNoSuffix = textPath.substring(0, textPath.length - ext.length);
var pathNoPrefix = pathNoSuffix.substring(queryPath.length); var pathNoPrefix = pathNoSuffix.substring(queryPath.length);
results.push(pathNoPrefix); results.push(pathNoPrefix);
} }
} }
return results; return results;
} }
} }

View file

@ -1,12 +1,12 @@
package util.macro; package util.macro;
class MacroUtil class MacroUtil
{ {
public static macro function getDefine(key:String, defaultValue:String = null):haxe.macro.Expr public static macro function getDefine(key:String, defaultValue:String = null):haxe.macro.Expr
{ {
var value = haxe.macro.Context.definedValue(key); var value = haxe.macro.Context.definedValue(key);
if (value == null) if (value == null)
value = defaultValue; value = defaultValue;
return macro $v{value}; return macro $v{value};
} }
} }