Merge branch 'master' into master
70
.github/workflows/analyse.yml
vendored
Normal 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
|
150
README.md
|
@ -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:
|
||||
|
||||
| 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
|
||||
|
||||
[string] **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!)
|
||||
|
||||
[array] **disabled:** An array of menu buttons to "disable" (mappacks, gauntlets, daily, weekly, etc). They appear greyed out but are still clickable.
|
||||
|
||||
[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.
|
||||
|
||||
[bool] **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
|
||||
|
||||
[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" }`)
|
||||
| identifier | description | type |
|
||||
|:----------------:|:-----------------------------:|:----:|
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|
||||
|
||||
|
||||
|
@ -143,23 +133,13 @@ comingsoon.html was used while the site was still in development, I just left it
|
|||
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
|
||||
parseIconPlist.js reads GJ_GameSheet02-uhd.plist and magically transforms it into gameSheet.json. Props to 101arrowz for helping make this
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
| name | description |
|
||||
|:----:|:-----------:|
|
||||
|`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 |
|
||||
|
||||
## Misc
|
||||
|
||||
|
@ -170,72 +150,32 @@ Inevitable misc folder
|
|||
**Level Analysis Stuff (in a separate folder)**
|
||||
|
||||
|
||||
|
||||
blocks.json - The object IDs in the different 'families' of blocks
|
||||
|
||||
|
||||
|
||||
colorProperties.json - Color channel cheatsheet
|
||||
|
||||
|
||||
|
||||
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
|
||||
| name | description |
|
||||
|:----:|:-----------:|
|
||||
| `blocks.json` | The object IDs in the different 'families' of blocks |
|
||||
| `colorProperties.json` | Color channel cheatsheet |
|
||||
| `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**
|
||||
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
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
|
||||
| name | description |
|
||||
|:----:|:-----------:|
|
||||
| `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) |
|
||||
| `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 |
|
||||
|
||||
|
||||
|
||||
|
@ -243,4 +183,4 @@ sizecheck.js - Excecuted on most pages. Used for the 'page isn't wide enough' me
|
|||
|
||||
|
||||
|
||||
happy gdbrowsing and god bless.
|
||||
happy gdbrowsing and god bless.
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
356
api/icon.js
|
@ -1,80 +1,253 @@
|
|||
// 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--; }
|
||||
}
|
||||
if (legSection.yScale) builtPart.resize({width: spriteSize[0], height: Math.floor(spriteSize[1] * legSection.yScale), fit: "fill"})
|
||||
if (legSection.xFlip) builtPart.flop()
|
||||
}
|
||||
|
||||
let ex = fromIcons(extra)
|
||||
let hasExtra = fs.existsSync(ex)
|
||||
let layerData = {
|
||||
partName, spriteOffset, spriteSize, leg,
|
||||
layerName: partNames[part],
|
||||
behind: legSection && legSection.darken,
|
||||
isGlow: part == "glow",
|
||||
input: await builtPart.toBuffer(),
|
||||
left, top
|
||||
}
|
||||
|
||||
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 (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[col2] || isNaN(colors[col2].r)) col2 = colors[+col2] ? +col2 : 3
|
||||
|
@ -88,33 +261,18 @@ module.exports = async (app, req, res) => {
|
|||
|
||||
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);
|
||||
robotOffset2b = icons[glows[1]].spriteOffset.map(minusOrigOffset).concat(icons[glows[1]].spriteSize);
|
||||
robotOffset3b = icons[glows[2]].spriteOffset.map(minusOrigOffset).concat(icons[glows[2]].spriteSize);
|
||||
// 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)
|
||||
|
||||
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]))
|
||||
let username = req.params.text
|
||||
let userCode;
|
||||
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) {
|
||||
|
||||
|
@ -355,35 +513,27 @@ module.exports = async (app, req, res) => {
|
|||
})
|
||||
}
|
||||
|
||||
let username = req.params.text
|
||||
let userCode;
|
||||
res.contentType('image/png');
|
||||
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")
|
||||
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
req.gdRequest('getGJUserInfo20', req.gdParams({ targetAccountID: result, forceGD }, !forceGD), function (err2, res2, body2) {
|
||||
|
||||
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);
|
||||
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
|
@ -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);
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
BIN
assets/iconkitbuttons/psd.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
assets/plus_blue.png
Normal file
After Width: | Height: | Size: 22 KiB |
|
@ -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>
|
||||
|
|
|
@ -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)">
|
||||
</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}">
|
||||
<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>
|
||||
</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();
|
||||
}).catch(e => $('#loading').hide())
|
||||
}).catch(e => $('#loading').hide())
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<meta name="twitter:card" content="summary">
|
||||
</head>
|
||||
|
||||
<body class="levelBG" onbeforeunload="saveUrl()">
|
||||
<body class="levelBG" onbeforeunload="saveFilters(); saveUrl()">
|
||||
|
||||
<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="customSong" class="gdButton inline" style="margin-left: 0.5%" src="../assets/song-custom.png" height="8%">
|
||||
<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">
|
||||
<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>
|
||||
|
@ -137,7 +137,8 @@
|
|||
</div>
|
||||
|
||||
<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()">
|
||||
</div>
|
||||
|
||||
|
@ -149,7 +150,6 @@
|
|||
<script>
|
||||
|
||||
let filters = []
|
||||
let minusCheck = []
|
||||
let demons = []
|
||||
let demonMode = false
|
||||
let customSong = true
|
||||
|
@ -176,7 +176,7 @@ $('.levelSearch').click(function() {
|
|||
demonFilter = demonMode && difficulties[0] > 0
|
||||
|
||||
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]
|
||||
|
||||
// === LENGTH === //
|
||||
|
@ -192,7 +192,7 @@ $('.levelSearch').click(function() {
|
|||
// === SONG === //
|
||||
|
||||
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
|
||||
|
||||
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() {
|
||||
|
||||
if ($(this).hasClass('goBack')) {
|
||||
demonMode = false;
|
||||
$('#difficulties').show();
|
||||
|
||||
$('#demonBtn').removeClass('selectedFilter')
|
||||
$('.demonDiff').removeClass('selectedFilter')
|
||||
return $('#demons').hide();
|
||||
savedFilters.demonDiff = false
|
||||
savedFilters.diff = []
|
||||
return hideDemonDiffs()
|
||||
}
|
||||
|
||||
minusCheck = []
|
||||
filters = []
|
||||
$(this).toggleClass('selectedFilter')
|
||||
|
||||
$('.diffDiv').each(function() {
|
||||
if ($(this).hasClass('selectedFilter')) filters.push($(this).attr('diff'))})
|
||||
filters = getDiffFilters()
|
||||
|
||||
minusCheck = filters.filter(x => x < 0)
|
||||
let minusCheck = filters.filter(x => x < 0)
|
||||
if (minusCheck.length || $(this).hasClass('demonDiff')) {
|
||||
filters = minusCheck
|
||||
$('.diffDiv').removeClass('selectedFilter')
|
||||
$(this).addClass('selectedFilter')
|
||||
}
|
||||
|
||||
if ($(this).attr('diff') == -2) {
|
||||
$('#difficulties').hide();
|
||||
$('#demons').show();
|
||||
demonMode = true;
|
||||
}
|
||||
savedFilters.diff = getDiffFilters()
|
||||
savedFilters.demonDiff = demonMode
|
||||
|
||||
if ($(this).attr('diff') == -2) showDemonDiffs()
|
||||
|
||||
})
|
||||
|
||||
$('.lengthDiv').click(function() {
|
||||
$(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) {
|
||||
|
@ -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() {
|
||||
customSong = false
|
||||
savedFilters.defaultSong = true
|
||||
savedFilters.song = officialSong
|
||||
$('#customSong').addClass('gray')
|
||||
$('#normalSong').removeClass('gray')
|
||||
$('#songSelect').show()
|
||||
$('#songID').hide()
|
||||
checkExtraFilters()
|
||||
})
|
||||
|
||||
$('#customSong').click(function() {
|
||||
customSong = true
|
||||
delete savedFilters.defaultSong
|
||||
savedFilters.song = Number($('#songID').val().slice(0, 16)) || 0
|
||||
$('#normalSong').addClass('gray')
|
||||
$('#customSong').removeClass('gray')
|
||||
$('#songID').show()
|
||||
$('#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 => {
|
||||
|
||||
$('#songName').html("1: " + music[1][0])
|
||||
|
@ -304,8 +384,16 @@ Fetch(`../api/music`).then(music => {
|
|||
officialSong += Number($(this).attr('jump'))
|
||||
if (officialSong < 1) officialSong = 1
|
||||
$('#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) {
|
||||
if (customSong) return;
|
||||
if (k.which == 37) $('#songDown').trigger('click') // left
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -72,6 +72,11 @@
|
|||
<img class="gdButton" src="../assets/smallinfo.png" width="32%" onclick="$('#infoDiv').show()">
|
||||
</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;">
|
||||
<img class="spin noSelect" src="../assets/loading.png" height="105%">
|
||||
</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.`
|
||||
|
||||
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 =
|
||||
`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')
|
||||
|
||||
function infoText(text) { $('#infoText').html(text) }
|
||||
infoText(accurateText)
|
||||
function infoText(text, altDiscord) {
|
||||
$('#infoText').html(text)
|
||||
if (altDiscord) { $('#discord').hide(); $('#altDiscord').show() }
|
||||
else { $('#discord').show(); $('#altDiscord').hide() }
|
||||
}
|
||||
|
||||
infoText(top250Text)
|
||||
|
||||
function leaderboard(val) {
|
||||
|
||||
|
@ -225,7 +235,7 @@ $('#accurateTabOff').click(function() {
|
|||
$('#topTabOff').show()
|
||||
$('#accurateTabOn').show()
|
||||
$('#creatorTabOff').show()
|
||||
infoText(accurateText)
|
||||
infoText(accurateText, true)
|
||||
$('.sortDiv').show()
|
||||
})
|
||||
|
||||
|
|
BIN
icons/bird_01_glow_001.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
icons/bird_02_glow_001.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
icons/bird_03_glow_001.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
icons/bird_04_glow_001.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
icons/bird_05_glow_001.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
icons/bird_06_glow_001.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
icons/bird_07_glow_001.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
icons/bird_08_glow_001.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
icons/bird_09_glow_001.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
icons/bird_10_glow_001.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
icons/bird_11_glow_001.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
icons/bird_12_glow_001.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
icons/bird_13_glow_001.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
icons/bird_14_glow_001.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
icons/bird_15_glow_001.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
icons/bird_16_glow_001.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
icons/bird_17_glow_001.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
icons/bird_18_glow_001.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
icons/bird_19_glow_001.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
icons/bird_20_glow_001.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
icons/bird_21_glow_001.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
icons/bird_22_glow_001.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
icons/bird_23_glow_001.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
icons/bird_24_glow_001.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
icons/bird_25_glow_001.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/bird_26_glow_001.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
icons/bird_27_glow_001.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
icons/bird_28_glow_001.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
icons/bird_29_glow_001.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
icons/bird_30_glow_001.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
icons/bird_31_glow_001.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
icons/bird_32_glow_001.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
icons/bird_33_glow_001.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
icons/bird_34_glow_001.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
icons/bird_35_glow_001.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
icons/dart_01_glow_001.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
icons/dart_02_glow_001.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
icons/dart_03_glow_001.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
icons/dart_04_glow_001.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
icons/dart_05_glow_001.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
icons/dart_06_glow_001.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
icons/dart_07_glow_001.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
icons/dart_08_glow_001.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
icons/dart_09_glow_001.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
icons/dart_10_glow_001.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
icons/dart_11_glow_001.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
icons/dart_12_glow_001.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
icons/dart_13_glow_001.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
icons/dart_14_glow_001.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
icons/dart_15_glow_001.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
icons/dart_16_glow_001.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
icons/dart_17_glow_001.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
icons/dart_18_glow_001.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
icons/dart_19_glow_001.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
icons/dart_20_glow_001.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
icons/dart_21_glow_001.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
icons/dart_22_glow_001.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
icons/dart_23_glow_001.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
icons/dart_24_glow_001.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
icons/dart_25_glow_001.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
icons/dart_26_glow_001.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
icons/dart_27_glow_001.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
icons/dart_28_glow_001.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
icons/dart_29_glow_001.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
icons/dart_30_glow_001.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
icons/dart_31_glow_001.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
icons/dart_32_glow_001.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
icons/dart_33_glow_001.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
icons/dart_34_glow_001.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/dart_35_glow_001.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
|
@ -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",
|
||||
|
|
BIN
icons/player_00_glow_001.png
Normal file
After Width: | Height: | Size: 898 B |
Before Width: | Height: | Size: 205 B After Width: | Height: | Size: 237 B |
BIN
icons/player_01_glow_001.png
Normal file
After Width: | Height: | Size: 873 B |
BIN
icons/player_02_glow_001.png
Normal file
After Width: | Height: | Size: 770 B |
BIN
icons/player_03_glow_001.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
icons/player_04_glow_001.png
Normal file
After Width: | Height: | Size: 770 B |
BIN
icons/player_05_glow_001.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
icons/player_06_glow_001.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
icons/player_07_glow_001.png
Normal file
After Width: | Height: | Size: 954 B |
BIN
icons/player_08_glow_001.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
icons/player_09_glow_001.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
icons/player_100_glow_001.png
Normal file
After Width: | Height: | Size: 740 B |
BIN
icons/player_101_glow_001.png
Normal file
After Width: | Height: | Size: 740 B |
BIN
icons/player_102_glow_001.png
Normal file
After Width: | Height: | Size: 740 B |
BIN
icons/player_103_glow_001.png
Normal file
After Width: | Height: | Size: 740 B |