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:
| 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 |

View file

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

View file

@ -1,79 +1,252 @@
// 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 isSpecial = ['robot', 'spider'].includes(form);
setBaseIcons();
let customSize = req.query.size == "auto" ? "auto" : +req.query.size || null
if (!fs.existsSync(fromIcons(icon)) || (isSpecial && !fs.existsSync(fromIcons(genImageName('02'))))) {
iconID = '01';
setBaseIcons();
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)
}
let ex = fromIcons(extra)
let hasExtra = fs.existsSync(ex)
// 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`
}
let cols = [col1, col2, colG, colW]
cols.forEach(col => {
if (!col) return
col = col.toString()
if (col.match(hexRegex)) colors[col.toLowerCase()] = hexConvert(col)
// 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]);
}
}
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
@ -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,27 +513,19 @@ 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) {
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();
@ -384,6 +534,6 @@ module.exports = async (app, req, res) => {
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;
}
.grayscale {
filter: grayscale(100%) brightness(0.7);
}
.gold {
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>
<br>
<p class="reveal" onclick="$('#params-icons').slideToggle(100)"><b>Parameters (10)</b></p>
<p class="reveal" onclick="$('#params-icons').slideToggle(100)"><b>Parameters (11)</b></p>
<div class="subdiv" id="params-icons">
<p><b>Parameters can be used to modify parts of a fetched user's icon</b></p>
<p>IDs generally correspond to their order of appearance in GD</p>
@ -831,10 +831,11 @@
<p>colG: Optional color ID or hex code to overwrite the glow for the icon</p>
<p>colW: Optional color ID or hex code to overwrite the 'white' layer used by some detailed icons</p>
<p>glow: If the icon should have a glow/outline (0 = off, anything else = on)</p>
<p>size: The size in pixels that the icon should be (always square), in case you don't want the default. "Auto" also works.</p>
<p>size: The size in pixels that the icon should be (always square), in case you don't want the default. "auto" also works.</p>
<p>topless: Removes the glass 'dome' from generated UFOs (legacy)</p>
<p>player: Forces the player ID to be used for fetching (normally Account ID is tried first)</p>
<p>noUser: Disables fetching the icon from the GD servers. Slightly faster, but comes at the cost of having to build icons from the ground up using the parameters listed above. It completely ignores the entered username and always returns the default icon</p>
<p>psd: Saves the icon as a layered .psd file (spiders + robots not supported since they still use the old icon code)</p>
</div>
<br>

View file

@ -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())

View file

@ -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

View file

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

View file

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

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
icons/bird_02_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/bird_03_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
icons/bird_04_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/bird_05_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/bird_06_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
icons/bird_07_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
icons/bird_08_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/bird_09_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/bird_10_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
icons/bird_11_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/bird_12_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
icons/bird_13_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/bird_14_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
icons/bird_15_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
icons/bird_16_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/bird_17_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
icons/bird_18_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
icons/bird_19_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/bird_20_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
icons/bird_21_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
icons/bird_22_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/bird_23_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
icons/bird_24_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/bird_25_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
icons/bird_26_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/bird_27_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
icons/bird_28_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
icons/bird_29_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
icons/bird_30_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
icons/bird_31_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
icons/bird_32_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
icons/bird_33_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
icons/bird_34_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
icons/bird_35_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/dart_01_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
icons/dart_02_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
icons/dart_03_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
icons/dart_04_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
icons/dart_05_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
icons/dart_06_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/dart_07_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/dart_08_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
icons/dart_09_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/dart_10_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
icons/dart_11_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
icons/dart_12_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/dart_13_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
icons/dart_14_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
icons/dart_15_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/dart_16_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/dart_17_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
icons/dart_18_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
icons/dart_19_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/dart_20_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
icons/dart_21_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/dart_22_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
icons/dart_23_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/dart_24_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
icons/dart_25_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
icons/dart_26_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
icons/dart_27_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/dart_28_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/dart_29_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/dart_30_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
icons/dart_31_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/dart_32_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/dart_33_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
icons/dart_34_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
icons/dart_35_glow_001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 B

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

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