@bscotch/stitch
Version:
Stitch: The GameMaker Studio 2 Asset Pipeline Development Kit.
305 lines • 10.8 kB
JavaScript
import { pathy } from '@bscotch/pathy';
import { randomString } from '@bscotch/utility';
import { difference, uniqBy } from 'lodash-es';
import { StitchError } from '../../utility/errors.js';
import { debug, info, warn } from '../../utility/log.js';
import paths from '../../utility/paths.js';
import { dehydrateArray } from '../hydrate.js';
import { Gms2Animation } from './resources/Gms2Animation.js';
import { Gms2Extension } from './resources/Gms2Extension.js';
import { Gms2Font } from './resources/Gms2Font.js';
import { Gms2Note } from './resources/Gms2Note.js';
import { Gms2Object } from './resources/Gms2Object.js';
import { Gms2Particle } from './resources/Gms2Particle.js';
import { Gms2Path } from './resources/Gms2Path.js';
import { Gms2Room } from './resources/Gms2Room.js';
import { Gms2Script } from './resources/Gms2Script.js';
import { Gms2Sequence } from './resources/Gms2Sequence.js';
import { Gms2Shader } from './resources/Gms2Shader.js';
import { Gms2Sound } from './resources/Gms2Sound.js';
import { Gms2Sprite } from './resources/Gms2Sprite.js';
import { Gms2Tileset } from './resources/Gms2Tileset.js';
import { Gms2Timeline } from './resources/Gms2Timeline.js';
export class Gms2ResourceArray {
project;
items;
constructor(project, data) {
this.project = project;
const uniqueData = uniqBy(data, 'id.name');
const removedItems = difference(data, uniqueData);
if (removedItems.length) {
warn(`Duplicate resources found: ${removedItems.length} duplicates removed`);
}
this.items = data.map((item) => Gms2ResourceArray.hydrateResource(item, project.io));
}
toJSON() {
return dehydrateArray(this.items);
}
get sprites() {
return this.filterByClass(Gms2Sprite);
}
get sounds() {
return this.filterByClass(Gms2Sound);
}
get scripts() {
return this.filterByClass(Gms2Script);
}
get objects() {
return this.filterByClass(Gms2Object);
}
get rooms() {
return this.filterByClass(Gms2Room);
}
get all() {
return [...this.items];
}
filterByClass(resourceClass) {
return this.items.filter((item) => item instanceof resourceClass);
}
filter(matchFunction) {
return this.items.filter(matchFunction);
}
forEach(doSomething) {
this.items.forEach(doSomething);
return this;
}
find(matchFunction) {
return this.items.find((item) => matchFunction(item));
}
findByClass(matchFunction, resourceClass) {
return this.find((item) => {
if (item instanceof resourceClass) {
return matchFunction(item);
}
return false;
});
}
findByName(name, resourceClass) {
const item = this.items.find((i) => {
if (resourceClass && !(i instanceof resourceClass)) {
return false;
}
const isMatch = i.isNamed(name);
if (isMatch && !isMatch.isExactMatch) {
throw new StitchError(`Resource names must always match case: found ${i.name} when looking for ${name}`);
}
return isMatch;
});
return item;
}
findByField(field, value, resourceClass) {
return this.findByClass((item) => item[field] == value, resourceClass);
}
/** Find all resources in a given folder */
filterByFolder(folder, recursive = true) {
return this.items.filter((item) => item.isInFolder(folder, recursive));
}
/** Find all resources of a given type within a folder */
filterByClassAndFolder(resourceClass, folder, recursive = true) {
return this.filterByFolder(folder, recursive).filter((item) => item instanceof resourceClass);
}
async addSound(source, comms) {
const { name } = paths.parse(source);
const existingSound = this.findByName(name, Gms2Sound);
if (existingSound) {
existingSound.replaceAudioFile(source);
info(`updated sound ${name}`);
}
else {
this.push(await Gms2Sound.create(source, comms));
info(`created sound ${name}`);
}
return this;
}
async addScript(name, code, comms) {
const script = this.findByName(name, Gms2Script);
if (script) {
if (script.code !== code) {
script.code = code;
info(`script ${name} changed`);
}
}
else {
this.push(await Gms2Script.create(name, code, comms));
info(`created script ${name}`);
}
return this;
}
async addSprite(sourceFolder, comms, nameOverride) {
const requestId = randomString(12, 'base64');
const name = nameOverride || paths.basename(sourceFolder);
debug(`adding sprite from ${sourceFolder} as name ${name}`);
let sprite = this.findByName(name, Gms2Sprite);
const spriteSource = {
name,
path: sourceFolder,
exists: !!sprite,
isSpine: false,
};
comms.plugins.forEach((plugin) => {
plugin.beforeSpriteAdded?.(this.project, {
requestId,
spriteSource,
});
});
const afterAddedInfo = {
requestId,
spriteSource,
created: false,
};
if (sprite) {
const results = await sprite.syncWithSource(sourceFolder, false);
afterAddedInfo.created = false;
afterAddedInfo.changes = results;
}
else {
info(`Adding new sprite '${name}'`);
const results = {};
sprite = await Gms2Sprite.create(sourceFolder, comms, name, results);
this.push(sprite);
afterAddedInfo.created = true;
afterAddedInfo.changes = results;
}
afterAddedInfo.sprite = sprite;
comms.plugins.forEach((plugin) => {
plugin.afterSpriteAdded?.(this.project, afterAddedInfo);
});
return this;
}
async addSpineSprite(_jsonSourcePath, comms, nameOverride) {
const requestId = randomString(12, 'base64');
const jsonSourcePath = pathy(_jsonSourcePath);
const name = nameOverride || jsonSourcePath.up().basename;
debug(`adding spine sprite`, { from: jsonSourcePath, name });
let sprite = this.findByName(name, Gms2Sprite);
const spriteSource = {
name,
path: jsonSourcePath.absolute,
exists: !!sprite,
isSpine: true,
};
comms.plugins.forEach((plugin) => {
plugin.beforeSpriteAdded?.(this.project, {
requestId,
spriteSource,
});
});
const afterAddedInfo = {
requestId,
spriteSource,
created: false,
};
if (sprite) {
const results = await sprite.syncWithSource(jsonSourcePath.absolute, false);
afterAddedInfo.created = false;
afterAddedInfo.changes = results;
}
else {
info(`Adding new spine sprite ${name}`);
const results = {};
sprite = await Gms2Sprite.createFromSpine(jsonSourcePath, comms, name, results);
this.push(sprite);
afterAddedInfo.created = true;
afterAddedInfo.changes = results;
}
afterAddedInfo.sprite = sprite;
comms.plugins.forEach((plugin) => {
plugin.afterSpriteAdded?.(this.project, afterAddedInfo);
});
return this;
}
async addObject(name, comms) {
let object = this.findByName(name, Gms2Object);
if (!object) {
object = await Gms2Object.create(name, comms);
this.push(object);
info(`created object ${name}`);
}
else {
info(`object ${name} already exists`);
}
return object;
}
async addRoom(name, comms) {
let room = this.findByName(name, Gms2Room);
if (!room) {
room = await Gms2Room.create(name, comms);
this.push(room);
info(`created room ${name}`);
}
else {
warn(`room ${name} already exists`);
}
return room;
}
/**
* Delete a resource, if it exists. **NOTE:** if other
* resources depend on this one you'll be creating errors
* by deleting it!
*/
deleteByName(name) {
const resourceIdx = this.items.findIndex((i) => i.name == name);
if (resourceIdx < 0) {
return this;
}
const [resource] = this.items.splice(resourceIdx, 1);
this.project.io.storage.emptyDirSync(resource.yyDirAbsolute);
return this;
}
/**
* Given Yyp data for a resource that **is not listed in the yyp file**
* but that **does have .yy and associated files**, add hydrate the object
* and add it to the Yyp.
*/
register(data, comms) {
this.items.push(Gms2ResourceArray.hydrateResource(data, comms));
}
push(newResource) {
this.items.push(newResource);
return this;
}
static get resourceClassMap() {
const classMap = {
animcurves: Gms2Animation,
extensions: Gms2Extension,
fonts: Gms2Font,
notes: Gms2Note,
objects: Gms2Object,
particles: Gms2Particle,
paths: Gms2Path,
rooms: Gms2Room,
scripts: Gms2Script,
sequences: Gms2Sequence,
shaders: Gms2Shader,
sounds: Gms2Sound,
sprites: Gms2Sprite,
tilesets: Gms2Tileset,
timelines: Gms2Timeline,
};
return classMap;
}
/**
* Get a new array listing the names of all resource types.
*/
static get resourceTypeNames() {
return Object.keys(this.resourceClassMap);
}
/**
* Get all global functions defined across all Scripts
* (does not include built-ins).
*/
getGlobalFunctions() {
return this.scripts.map((script) => script.globalFunctions).flat(2);
}
static hydrateResource(data, comms) {
const resourceType = data.id.path.split('/')[0];
// const subclass = Gms2Timeline;
const subclass = Gms2ResourceArray.resourceClassMap[resourceType];
if (!subclass) {
throw new StitchError(`No constructor for resource ${resourceType} exists.`);
}
const resource = new subclass(data, comms);
return resource;
}
}
//# sourceMappingURL=Gms2ResourceArray.js.map