Icon code rewrite (mostly)

Rewrote the icon code so it's not nearly as bad. Spiders and robots still use the old code because I don't have the mental strength to pull it off
This commit is contained in:
GDColon 2021-12-03 16:30:58 -05:00
parent 7da5632f7c
commit 84d02b7271
329 changed files with 12831 additions and 377 deletions

View file

@ -6,6 +6,14 @@ const properties = require('../misc/analysis/objectProperties.json')
const ids = require('../misc/analysis/objects.json')
module.exports = async (app, req, res, level) => {
if (!level) {
level = {
name: (req.body.name || "Unnamed").slice(0, 64),
data: (req.body.data || "")
}
}
let unencrypted = level.data.startsWith('kS') // some gdps'es don't encrypt level data
let levelString = unencrypted ? level.data : Buffer.from(level.data, 'base64')

View file

@ -1,389 +1,311 @@
// this file is a potential candidate for worst code on github
// i advise you to turn back now
// seriously, it's not too late
const Jimp = require('jimp');
const sharp = require('sharp');
const Canvas = require('canvas')
const psd = require('ag-psd')
const fs = require('fs');
const icons = require('../icons/gameSheet.json');
const colors = require('../icons/colors.json');
const forms = require('../icons/forms.json')
const offsets = require('../icons/offsets.json');
const mainPath = `../icons/`
const icons = require('../misc/icons/gameSheet.json');
const colors = require('../misc/icons/colors.json');
const forms = require('../misc/icons/forms.json')
const offsets = require('../misc/icons/offsets.json')
const legOffsets = require('../misc/icons/legOffsets.json')
let hexRegex = /^[A-Fa-f0-9]{6}$/
function hexConvert(hex) { hex = hex.replace('#', ''); return {r: '0x' + hex[0] + hex[1] | 0, g: '0x' + hex[2] + hex[3] | 0, b: '0x' + hex[4] + hex[5] | 0}; }
function recolor(img, col) {
return img.scan(0, 0, img.bitmap.width, img.bitmap.height, function (x, y, idx) {
if (img.bitmap.data.slice(idx, idx+3).every(function(val) {return val >= 20 && val <= 255})) { // If it's not "black, i.e. we want to recolor it"
this.bitmap.data[idx] = colors[col].r / (255 / this.bitmap.data[idx]);
this.bitmap.data[idx + 1] = colors[col].g / (255 / this.bitmap.data[idx + 1]);
this.bitmap.data[idx + 2] = colors[col].b / (255 / this.bitmap.data[idx + 2]);
}
})
let canvasSize = 300
let halfCanvas = canvasSize/2
let TRANSPARENT = {r: 0, g: 0, b: 0, alpha: 0}
let cache = {}
let partNames = {
"1": "Primary",
"2": "Secondary",
"3": "UFO Dome",
"glow": "Glow",
"extra": "White",
}
/* Caveat of genFileName is that if there are any falsey values in the arguments they are ignored.
This is usually a good thing though - avoid issues by not putting something like 0 instead of '0' */
function genFileName(...args) { return args.filter(function(val) {return val}).join('_') +'_001.png' }
function fromIcons(filename) { return `./icons/${filename}` }
let cache = {};
// convert hex to RGB
let hexRegex = /^[A-Fa-f0-9]{6}$/
function hexConvert(hex) { hex = hex.replace('#', ''); return {val: hex, r: '0x' + hex[0] + hex[1] | 0, g: '0x' + hex[2] + hex[3] | 0, b: '0x' + hex[4] + hex[5] | 0}; }
// get path name from icon form and ID
function getIconPath(icon, formName) {
return `${mainPath}${formName}_${icon < 10 ? "0" : ""}${icon}`
}
// get color from param input
function getColor(colInput, defaultCol) {
colInput = String(colInput)
let foundColor = colors[colInput]
if (foundColor) {
foundColor.val = colInput
return foundColor
}
else if (colInput.match(hexRegex)) { // custom hex code
let hexCol = hexConvert(colInput)
colors[colInput.toLowerCase()] = hexCol
return hexCol
}
else if (!foundColor && defaultCol) {
let def = colors[defaultCol]
def.val = defaultCol
return def
}
}
module.exports = async (app, req, res) => {
function buildIcon(account=[], usercode) {
async function buildIcon(account=[], userCode) {
let { form, ind } = forms[req.query.form] || {};
form = form || 'player';
ind = ind || 21;
let form = forms[req.query.form] || forms["icon"]
let iconID = req.query.icon || account[ind] || 1;
let col1 = req.query.col1 || account[10] || 0;
let col2 = req.query.col2 || account[11] || 3;
let colG = req.query.colG || req.query.colg
let colW = req.query.colW || req.query.colw || req.query.col3
let outline = req.query.glow || account[28] || "0";
let iconID = req.query.icon || account[form.index] || 1;
let col1 = getColor(req.query.col1 || account[10], "0")
let col2 = getColor(req.query.col2 || account[11], "3")
let colG = getColor(req.query.colG || req.query.colg)
let colW = getColor(req.query.colW || req.query.colw || req.query.col3)
let topless = form == "bird" && req.query.topless
let drawLegs = !(req.query.noLegs > 0)
let autoSize = req.query.size == "auto"
let sizeParam = autoSize || (req.query.size && !isNaN(req.query.size))
if (outline == "0" || outline == "false") outline = false;
let useGlow = req.query.glow || account[28] || false;
if (useGlow && ["false", "0"].includes(useGlow)) useGlow = false
if (col1.r == 0 && col1.g == 0 && col1.b == 0 ) useGlow = true
if (iconID && iconID.toString().length == 1) iconID = "0" + iconID;
// bit of a hacky solution for glow color but whatev
let glowColor = colG || col2
if (glowColor.r == 0 && glowColor.g == 0 && glowColor.b == 0) glowColor = col1
if (glowColor.r == 0 && glowColor.g == 0 && glowColor.b == 0) glowColor = {r: 255, g: 255, b: 255}
function genImageName(...args) { return genFileName(form, iconID, ...args) }
let psdExport = req.query.psd || false
let topless = form.name == "UFO" ? req.query.topless || false : false
let icon, glow, extra;
function setBaseIcons() {
icon = genImageName(isSpecial && '01');
glow = genImageName(isSpecial && '01', '2');
extra = genImageName(isSpecial && '01', 'extra');
let customSize = req.query.size == "auto" ? "auto" : +req.query.size || null
let iconPath = getIconPath(iconID, form.form)
let iconCode = `${form.name}-${iconID}-${col1.val}-${col2.val}-${colG ? colG.val : "x"}-${colW ? colW.val : "x"}-${useGlow ? 1 : 0}`
let cachable = !topless && !customSize && !psdExport
if (cachable && cache[iconCode]) return res.end(cache[iconCode].buffer)
// default to 1 if icon ID does not exist
if (!fs.existsSync(getPartName(1).slice(1))) { // slice 1 from filename since fs reads paths differently
iconID = 1
iconPath = getIconPath(1, form.form)
}
// get path of icon 'part' (1: primary, 2: secondary, 3: ufo top, extra: white, glow: glow, )
function getPartName(part, robotPart) {
let path = iconPath
if (form.legs) path += `_0${robotPart || 1}`
if (!part || part == "1") return `${path}_001.png`
else return `${path}_${part}_001.png`
}
// recolor white parts of icon to specified color
async function recolor(img, col) {
let rawData = await img.raw().toBuffer({resolveWithObject: true})
for (let i=0; i<rawData.data.length; i += 4) { // [R, G, B, A]
if (rawData.data[i + 3] > 0) {
rawData.data[i] = col.r / (255 / rawData.data[i]);
rawData.data[i + 1] = col.g / (255 / rawData.data[i + 1]);
rawData.data[i + 2] = col.b / (255 / rawData.data[i + 2]);
}
let isSpecial = ['robot', 'spider'].includes(form);
setBaseIcons();
}
return sharp(rawData.data, {raw: {width: rawData.info.width, height: rawData.info.height, channels: 4, background: TRANSPARENT}}).png()
}
if (!fs.existsSync(fromIcons(icon)) || (isSpecial && !fs.existsSync(fromIcons(genImageName('02'))))) {
iconID = '01';
setBaseIcons();
// color icon part and add to layer list
async function addLayer(part, color, legSection) {
let leg = legSection ? legSection.leg : null
let partName = getPartName(part, leg)
let offsetData = icons[partName.slice(mainPath.length)]
let { spriteSize, spriteOffset } = offsetData
let builtPart = sharp(partName.slice(1)) // slice 1 from filename since sharp also reads paths differently
if (color) builtPart = await recolor(builtPart, color)
let left = halfCanvas - Math.floor(spriteSize[0] / 2) + spriteOffset[0]
let top = halfCanvas - Math.floor(spriteSize[1] / 2) - spriteOffset[1]
if (legSection) {
left += Math.floor(legSection.xPos)
top -= Math.floor(legSection.yPos)
// if (legSection.darken) builtPart.tint({r: 100, g: 100, b: 100})
if (legSection.rotation) {
builtPart.rotate(legSection.rotation, {background: TRANSPARENT})
if (part == "glow") { left--; top--; }
}
let ex = fromIcons(extra)
let hasExtra = fs.existsSync(ex)
let cols = [col1, col2, colG, colW]
cols.forEach(col => {
if (!col) return
col = col.toString()
if (col.match(hexRegex)) colors[col.toLowerCase()] = hexConvert(col)
})
if (!colors[col1] || isNaN(colors[col1].r)) col1 = colors[+col1] ? +col1 : 0
if (!colors[col2] || isNaN(colors[col2].r)) col2 = colors[+col2] ? +col2 : 3
if (!colors[colG] || isNaN(colors[colG].r)) colG = colors[+colG] ? +colG : null
if (!colors[colW] || isNaN(colors[colW].r)) colW = colors[+colW] ? +colW : null
if (colW && (!hasExtra || colW == 12)) colW = null
if (col1 == 15 || col1 === "000000") outline = true;
let iconCode = `${req.query.form == "cursed" ? "cursed" : form}${topless ? "top" : ""}-${iconID}-${col1}-${col2}-${colG || "x"}-${colW || "x"}-${outline ? 1 : 0}`
if (!sizeParam && (!isSpecial || drawLegs) && cache[iconCode]) return res.end(cache[iconCode].value)
let useExtra = false
let originalOffset = icons[icon].spriteOffset;
let minusOrigOffset = function(x, y) { return x - originalOffset[y] }
let offset = icons[glow].spriteOffset.map(minusOrigOffset);
let robotLeg1, robotLeg2, robotLeg3, robotLeg3b, robotLeg2b, robotLeg1b, robotLeg1c;
let robotOffset1, robotOffset2, robotOffset3, robotOffset1b, robotOffset2b, robotOffset3b;
let robotGlow1, robotGlow2, robotGlow3, glowOffset
let ufoTop, ufoOffset, ufoCoords, ufoSprite
let extrabit, offset2, size2;
if (isSpecial) {
const legs = [1,2,3].map(function(val) {return genImageName(`0${val+1}`)});
const glows = [1,2,3].map(function(val) {return genImageName(`0${val+1}`, '2')});
robotOffset1 = icons[legs[0]].spriteOffset.map(minusOrigOffset).concat(icons[legs[0]].spriteSize);
robotOffset2 = icons[legs[1]].spriteOffset.map(minusOrigOffset).concat(icons[legs[1]].spriteSize);
robotOffset3 = icons[legs[2]].spriteOffset.map(minusOrigOffset).concat(icons[legs[2]].spriteSize);
robotOffset1b = icons[glows[0]].spriteOffset.map(minusOrigOffset).concat(icons[glows[0]].spriteSize);
robotOffset2b = icons[glows[1]].spriteOffset.map(minusOrigOffset).concat(icons[glows[1]].spriteSize);
robotOffset3b = icons[glows[2]].spriteOffset.map(minusOrigOffset).concat(icons[glows[2]].spriteSize);
robotLeg1 = new Jimp(fromIcons(legs[0])); robotGlow1 = new Jimp(fromIcons(glows[0]))
robotLeg2 = new Jimp(fromIcons(legs[1])); robotGlow2 = new Jimp(fromIcons(glows[1]))
robotLeg3 = new Jimp(fromIcons(legs[2])); robotGlow3 = new Jimp(fromIcons(glows[2]))
glowOffset = offsets[form][+iconID] || []
}
Jimp.read(fromIcons(glow)).then(async function (image) {
let size = [image.bitmap.width, image.bitmap.height]
let glow = recolor(image, col2)
let imgOff = isSpecial ? 100 : 0
let eb = fromIcons(extra)
if (fs.existsSync(eb)) {
extrabit = icons[extra]
offset2 = extrabit.spriteOffset.map(minusOrigOffset);
size2 = extrabit.spriteSize;
extra = new Jimp(eb);
if (colW) await Jimp.read(eb).then(e => { extra = recolor(e, colW) })
useExtra = true
}
Jimp.read(fromIcons(icon)).then(async function (ic) {
let iconSize = [ic.bitmap.width, ic.bitmap.height]
recolor(ic, col1)
ic.composite(glow, (iconSize[0] / 2) - (size[0] / 2) + offset[0], (iconSize[1] / 2) - (size[1] / 2) - offset[1], { mode: Jimp.BLEND_DESTINATION_OVER })
if (form == "bird" && !topless) {
ufoTop = genImageName('3')
ufoOffset = icons[ufoTop].spriteOffset.map(minusOrigOffset).concat(icons[ufoTop].spriteSize);
ufoCoords = [imgOff + (iconSize[0] / 2) - (ufoOffset[2] / 2) + ufoOffset[0], (iconSize[1] / 2) - (ufoOffset[3] / 2) - ufoOffset[1] + 300 - iconSize[1]]
ufoSprite = fromIcons(ufoTop)
ic.contain(iconSize[0], 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_BOTTOM)
// Only add dome if there's no glow, otherwise the dome will be outlined as well
if (!outline) ic.composite(await Jimp.read(ufoSprite), ufoCoords[0], ufoCoords[1], {mode: Jimp.BLEND_DESTINATION_OVER})
}
if (drawLegs && (form == "robot" || req.query.form == "cursed")) {
ic.contain(iconSize[0], 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP)
ic.contain(iconSize[0] + 200, 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP)
await Jimp.read(new Jimp(robotGlow1)).then(rob => {
rob.rotate(-45)
robotGlow1 = recolor(rob, col2)
})
await Jimp.read(new Jimp(robotGlow2)).then(rob => {
rob.rotate(45)
robotGlow2 = recolor(rob, col2)
})
await Jimp.read(new Jimp(robotGlow3)).then(rob => {
robotGlow3 = recolor(rob, col2)
})
await Jimp.read(new Jimp(robotLeg1)).then(rob => {
rob.rotate(-45)
recolor(rob, col1)
rob.composite(robotGlow1, (robotOffset1[2] - robotOffset1b[2]) + (glowOffset[0] || 1), ((robotOffset1[3] - robotOffset1b[3]) / 2) + (glowOffset[1] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
robotLeg1 = rob
})
await Jimp.read(new Jimp(robotLeg2)).then(rob => {
rob.rotate(45)
recolor(rob, col1)
rob.composite(robotGlow2, ((robotOffset2[2] - robotOffset2b[2]) / 4) + (glowOffset[4] || 0), ((robotOffset2[3] - robotOffset2b[3]) / 2) + (glowOffset[5] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
robotLeg2 = rob
})
await Jimp.read(new Jimp(robotLeg2)).then(rob => {
robotLeg2b = rob.color([{ apply: 'darken', params: [20] }]).rotate(-5)
})
await Jimp.read(new Jimp(robotLeg3)).then(rob => {
recolor(rob, col1)
rob.composite(robotGlow3, ((robotOffset3[2] - robotOffset3b[2]) / 2) + (glowOffset[2] || 0), ((robotOffset3[3] - robotOffset3b[3]) / 2) + (glowOffset[3] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
robotLeg3 = rob
})
await Jimp.read(new Jimp(robotLeg3)).then(rob => {
robotLeg3b = rob.color([{ apply: 'darken', params: [10] }])
})
ic.composite(robotLeg2b, 100 + (iconSize[0] / 2) - (robotOffset2[2]) + robotOffset2[0] - 31, (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 73)
ic.composite(robotLeg3b, 100 + (iconSize[0] / 2) - (robotOffset3[2]) + robotOffset3[0] + 20, (iconSize[1] / 2) - (robotOffset3[3]) - robotOffset3[1] + 78)
ic.composite(robotLeg2, 100 + (iconSize[0] / 2) - (robotOffset2[2]) + robotOffset2[0] - 20, (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 73)
ic.composite(robotLeg3, 100 + (iconSize[0] / 2) - (robotOffset3[2]) + robotOffset3[0] + 40, (iconSize[1] / 2) - (robotOffset3[3]) - robotOffset3[1] + 78)
ic.composite(robotLeg1, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] - 20, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 50)
}
else if (drawLegs && form == "spider") {
let spiderBody;
ic.contain(iconSize[0], 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP)
ic.contain(iconSize[0] + 200, 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP)
if (iconID == "07") {
robotOffset2[2] -= 10
robotOffset2[1] += 12
robotOffset1b[3] -= 105
robotOffset2b[3] -= 150
robotOffset2b[2] -= 60
}
if (iconID == "16") {
robotOffset1b[3] -= 100
robotOffset2b[3] -= 200
robotOffset2b[2] -= 30
}
await Jimp.read(new Jimp(robotGlow1)).then(rob => {
if (robotGlow1.bitmap.width < 10) robotGlow1.opacity(0)
else robotGlow1 = recolor(rob, col2)
})
await Jimp.read(new Jimp(robotGlow2)).then(rob => {
robotGlow2 = recolor(rob, col2)
})
await Jimp.read(new Jimp(robotGlow3)).then(rob => {
robotGlow3 = recolor(rob, col2)
})
await Jimp.read(new Jimp(robotLeg1)).then(rob => {
recolor(rob, col1)
rob.composite(robotGlow1, ((robotOffset1[2] - robotOffset1b[2]) / 2) + (glowOffset[2] || 0), ((robotOffset1[3] - robotOffset1b[3]) / 4) + (glowOffset[3] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
robotLeg1 = rob
})
await Jimp.read(new Jimp(robotLeg2)).then(rob => {
recolor(rob, col1)
rob.composite(robotGlow2, ((robotOffset2[2] - robotOffset2b[2]) / 6) + (glowOffset[0] || 0), ((robotOffset2[3] - robotOffset2b[3]) / 6) + (glowOffset[1] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
rob.rotate(-40)
robotLeg2 = rob
})
await Jimp.read(new Jimp(robotLeg1)).then(rob => {
robotLeg1b = rob.color([{ apply: 'darken', params: [20] }])
})
await Jimp.read(new Jimp(robotLeg1b)).then(rob => {
robotLeg1c = rob.mirror(true, false)
})
await Jimp.read(new Jimp(robotLeg3)).then(rob => {
recolor(rob, col1)
rob.composite(robotGlow3, ((robotOffset3[2] - robotOffset3b[2]) / 2) + (glowOffset[4] || 0), ((robotOffset3[3] - robotOffset3b[3]) / 2) + (glowOffset[5] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
robotLeg3 = rob
})
await Jimp.read(new Jimp(ic)).then(rob => {
spiderBody = rob
})
ic.composite(robotLeg3, 100 + (iconSize[0] / 2) - (robotOffset3[2]) + (robotOffset3[0]), (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 77)
ic.composite(robotLeg1b, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] + 35, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 70)
ic.composite(robotLeg1c, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] + 75, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 70)
// ^ BELOW
ic.composite(spiderBody, 0, 0)
// v ABOVE
ic.composite(robotLeg2, 100 + (iconSize[0] / 2) - (robotOffset2[2]) + robotOffset2[0] - 60, (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 75)
ic.composite(robotLeg1, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] + 7, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 70)
}
// every now and then jimp does a fucky wucky uwu and this line errors. seems to be an issue with the lib itself :v
try { if (useExtra) ic.composite(extra, imgOff + (iconSize[0] / 2) - (size2[0] / 2) + offset2[0], (iconSize[1] / 2) - (size2[1] / 2) - offset2[1] + (form == "bird" && !req.query.topless ? 300 - iconSize[1] : 0)) }
catch(e) {}
let finalSize = [ic.bitmap.width, ic.bitmap.height]
function finish(img) {
img.autocrop(0.01, false)
if (form == "swing") img.resize(120, 111)
if (img.bitmap.height == 300) ic.autocrop(1, false)
if (sizeParam) {
let thicc = img.bitmap.width > img.bitmap.height
let imgSize = req.query.size == "auto" ? (thicc ? img.bitmap.width : img.bitmap.height) : Math.round(req.query.size)
if (imgSize < 32) imgSize = 32
if (imgSize > 512) imgSize = 512
if (thicc) img.contain(img.bitmap.width, img.bitmap.width, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
else img.contain(img.bitmap.height, img.bitmap.height, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
img.resize(imgSize, Jimp.AUTO)
}
img.getBuffer(Jimp.AUTO, (err, buffer) => {
if (!sizeParam && drawLegs) {
cache[iconCode] = { value: buffer, timeoutID: setTimeout(function() {delete cache[iconCode]}, 10000000) } // 3 hour cache
if (usercode) cache[usercode] = { value: buffer, timeoutID: setTimeout(function() {delete cache[usercode]}, 300000) } // 5 min cache for player icons
}
return res.end(buffer, 'base64')
})
}
if (!outline) return finish(ic)
else {
ic.getBuffer(Jimp.AUTO, function (err, buff) {
const Canvas = require('canvas')
, Image = Canvas.Image
, canvas = Canvas.createCanvas(finalSize[0] + 10, finalSize[1] + 10)
, ctx = canvas.getContext('2d');
if (!colG) colG = (col2 == 15 || col2 == "000000" ? col1 : col2)
if (colG == 15 || colG == "000000") colG = 12
const img = new Image()
img.onload = () => {
var dArr = [-1, -1, 0, -1, 1, -1, -1, 0, 1, 0, -1, 1, 0, 1, 1, 1], // offset array
s = 2, i = 0, x = canvas.width / 2 - finalSize[0] / 2, y = canvas.height / 2 - finalSize[1] / 2;
for (; i < dArr.length; i += 2) ctx.drawImage(img, x + dArr[i] * s, y + dArr[i + 1] * s);
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = `rgba(${colors[colG].r}, ${colors[colG].g}, ${colors[colG].b}, 1})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "source-over";
ctx.imageSmoothingEnabled = false;
// Add UFO top last so it doesn't get glow'd
if (form == "bird" && !topless) {
const dome = new Image()
dome.src = ufoSprite
ctx.drawImage(dome, ufoCoords[0]+5, ufoCoords[1]+5)
}
ctx.drawImage(img, x, y)
}
img.onerror = err => { throw err }
img.src = buff
Jimp.read(canvas.toBuffer()).then(b => {
return finish(b)
})
})
}
})
})
if (legSection.yScale) builtPart.resize({width: spriteSize[0], height: Math.floor(spriteSize[1] * legSection.yScale), fit: "fill"})
if (legSection.xFlip) builtPart.flop()
}
let username = req.params.text
let userCode;
res.contentType('image/png');
if (req.offline || req.query.hasOwnProperty("noUser") || req.query.hasOwnProperty("nouser") || username == "icon") return buildIcon()
else if (app.config.cachePlayerIcons && !Object.keys(req.query).filter(x => !["form", "forceGD"].includes(x)).length) {
userCode = `${req.id}u-${username.toLowerCase()}-${forms[req.query.form] ? req.query.form : 'cube'}`
if (cache[userCode]) return res.end(cache[userCode].value)
let layerData = {
partName, spriteOffset, spriteSize, leg,
layerName: partNames[part],
behind: legSection && legSection.darken,
isGlow: part == "glow",
input: await builtPart.toBuffer(),
left, top
}
let accountMode = !req.query.hasOwnProperty("player") && Number(req.params.id)
let foundID = app.userCache(req.id, username)
let skipRequest = accountMode || foundID
let forceGD = req.query.hasOwnProperty("forceGD")
// skip request by causing fake error lmao
req.gdRequest(skipRequest ? "" : 'getGJUsers20', skipRequest ? {} : req.gdParams({ str: username, forceGD }, !forceGD), function (err1, res1, body1) {
if (legSection) {
if (!legLayers[legSection.leg]) legLayers[legSection.leg] = [layerData]
else legLayers[legSection.leg].push(layerData)
}
let result = foundID ? foundID[0] : (accountMode || err1) ? username : app.parseResponse(body1)[16];
req.gdRequest('getGJUserInfo20', req.gdParams({ targetAccountID: result, forceGD }, !forceGD), function (err2, res2, body2) {
if (err2) return buildIcon();
let iconData = app.parseResponse(body2)
if (!foundID && !forceGD) app.userCache(req.id, iconData[16], iconData[2], iconData[1])
return buildIcon(iconData, userCode);
else layers.push(layerData)
}
// build all layers of icon segment (col1, col2, glow, extra)
async function buildFullLayer(legSection) {
let hasExtra = fs.existsSync(getPartName("extra", legSection ? legSection.leg : null).slice(1))
if (form.form == "bird" && !topless) await addLayer(3, null, legSection) // ufo top
await addLayer(2, col2, legSection) // secondary color
if (useGlow) await addLayer("glow", glowColor, legSection) // glow
await addLayer(1, col1, legSection) // primary color
if (hasExtra) await addLayer("extra", colW, legSection) // extra
// if (legSection) {
// let foundLeg = legLayers[legSection.leg]
// foundLeg.forEach(x => layers.push(x))
// }
}
let layers = []
let legLayers = []
let legData = form.legs ? legOffsets[form.form] || [] : []
let parentSize = icons[getPartName(1).slice(mainPath.length)].spriteSize
let canvas = sharp({create: {width: canvasSize, height: canvasSize, channels: 4, background: TRANSPARENT}})
// if (legData.length) {
// for (let i=0; i<legData.length; i++) {
// await buildFullLayer(legData[i])
// }
// }
await buildFullLayer()
// if (legData.length) layers = legLayers.flat().filter(x => x).sort((a, b) => !!b.behind - !!a.behind).sort((a, b) => !!b.isGlow - !!a.isGlow)
canvas.composite(layers)
let rawData = await canvas.toBuffer({resolveWithObject: true})
let minX = canvasSize; let maxX = 0;
let minY = canvasSize; let maxY = 0;
for (let i=0; i<rawData.data.length; i += 4) { // [R, G, B, A]
let pixelIndex = i/4
let x = pixelIndex % canvasSize;
let y = Math.floor(pixelIndex / canvasSize);
let alpha = rawData.data[i + 3];
if (alpha > 0) {
if (x < minX) minX = x
if (x > maxX) maxX = x
if (y < minY) minY = y
if (y > maxY) maxY = y
}
}
// need to make a new sharp instance so everything is merged. bit hacky but it works
let dimensions = [maxX - minX, maxY - minY]
if (!psdExport) {
let finalIcon = sharp(rawData.data, {raw: {width: canvasSize, height: canvasSize, channels: 4}})
.extract({left: minX, top: minY, width: dimensions[0], height: dimensions[1]})
if (customSize) {
let isThicc = dimensions[0] > dimensions[1]
let squareSize = req.query.size == "auto" ? (isThicc ? dimensions[0] : dimensions[1]) : Math.floor(req.query.size)
if (squareSize < 32) squareSize = 32
if (squareSize > 256) squareSize = 256
// use longest side to make square
if (isThicc) finalIcon.resize({
width: dimensions[isThicc ? 0 : 1],
height: dimensions[isThicc ? 0 : 1],
fit: "contain",
background: TRANSPARENT
})
finalIcon.resize({width: squareSize, height: squareSize, fit: "contain", background: TRANSPARENT})
}
finalIcon.png().toBuffer().then(x => {
res.end(x) // send file
if (cachable) { // cache for a bit
cache[iconCode] = { buffer: x, timeoutID: setTimeout(function() {delete cache[iconCode]}, 10000000) } // cache file for 3 hours
if (userCode) cache[userCode] = { buffer: x, timeoutID: setTimeout(function() {delete cache[userCode]}, 300000) } // 5 min cache for player icons
}
})
}
else {
let psdLayers = layers.map(x => {
let Image = Canvas.Image
let canvas = Canvas.createCanvas(...dimensions)
let ctx = canvas.getContext('2d');
const img = new Image()
img.onload = () => {
ctx.drawImage(img, 0 + x.left - minX, 0 + x.top - minY)
}
img.onerror = err => { throw err }
img.src = x.input
return {name: x.layerName, canvas, leg: x.leg}
})
});
if (form.legs) {
let legLayers = []
for (let i=1; i<=form.legs + 1; i++) legLayers.push({name: i == 1 ? "Base" : `Leg ${i}`, opened: true, children: []})
psdLayers.forEach(x => {
legLayers[x.leg-1].children.push(x)
})
psdLayers = legLayers.reverse()
}
const photoshop = {
width: dimensions[0],
height: dimensions[1],
children: psdLayers
};
const buffer = psd.writePsdBuffer(photoshop);
return res.end(buffer)
}
}
// ==================================== //
// OLD CODE IS BEING USED FOR ROBOTS AND SPIDERS
let formCheck = forms[req.query.form]
if (formCheck && formCheck.legs) return app.run.icon_old(app, req, res)
let username = req.params.text
let userCode;
res.contentType('image/png');
if (req.offline || req.query.hasOwnProperty("noUser") || req.query.hasOwnProperty("nouser") || username == "icon") return buildIcon()
else if (app.config.cachePlayerIcons && !Object.keys(req.query).filter(x => !["form", "forceGD"].includes(x)).length) {
userCode = `${req.id}u-${username.toLowerCase()}-${forms[req.query.form] ? req.query.form : 'cube'}`
if (cache[userCode]) return res.end(cache[userCode].value)
}
let accountMode = !req.query.hasOwnProperty("player") && Number(req.params.id)
let foundID = app.userCache(req.id, username)
let skipRequest = accountMode || foundID
let forceGD = req.query.hasOwnProperty("forceGD")
// skip request by causing fake error lmao
req.gdRequest(skipRequest ? "" : 'getGJUsers20', skipRequest ? {} : req.gdParams({ str: username, forceGD }, !forceGD), function (err1, res1, body1) {
let result = foundID ? foundID[0] : (accountMode || err1) ? username : app.parseResponse(body1)[16];
req.gdRequest('getGJUserInfo20', req.gdParams({ targetAccountID: result, forceGD }, !forceGD), function (err2, res2, body2) {
if (err2) return buildIcon();
let iconData = app.parseResponse(body2)
if (!foundID && !forceGD) app.userCache(req.id, iconData[16], iconData[2], iconData[1])
return buildIcon(iconData, userCode);
})
});
}

392
api/icon_old.js Normal file
View file

@ -0,0 +1,392 @@
// this file is a potential candidate for worst code on github
// i advise you to turn back now
// seriously, it's not too late
// update: there is now a new system being used for icons, however, spiders and robots do not work
// this old code is being used in the meantime
const Jimp = require('jimp');
const fs = require('fs');
const icons = require('../icons/gameSheet.json');
const colors = require('../icons/colors.json');
const forms = require('../icons/forms.json')
const offsets = require('../icons/offsets.json');
let hexRegex = /^[A-Fa-f0-9]{6}$/
function hexConvert(hex) { hex = hex.replace('#', ''); return {r: '0x' + hex[0] + hex[1] | 0, g: '0x' + hex[2] + hex[3] | 0, b: '0x' + hex[4] + hex[5] | 0}; }
function recolor(img, col) {
return img.scan(0, 0, img.bitmap.width, img.bitmap.height, function (x, y, idx) {
if (img.bitmap.data.slice(idx, idx+3).every(function(val) {return val >= 20 && val <= 255})) { // If it's not "black, i.e. we want to recolor it"
this.bitmap.data[idx] = colors[col].r / (255 / this.bitmap.data[idx]);
this.bitmap.data[idx + 1] = colors[col].g / (255 / this.bitmap.data[idx + 1]);
this.bitmap.data[idx + 2] = colors[col].b / (255 / this.bitmap.data[idx + 2]);
}
})
}
/* Caveat of genFileName is that if there are any falsey values in the arguments they are ignored.
This is usually a good thing though - avoid issues by not putting something like 0 instead of '0' */
function genFileName(...args) { return args.filter(function(val) {return val}).join('_') +'_001.png' }
function fromIcons(filename) { return `./icons/${filename}` }
let cache = {};
module.exports = async (app, req, res) => {
function buildIcon(account=[], usercode) {
let { form, ind } = forms[req.query.form] || {};
form = form || 'player';
ind = ind || 21;
let iconID = req.query.icon || account[ind] || 1;
let col1 = req.query.col1 || account[10] || 0;
let col2 = req.query.col2 || account[11] || 3;
let colG = req.query.colG || req.query.colg
let colW = req.query.colW || req.query.colw || req.query.col3
let outline = req.query.glow || account[28] || "0";
let topless = form == "bird" && req.query.topless
let drawLegs = !(req.query.noLegs > 0)
let autoSize = req.query.size == "auto"
let sizeParam = autoSize || (req.query.size && !isNaN(req.query.size))
if (outline == "0" || outline == "false") outline = false;
if (iconID && iconID.toString().length == 1) iconID = "0" + iconID;
function genImageName(...args) { return genFileName(form, iconID, ...args) }
let icon, glow, extra;
function setBaseIcons() {
icon = genImageName(isSpecial && '01');
glow = genImageName(isSpecial && '01', '2');
extra = genImageName(isSpecial && '01', 'extra');
}
let isSpecial = ['robot', 'spider'].includes(form);
setBaseIcons();
if (!fs.existsSync(fromIcons(icon)) || (isSpecial && !fs.existsSync(fromIcons(genImageName('02'))))) {
iconID = '01';
setBaseIcons();
}
let ex = fromIcons(extra)
let hasExtra = fs.existsSync(ex)
let cols = [col1, col2, colG, colW]
cols.forEach(col => {
if (!col) return
col = col.toString()
if (col.match(hexRegex)) colors[col.toLowerCase()] = hexConvert(col)
})
if (!colors[col1] || isNaN(colors[col1].r)) col1 = colors[+col1] ? +col1 : 0
if (!colors[col2] || isNaN(colors[col2].r)) col2 = colors[+col2] ? +col2 : 3
if (!colors[colG] || isNaN(colors[colG].r)) colG = colors[+colG] ? +colG : null
if (!colors[colW] || isNaN(colors[colW].r)) colW = colors[+colW] ? +colW : null
if (colW && (!hasExtra || colW == 12)) colW = null
if (col1 == 15 || col1 === "000000") outline = true;
let iconCode = `${req.query.form == "cursed" ? "cursed" : form}${topless ? "top" : ""}-${iconID}-${col1}-${col2}-${colG || "x"}-${colW || "x"}-${outline ? 1 : 0}`
if (!sizeParam && (!isSpecial || drawLegs) && cache[iconCode]) return res.end(cache[iconCode].value)
let useExtra = false
let originalOffset = icons[icon].spriteOffset;
let minusOrigOffset = function(x, y) { return x - originalOffset[y] }
let offset = icons[glow].spriteOffset.map(minusOrigOffset);
let robotLeg1, robotLeg2, robotLeg3, robotLeg3b, robotLeg2b, robotLeg1b, robotLeg1c;
let robotOffset1, robotOffset2, robotOffset3, robotOffset1b, robotOffset2b, robotOffset3b;
let robotGlow1, robotGlow2, robotGlow3, glowOffset
let ufoTop, ufoOffset, ufoCoords, ufoSprite
let extrabit, offset2, size2;
if (isSpecial) {
const legs = [1,2,3].map(function(val) {return genImageName(`0${val+1}`)});
const glows = [1,2,3].map(function(val) {return genImageName(`0${val+1}`, '2')});
robotOffset1 = icons[legs[0]].spriteOffset.map(minusOrigOffset).concat(icons[legs[0]].spriteSize);
robotOffset2 = icons[legs[1]].spriteOffset.map(minusOrigOffset).concat(icons[legs[1]].spriteSize);
robotOffset3 = icons[legs[2]].spriteOffset.map(minusOrigOffset).concat(icons[legs[2]].spriteSize);
robotOffset1b = icons[glows[0]].spriteOffset.map(minusOrigOffset).concat(icons[glows[0]].spriteSize);
robotOffset2b = icons[glows[1]].spriteOffset.map(minusOrigOffset).concat(icons[glows[1]].spriteSize);
robotOffset3b = icons[glows[2]].spriteOffset.map(minusOrigOffset).concat(icons[glows[2]].spriteSize);
robotLeg1 = new Jimp(fromIcons(legs[0])); robotGlow1 = new Jimp(fromIcons(glows[0]))
robotLeg2 = new Jimp(fromIcons(legs[1])); robotGlow2 = new Jimp(fromIcons(glows[1]))
robotLeg3 = new Jimp(fromIcons(legs[2])); robotGlow3 = new Jimp(fromIcons(glows[2]))
glowOffset = offsets[form][+iconID] || []
}
Jimp.read(fromIcons(glow)).then(async function (image) {
let size = [image.bitmap.width, image.bitmap.height]
let glow = recolor(image, col2)
let imgOff = isSpecial ? 100 : 0
let eb = fromIcons(extra)
if (fs.existsSync(eb)) {
extrabit = icons[extra]
offset2 = extrabit.spriteOffset.map(minusOrigOffset);
size2 = extrabit.spriteSize;
extra = new Jimp(eb);
if (colW) await Jimp.read(eb).then(e => { extra = recolor(e, colW) })
useExtra = true
}
Jimp.read(fromIcons(icon)).then(async function (ic) {
let iconSize = [ic.bitmap.width, ic.bitmap.height]
recolor(ic, col1)
ic.composite(glow, (iconSize[0] / 2) - (size[0] / 2) + offset[0], (iconSize[1] / 2) - (size[1] / 2) - offset[1], { mode: Jimp.BLEND_DESTINATION_OVER })
if (form == "bird" && !topless) {
ufoTop = genImageName('3')
ufoOffset = icons[ufoTop].spriteOffset.map(minusOrigOffset).concat(icons[ufoTop].spriteSize);
ufoCoords = [imgOff + (iconSize[0] / 2) - (ufoOffset[2] / 2) + ufoOffset[0], (iconSize[1] / 2) - (ufoOffset[3] / 2) - ufoOffset[1] + 300 - iconSize[1]]
ufoSprite = fromIcons(ufoTop)
ic.contain(iconSize[0], 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_BOTTOM)
// Only add dome if there's no glow, otherwise the dome will be outlined as well
if (!outline) ic.composite(await Jimp.read(ufoSprite), ufoCoords[0], ufoCoords[1], {mode: Jimp.BLEND_DESTINATION_OVER})
}
if (drawLegs && (form == "robot" || req.query.form == "cursed")) {
ic.contain(iconSize[0], 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP)
ic.contain(iconSize[0] + 200, 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP)
await Jimp.read(new Jimp(robotGlow1)).then(rob => {
rob.rotate(-45)
robotGlow1 = recolor(rob, col2)
})
await Jimp.read(new Jimp(robotGlow2)).then(rob => {
rob.rotate(45)
robotGlow2 = recolor(rob, col2)
})
await Jimp.read(new Jimp(robotGlow3)).then(rob => {
robotGlow3 = recolor(rob, col2)
})
await Jimp.read(new Jimp(robotLeg1)).then(rob => {
rob.rotate(-45)
recolor(rob, col1)
rob.composite(robotGlow1, (robotOffset1[2] - robotOffset1b[2]) + (glowOffset[0] || 1), ((robotOffset1[3] - robotOffset1b[3]) / 2) + (glowOffset[1] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
robotLeg1 = rob
})
await Jimp.read(new Jimp(robotLeg2)).then(rob => {
rob.rotate(45)
recolor(rob, col1)
rob.composite(robotGlow2, ((robotOffset2[2] - robotOffset2b[2]) / 4) + (glowOffset[4] || 0), ((robotOffset2[3] - robotOffset2b[3]) / 2) + (glowOffset[5] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
robotLeg2 = rob
})
await Jimp.read(new Jimp(robotLeg2)).then(rob => {
robotLeg2b = rob.color([{ apply: 'darken', params: [20] }]).rotate(-5)
})
await Jimp.read(new Jimp(robotLeg3)).then(rob => {
recolor(rob, col1)
rob.composite(robotGlow3, ((robotOffset3[2] - robotOffset3b[2]) / 2) + (glowOffset[2] || 0), ((robotOffset3[3] - robotOffset3b[3]) / 2) + (glowOffset[3] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
robotLeg3 = rob
})
await Jimp.read(new Jimp(robotLeg3)).then(rob => {
robotLeg3b = rob.color([{ apply: 'darken', params: [10] }])
})
ic.composite(robotLeg2b, 100 + (iconSize[0] / 2) - (robotOffset2[2]) + robotOffset2[0] - 31, (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 73)
ic.composite(robotLeg3b, 100 + (iconSize[0] / 2) - (robotOffset3[2]) + robotOffset3[0] + 20, (iconSize[1] / 2) - (robotOffset3[3]) - robotOffset3[1] + 78)
ic.composite(robotLeg2, 100 + (iconSize[0] / 2) - (robotOffset2[2]) + robotOffset2[0] - 20, (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 73)
ic.composite(robotLeg3, 100 + (iconSize[0] / 2) - (robotOffset3[2]) + robotOffset3[0] + 40, (iconSize[1] / 2) - (robotOffset3[3]) - robotOffset3[1] + 78)
ic.composite(robotLeg1, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] - 20, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 50)
}
else if (drawLegs && form == "spider") {
let spiderBody;
ic.contain(iconSize[0], 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP)
ic.contain(iconSize[0] + 200, 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP)
if (iconID == "07") {
robotOffset2[2] -= 10
robotOffset2[1] += 12
robotOffset1b[3] -= 105
robotOffset2b[3] -= 150
robotOffset2b[2] -= 60
}
if (iconID == "16") {
robotOffset1b[3] -= 100
robotOffset2b[3] -= 200
robotOffset2b[2] -= 30
}
await Jimp.read(new Jimp(robotGlow1)).then(rob => {
if (robotGlow1.bitmap.width < 10) robotGlow1.opacity(0)
else robotGlow1 = recolor(rob, col2)
})
await Jimp.read(new Jimp(robotGlow2)).then(rob => {
robotGlow2 = recolor(rob, col2)
})
await Jimp.read(new Jimp(robotGlow3)).then(rob => {
robotGlow3 = recolor(rob, col2)
})
await Jimp.read(new Jimp(robotLeg1)).then(rob => {
recolor(rob, col1)
rob.composite(robotGlow1, ((robotOffset1[2] - robotOffset1b[2]) / 2) + (glowOffset[2] || 0), ((robotOffset1[3] - robotOffset1b[3]) / 4) + (glowOffset[3] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
robotLeg1 = rob
})
await Jimp.read(new Jimp(robotLeg2)).then(rob => {
recolor(rob, col1)
rob.composite(robotGlow2, ((robotOffset2[2] - robotOffset2b[2]) / 6) + (glowOffset[0] || 0), ((robotOffset2[3] - robotOffset2b[3]) / 6) + (glowOffset[1] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
rob.rotate(-40)
robotLeg2 = rob
})
await Jimp.read(new Jimp(robotLeg1)).then(rob => {
robotLeg1b = rob.color([{ apply: 'darken', params: [20] }])
})
await Jimp.read(new Jimp(robotLeg1b)).then(rob => {
robotLeg1c = rob.mirror(true, false)
})
await Jimp.read(new Jimp(robotLeg3)).then(rob => {
recolor(rob, col1)
rob.composite(robotGlow3, ((robotOffset3[2] - robotOffset3b[2]) / 2) + (glowOffset[4] || 0), ((robotOffset3[3] - robotOffset3b[3]) / 2) + (glowOffset[5] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
robotLeg3 = rob
})
await Jimp.read(new Jimp(ic)).then(rob => {
spiderBody = rob
})
ic.composite(robotLeg3, 100 + (iconSize[0] / 2) - (robotOffset3[2]) + (robotOffset3[0]), (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 77)
ic.composite(robotLeg1b, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] + 35, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 70)
ic.composite(robotLeg1c, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] + 75, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 70)
// ^ BELOW
ic.composite(spiderBody, 0, 0)
// v ABOVE
ic.composite(robotLeg2, 100 + (iconSize[0] / 2) - (robotOffset2[2]) + robotOffset2[0] - 60, (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 75)
ic.composite(robotLeg1, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] + 7, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 70)
}
// every now and then jimp does a fucky wucky uwu and this line errors. seems to be an issue with the lib itself :v
try { if (useExtra) ic.composite(extra, imgOff + (iconSize[0] / 2) - (size2[0] / 2) + offset2[0], (iconSize[1] / 2) - (size2[1] / 2) - offset2[1] + (form == "bird" && !req.query.topless ? 300 - iconSize[1] : 0)) }
catch(e) {}
let finalSize = [ic.bitmap.width, ic.bitmap.height]
function finish(img) {
img.autocrop(0.01, false)
if (form == "swing") img.resize(120, 111)
if (img.bitmap.height == 300) ic.autocrop(1, false)
if (sizeParam) {
let thicc = img.bitmap.width > img.bitmap.height
let imgSize = req.query.size == "auto" ? (thicc ? img.bitmap.width : img.bitmap.height) : Math.round(req.query.size)
if (imgSize < 32) imgSize = 32
if (imgSize > 512) imgSize = 512
if (thicc) img.contain(img.bitmap.width, img.bitmap.width, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
else img.contain(img.bitmap.height, img.bitmap.height, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
img.resize(imgSize, Jimp.AUTO)
}
img.getBuffer(Jimp.AUTO, (err, buffer) => {
if (!sizeParam && drawLegs) {
cache[iconCode] = { value: buffer, timeoutID: setTimeout(function() {delete cache[iconCode]}, 10000000) } // 3 hour cache
if (usercode) cache[usercode] = { value: buffer, timeoutID: setTimeout(function() {delete cache[usercode]}, 300000) } // 5 min cache for player icons
}
return res.end(buffer, 'base64')
})
}
if (!outline) return finish(ic)
else {
ic.getBuffer(Jimp.AUTO, function (err, buff) {
const Canvas = require('canvas')
, Image = Canvas.Image
, canvas = Canvas.createCanvas(finalSize[0] + 10, finalSize[1] + 10)
, ctx = canvas.getContext('2d');
if (!colG) colG = (col2 == 15 || col2 == "000000" ? col1 : col2)
if (colG == 15 || colG == "000000") colG = 12
const img = new Image()
img.onload = () => {
var dArr = [-1, -1, 0, -1, 1, -1, -1, 0, 1, 0, -1, 1, 0, 1, 1, 1], // offset array
s = 2, i = 0, x = canvas.width / 2 - finalSize[0] / 2, y = canvas.height / 2 - finalSize[1] / 2;
for (; i < dArr.length; i += 2) ctx.drawImage(img, x + dArr[i] * s, y + dArr[i + 1] * s);
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = `rgba(${colors[colG].r}, ${colors[colG].g}, ${colors[colG].b}, 1})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "source-over";
ctx.imageSmoothingEnabled = false;
// Add UFO top last so it doesn't get glow'd
if (form == "bird" && !topless) {
const dome = new Image()
dome.src = ufoSprite
ctx.drawImage(dome, ufoCoords[0]+5, ufoCoords[1]+5)
}
ctx.drawImage(img, x, y)
}
img.onerror = err => { throw err }
img.src = buff
Jimp.read(canvas.toBuffer()).then(b => {
return finish(b)
})
})
}
})
})
}
let username = req.params.text
let userCode;
res.contentType('image/png');
if (req.offline || req.query.hasOwnProperty("noUser") || req.query.hasOwnProperty("nouser") || username == "icon") return buildIcon()
else if (app.config.cachePlayerIcons && !Object.keys(req.query).filter(x => !["form", "forceGD"].includes(x)).length) {
userCode = `${req.id}u-${username.toLowerCase()}-${forms[req.query.form] ? req.query.form : 'cube'}`
if (cache[userCode]) return res.end(cache[userCode].value)
}
let accountMode = !req.query.hasOwnProperty("player") && Number(req.params.id)
let foundID = app.userCache(req.id, username)
let skipRequest = accountMode || foundID
let forceGD = req.query.hasOwnProperty("forceGD")
// skip request by causing fake error lmao
req.gdRequest(skipRequest ? "" : 'getGJUsers20', skipRequest ? {} : req.gdParams({ str: username, forceGD }, !forceGD), function (err1, res1, body1) {
let result = foundID ? foundID[0] : (accountMode || err1) ? username : app.parseResponse(body1)[16];
req.gdRequest('getGJUserInfo20', req.gdParams({ targetAccountID: result, forceGD }, !forceGD), function (err2, res2, body2) {
if (err2) return buildIcon();
let iconData = app.parseResponse(body2)
if (!foundID && !forceGD) app.userCache(req.id, iconData[16], iconData[2], iconData[1])
return buildIcon(iconData, userCode);
})
});
}

View file

@ -11,7 +11,6 @@ module.exports = async (app, req, res) => {
if (err) return res.send('-1')
else if (body < 0) return res.send(false)
request.get('https://www.newgrounds.com/audio/listen/' + songID, function(err2, resp2, song) {
console.log(resp2.statusCode)
return res.send(resp2.statusCode == 200)
})
})

View file

@ -482,6 +482,10 @@ input[type=checkbox]:checked + label.gdcheckbox {
animation: boxAnimator 0.25s;
}
.grayscale {
filter: grayscale(100%) brightness(0.7);
}
.gold {
color: rgb(255, 200, 0);
}

View file

@ -820,7 +820,7 @@
<p style="font-size:15px; margin-top:-7px">This one isn't really part of the API, but dammit, my website my rules</p>
<br>
<p class="reveal" onclick="$('#params-icons').slideToggle(100)"><b>Parameters (10)</b></p>
<p class="reveal" onclick="$('#params-icons').slideToggle(100)"><b>Parameters (11)</b></p>
<div class="subdiv" id="params-icons">
<p><b>Parameters can be used to modify parts of a fetched user's icon</b></p>
<p>IDs generally correspond to their order of appearance in GD</p>
@ -831,10 +831,11 @@
<p>colG: Optional color ID or hex code to overwrite the glow for the icon</p>
<p>colW: Optional color ID or hex code to overwrite the 'white' layer used by some detailed icons</p>
<p>glow: If the icon should have a glow/outline (0 = off, anything else = on)</p>
<p>size: The size in pixels that the icon should be (always square), in case you don't want the default. "Auto" also works.</p>
<p>size: The size in pixels that the icon should be (always square), in case you don't want the default. "auto" also works.</p>
<p>topless: Removes the glass 'dome' from generated UFOs (legacy)</p>
<p>player: Forces the player ID to be used for fetching (normally Account ID is tried first)</p>
<p>noUser: Disables fetching the icon from the GD servers. Slightly faster, but comes at the cost of having to build icons from the ground up using the parameters listed above. It completely ignores the entered username and always returns the default icon</p>
<p>psd: Saves the icon as a layered .psd file (spiders + robots not supported since they still use the old icon code)</p>
</div>
<br>

View file

@ -337,6 +337,7 @@ function clearFilters() {
$('.selectedFilter').removeClass('selectedFilter')
$('input[url]').prop('checked', false)
$('#songID').val("")
$('#levelName').val("")
$('#customSong').click()
hideDemonDiffs()
officialSong = 1

View file

@ -44,7 +44,7 @@
<div id="dl" style="display: none; position:absolute; top: 15%; right: 0.5%; text-align: center; width: 17%">
<h1 class="smaller" style="margin-bottom: 1%">Note</h1>
<p style="font-size: 2.2vh; margin-top: 1.2%"><ca>Level downloading</ca> has been <cr>blocked</cr> by RobTop.
<cy>Level analysis, daily levels, and downloading extra info</cy> will <cg>not work</cg> until he choses to unblock downloads.
<cy>Level analysis, daily levels, and downloading extra info</cy> will <cg>not work</cg> until he chooses to unblock downloads.
These features still work <ca><a class="underline" target="_blank" href="https://github.com/GDColon/GDBrowser">locally</a></ca> and on <ca><a class="underline" href="../gdps">private servers</a></ca></p>
</div>

View file

@ -50,6 +50,8 @@
<hr id="gdfloor">
<div id="menuButtons" style="height: 65px; margin: 0 0 8 0;">
<button class="blankButton menuButton" id="customColors" title="Settings" onclick="$('#settings').show()"><img src="../assets/iconkitbuttons/cog.png" width=60px></button>
<button class="blankButton menuButton" id="downloadPSD" title="Download layered PSD"><a id="psdLink" download="cube_1.psd" href=""><img src="../assets/iconkitbuttons/psd.png" width=60px></a></button>
<button class="blankButton menuButton" id="psdDisabled" style="display: none" onclick="alert('Robots and spiders still use older icon code and cannot be downloaded as a PSD! (hopefully soon)')"><img class="grayscale" src="../assets/iconkitbuttons/psd.png" width=60px></button>
<button class="blankButton menuButton" id="downloadIcon" title="Download icon"><a id="downloadLink" download="cube_1.png" href=""><img src="../assets/iconkitbuttons/save.png" width=60px></a></button>
<button class="blankButton menuButton" id="getUserIcon" title="Get player icon"><img src="../assets/iconkitbuttons/steal.png" width=60px></button>
<button class="blankButton menuButton" id="randomIcon" title="Random Icon"><img src="../assets/iconkitbuttons/shuffle.png" width=60px></button>
@ -84,7 +86,13 @@
</div>
<br>
<p style="color: rgb(20, 20, 20); margin-top: 4px">Created by <a target=_blank href="https://twitter.com/TheRealGDColon">Colon</a> • All sprites/assets belong to <a target=_blank href="http://robtopgames.com">RobTop Games</a><a target=_blank href="https://gdbrowser.com/api#icons">API Reference</a></p>
<p style="color: rgb(20, 20, 20); margin-top: 0px; line-height: 16px">
Created by <a target=_blank href="https://twitter.com/TheRealGDColon">Colon</a>
• All sprites/assets belong to <a target=_blank href="http://robtopgames.com">RobTop Games</a>
<br><br>
<a target=_blank href="https://gdbrowser.com/api#icons">API Reference</a>
<a target=_blank href="https://github.com/GDColon/GDBrowser/blob/master/api/icon.js">Source Code</a>
</p>
<div style="position:absolute; top: 2%; left: -1.95%; width: 10%; height: 25%; pointer-events: none">
<img class="gdButton" style="pointer-events: all" id="backButton" src="../assets/back.png" height="30%" onclick="backButton()">
@ -144,6 +152,11 @@ function colorSplit() {
if ($("#colG").is(':visible') || $("#colW").is(':visible')) $('.colorSplit').show()
else $('.colorSplit').hide()
}
function psdCheck() {
let psdHref = $('#psdLink').attr('href').toLowerCase()
if (psdHref.includes("&form=robot") || psdHref.includes("&form=spider")) { $('#downloadPSD').hide(); $('#psdDisabled').show() }
else { $('#downloadPSD').show(); $('#psdDisabled').hide() }
}
forms.forEach(form => {
$("#iconTabs").append(`<button form="${form}" class="blankButton iconTabButton"><img src="../assets/iconkitbuttons/${form}_off.png" width=50px></button>`)
@ -166,6 +179,8 @@ function generateIcon() {
let finalURL = `../icon/icon?icon=${selectedIcon}&form=${selectedForm}${noDome ? "&topless=1" : ""}&col1=${selectedCol1}&col2=${selectedCol2}${selectedColG != selectedCol2 ? `&colG=${selectedColG}` : ""}${selectedColW ? `&colW=${selectedColW}` : ""}${enableGlow > 0 ? "&glow=1" : ""}${square ? "&size=auto" : ""}`
$("#result").attr('src', finalURL).attr('download', `${selectedForm}_${selectedIcon}.png`)
$("#downloadLink").attr('href', finalURL).attr('download', `${selectedForm}_${selectedIcon}.png`)
$("#psdLink").attr('href', finalURL + "&psd=1").attr('download', `${selectedForm}_${selectedIcon}.psd`)
psdCheck()
if (enableGlow == 2) enableGlow = 0
}
@ -244,7 +259,8 @@ fetch('./api/icons').then(res => {
currentForm = form
$('.iconTabButton').each(function(x, y) {
$(this).children().first().attr('src', $(this).children().first().attr('src').replace('_on', '_off'))})
$(this).children().first().attr('src', $(this).children().first().attr('src').replace('_on', '_off'))
})
var img = $(this).children().first()
img.attr('src', img.attr('src').replace('_off', '_on'));
@ -471,8 +487,10 @@ fetch('./api/icons').then(res => {
$("#steal").hide()
$("#loading").show()
$("#result").hide()
$("#result").attr('src', iconURL).attr('download', `${user}_${formCopy}.png`)
$("#downloadLink").attr('href', iconURL).attr('download', `${user}_${formCopy}.png`)
$("#result").attr('src', iconURL).attr('download', `${user}_${formCopy}.png`)
$("#downloadLink").attr('href', iconURL).attr('download', `${user}_${formCopy}.png`)
$("#psdLink").attr('href', iconURL + "&psd=1").attr('download', `${user}_${formCopy}.psd`)
psdCheck()
$('#glow').attr('src', '../assets/iconkitbuttons/streak_off.png')
enableGlow = 0

BIN
icons/bird_01_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
icons/bird_02_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/bird_03_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
icons/bird_04_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/bird_05_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/bird_06_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
icons/bird_07_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
icons/bird_08_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/bird_09_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/bird_10_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
icons/bird_11_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/bird_12_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
icons/bird_13_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/bird_14_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
icons/bird_15_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
icons/bird_16_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/bird_17_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
icons/bird_18_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
icons/bird_19_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/bird_20_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
icons/bird_21_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
icons/bird_22_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/bird_23_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
icons/bird_24_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/bird_25_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
icons/bird_26_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/bird_27_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
icons/bird_28_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
icons/bird_29_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
icons/bird_30_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
icons/bird_31_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
icons/bird_32_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
icons/bird_33_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
icons/bird_34_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
icons/bird_35_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/dart_01_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
icons/dart_02_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
icons/dart_03_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
icons/dart_04_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
icons/dart_05_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
icons/dart_06_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/dart_07_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/dart_08_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
icons/dart_09_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/dart_10_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
icons/dart_11_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
icons/dart_12_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/dart_13_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
icons/dart_14_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
icons/dart_15_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/dart_16_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/dart_17_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
icons/dart_18_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
icons/dart_19_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/dart_20_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
icons/dart_21_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/dart_22_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
icons/dart_23_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/dart_24_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
icons/dart_25_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
icons/dart_26_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
icons/dart_27_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/dart_28_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/dart_29_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/dart_30_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
icons/dart_31_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/dart_32_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/dart_33_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/dart_34_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
icons/dart_35_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -17,15 +17,18 @@
},
"robot": {
"form": "robot",
"ind": 26
"ind": 26,
"legs": true
},
"spider": {
"form": "spider",
"ind": 43
"ind": 43,
"legs": true
},
"cursed": {
"form": "spider",
"ind": 43
"ind": 43,
"legs": true
},
"swing": {
"form": "swing",

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 B

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Some files were not shown because too many files have changed in this diff Show more