diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..01fe385 Binary files /dev/null and b/.DS_Store differ diff --git a/config85.jag b/config85.jag index 22656aa..b1a641f 100644 Binary files a/config85.jag and b/config85.jag differ diff --git a/package-lock.json b/package-lock.json index 18a2c05..8fe62c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -952,4 +952,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/bin.js b/src/bin.js index c8bfda2..59218c3 100755 --- a/src/bin.js +++ b/src/bin.js @@ -2,24 +2,26 @@ const { Config } = require('@2003scape/rsc-config'); const fs = require('fs').promises; -const mkdirp = require('mkdirp-promise'); const path = require('path'); const pkg = require('../package'); const yargs = require('yargs'); const { EntitySprites, MediaSprites, Textures } = require('./'); +const compressSprites = require('./compress-sprites'); +const { Image, createCanvas, loadImage } = require('canvas'); + +let fileNames = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,zz,zzz,zzzz".split(","); async function dumpSprites(output, spriteMap) { for (let [name, sprites] of spriteMap.entries()) { sprites = Array.isArray(sprites) ? sprites : [sprites]; - if (sprites.length > 1) { - await mkdirp(path.join(output, name)); + fs.mkdir(path.join(output, name)) let index = 0; for (const sprite of sprites) { await fs.writeFile( - path.join(output, name, `${index}.png`), + path.join(output, name, `${fileNames[index]}.png`), sprite.toBuffer() ); @@ -34,6 +36,25 @@ async function dumpSprites(output, spriteMap) { } } +async function createImage(path) { + return new Promise((resolve, reject) => { + const image = new Image(); + image.onerror = err => reject(err); + image.onload = () => resolve(image); + image.src = path; + }) +} + +async function toCanvas(path) { + const image = await createImage(path); + + const canvas = createCanvas(image.width, image.height); + const context = canvas.getContext('2d'); + let loadedImage = await loadImage(path); + context.drawImage(loadedImage, 0, 0) + return canvas; +} + yargs .scriptName('rsc-sprites') .version(pkg.version) @@ -75,9 +96,9 @@ yargs try { const config = new Config(); config.loadArchive(await fs.readFile(argv.config)); - + let spriteArchive; - + if (argv.type === 'entity' || /entity/i.test(argv.archive)) { spriteArchive = new EntitySprites(config); } else if ( @@ -99,13 +120,13 @@ yargs spriteArchive.loadArchive(await fs.readFile(argv.archive)); let output = argv.output; - if (!output) { const ext = path.extname(argv.archive); output = `${path.basename(argv.archive, ext)}-png`; } - await mkdirp(output); + fs.mkdir(output); + await dumpSprites(output, spriteArchive.sprites); if ( @@ -135,10 +156,60 @@ yargs ) .command( 'pack-sprites ', - 'pack PNG file(s) into a sprites jag or mem archive', - (yargs) => {}, - async (argv) => {} + 'pack PNG file(s) into a sprites jag archive', + yargs => { + yargs.positional('archive', { + description: 'entity, media or textures jag/mem archive', + type: 'string' + }); + yargs.positional('files', { + description: 'Dumped files to create a new archive' + }); + yargs.positional('type', { + description: 'entity, media, or textures', + type: 'string' + }); + }, + async argv => { + const writeArchive = async (archive, filename) => { + await fs.writeFile(filename, archive); + }; + if (argv.type === 'entity' || /entity/i.test(argv.archive)) { + try { + const ext = path.extname(argv.archive); + const archiveFileBase = argv.archive.split(ext)[0].replace(/[0-9]/g, ''); + const archiveAddEdition = Number(argv.archive.split(ext)[0].replace(/\D/g,'')) + 1; + const newArchiveFileName = archiveFileBase + archiveAddEdition + ext; + + let spriteMapToEncode = new Map(); + + for (const filename in argv.files) { + + let subFolderFullName = argv.files[filename]; + let subFolderPartialName = argv.files[filename].split("/")[1]; + let subFolderValue = await fs.readdir(argv.files[filename]); + + let canvasArray = []; + + for(const png of subFolderValue) { + let pngFileLocation = subFolderFullName + "/" + png; + let newCanvas = await toCanvas(`${pngFileLocation}`); + + canvasArray.push(newCanvas); + } + spriteMapToEncode.set(subFolderPartialName, canvasArray); + } + + let spritesToEncode = compressSprites(spriteMapToEncode); + + await writeArchive(spritesToEncode, `${newArchiveFileName}`); + + } catch (e) { + console.log(e); + } + } + } ) .command('dump-items ') .command('dump-npcs ') - .demandCommand().argv; + .demandCommand().argv; \ No newline at end of file diff --git a/src/compress-sprites.js b/src/compress-sprites.js index babd751..36a73c5 100644 --- a/src/compress-sprites.js +++ b/src/compress-sprites.js @@ -4,21 +4,104 @@ const { JagArchive } = require('@2003scape/rsc-archiver'); function compressSprites(sprites) { let indexLength = 0; - const encodedSprites = new Map(); + + const encodedSprites = new Map(); for (const [name, sprite] of sprites.entries()) { - const encoded = encodeSprite(sprite); - encodedSprites.set(name, encoded); - encoded.offset = indexLength; - // 2 shorts for full width and height 1 byte for palette length - indexLength += 5; + // A Sprite is an array of canvas objects + if (name === "gobweap" || name === "skelweap" || name === "zombweap" || name === "eyepatch") { + let spriteData = sprite.slice(0,15); + const encodedRegular = encodeSprite(spriteData); + + encodedSprites.set(name, encodedRegular); + + encodedRegular.offset = indexLength; + + // 2 shorts (4 bytes) for full width and height 1 byte for palette length + indexLength = indexLength + 2 + 2 + 1; + // palette, 3 bytes per colour + indexLength += ((encodedRegular.palette.length - 1) * 3); + // 2 sprite offset bytes, width and height shorts, index order byte + indexLength += (encodedRegular.frames.length * 7); + + + let spriteDataFighting = sprite.slice(15,18); + const encodedFighting = encodeSprite(spriteDataFighting); + + encodedSprites.set(`${name}a`, encodedFighting); + + encodedFighting.offset = indexLength; + + // 2 shorts (4 bytes) for full width and height 1 byte for palette length + indexLength = indexLength + 2 + 2 + 1; + // palette, 3 bytes per colour + indexLength += ((encodedFighting.palette.length - 1) * 3); + // 2 sprite offset bytes, width and height shorts, index order byte + indexLength += (encodedFighting.frames.length * 7); + - // palette, 3 bytes per colour - indexLength += ((encoded.palette.length - 1) * 3); - // 2 sprite offset bytes, width and height shorts, index order byte - indexLength += (encoded.frames.length * 7); + let spriteDataAnimated = sprite.slice(18, 27); + + const encodedAnimated = encodeSprite(spriteDataAnimated); + + encodedSprites.set(`${name}f`, encodedAnimated); + + encodedAnimated.offset = indexLength; + + // 2 shorts (4 bytes) for full width and height 1 byte for palette length + indexLength = indexLength + 2 + 2 + 1; + // palette, 3 bytes per colour + indexLength += ((encodedAnimated.palette.length - 1) * 3); + // 2 sprite offset bytes, width and height shorts, index order byte + indexLength += (encodedAnimated.frames.length * 7); + count++; + } + if (name !== "crossbow" && name !== "longbow" && name !== "sheep" && name !== "gobweap" && name !== "skelweap" && name !== "zombweap" && name !== "eyepatch") { + let spriteData = sprite.slice(0,15); + const encodedRegular = encodeSprite(spriteData); + encodedSprites.set(name, encodedRegular); + + encodedRegular.offset = indexLength; + + // 2 shorts (4 bytes) for full width and height 1 byte for palette length + indexLength = indexLength + 2 + 2 + 1; + // palette, 3 bytes per colour + indexLength += ((encodedRegular.palette.length - 1) * 3); + // 2 sprite offset bytes, width and height shorts, index order byte + indexLength += (encodedRegular.frames.length * 7); + + + let spriteDataFighting = sprite.slice(15,18); + + const encodedFighting = encodeSprite(spriteDataFighting); + + encodedSprites.set(`${name}a`, encodedFighting); + + encodedFighting.offset = indexLength; + + // 2 shorts (4 bytes) for full width and height 1 byte for palette length + indexLength = indexLength + 2 + 2 + 1; + // palette, 3 bytes per colour + indexLength += ((encodedFighting.palette.length - 1) * 3); + // 2 sprite offset bytes, width and height shorts, index order byte + indexLength += (encodedFighting.frames.length * 7); + } + if (name === "crossbow" || name === "longbow" || name === "sheep") { + let spriteData = sprite; + const encodedRegular = encodeSprite(spriteData); + + encodedSprites.set(name, encodedRegular); + + encodedRegular.offset = indexLength; + // 2 shorts for full width and height 1 byte for palette length + indexLength += 5; + // palette, 3 bytes per colour + indexLength += (encodedRegular.palette.length - 1) * 3; + // 2 sprite offset bytes, width and height shorts, index order byte + indexLength += encodedRegular.frames.length * 7; + } } const archive = new JagArchive(); diff --git a/src/encode-sprite.js b/src/encode-sprite.js index 0202292..77eb300 100644 --- a/src/encode-sprite.js +++ b/src/encode-sprite.js @@ -40,10 +40,8 @@ function getOffset(frame) { // encode a collection of frames with the same palette function encodeSprite(frames) { frames = Array.isArray(frames) ? frames : [frames]; - const fullWidth = frames[0].width; const fullHeight = frames[0].height; - const palette = [0xff00ff]; const encodedFrames = []; diff --git a/src/entity-sprites.js b/src/entity-sprites.js index da66620..2d803e7 100644 --- a/src/entity-sprites.js +++ b/src/entity-sprites.js @@ -31,7 +31,6 @@ class EntitySprites { loadArchive(buffer) { const archive = new JagArchive(); archive.readArchive(buffer); - const indexData = getDataBuffer(archive, 'index'); for (const { name, hasA, hasF } of this.animations) { @@ -41,8 +40,7 @@ class EntitySprites { try { let spriteData = getDataBuffer(archive, name); - const frames = parseSprite(spriteData, indexData, 15); - + let frames = parseSprite(spriteData, indexData, 15); if (hasA) { spriteData = getDataBuffer(archive, `${name}a`); frames.push(...parseSprite(spriteData, indexData, 3)); @@ -52,7 +50,6 @@ class EntitySprites { spriteData = getDataBuffer(archive, `${name}f`); frames.push(...parseSprite(spriteData, indexData, 9)); } - this.sprites.set(name.toLowerCase(), frames); } catch (e) { // probably members animations when we only loaded free archive diff --git a/textures17.jag b/textures17.jag deleted file mode 100644 index 2bbfdec..0000000 Binary files a/textures17.jag and /dev/null differ