From 75ede122376f08aad413def1d67347f3117b11ad Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 19 Dec 2021 04:26:26 +0000 Subject: [PATCH] rarity groups and drawrate implemented, config updated and simplified, readme updated accordingly --- README.md | 39 ++++++++++++------- src/config.js | 50 +++++++++++++++++-------- src/main.js | 102 ++++++++++++++++++++++++++++++++------------------ 3 files changed, 126 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index f81474d..907e07f 100644 --- a/README.md +++ b/README.md @@ -33,33 +33,46 @@ yarn install ## Usage -Create your different layers as folders in the 'layers' directory, and add all the layer assets in these directories. Optionally, append '_r' and '_sr' to the layer file names to make those layer files rare or super rare respectively. +Create your different layers as folders in the 'layers' directory, and add all the layer assets in these directories. Optionally, append '\_r' and '\_sr' to the layer file names to make those layer files rare or super rare respectively. -*Example:* If you had an ball layer you would create a ball directory, and then a file might be called: +_Example:_ If you had an ball layer you would create a ball directory, and then a file might be called: - `red_eye_ball_sr.png` - `red_eye_ball_r.png` - `red_eye_ball.png` -> Rarity is customizable in `src/config.js`. - Once you have all your layers, go into `src/config.js` and update the `layersOrder` array to be your layer folders name in order of the back layer to the front layer. -*Example:* If you were creating a portrait design, you might have a background, then a head, a mouth, eyes, eyewear, and then headwear, so your `layersOrder` would look something like this: +_Example:_ If you were creating a portrait design, you might have a background, then a head, a mouth, eyes, eyewear, and then headwear, so your `layersOrder` would look something like this: ```js const layersOrder = [ - { name: 'background', number: 1 }, - { name: 'ball', number: 2 }, - { name: 'eye color', number: 12 }, - { name: 'iris', number: 3 }, - { name: 'shine', number: 1 }, - { name: 'bottom lid', number: 3 }, - { name: 'top lid', number: 3 }, + { name: "background" }, + { name: "ball" }, + { name: "eye color" }, + { name: "iris", drawRate: 0.4 }, + { name: "shine" }, + { name: "shine" }, + { name: "bottom lid" }, + { name: "top lid" }, +]; +``` + +The `name` of each layer object represents the name of the folder (in `/layers/`) that the images reside in. The optional `drawRate` field can be filled in if the layer should not render on each variation, it accepts float values between 0.0 - 1.0. ( 0.5 being 50% render rate ). + +Rarity groups, porportions and suffices can be further defined and changed in the config file. + +```js +const rarity = [ + { key: "", val: 16 }, + { key: "_r", val: 8 }, + { key: "_sr", val: 2 }, ]; ``` -The `name` of each layer object represents the name of the folder (in `/layers/`) that the images reside in. The `number` of each layer object represents the total number of image files you want to select from (possibly including blanks.) For instance, if you have three images in a layer folder and want to pick one of those each time, the `number` should be `3`. If you have a single image in a layer that you want to increase the rarity of to 1 in 100, the `number` for that layer should be `100`. In this case, 99 times out of 100, you will get a completely transparent layer. +The `key` field is the filename suffix of which you use for the layer assets and the `val` fields, together, create the difference in renderrate between layer assets. + +_Example:_ in the config snippet above, \_sr val: 2 ( super rare ) renders 4 times fewer than \_r val: 8 ( rare ) because 2 is 4 times smaller than 8. Then optionally, update your `format` size, ie the outputted image size, and the defaultEdition, which is the amount of variation outputted. diff --git a/src/config.js b/src/config.js index 016ba95..5526edf 100644 --- a/src/config.js +++ b/src/config.js @@ -1,25 +1,43 @@ -const layersOrder = [ - { name: 'background', number: 1 }, - { name: 'ball', number: 2 }, - { name: 'eye color', number: 12 }, - { name: 'iris', number: 3 }, - { name: 'shine', number: 1 }, - { name: 'shine', number: 1 }, - { name: 'bottom lid', number: 3 }, - { name: 'top lid', number: 3 }, +const fs = require("fs"); +// Layers of design +// Increase number value above real layer file count to create the option and porportions of empty render + +const countLayerFiles = (layer) => fs.readdirSync(`./layers/${layer}`).length; + +let layersOrder = [ + { name: "background" }, + { name: "ball" }, + { name: "eye color" }, + { name: "iris", drawRate: 0.4 }, + { name: "shine" }, + { name: "shine" }, + { name: "bottom lid" }, + { name: "top lid" }, ]; - + +// Dimensions of rendered images const format = { - width: 230, - height: 230 + width: 230, + height: 230, }; +// Rarity - change val to control rarity group porportions +// name layer files with key at end const rarity = [ - { key: "", val: "original" }, - { key: "_r", val: "rare" }, - { key: "_sr", val: "super rare" }, + { key: "", val: 15 }, + { key: "_r", val: 8 }, + { key: "_sr", val: 2 }, ]; const defaultEdition = 5; -module.exports = { layersOrder, format, rarity, defaultEdition }; \ No newline at end of file +// Automatic layer file count +module.exports = { + layersOrder: layersOrder.map((layer) => ({ + ...layer, + number: countLayerFiles(layer.name), + })), + format, + rarity, + defaultEdition, +}; diff --git a/src/main.js b/src/main.js index 365bd04..be093d5 100644 --- a/src/main.js +++ b/src/main.js @@ -11,7 +11,7 @@ if (!process.env.PWD) { } const buildDir = `${process.env.PWD}/build`; -const metDataFile = '_metadata.json'; +const metDataFile = "_metadata.json"; const layersDir = `${process.env.PWD}/layers`; let metadata = []; @@ -20,8 +20,7 @@ let hash = []; let decodedHash = []; const Exists = new Map(); - -const addRarity = _str => { +const addRarity = (_str) => { let itemRarity; rarity.forEach((r) => { @@ -33,7 +32,7 @@ const addRarity = _str => { return itemRarity; }; -const cleanName = _str => { +const cleanName = (_str) => { let name = _str.slice(0, -4); rarity.forEach((r) => { name = name.replace(r.key, ""); @@ -41,7 +40,7 @@ const cleanName = _str => { return name; }; -const getElements = path => { +const getElements = (path) => { return fs .readdirSync(path) .filter((item) => !/(^|\/)\.[^\/\.]/g.test(item)) @@ -55,7 +54,7 @@ const getElements = path => { }); }; -const layersSetup = layersOrder => { +const layersSetup = (layersOrder) => { const layers = layersOrder.map((layerObj, index) => ({ id: index, name: layerObj.name, @@ -63,7 +62,8 @@ const layersSetup = layersOrder => { elements: getElements(`${layersDir}/${layerObj.name}/`), position: { x: 0, y: 0 }, size: { width: format.width, height: format.height }, - number: layerObj.number + number: layerObj.number, + drawRate: layerObj.drawRate, })); return layers; @@ -77,10 +77,13 @@ const buildSetup = () => { }; const saveLayer = (_canvas, _edition) => { - fs.writeFileSync(`${buildDir}/${_edition}.png`, _canvas.toBuffer("image/png")); + fs.writeFileSync( + `${buildDir}/${_edition}.png`, + _canvas.toBuffer("image/png") + ); }; -const addMetadata = _edition => { +const addMetadata = (_edition) => { let dateTime = Date.now(); let tempMetadata = { hash: hash.join(""), @@ -110,8 +113,32 @@ const addAttributes = (_element, _layer) => { const drawLayer = async (_layer, _edition) => { const rand = Math.random(); - let element = - _layer.elements[Math.floor(rand * _layer.number)] ? _layer.elements[Math.floor(rand * _layer.number)] : null; + + // dup elements across array according to rarity + let weightedElementArray = _layer.elements + .map((element) => { + let elementArray = new Array(element.rarity).fill(element); + return elementArray; + }) + .flat(); + + if (!!_layer.drawRate && _layer.drawRate !== 1) { + // Posibility of empty - add empties to array + let nonRenderMultiplier = Math.round(1 / _layer.drawRate); + weightedElementArray = [ + ...weightedElementArray, + ...new Array( + Math.round(nonRenderMultiplier * weightedElementArray.length) + ), + ]; + } + + let weightedNumber = weightedElementArray.length; + + let element = weightedElementArray[Math.floor(rand * weightedNumber)] + ? weightedElementArray[Math.floor(rand * weightedNumber)] + : null; + if (element) { addAttributes(element, _layer); const image = await loadImage(`${_layer.location}${element.fileName}`); @@ -127,39 +154,42 @@ const drawLayer = async (_layer, _edition) => { } }; -const createFiles = async edition => { +const createFiles = async (edition) => { const layers = layersSetup(layersOrder); let numDupes = 0; - for (let i = 1; i <= edition; i++) { - await layers.forEach(async (layer) => { - await drawLayer(layer, i); - }); - - let key = hash.toString(); - if (Exists.has(key)) { - console.log( - `Duplicate creation for edition ${i}. Same as edition ${Exists.get( - key - )}` - ); - numDupes++; - if (numDupes > edition) break; //prevents infinite loop if no more unique items can be created - i--; - } else { - Exists.set(key, i); - addMetadata(i); - console.log("Creating edition " + i); - } - } + for (let i = 1; i <= edition; i++) { + await layers.forEach(async (layer) => { + await drawLayer(layer, i); + }); + + let key = hash.toString(); + if (Exists.has(key)) { + console.log( + `Duplicate creation for edition ${i}. Same as edition ${Exists.get( + key + )}` + ); + numDupes++; + if (numDupes > edition) break; //prevents infinite loop if no more unique items can be created + i--; + } else { + Exists.set(key, i); + addMetadata(i); + console.log("Creating edition " + i); + } + } }; const createMetaData = () => { fs.stat(`${buildDir}/${metDataFile}`, (err) => { - if(err == null || err.code === 'ENOENT') { - fs.writeFileSync(`${buildDir}/${metDataFile}`, JSON.stringify(metadata, null, 2)); + if (err == null || err.code === "ENOENT") { + fs.writeFileSync( + `${buildDir}/${metDataFile}`, + JSON.stringify(metadata, null, 2) + ); } else { - console.log('Oh no, error: ', err.code); + console.log("Oh no, error: ", err.code); } }); };