@bscotch/yy
Version:
Stringify, parse, read, and write GameMaker yy and yyp files.
461 lines • 17.7 kB
JavaScript
// 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, fixed0, fixed1, fixedNumber, unstable, yyResourceIdSchemaGenerator, } from './utility.js';
export var SpriteType;
(function (SpriteType) {
SpriteType[SpriteType["Default"] = 0] = "Default";
SpriteType[SpriteType["UNKNOWN1"] = 1] = "UNKNOWN1";
SpriteType[SpriteType["Spine"] = 2] = "Spine";
SpriteType[SpriteType["Svg"] = 3] = "Svg";
SpriteType[SpriteType["UNKNOWN4"] = 4] = "UNKNOWN4";
SpriteType[SpriteType["UNKNOWN5"] = 5] = "UNKNOWN5";
SpriteType[SpriteType["UNKNOWN6"] = 6] = "UNKNOWN6";
})(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(new FixedNumber(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(new FixedNumber(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(fixed0),
/** Seems to always be 1? */
Length: fixedNumber().default(fixed1),
Stretch: z.boolean().default(false),
Disabled: z.boolean().default(false),
IsCreationKey: z.boolean().default(false),
Channels: z
.record(z.string(), 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': {
Id: { name: '', path: '' },
resourceVersion: '1.0',
resourceType: 'SpriteFrameKeyframe',
},
}),
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.enum(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(new FixedNumber(60)),
/**
* FPS type, set via the editor
*/
playbackSpeedType: spritePlaybackSpeedTypeSchema.default(0),
autoRecord: z.boolean().default(true),
volume: fixedNumber().default(fixed1),
/**
* Number of frames
*/
length: fixedNumber().default(fixed0),
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(fixed0),
backdropWidth: z.number().default(1366),
backdropHeight: z.number().default(768),
backdropXOffset: fixedNumber().default(fixed0),
backdropYOffset: fixedNumber().default(fixed0),
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: z
.intersection(z.object({
Keyframes: z.array(z.unknown()).default([]),
resourceVersion: z.string().default('1.0'),
}), spriteSequenceEventTypeSchema)
.default({
Keyframes: [],
resourceVersion: '1.0',
resourceType: 'KeyframeStore<MessageEventKeyframe>',
}),
moments: z
.intersection(z.object({
Keyframes: z.array(z.unknown()).default([]),
resourceVersion: z.string().default('1.0'),
}), spriteSequenceMomentTypeSchema)
.default({
resourceType: 'KeyframeStore<MomentsEventKeyframe>',
Keyframes: [],
resourceVersion: '1.0',
}),
tracks: z.array(spriteSequenceTrackSchema).prefault([{}]),
/**
* 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: spriteSequenceSchema.prefault({}),
layers: z.array(spriteLayerSchema).prefault([{}]),
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