From c0c1fb482f2584a274e505ca6b0bd0217d710eb3 Mon Sep 17 00:00:00 2001 From: Eric Myllyoja <ericmyllyoja@gmail.com> Date: Wed, 23 Feb 2022 16:58:23 -0500 Subject: [PATCH] Add stage data files --- source/play/character/Character.hx | 18 +- source/play/stage/ScriptedStage.hx | 34 +- source/play/stage/StageData.hx | 746 ++++++++++++++--------------- source/util/assets/DataAssets.hx | 60 +-- source/util/macro/MacroUtil.hx | 24 +- 5 files changed, 441 insertions(+), 441 deletions(-) diff --git a/source/play/character/Character.hx b/source/play/character/Character.hx index ec8ec8a30..b4ee9bb03 100644 --- a/source/play/character/Character.hx +++ b/source/play/character/Character.hx @@ -1,9 +1,9 @@ -package play.character; - -enum CharacterType -{ - BF; - GF; - DAD; - OTHER; -} +package play.character; + +enum CharacterType +{ + BF; + GF; + DAD; + OTHER; +} diff --git a/source/play/stage/ScriptedStage.hx b/source/play/stage/ScriptedStage.hx index d43579097..40bbdb30e 100644 --- a/source/play/stage/ScriptedStage.hx +++ b/source/play/stage/ScriptedStage.hx @@ -1,17 +1,17 @@ -package play.stage; - -import modding.IHook; - -/** - * 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. - * 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. - * -Eric - */ -// @:hscriptClass -// class ScriptedStage extends Stage implements IHook -// { -// // No body needed for this class, it's magic ;) -// } +package play.stage; + +import modding.IHook; + +/** + * 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. + * 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. + * -Eric + */ +// @:hscriptClass +// class ScriptedStage extends Stage implements IHook +// { +// // No body needed for this class, it's magic ;) +// } diff --git a/source/play/stage/StageData.hx b/source/play/stage/StageData.hx index ba97b3140..0ecf3a8cf 100644 --- a/source/play/stage/StageData.hx +++ b/source/play/stage/StageData.hx @@ -1,373 +1,373 @@ -package play.stage; - -import openfl.Assets; -import util.assets.DataAssets; -import haxe.Json; -import flixel.util.typeLimit.OneOfTwo; - -using StringTools; - -/** - * 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"; - - static final stageCache:Map<String, Stage> = new Map<String, Stage>(); - - public static function loadStageCache():Void - { - trace("Loading stage cache..."); - var stageIdList:Array<String> = DataAssets.listDataFilesInPath('stages/'); - for (stageId in stageIdList) - { - var stage:Stage = new Stage(stageId); - if (stage != null) - { - trace(' Loaded stage data: ${stage.stageName}'); - stageCache.set(stageId, stage); - } - } - trace(' Successfully loaded ${Lambda.count(stageCache)} stages.'); - } - - public static function fetchStage(stageId:String):Null<Stage> - { - if (stageCache.exists(stageId)) - { - trace('Successfully fetch stage: ${stageId}'); - return stageCache.get(stageId); - } - else - { - trace('Failed to fetch stage, not found in cache: ${stageId}'); - return null; - } - } - - /** - * 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); - - return validateStageData(stageId, stageData); - } - - 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) - { - // If you update the stage data format in a breaking way, - // handle migration here by checking the `version` value. - - var stageData:StageData = cast Json.parse(rawJson); - - return stageData; - } - - static final DEFAULT_NAME:String = "Untitled Stage"; - static final DEFAULT_CAMERAZOOM:Float = 1.0; - static final DEFAULT_ZINDEX:Int = 0; - static final DEFAULT_SCALE:Float = 1.0; - static final DEFAULT_POSITION:Array<Float> = [0, 0]; - static final DEFAULT_SCROLL:Array<Float> = [0, 0]; - - static final DEFAULT_CHARACTER_DATA:StageDataCharacter = { - zIndex: DEFAULT_ZINDEX, - position: DEFAULT_POSITION, - } - - /** - * 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.version != STAGE_DATA_VERSION) - { - trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing version'); - return null; - } - - if (input.name == null) - { - trace('[STAGEDATA] 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.length == 0) - { - trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing props'); - return null; - } - - for (inputProp in input.props) - { - // It's fine for inputProp.name to be null - - if (inputProp.assetPath == null) - { - trace('[STAGEDATA] 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.scale == null) - { - inputProp.scale = DEFAULT_SCALE; - } - - if (Std.isOfType(inputProp.scale, Float)) - { - inputProp.scale = [inputProp.scale, inputProp.scale]; - } - - if (inputProp.scroll == null) - { - inputProp.scroll = DEFAULT_SCROLL; - } - - if (Std.isOfType(inputProp.scroll, Float)) - { - inputProp.scroll = [inputProp.scroll, inputProp.scroll]; - } - - if (inputProp.animations == null) - { - inputProp.animations = []; - } - - if (inputProp.animations.length == 0 && inputProp.startingAnimation != null) - { - trace('[STAGEDATA] 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('[STAGEDATA] 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.loop == null) - { - inputAnimation.loop = true; - } - - if (inputAnimation.flipX == null) - { - inputAnimation.flipX = false; - } - - if (inputAnimation.flipY == null) - { - inputAnimation.flipY = false; - } - } - } - - if (input.characters == null) - { - trace('[STAGEDATA] 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.zIndex == null) - { - inputCharacter.zIndex = 0; - } - if (inputCharacter.position == null || inputCharacter.position.length != 2) - { - inputCharacter.position = [0, 0]; - } - } - - // All good! - return input; - } -} - -typedef StageData = -{ - // Uses semantic versioning. - var version:String; - var name:String; - var cameraZoom:Null<Float>; - var props:Array<StageDataProp>; - var characters: - { - bf:StageDataCharacter, - dad:StageDataCharacter, - 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. - */ - 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 - */ - var zIndex:Null<Int>; - - /** - * Either the scale of the prop as a float, or the [w, h] scale as an array of two floats. - * @default 1 - */ - var scale:OneOfTwo<Float, Array<Float>>; - - /** - * 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] - */ - var scroll:OneOfTwo<Float, Array<Float>>; - - /** - * An optional array of animations which the prop can play. - * @default Prop has no animations. - */ - var animations:Array<StageDataPropAnimation>; - - /** - * If animations are used, this is the name of the animation to play first. - * @default Don't play an animation. - */ - var startingAnimation:String; -}; - -typedef StageDataPropAnimation = -{ - /** - * The name of the animation. - */ - var name:String; - - /** - * 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". - */ - var prefix:String; - - /** - * The speed of the animation in frames per second. - * @default 24 - */ - var frameRate:Null<Int>; - - /** - * Whether the animation should loop. - * @default false - */ - var loop:Null<Bool>; - - /** - * Whether to flip the sprite horizontally while animating. - * @default false - */ - var flipX:Null<Bool>; - - /** - * Whether to flip the sprite vertically while animating. - * @default false - */ - var flipY:Null<Bool>; -}; - -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 - */ - zIndex:Null<Int>, - - /** - * The position to render the character at. - */ position:Array<Float> -}; +package play.stage; + +import openfl.Assets; +import util.assets.DataAssets; +import haxe.Json; +import flixel.util.typeLimit.OneOfTwo; + +using StringTools; + +/** + * 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"; + + static final stageCache:Map<String, Stage> = new Map<String, Stage>(); + + public static function loadStageCache():Void + { + trace("Loading stage cache..."); + var stageIdList:Array<String> = DataAssets.listDataFilesInPath('stages/'); + for (stageId in stageIdList) + { + var stage:Stage = new Stage(stageId); + if (stage != null) + { + trace(' Loaded stage data: ${stage.stageName}'); + stageCache.set(stageId, stage); + } + } + trace(' Successfully loaded ${Lambda.count(stageCache)} stages.'); + } + + public static function fetchStage(stageId:String):Null<Stage> + { + if (stageCache.exists(stageId)) + { + trace('Successfully fetch stage: ${stageId}'); + return stageCache.get(stageId); + } + else + { + trace('Failed to fetch stage, not found in cache: ${stageId}'); + return null; + } + } + + /** + * 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); + + return validateStageData(stageId, stageData); + } + + 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) + { + // If you update the stage data format in a breaking way, + // handle migration here by checking the `version` value. + + var stageData:StageData = cast Json.parse(rawJson); + + return stageData; + } + + static final DEFAULT_NAME:String = "Untitled Stage"; + static final DEFAULT_CAMERAZOOM:Float = 1.0; + static final DEFAULT_ZINDEX:Int = 0; + static final DEFAULT_SCALE:Float = 1.0; + static final DEFAULT_POSITION:Array<Float> = [0, 0]; + static final DEFAULT_SCROLL:Array<Float> = [0, 0]; + + static final DEFAULT_CHARACTER_DATA:StageDataCharacter = { + zIndex: DEFAULT_ZINDEX, + position: DEFAULT_POSITION, + } + + /** + * 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.version != STAGE_DATA_VERSION) + { + trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing version'); + return null; + } + + if (input.name == null) + { + trace('[STAGEDATA] 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.length == 0) + { + trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing props'); + return null; + } + + for (inputProp in input.props) + { + // It's fine for inputProp.name to be null + + if (inputProp.assetPath == null) + { + trace('[STAGEDATA] 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.scale == null) + { + inputProp.scale = DEFAULT_SCALE; + } + + if (Std.isOfType(inputProp.scale, Float)) + { + inputProp.scale = [inputProp.scale, inputProp.scale]; + } + + if (inputProp.scroll == null) + { + inputProp.scroll = DEFAULT_SCROLL; + } + + if (Std.isOfType(inputProp.scroll, Float)) + { + inputProp.scroll = [inputProp.scroll, inputProp.scroll]; + } + + if (inputProp.animations == null) + { + inputProp.animations = []; + } + + if (inputProp.animations.length == 0 && inputProp.startingAnimation != null) + { + trace('[STAGEDATA] 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('[STAGEDATA] 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.loop == null) + { + inputAnimation.loop = true; + } + + if (inputAnimation.flipX == null) + { + inputAnimation.flipX = false; + } + + if (inputAnimation.flipY == null) + { + inputAnimation.flipY = false; + } + } + } + + if (input.characters == null) + { + trace('[STAGEDATA] 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.zIndex == null) + { + inputCharacter.zIndex = 0; + } + if (inputCharacter.position == null || inputCharacter.position.length != 2) + { + inputCharacter.position = [0, 0]; + } + } + + // All good! + return input; + } +} + +typedef StageData = +{ + // Uses semantic versioning. + var version:String; + var name:String; + var cameraZoom:Null<Float>; + var props:Array<StageDataProp>; + var characters: + { + bf:StageDataCharacter, + dad:StageDataCharacter, + 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. + */ + 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 + */ + var zIndex:Null<Int>; + + /** + * Either the scale of the prop as a float, or the [w, h] scale as an array of two floats. + * @default 1 + */ + var scale:OneOfTwo<Float, Array<Float>>; + + /** + * 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] + */ + var scroll:OneOfTwo<Float, Array<Float>>; + + /** + * An optional array of animations which the prop can play. + * @default Prop has no animations. + */ + var animations:Array<StageDataPropAnimation>; + + /** + * If animations are used, this is the name of the animation to play first. + * @default Don't play an animation. + */ + var startingAnimation:String; +}; + +typedef StageDataPropAnimation = +{ + /** + * The name of the animation. + */ + var name:String; + + /** + * 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". + */ + var prefix:String; + + /** + * The speed of the animation in frames per second. + * @default 24 + */ + var frameRate:Null<Int>; + + /** + * Whether the animation should loop. + * @default false + */ + var loop:Null<Bool>; + + /** + * Whether to flip the sprite horizontally while animating. + * @default false + */ + var flipX:Null<Bool>; + + /** + * Whether to flip the sprite vertically while animating. + * @default false + */ + var flipY:Null<Bool>; +}; + +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 + */ + zIndex:Null<Int>, + + /** + * The position to render the character at. + */ position:Array<Float> +}; diff --git a/source/util/assets/DataAssets.hx b/source/util/assets/DataAssets.hx index 219bcbfc0..d35cd7ab6 100644 --- a/source/util/assets/DataAssets.hx +++ b/source/util/assets/DataAssets.hx @@ -1,30 +1,30 @@ -package util.assets; - -using StringTools; - -class DataAssets -{ - static function buildDataPath(path:String):String - { - return 'assets/data/${path}'; - } - - public static function listDataFilesInPath(path:String, ?ext:String = '.json'):Array<String> - { - var textAssets = openfl.utils.Assets.list(); - var queryPath = buildDataPath(path); - - var results:Array<String> = []; - for (textPath in textAssets) - { - if (textPath.startsWith(queryPath) && textPath.endsWith(ext)) - { - var pathNoSuffix = textPath.substring(0, textPath.length - ext.length); - var pathNoPrefix = pathNoSuffix.substring(queryPath.length); - results.push(pathNoPrefix); - } - } - - return results; - } -} +package util.assets; + +using StringTools; + +class DataAssets +{ + static function buildDataPath(path:String):String + { + return 'assets/data/${path}'; + } + + public static function listDataFilesInPath(path:String, ?ext:String = '.json'):Array<String> + { + var textAssets = openfl.utils.Assets.list(); + var queryPath = buildDataPath(path); + + var results:Array<String> = []; + for (textPath in textAssets) + { + if (textPath.startsWith(queryPath) && textPath.endsWith(ext)) + { + var pathNoSuffix = textPath.substring(0, textPath.length - ext.length); + var pathNoPrefix = pathNoSuffix.substring(queryPath.length); + results.push(pathNoPrefix); + } + } + + return results; + } +} diff --git a/source/util/macro/MacroUtil.hx b/source/util/macro/MacroUtil.hx index 5c9a3edb7..96f86223b 100644 --- a/source/util/macro/MacroUtil.hx +++ b/source/util/macro/MacroUtil.hx @@ -1,12 +1,12 @@ -package util.macro; - -class MacroUtil -{ - public static macro function getDefine(key:String, defaultValue:String = null):haxe.macro.Expr - { - var value = haxe.macro.Context.definedValue(key); - if (value == null) - value = defaultValue; - return macro $v{value}; - } -} +package util.macro; + +class MacroUtil +{ + public static macro function getDefine(key:String, defaultValue:String = null):haxe.macro.Expr + { + var value = haxe.macro.Context.definedValue(key); + if (value == null) + value = defaultValue; + return macro $v{value}; + } +}