Merge branch 'master' into master

This commit is contained in:
Matt 2021-12-04 21:36:46 -08:00 committed by GitHub
commit e01764f575
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
334 changed files with 13036 additions and 245 deletions

70
.github/workflows/analyse.yml vendored Normal file
View file

@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '42 17 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

148
README.md
View file

@ -37,38 +37,28 @@ If you would like to add your GDPS to GDBrowser, [fill out this quick form](http
If you 100% insist on adding a private server to your own magical little fork, you can do so by adding it to **servers.json**. Simply add a new object to the array with the following information: If you 100% insist on adding a private server to your own magical little fork, you can do so by adding it to **servers.json**. Simply add a new object to the array with the following information:
| identifier | description |
|:----------------:|:-----------------------------:|
| `name` | The display name of the server |
| `link` | The server's website URL (unrelated to the actual endpoint) |
| `author` | The creator(s) of the server |
| `authorLink` | The URL to open when clicking on the creator's name |
| `id` | An ID for the server, also used as the subdomain (e.g. `something` would become `something.gdbrowser.com`) |
| `endpoint` | The actual endpoint to ~~spam~~ send requests to (e.g. `http://boomlings.com/database/` - make sure it ends with a slash!) |
**name:** The display name of the server
**link:** The server's website URL (unrelated to the actual endpoint)
**author:** The creator(s) of the server
**authorLink:** The URL to open when clicking on the creator's name
**id:** An ID for the server, also used as the subdomain (e.g. `something` would become `something.gdbrowser.com`)
**endpoint:** The actual endpoint to ~~spam~~ send requests to (e.g. `http://boomlings.com/database/` - make sure it ends with a slash!)
There's also a few optional values for fine-tuning. I'll add more over time There's also a few optional values for fine-tuning. I'll add more over time
[string] **timestampSuffix:** A string to append at the end of timestamps. Vanilla GD uses " ago" | identifier | description | type |
|:----------------:|:-----------------------------:|:----:|
[string] **demonList:** The URL of the server's Demon List API, if it has one (e.g. `http://pointercrate.com/` - make sure it ends with a slash!) | `timestampSuffix` | A string to append at the end of timestamps. Vanilla GD uses " ago" | string |
| `demonList` | The URL of the server's Demon List API, if it has one (e.g. `http://pointercrate.com/` - make sure it ends with a slash!) | string |
[array] **disabled:** An array of menu buttons to "disable" (mappacks, gauntlets, daily, weekly, etc). They appear greyed out but are still clickable. | `disabled` | An array of menu buttons to "disable" (mappacks, gauntlets, daily, weekly, etc). They appear greyed out but are still clickable. | array |
| `pinned` | "Pins" the server to the top of the GDPS list. It appears above all unpinned servers and is not placed in alphabetical order. | bool |
[bool] **pinned:** "Pins" the server to the top of the GDPS list. It appears above all unpinned servers and is not placed in alphabetical order. | `onePointNine` | Makes a bunch of fancy changes to better fit 1.9 servers. (removes orbs/diamonds, hides some pointless buttons, etc) | bool |
| `weeklyLeaderboard` | Enables the lost but not forgotten Weekly Leaderboard, for servers that still milk it | bool |
[bool] **onePointNine:** Makes a bunch of fancy changes to better fit 1.9 servers. (removes orbs/diamonds, hides some pointless buttons, etc) | `substitutions` | A list of parameter substitutions, because some servers rename/obfuscate them. (e.g. `{ "levelID": "oiuyhxp4w9I" }`) | object |
| `overrides` | A list of endpoint substitutions, because some servers use renamed or older versions. (e.g. `{ "getGJLevels21": "dorabaeChooseLevel42" }`) | object |
[bool] **weeklyLeaderboard:** Enables the lost but not forgotten Weekly Leaderboard, for servers that still milk it
[object] **substitutions:** A list of parameter substitutions, because some servers rename/obfuscate them. (e.g. `{ "levelID": "oiuyhxp4w9I" }`)
[object] **overrides:** A list of endpoint substitutions, because some servers use renamed or older versions. (e.g. `{ "getGJLevels21": "dorabaeChooseLevel42" }`)
@ -143,23 +133,13 @@ comingsoon.html was used while the site was still in development, I just left it
## Icons ## Icons
It's GJ_Gamesheet02 but split into a much more intimidating cluster of a million files. These icons are put together and colored in the monstrosity that is icon.js It's GJ_Gamesheet02 but split into a much more intimidating cluster of a million files. These icons are put together and colored in the monstrosity that is icon.js
| name | description |
|:----:|:-----------:|
|`parseIconPlist.js`| Reads GJ_GameSheet02-uhd.plist and magically transforms it into gameSheet.json. Props to 101arrowz for helping make this |
parseIconPlist.js reads GJ_GameSheet02-uhd.plist and magically transforms it into gameSheet.json. Props to 101arrowz for helping make this | `colors.json` | List of the player colors in GD. Fairly straight forward |
| `forms.json` | List of the different icon forms, their ingame filenames, and their index in responses from the GD servers
| `offsets.json` | A bunch of hardcoded offsets. Desperate times call for desperate measures |
colors.json is a list of the player colors in GD. Fairly straight forward
forms.json is a list of the different icon forms, their ingame filenames, and their index in responses from the GD servers
offsets.json is a bunch of hardcoded offsets. Desperate times call for desperate measures
## Misc ## Misc
@ -170,72 +150,32 @@ Inevitable misc folder
**Level Analysis Stuff (in a separate folder)** **Level Analysis Stuff (in a separate folder)**
| name | description |
blocks.json - The object IDs in the different 'families' of blocks |:----:|:-----------:|
| `blocks.json` | The object IDs in the different 'families' of blocks |
| `colorProperties.json` | Color channel cheatsheet |
| `initialProperties.json` | Level settings cheatsheet |
colorProperties.json - Color channel cheatsheet | `objectProperties.json` | Object property cheatsheet. Low budget version of [AlFas' one](https://github.com/AlFasGD/GDAPI/blob/master/GDAPI/GDAPI/Enumerations/GeometryDash/ObjectProperty.cs) |
| `objects.json` | IDs for portals, orbs, triggers, and misc stuff |
initialProperties.json - Level settings cheatsheet
objectProperties.json - Object property cheatsheet. Low budget version of [AlFas' one](https://github.com/AlFasGD/GDAPI/blob/master/GDAPI/GDAPI/Enumerations/GeometryDash/ObjectProperty.cs)
objects.json - IDs for portals, orbs, triggers, and misc stuff
**Everything Else** **Everything Else**
| name | description |
achievements.json - List of all GD/meltdown/subzero/etc achievements. `parseAchievementPlist.js` automatically creates this file |:----:|:-----------:|
| `achievements.json` | List of all GD/meltdown/subzero/etc achievements. `parseAchievementPlist.js` automatically creates this file |
| `achievementTypes.json` | An object containing different categories of achievements (stars, shards, vault, etc) and how to identify them |
| `credits.json` | Credits! (shown on the homepage) |
achievementTypes.json - An object containing different categories of achievements (stars, shards, vault, etc) and how to identify them | `dragscroll.js` | Used on several pages for drag scrolling |
| `music.json` | An array of the official GD tracks (name, artist) |
| `parseAchievementPlist.js` | A script that reads GD's achievement .plist files and converts it into achievements.json |
| `sampleIcons.json` | A pool of icons, one of which will randomly appear when visiting the icon kit. Syntax is [Name, ID, Col1, Col2, Glow] |
credits.json - Credits! (shown on the homepage) | `secretStuff.json` | GJP goes here, needed for level leaderboards. Not included in the repo for obvious reasons |
| `settings.js` | Tweak small settings here, mainly for local use or GDPS'es |
| `shops.js` | A hardcoded list of all the shop icons in GD |
| `sizecheck.js` | Excecuted on most pages. Used for the 'page isn't wide enough' message, back button, and a few other things |
dragscroll.js - Used on several pages for drag scrolling
music.json - An array of the official GD tracks (name, artist)
parseAchievementPlist.js - A script that reads GD's achievement .plist files and converts it into achievements.json
sampleIcons.json - A pool of icons, one of which will randomly appear when visiting the icon kit. Syntax is [Name, ID, Col1, Col2, Glow],
secretStuff.json - GJP goes here, needed for level leaderboards. Not included in the repo for obvious reasons
settings.js - Tweak small settings here, mainly for local use or GDPS'es
shops.js - A hardcoded list of all the shop icons in GD
sizecheck.js - Excecuted on most pages. Used for the 'page isn't wide enough' message, back button, and a few other things

View file

@ -6,6 +6,14 @@ const properties = require('../misc/analysis/objectProperties.json')
const ids = require('../misc/analysis/objects.json') const ids = require('../misc/analysis/objects.json')
module.exports = async (app, req, res, level) => { 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 unencrypted = level.data.startsWith('kS') // some gdps'es don't encrypt level data
let levelString = unencrypted ? level.data : Buffer.from(level.data, 'base64') let levelString = unencrypted ? level.data : Buffer.from(level.data, 'base64')

View file

@ -1,79 +1,252 @@
// this file is a potential candidate for worst code on github const sharp = require('sharp');
// i advise you to turn back now const Canvas = require('canvas')
// seriously, it's not too late const psd = require('ag-psd')
const Jimp = require('jimp');
const fs = require('fs'); const fs = require('fs');
const icons = require('../icons/gameSheet.json'); const mainPath = `../icons/`
const colors = require('../icons/colors.json'); const icons = require('../misc/icons/gameSheet.json');
const forms = require('../icons/forms.json') const colors = require('../misc/icons/colors.json');
const offsets = require('../icons/offsets.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}$/ let canvasSize = 300
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}; } let halfCanvas = canvasSize/2
function recolor(img, col) { let TRANSPARENT = {r: 0, g: 0, b: 0, alpha: 0}
return img.scan(0, 0, img.bitmap.width, img.bitmap.height, function (x, y, idx) { let cache = {}
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]); let partNames = {
this.bitmap.data[idx + 1] = colors[col].g / (255 / this.bitmap.data[idx + 1]); "1": "Primary",
this.bitmap.data[idx + 2] = colors[col].b / (255 / this.bitmap.data[idx + 2]); "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. // convert hex to RGB
This is usually a good thing though - avoid issues by not putting something like 0 instead of '0' */ let hexRegex = /^[A-Fa-f0-9]{6}$/
function genFileName(...args) { return args.filter(function(val) {return val}).join('_') +'_001.png' } 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}; }
function fromIcons(filename) { return `./icons/${filename}` }
let cache = {}; // 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) => { module.exports = async (app, req, res) => {
function buildIcon(account=[], usercode) { async function buildIcon(account=[], userCode) {
let { form, ind } = forms[req.query.form] || {}; let form = forms[req.query.form] || forms["icon"]
form = form || 'player';
ind = ind || 21;
let iconID = req.query.icon || account[ind] || 1; let iconID = req.query.icon || account[form.index] || 1;
let col1 = req.query.col1 || account[10] || 0; let col1 = getColor(req.query.col1 || account[10], "0")
let col2 = req.query.col2 || account[11] || 3; let col2 = getColor(req.query.col2 || account[11], "3")
let colG = req.query.colG || req.query.colg let colG = getColor(req.query.colG || req.query.colg)
let colW = req.query.colW || req.query.colw || req.query.col3 let colW = getColor(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 useGlow = req.query.glow || account[28] || false;
let drawLegs = !(req.query.noLegs > 0) if (useGlow && ["false", "0"].includes(useGlow)) useGlow = false
let autoSize = req.query.size == "auto" if (col1.r == 0 && col1.g == 0 && col1.b == 0 ) useGlow = true
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; // 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; let customSize = req.query.size == "auto" ? "auto" : +req.query.size || null
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'))))) { let iconPath = getIconPath(iconID, form.form)
iconID = '01';
setBaseIcons(); 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)
} }
let ex = fromIcons(extra) // get path of icon 'part' (1: primary, 2: secondary, 3: ufo top, extra: white, glow: glow, )
let hasExtra = fs.existsSync(ex) 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`
}
let cols = [col1, col2, colG, colW] // recolor white parts of icon to specified color
cols.forEach(col => { async function recolor(img, col) {
if (!col) return let rawData = await img.raw().toBuffer({resolveWithObject: true})
col = col.toString() for (let i=0; i<rawData.data.length; i += 4) { // [R, G, B, A]
if (col.match(hexRegex)) colors[col.toLowerCase()] = hexConvert(col) 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]);
}
}
return sharp(rawData.data, {raw: {width: rawData.info.width, height: rawData.info.height, channels: 4, background: TRANSPARENT}}).png()
}
// 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--; }
}
if (legSection.yScale) builtPart.resize({width: spriteSize[0], height: Math.floor(spriteSize[1] * legSection.yScale), fit: "fill"})
if (legSection.xFlip) builtPart.flop()
}
let layerData = {
partName, spriteOffset, spriteSize, leg,
layerName: partNames[part],
behind: legSection && legSection.darken,
isGlow: part == "glow",
input: await builtPart.toBuffer(),
left, top
}
if (legSection) {
if (!legLayers[legSection.leg]) legLayers[legSection.leg] = [layerData]
else legLayers[legSection.leg].push(layerData)
}
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 (!colors[col1] || isNaN(colors[col1].r)) col1 = colors[+col1] ? +col1 : 0 if (!colors[col1] || isNaN(colors[col1].r)) col1 = colors[+col1] ? +col1 : 0
@ -88,33 +261,18 @@ module.exports = async (app, req, res) => {
if (!sizeParam && (!isSpecial || drawLegs) && cache[iconCode]) return res.status(200).end(cache[iconCode].value) if (!sizeParam && (!isSpecial || drawLegs) && cache[iconCode]) return res.status(200).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); // OLD CODE IS BEING USED FOR ROBOTS AND SPIDERS
robotOffset2b = icons[glows[1]].spriteOffset.map(minusOrigOffset).concat(icons[glows[1]].spriteSize); let formCheck = forms[req.query.form]
robotOffset3b = icons[glows[2]].spriteOffset.map(minusOrigOffset).concat(icons[glows[2]].spriteSize); if (formCheck && formCheck.legs) return app.run.icon_old(app, req, res)
robotLeg1 = new Jimp(fromIcons(legs[0])); robotGlow1 = new Jimp(fromIcons(glows[0])) let username = req.params.text
robotLeg2 = new Jimp(fromIcons(legs[1])); robotGlow2 = new Jimp(fromIcons(glows[1])) let userCode;
robotLeg3 = new Jimp(fromIcons(legs[2])); robotGlow3 = new Jimp(fromIcons(glows[2])) res.contentType('image/png');
glowOffset = offsets[form][+iconID] || [] if (req.offline || req.query.hasOwnProperty("noUser") || req.query.hasOwnProperty("nouser") || username == "icon") return buildIcon()
}
Jimp.read(fromIcons(glow)).then(async function (image) { Jimp.read(fromIcons(glow)).then(async function (image) {
@ -355,27 +513,19 @@ module.exports = async (app, req, res) => {
}) })
} }
let username = req.params.text let accountMode = !req.query.hasOwnProperty("player") && Number(req.params.id)
let userCode; let foundID = app.userCache(req.id, username)
res.contentType('image/png'); let skipRequest = accountMode || foundID
let forceGD = req.query.hasOwnProperty("forceGD")
if (req.offline || req.query.hasOwnProperty("noUser") || req.query.hasOwnProperty("nouser") || username == "icon") return buildIcon() // skip request by causing fake error lmao
req.gdRequest(skipRequest ? "" : 'getGJUsers20', skipRequest ? {} : req.gdParams({ str: username, forceGD }, !forceGD), function (err1, res1, body1) {
else if (app.config.cachePlayerIcons && !Object.keys(req.query).filter(x => !["form", "forceGD"].includes(x)).length) { 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'}` userCode = `${req.id}u-${username.toLowerCase()}-${forms[req.query.form] ? req.query.form : 'cube'}`
if (cache[userCode]) return res.status(200).end(cache[userCode].value) if (cache[userCode]) return res.status(200).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) { req.gdRequest('getGJUserInfo20', req.gdParams({ targetAccountID: result, forceGD }, !forceGD), function (err2, res2, body2) {
if (err2) return buildIcon(); if (err2) return buildIcon();
@ -384,6 +534,6 @@ module.exports = async (app, req, res) => {
return buildIcon(iconData, userCode); 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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
assets/plus_blue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

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> <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> <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"> <div class="subdiv" id="params-icons">
<p><b>Parameters can be used to modify parts of a fetched user's icon</b></p> <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> <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>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>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>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>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>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>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> </div>
<br> <br>

View file

@ -126,9 +126,9 @@ fetch(`${server.demonList}api/v2/demons/listed?after=${demonID-1}&before=${demon
<img class="inline spaced" src="../assets/trophies/${trophies.findIndex(z => y+1 <= z) + 1}.png" height="120%" style="margin-bottom: 0%; transform:scale(1.1)"> <img class="inline spaced" src="../assets/trophies/${trophies.findIndex(z => y+1 <= z) + 1}.png" height="120%" style="margin-bottom: 0%; transform:scale(1.1)">
</div> </div>
<div style="${!x.video ? "display: none; " : ""}position:absolute; width: 12.5%; height: 15%; right: 0%"> <div style="${!x.video ? "display: none; " : ""}position:absolute; width: 12.5%; height: 15%; right: 0%; transform: translateY(-8.5vh)">
<a target="_blank" href="${x.video}"> <a target="_blank" href="${x.video}">
<img class="gdButton inline spaced" src="../assets/${videoIcon}.png" height="80%" style="transform: translateY(-8.5vh)"> <img class="gdButton inline spaced" src="../assets/${videoIcon}.png" height="80%">
</a> </a>
</div> </div>
@ -136,6 +136,8 @@ fetch(`${server.demonList}api/v2/demons/listed?after=${demonID-1}&before=${demon
}) })
$('#searchBox').append('<div style="height: 4.5%"></div>')
$('#loading').hide(); $('#loading').hide();
}).catch(e => $('#loading').hide()) }).catch(e => $('#loading').hide())
}).catch(e => $('#loading').hide()) }).catch(e => $('#loading').hide())

View file

@ -10,7 +10,7 @@
<meta name="twitter:card" content="summary"> <meta name="twitter:card" content="summary">
</head> </head>
<body class="levelBG" onbeforeunload="saveUrl()"> <body class="levelBG" onbeforeunload="saveFilters(); saveUrl()">
<div id="everything"> <div id="everything">
@ -28,7 +28,7 @@
<img id="normalSong" class="gdButton inline gray" style="margin-right: 0.5%" src="../assets/song-normal.png" height="8%"> <img id="normalSong" class="gdButton inline gray" style="margin-right: 0.5%" src="../assets/song-normal.png" height="8%">
<img id="customSong" class="gdButton inline" style="margin-left: 0.5%" src="../assets/song-custom.png" height="8%"> <img id="customSong" class="gdButton inline" style="margin-left: 0.5%" src="../assets/song-custom.png" height="8%">
<br> <br>
<input id="songID" type="number" placeholder="Custom Song ID" style="height: 15%; width: 70%; text-align: center; margin-top: 2%" maxlength="22"> <input id="songID" type="number" placeholder="Custom Song ID" style="height: 15%; width: 70%; text-align: center; margin-top: 2%">
<div id="songSelect" style="width: 100%; display: none; margin-top: 3%; text-align: center"> <div id="songSelect" style="width: 100%; display: none; margin-top: 3%; text-align: center">
<img style="width: 4%" id="songDown" class="gdButton inline valign songChange" jump="-1" src="../assets/whitearrow-left.png"> <img style="width: 4%" id="songDown" class="gdButton inline valign songChange" jump="-1" src="../assets/whitearrow-left.png">
<h1 class="inline valign smallerer center" id="songName" style="min-width: 60%"></h1> <h1 class="inline valign smallerer center" id="songName" style="min-width: 60%"></h1>
@ -137,7 +137,8 @@
</div> </div>
<div style="position:absolute; top: 2%; right: 1.5%; width: 10%; text-align: right"> <div style="position:absolute; top: 2%; right: 1.5%; width: 10%; text-align: right">
<img class="gdButton" style="margin-bottom: 15%" src="../assets/plus.png" width="60%" onclick="$('#filters').show()"> <img class="gdButton" style="margin-bottom: 12%" src="../assets/close.png" width="60%" onclick="clearFilters()">
<img class="gdButton" style="margin-bottom: 12%" id="showFilters" src="../assets/plus.png" width="60%" onclick="$('#filters').show()">
<img class="gdButton" src="../assets/listbutton.png" width="60%" onclick="$('#customlist').show()"> <img class="gdButton" src="../assets/listbutton.png" width="60%" onclick="$('#customlist').show()">
</div> </div>
@ -149,7 +150,6 @@
<script> <script>
let filters = [] let filters = []
let minusCheck = []
let demons = [] let demons = []
let demonMode = false let demonMode = false
let customSong = true let customSong = true
@ -176,7 +176,7 @@ $('.levelSearch').click(function() {
demonFilter = demonMode && difficulties[0] > 0 demonFilter = demonMode && difficulties[0] > 0
if (!difficulties.length) url += "" if (!difficulties.length) url += ""
else if (!demonFilter) url += "&diff=" + difficulties.join(",") else if (!demonFilter) url += "&diff=" + undupe(difficulties).join(",")
else url += "&diff=-2&demonFilter=" + difficulties[0] else url += "&diff=-2&demonFilter=" + difficulties[0]
// === LENGTH === // // === LENGTH === //
@ -192,7 +192,7 @@ $('.levelSearch').click(function() {
// === SONG === // // === SONG === //
let selectedOfficial = customSong ? null : officialSong let selectedOfficial = customSong ? null : officialSong
let selectedCustom = customSong && $('#songID').val() ? $('#songID').val() : null let selectedCustom = customSong && $('#songID').val() ? $('#songID').val().slice(0, 16) : null
let selectedSong = selectedCustom || selectedOfficial let selectedSong = selectedCustom || selectedOfficial
if (selectedSong) { if (selectedSong) {
@ -207,39 +207,56 @@ $('.levelSearch').click(function() {
}) })
function getDiffFilters() {
return $('.diffDiv.selectedFilter').map(function() { return $(this).attr('diff') }).toArray()
}
function showDemonDiffs() {
$('#difficulties').hide();
$('#demons').show();
demonMode = true;
}
function hideDemonDiffs() {
$('#difficulties').show();
$('#demons').hide();
demonMode = false;
}
$('.diffDiv').click(function() { $('.diffDiv').click(function() {
if ($(this).hasClass('goBack')) { if ($(this).hasClass('goBack')) {
demonMode = false;
$('#difficulties').show();
$('#demonBtn').removeClass('selectedFilter') $('#demonBtn').removeClass('selectedFilter')
$('.demonDiff').removeClass('selectedFilter') $('.demonDiff').removeClass('selectedFilter')
return $('#demons').hide(); savedFilters.demonDiff = false
savedFilters.diff = []
return hideDemonDiffs()
} }
minusCheck = []
filters = []
$(this).toggleClass('selectedFilter') $(this).toggleClass('selectedFilter')
$('.diffDiv').each(function() { filters = getDiffFilters()
if ($(this).hasClass('selectedFilter')) filters.push($(this).attr('diff'))})
minusCheck = filters.filter(x => x < 0) let minusCheck = filters.filter(x => x < 0)
if (minusCheck.length || $(this).hasClass('demonDiff')) { if (minusCheck.length || $(this).hasClass('demonDiff')) {
filters = minusCheck filters = minusCheck
$('.diffDiv').removeClass('selectedFilter') $('.diffDiv').removeClass('selectedFilter')
$(this).addClass('selectedFilter') $(this).addClass('selectedFilter')
} }
if ($(this).attr('diff') == -2) { savedFilters.diff = getDiffFilters()
$('#difficulties').hide(); savedFilters.demonDiff = demonMode
$('#demons').show();
demonMode = true; if ($(this).attr('diff') == -2) showDemonDiffs()
}
}) })
$('.lengthDiv').click(function() { $('.lengthDiv').click(function() {
$(this).toggleClass('selectedFilter') $(this).toggleClass('selectedFilter')
savedFilters.len = $('.lengthDiv.selectedFilter').map(function() { return $(this).attr('len') }).toArray()
savedFilters.starred = $('#starCheck').hasClass('selectedFilter')
if (!savedFilters.starred) delete savedFilters.starred
}) })
$(document).keydown(function(k) { $(document).keydown(function(k) {
@ -280,22 +297,85 @@ $('#listLevels, #listName').on('input blur', function (event) {
}) })
$(document).on('change', 'input[url]', function () {
savedFilters.checked = $('input[url]:checked').map(function() { return $(this).attr('id').slice(4) }).toArray()
checkExtraFilters()
})
$('#normalSong').click(function() { $('#normalSong').click(function() {
customSong = false customSong = false
savedFilters.defaultSong = true
savedFilters.song = officialSong
$('#customSong').addClass('gray') $('#customSong').addClass('gray')
$('#normalSong').removeClass('gray') $('#normalSong').removeClass('gray')
$('#songSelect').show() $('#songSelect').show()
$('#songID').hide() $('#songID').hide()
checkExtraFilters()
}) })
$('#customSong').click(function() { $('#customSong').click(function() {
customSong = true customSong = true
delete savedFilters.defaultSong
savedFilters.song = Number($('#songID').val().slice(0, 16)) || 0
$('#normalSong').addClass('gray') $('#normalSong').addClass('gray')
$('#customSong').removeClass('gray') $('#customSong').removeClass('gray')
$('#songID').show() $('#songID').show()
$('#songSelect').hide() $('#songSelect').hide()
checkExtraFilters()
}) })
$('#songID').on('input change blur', function() {
savedFilters.song = Number($(this).val().slice(0, 16))
checkExtraFilters()
})
function saveFilters() {
localStorage.filters = JSON.stringify(savedFilters)
}
function clearFilters() {
$('.selectedFilter').removeClass('selectedFilter')
$('input[url]').prop('checked', false)
$('#songID').val("")
$('#levelName').val("")
$('#customSong').click()
hideDemonDiffs()
officialSong = 1
savedFilters = { diff: [], len: [], checked: [] }
delete localStorage.saveFilters
checkExtraFilters()
}
function checkExtraFilters() {
let hasExtra = savedFilters.checked.length || savedFilters.defaultSong || savedFilters.song > 0
$('#showFilters').attr('src', `../assets/plus${hasExtra ? "_blue" : ""}.png`)
}
let savedFilters = JSON.parse(localStorage.filters || "{}")
$('input[url]').prop('checked', false)
if (!savedFilters.diff) savedFilters.diff = []
else if (savedFilters.demonDiff) { showDemonDiffs(); $(`.demonDiff[diff=${savedFilters.diff}]`).trigger('click') }
else if (savedFilters.diff[0] == -2) { $('.diffDiv[diff=-2]').first().addClass('selectedFilter'); showDemonDiffs() }
else (savedFilters.diff.forEach(x => $(`.diffDiv:not(.demonDiff)[diff=${x}]`).addClass('selectedFilter')))
if (!savedFilters.len) savedFilters.len = []
else (savedFilters.len.forEach(x => $(`.lengthDiv[len=${x}]`).addClass('selectedFilter')))
if (savedFilters.starred) $('#starCheck').addClass('selectedFilter')
if (!savedFilters.checked) savedFilters.checked = []
else (savedFilters.checked.forEach(x => $(`input[id=box-${x}]`).prop('checked', true)))
let hadDefaultSong = savedFilters.defaultSong
if (savedFilters.defaultSong) {
officialSong = +savedFilters.song || 1
$('#normalSong').trigger('click')
}
else if (+savedFilters.song && +savedFilters.song > 0) $('#songID').val(savedFilters.song)
checkExtraFilters()
Fetch(`../api/music`).then(music => { Fetch(`../api/music`).then(music => {
$('#songName').html("1: " + music[1][0]) $('#songName').html("1: " + music[1][0])
@ -304,8 +384,16 @@ Fetch(`../api/music`).then(music => {
officialSong += Number($(this).attr('jump')) officialSong += Number($(this).attr('jump'))
if (officialSong < 1) officialSong = 1 if (officialSong < 1) officialSong = 1
$('#songName').html(`${officialSong}: ${music[officialSong] ? music[officialSong][0] : officialSong == 69 ? "Nice" : "Unknown"}`) $('#songName').html(`${officialSong}: ${music[officialSong] ? music[officialSong][0] : officialSong == 69 ? "Nice" : "Unknown"}`)
savedFilters.song = officialSong
savedFilters.defaultSong = true
checkExtraFilters()
}) })
if (hadDefaultSong) {
checkExtraFilters()
$('.songChange').trigger('click')
}
$(document).keydown(function(k) { $(document).keydown(function(k) {
if (customSong) return; if (customSong) return;
if (k.which == 37) $('#songDown').trigger('click') // left if (k.which == 37) $('#songDown').trigger('click') // left

View file

@ -44,7 +44,7 @@
<div id="dl" style="display: none; position:absolute; top: 15%; right: 0.5%; text-align: center; width: 17%"> <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> <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. <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> 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> </div>

View file

@ -50,6 +50,8 @@
<hr id="gdfloor"> <hr id="gdfloor">
<div id="menuButtons" style="height: 65px; margin: 0 0 8 0;"> <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="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="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="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> <button class="blankButton menuButton" id="randomIcon" title="Random Icon"><img src="../assets/iconkitbuttons/shuffle.png" width=60px></button>
@ -84,7 +86,13 @@
</div> </div>
<br> <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"> <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()"> <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() if ($("#colG").is(':visible') || $("#colW").is(':visible')) $('.colorSplit').show()
else $('.colorSplit').hide() 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 => { forms.forEach(form => {
$("#iconTabs").append(`<button form="${form}" class="blankButton iconTabButton"><img src="../assets/iconkitbuttons/${form}_off.png" width=50px></button>`) $("#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" : ""}` 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`) $("#result").attr('src', finalURL).attr('download', `${selectedForm}_${selectedIcon}.png`)
$("#downloadLink").attr('href', 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 if (enableGlow == 2) enableGlow = 0
} }
@ -244,7 +259,8 @@ fetch('./api/icons').then(res => {
currentForm = form currentForm = form
$('.iconTabButton').each(function(x, y) { $('.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() var img = $(this).children().first()
img.attr('src', img.attr('src').replace('_off', '_on')); img.attr('src', img.attr('src').replace('_off', '_on'));
@ -473,6 +489,8 @@ fetch('./api/icons').then(res => {
$("#result").hide() $("#result").hide()
$("#result").attr('src', iconURL).attr('download', `${user}_${formCopy}.png`) $("#result").attr('src', iconURL).attr('download', `${user}_${formCopy}.png`)
$("#downloadLink").attr('href', 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') $('#glow').attr('src', '../assets/iconkitbuttons/streak_off.png')
enableGlow = 0 enableGlow = 0

View file

@ -72,6 +72,11 @@
<img class="gdButton" src="../assets/smallinfo.png" width="32%" onclick="$('#infoDiv').show()"> <img class="gdButton" src="../assets/smallinfo.png" width="32%" onclick="$('#infoDiv').show()">
</div> </div>
<div style="position:absolute; top: 12%; right: 1.5%; width: 21%; text-align: right;">
<a id="discord" target="_blank" title="Official leaderboard Discord!" href="https://discord.gg/leaderboard"><img class="gdButton" src="../assets/discord.png" width="20%"></a>
<a id="altDiscord" target="_blank" title="Accurate leaderboard Discord!" style="display: none; filter: hue-rotate(300deg)" href="https://discord.gg/Uz7pd4"><img class="gdButton" src="../assets/discord.png" width="20%"></a>
</div>
<div class="supercenter" id="loading" style="height: 10%; top: 47%; display: none;"> <div class="supercenter" id="loading" style="height: 10%; top: 47%; display: none;">
<img class="spin noSelect" src="../assets/loading.png" height="105%"> <img class="spin noSelect" src="../assets/loading.png" height="105%">
</div> </div>
@ -103,15 +108,20 @@ let weeklyText =
`The <cg>Weekly</cg> leaderboard displays the players who have gained the most <cy>stars</cy> in the <cb>past week</cb>. It was officially <co>removed</co> in update 2.0, but lives on in some GDPS'es.` `The <cg>Weekly</cg> leaderboard displays the players who have gained the most <cy>stars</cy> in the <cb>past week</cb>. It was officially <co>removed</co> in update 2.0, but lives on in some GDPS'es.`
let accurateText = let accurateText =
`The <cg>Accurate Leaderboard</cg> is a highly accurate, hacker-proof leaderboard with <cy>proper stats and positioning</cy> (unlike the regular one). It is managed by <cb>XShadowWizardX, Pepper360, Octeract</cb>, and many many other helpers. Be sure to check out their <a target="_blank" href="https://docs.google.com/spreadsheets/d/10lbPnDYJXhbtlA0ls0cGjjX_osFSG559IDrTbhgPHvc"><ca class="underline">interactive leaderboard spreadsheet</ca></a> or join their <a target="_blank" href="https://discord.gg/Uz7pd4d"><ca class="underline">Discord server<ca></a>.` `The <cg>Accurate Leaderboard</cg> is an <cy>externally managed</cy> leaderboard which aims to provide <ca>detailed</ca> and hacker-proof stats on top players. It also once provided a way to view an <cg>accurate</cg> list of players with the most <cy>stars</cy> when the official leaderboards were <ca>frozen</ca>. It is managed by <cb>XShadowWizardX, Pepper360, Octeract</cb>, and many many other helpers.`
let creatorText = let creatorText =
`The <cg>Creators Leaderboard</cg> is sorted by <cg>creator points</cg>, rather than stars. A player's <cg>creator points</cg> (CP) is calculated by counting their number of <cy>star rated</cy> levels, plus an extra point for every level that has been <cb>featured</cb>, plus an additional point for <co>epic rated</co> levels.` `The <cg>Creators Leaderboard</cg> is sorted by <cg>creator points</cg>, rather than stars. A player's <cg>creator points</cg> (CP) is calculated by counting their number of <cy>star rated</cy> levels, plus an extra point for every level that has been <cb>featured</cb>, plus an additional point for <co>epic rated</co> levels.`
if (showWeek) $('#weeklyStats').attr('src', '../assets/sort-week-on.png') if (showWeek) $('#weeklyStats').attr('src', '../assets/sort-week-on.png')
function infoText(text) { $('#infoText').html(text) } function infoText(text, altDiscord) {
infoText(accurateText) $('#infoText').html(text)
if (altDiscord) { $('#discord').hide(); $('#altDiscord').show() }
else { $('#discord').show(); $('#altDiscord').hide() }
}
infoText(top250Text)
function leaderboard(val) { function leaderboard(val) {
@ -225,7 +235,7 @@ $('#accurateTabOff').click(function() {
$('#topTabOff').show() $('#topTabOff').show()
$('#accurateTabOn').show() $('#accurateTabOn').show()
$('#creatorTabOff').show() $('#creatorTabOff').show()
infoText(accurateText) infoText(accurateText, true)
$('.sortDiv').show() $('.sortDiv').show()
}) })

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": { "robot": {
"form": "robot", "form": "robot",
"ind": 26 "ind": 26,
"legs": true
}, },
"spider": { "spider": {
"form": "spider", "form": "spider",
"ind": 43 "ind": 43,
"legs": true
}, },
"cursed": { "cursed": {
"form": "spider", "form": "spider",
"ind": 43 "ind": 43,
"legs": true
}, },
"swing": { "swing": {
"form": "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

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