UNPKG

@bscotch/yy

Version:

Stringify, parse, read, and write GameMaker yy and yyp files.

440 lines 17 kB
// Generated by ts-to-zod import { v4 as uuidV4 } from 'uuid'; import { z } from 'zod'; import { yyBaseSchema } from './YyBase.js'; import { ensureTrackKeyFrames } from './YySprite.lib.js'; import { FixedNumber, ensureObject, ensureObjects, fixedNumber, unstable, yyResourceIdSchemaGenerator, } from './utility.js'; export var SpriteType; (function (SpriteType) { SpriteType[SpriteType["Default"] = 0] = "Default"; SpriteType[SpriteType["Spine"] = 2] = "Spine"; })(SpriteType || (SpriteType = {})); export var SpriteLayerBlendMode; (function (SpriteLayerBlendMode) { SpriteLayerBlendMode[SpriteLayerBlendMode["Normal"] = 0] = "Normal"; SpriteLayerBlendMode[SpriteLayerBlendMode["Add"] = 1] = "Add"; SpriteLayerBlendMode[SpriteLayerBlendMode["Subtract"] = 2] = "Subtract"; SpriteLayerBlendMode[SpriteLayerBlendMode["Multiply"] = 3] = "Multiply"; })(SpriteLayerBlendMode || (SpriteLayerBlendMode = {})); export var SpriteCollisionKind; (function (SpriteCollisionKind) { SpriteCollisionKind[SpriteCollisionKind["Precise"] = 0] = "Precise"; SpriteCollisionKind[SpriteCollisionKind["Rectangle"] = 1] = "Rectangle"; SpriteCollisionKind[SpriteCollisionKind["Ellipse"] = 2] = "Ellipse"; SpriteCollisionKind[SpriteCollisionKind["Diamond"] = 3] = "Diamond"; SpriteCollisionKind[SpriteCollisionKind["PrecisePerFrame"] = 4] = "PrecisePerFrame"; SpriteCollisionKind[SpriteCollisionKind["RectangleWithRotation"] = 5] = "RectangleWithRotation"; SpriteCollisionKind[SpriteCollisionKind["SpineCollisionMesh"] = 6] = "SpineCollisionMesh"; })(SpriteCollisionKind || (SpriteCollisionKind = {})); export var SpriteBoundingBoxMode; (function (SpriteBoundingBoxMode) { /** what does this imply about bboxes? */ SpriteBoundingBoxMode[SpriteBoundingBoxMode["Automatic"] = 0] = "Automatic"; SpriteBoundingBoxMode[SpriteBoundingBoxMode["FullImage"] = 1] = "FullImage"; SpriteBoundingBoxMode[SpriteBoundingBoxMode["Manual"] = 2] = "Manual"; })(SpriteBoundingBoxMode || (SpriteBoundingBoxMode = {})); export var SpriteOrigin; (function (SpriteOrigin) { SpriteOrigin[SpriteOrigin["TopLeft"] = 0] = "TopLeft"; SpriteOrigin[SpriteOrigin["TopCenter"] = 1] = "TopCenter"; SpriteOrigin[SpriteOrigin["TopRight"] = 2] = "TopRight"; SpriteOrigin[SpriteOrigin["MiddleLeft"] = 3] = "MiddleLeft"; SpriteOrigin[SpriteOrigin["MiddleCenter"] = 4] = "MiddleCenter"; SpriteOrigin[SpriteOrigin["MiddleRight"] = 5] = "MiddleRight"; SpriteOrigin[SpriteOrigin["BottomLeft"] = 6] = "BottomLeft"; SpriteOrigin[SpriteOrigin["BottomCenter"] = 7] = "BottomCenter"; SpriteOrigin[SpriteOrigin["BottomRight"] = 8] = "BottomRight"; SpriteOrigin[SpriteOrigin["Custom"] = 9] = "Custom"; })(SpriteOrigin || (SpriteOrigin = {})); export var SpritePlaybackSpeedType; (function (SpritePlaybackSpeedType) { SpritePlaybackSpeedType[SpritePlaybackSpeedType["FramesPerSecond"] = 0] = "FramesPerSecond"; SpritePlaybackSpeedType[SpritePlaybackSpeedType["FramesPerGameFrame"] = 1] = "FramesPerGameFrame"; })(SpritePlaybackSpeedType || (SpritePlaybackSpeedType = {})); const spriteCollisionKindSchema = z.nativeEnum(SpriteCollisionKind); const spriteBoundingBoxModeSchema = z.nativeEnum(SpriteBoundingBoxMode); const spriteOriginSchema = z.nativeEnum(SpriteOrigin); const spritePlaybackSpeedTypeSchema = z.nativeEnum(SpritePlaybackSpeedType); const spriteImageBaseSchema = unstable({ FrameId: z.object({ name: z.string(), /** Path to the sprite's .yy file */ path: z.string(), }), resourceVersion: z.literal('1.0').default('1.0'), name: z.string().default("''"), /** Seems to always be empty */ tags: z.array(z.string()).optional(), resourceType: z.literal('GMSpriteBitmap').default('GMSpriteBitmap'), }); const spriteImageSchema = spriteImageBaseSchema.extend({ LayerId: z.object({ /** * Name of the layer. Corresponds to an image in each layer folder, * and should be found in once in *each frame*. Must be found in the * sprite's root "layers" list. */ name: z.string(), /** Path to the sprite's .yy file */ path: z.string(), }), }); const spriteCompositeImageSchema = spriteImageBaseSchema.extend({ LayerId: z.null(), }); const spriteFrameSchema = unstable({ /** * Unique GUID. Matches the name of an image file (+'.png') * that sits alongside the .yy file. Also matches a corresponding * folder name inside the "layers" folder. The Composite image * and each one listed in 'images' all have the same value here * for their "FrameId.name" field. */ name: z.string().default(uuidV4), tags: z.array(z.string()).optional(), resourceType: z.literal('GMSpriteFrame').default('GMSpriteFrame'), /** * Image created by flattening layers. * * Automatically generated by the IDE for resourceVersion 1.0, * removed in 1.1. */ compositeImage: spriteCompositeImageSchema.optional(), /** * One image per layer. * * Automatically generated by the IDE for resourceVersion 1.0, * removed in 1.1. */ images: z.array(spriteImageSchema).optional(), /** * The parent sprite, same as the sprite's ID from the YYP. * * Automatically generated by the IDE for resourceVersion 1.0, * removed in 1.1. */ parent: z .object({ name: z.string(), path: z.string(), }) .optional(), resourceVersion: z.string().default('1.1'), }); const spriteLayerBlendModeSchema = z.nativeEnum(SpriteLayerBlendMode); const spriteImageLayerSchema = unstable({ visible: z.boolean().default(true), isLocked: z.boolean().default(false), blendMode: spriteLayerBlendModeSchema.default(0), opacity: fixedNumber(z.number().min(0).max(100)).default(100), displayName: z.string().default('default'), resourceVersion: z.string().default('1.0'), /** * The unique GUID for this layer, used by Frames in their LayerId field. */ name: z.string().default(uuidV4), /** Seems to be unused -- always an empty array. */ tags: z.array(z.string()).optional(), resourceType: z.literal('GMImageLayer').default('GMImageLayer'), }); const spriteFolderLayerSchema = unstable({ resourceType: z.literal('GMImageFolderLayer').default('GMImageFolderLayer'), resourceVersion: z.string().default('1.0'), name: z.string().default(uuidV4), blendMode: spriteLayerBlendModeSchema.default(0), displayName: z.string().default('Layer Group'), isLocked: z.boolean().default(false), opacity: fixedNumber(z.number().min(0).max(100)).default(100), visible: z.boolean().default(true), layers: z.array(spriteImageLayerSchema).default([]), }); const spriteLayerSchema = z.preprocess((arg) => { if (typeof arg === 'object' && arg !== null && !('resourceType' in arg)) { // Default to an image layer return { ...arg, resourceType: 'GMImageLayer' }; } return arg; }, z.discriminatedUnion('resourceType', [ spriteImageLayerSchema, spriteFolderLayerSchema, ])); const spriteSequenceTrackKeyframeBaseSchema = z.object({ /** * Unique GUID for the keyframe. */ id: z.string().default(uuidV4), /** * Appears to be the index position within the keyframes array */ Key: fixedNumber(z.number().min(0)).default(0), /** Seems to always be 1? */ Length: fixedNumber().default(1), Stretch: z.boolean().default(false), Disabled: z.boolean().default(false), IsCreationKey: z.boolean().default(false), Channels: ensureObject(z.record(z.object({ Id: z .object({ /** Frame/subimage GUID */ name: z.string(), /** Sprite .yy file (e.g. sprites/sprites/thisSprite.yy) */ path: z.string(), }) .default({ name: '', path: '' }), resourceVersion: z.string().default('1.0'), resourceType: z .literal('SpriteFrameKeyframe') .default('SpriteFrameKeyframe'), }))).default({ '0': {} }), resourceVersion: z.string().default('1.0'), }); const spriteSequenceTrackKeyframeTypeSchema = z.discriminatedUnion('resourceType', [ z.object({ resourceType: z.literal('Keyframe`1'), elementType: z.literal('SpriteFrameKeyframe'), }), z.object({ resourceType: z.literal('Keyframe<SpriteFrameKeyframe>'), }), ]); const spriteSequenceTrackKeyframeSchema = spriteSequenceTrackKeyframeBaseSchema.and(spriteSequenceTrackKeyframeTypeSchema); const spriteSequenceTrackKeyframesTypeSchema = z .discriminatedUnion('resourceType', [ z.object({ resourceType: z.literal('KeyframeStore<SpriteFrameKeyframe>'), }), z.object({ resourceType: z.literal('KeyframeStore`1'), elementType: z.literal('SpriteFrameKeyframe'), }), ]) .default({ resourceType: 'KeyframeStore<SpriteFrameKeyframe>' }); const spriteSequenceMomentTypeSchema = z.discriminatedUnion('resourceType', [ z.object({ resourceType: z.literal('KeyframeStore<MomentsEventKeyframe>'), }), z.object({ resourceType: z.literal('KeyframeStore`1'), elementType: z.literal('MomentsEventKeyframe'), }), ]); const spriteSequenceEventTypeSchema = z.discriminatedUnion('resourceType', [ z.object({ resourceType: z.literal('KeyframeStore<MessageEventKeyframe>'), }), z.object({ resourceType: z.literal('KeyframeStore`1'), elementType: z.literal('MessageEventKeyframe'), }), ]); const spriteKeyframesSchema = z.preprocess((input) => { const baseDefault = { resourceType: 'KeyframeStore<SpriteFrameKeyframe>', }; if (input === undefined) { return baseDefault; } else if (typeof input === 'object' && input !== null) { return { ...baseDefault, ...input, }; } return input; }, z .object({ Keyframes: z.preprocess((frames) => { if (frames === undefined) { return []; } if (Array.isArray(frames)) { return frames.map((f) => ({ resourceType: 'Keyframe<SpriteFrameKeyframe>', ...f, })); } return frames; }, z.array(spriteSequenceTrackKeyframeSchema).default([])), resourceVersion: z.string().default('1.0'), }) .and(spriteSequenceTrackKeyframesTypeSchema) .transform((arg) => { arg.Keyframes.forEach((k, i) => { k.Key = new FixedNumber(i); }); return arg; })); const spriteTypeSchema = z.nativeEnum(SpriteType); const spriteSequenceTrackSchema = unstable({ name: z.string().default('frames'), spriteId: z.unknown().nullable().default(null), trackColour: z.number().default(0), inheritsTrackColour: z.boolean().default(true), builtinName: z.number().default(0), traits: z.number().default(0), interpolation: z.number().default(1), tracks: z.array(z.unknown()).default([]), events: z.array(z.unknown()).default([]), modifiers: z.array(z.unknown()).default([]), isCreationTrack: z.boolean().default(false), resourceVersion: z.string().default('1.0'), tags: z.array(z.string()).optional(), resourceType: z.literal('GMSpriteFramesTrack').default('GMSpriteFramesTrack'), keyframes: spriteKeyframesSchema, }); const spriteSequenceSchema = unstable({ timeUnits: z.number().default(1), playback: z.number().default(1), /** * FPS (probably 30, 45, or 60), set via the editor */ playbackSpeed: fixedNumber(z.number().min(0)).default(60), /** * FPS type, set via the editor */ playbackSpeedType: spritePlaybackSpeedTypeSchema.default(0), autoRecord: z.boolean().default(true), volume: fixedNumber().default(1), /** * Number of frames */ length: fixedNumber().default(0), visibleRange: z .object({ x: fixedNumber(), y: fixedNumber(), }) .nullable() .default(null), lockOrigin: z.boolean().default(false), showBackdrop: z.boolean().default(true), showBackdropImage: z.boolean().default(false), backdropImagePath: z.string().default(''), backdropImageOpacity: fixedNumber().default(0), backdropWidth: z.number().default(1366), backdropHeight: z.number().default(768), backdropXOffset: fixedNumber().default(0), backdropYOffset: fixedNumber().default(0), xorigin: z.number().default(0), yorigin: z.number().default(0), eventToFunction: z.unknown().default({}), eventStubScript: z.unknown().default(null), name: z.string().optional(), tags: z.array(z.string()).optional(), resourceType: z.literal('GMSequence').default('GMSequence'), resourceVersion: z.string().default('1.4'), events: ensureObject(z .object({ Keyframes: z.array(z.unknown()).default([]), resourceVersion: z.string().default('1.0'), }) .and(spriteSequenceEventTypeSchema)).default({ resourceType: 'KeyframeStore<MessageEventKeyframe>' }), moments: ensureObject(z .object({ Keyframes: z.array(z.unknown()).default([]), resourceVersion: z.string().default('1.0'), }) .and(spriteSequenceMomentTypeSchema)).default({ resourceType: 'KeyframeStore<MomentsEventKeyframe>' }), tracks: ensureObjects(spriteSequenceTrackSchema), /** * Matches the YYP resource's 'id' value. */ spriteId: yyResourceIdSchemaGenerator('sprites').optional(), parent: yyResourceIdSchemaGenerator('sprites').optional(), }); const yySpriteSchemaStrict = yyBaseSchema .extend({ bboxMode: spriteBoundingBoxModeSchema.default(2), collisionKind: spriteCollisionKindSchema.default(1), /** * The sprite type (Spine or Regular) */ type: spriteTypeSchema.default(0), /** * The method used for specifying the origin */ origin: spriteOriginSchema.default(SpriteOrigin.Custom), preMultiplyAlpha: z.boolean().default(false), edgeFiltering: z.boolean().default(false), /** * Only meaningful if collision type is "Precise". */ collisionTolerance: z.number().min(0).max(255).default(0), /** * (What is this?) */ swfPrecision: z.number().default(2.525), bbox_left: z.number().default(0), bbox_right: z.number().default(0), bbox_top: z.number().default(0), bbox_bottom: z.number().default(0), /** * Horizontally tiled */ HTile: z.boolean().default(false), /** * Vertically tiled */ VTile: z.boolean().default(false), /** * Used for 3d (not sure how set...) */ For3D: z.boolean().default(false), DynamicTexturePage: z.boolean().default(false), width: z.number().default(64), height: z.number().default(64), /** * Matches the texture's id from the YYP file */ textureGroupId: z .object({ /** the name of the Texture Group */ name: z.string(), /** seems to just be `texturegroups/${name}` */ path: z.string(), }) .default({ name: 'Default', path: 'texturegroups/Default' }), swatchColours: z.unknown().default(null), gridX: z.number().default(0), gridY: z.number().default(0), frames: z.array(spriteFrameSchema).default([]), sequence: ensureObject(spriteSequenceSchema), layers: ensureObjects(spriteLayerSchema), resourceType: z.literal('GMSprite').default('GMSprite'), nineSlice: z.unknown().optional().default(null), }) .transform((sprite) => { // Remove any excess keyframes const frameCount = sprite.frames.length; sprite.sequence.tracks[0].keyframes.Keyframes = sprite.sequence.tracks[0].keyframes.Keyframes.slice(0, frameCount); // Ensure that sequence length is a tally of the frames sprite.sequence.length = new FixedNumber(frameCount); // Ensure keyframes map onto frames for (const [i, keyframe,] of sprite.sequence.tracks[0].keyframes.Keyframes.entries()) { keyframe.Channels['0'].Id.name = sprite.frames[i].name; } // Ensure starting size sprite.width ||= 64; sprite.height ||= 64; sprite.bbox_right ||= sprite.width; sprite.bbox_bottom ||= sprite.height; // Ensure that the sequence has a name sprite.sequence.name = sprite.name; return sprite; }); /** * Schema for creating/updating a YySprite, with very * forgiving preprocessing to allow for sparse inputs. */ export const yySpriteSchema = z.preprocess((input) => { if (!input || typeof input !== 'object' || !('name' in input)) { return input; } // Cast stuff that can't be handled by local defaults // (can use the final transform to override with more // context after defaults are applied) const sprite = ensureTrackKeyFrames(input); return sprite; }, yySpriteSchemaStrict); //# sourceMappingURL=YySprite.js.map