Leaderboard sorting + level data size
SMJS updated his accurate leaderboard API to allow sorting by user coins or demons. What a nice guy! Also, level analysis now shows the size of the level in MB or KB. Thanks to ItzSwirlz for suggesting this in a PR. Packages were updated as well.
|
@ -3,14 +3,22 @@ const request = require('request')
|
|||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (app.endpoint != "http://boomlings.com/database/") return res.send([])
|
||||
|
||||
let type = req.query.type ? req.query.type.toLowerCase() : ''
|
||||
if (type == "usercoins") type = "coins"
|
||||
if (type == "demons" || type == "coins") type = `?${type}=1`
|
||||
else type = ''
|
||||
|
||||
request.get(`https://gdleaderboards.com/incl/lbxml.php`, function (err, resp, topPlayers) {
|
||||
request.get(`https://gdleaderboards.com/incl/lbxml.php${type}`, function (err, resp, topPlayers) {
|
||||
if (err || !topPlayers) topPlayers = ""
|
||||
let idArray = topPlayers.split(",")
|
||||
|
||||
let leaderboard = []
|
||||
let total = idArray.length
|
||||
|
||||
if (!type.length) type = "stars"
|
||||
if (type == "coins") type = "usercoins"
|
||||
|
||||
idArray.forEach((x, y) => {
|
||||
|
||||
request.post(app.endpoint + 'getGJUserInfo20.php', {
|
||||
|
@ -34,7 +42,7 @@ module.exports = async (app, req, res) => {
|
|||
|
||||
leaderboard.push(accObj)
|
||||
if (leaderboard.length == total) {
|
||||
leaderboard = leaderboard.filter(x => x.stars).sort(function (a, b) {return parseInt(b.stars) - parseInt(a.stars)})
|
||||
leaderboard = leaderboard.filter(x => x.stars).sort(function (a, b) {return parseInt(b[type]) - parseInt(a[type])})
|
||||
leaderboard.forEach((a, b) => a.rank = b + 1)
|
||||
return res.send(leaderboard)
|
||||
}
|
||||
|
|
BIN
assets/playbutton.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
assets/sort-coins-on.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
assets/sort-coins.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
assets/sort-demons-on.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
assets/sort-demons.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
assets/sort-stars-on.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
assets/sort-stars.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
assets/stopbutton.png
Normal file
After Width: | Height: | Size: 17 KiB |
|
@ -26,6 +26,7 @@
|
|||
|
||||
<div id="analysisDiv" style="margin-top: 2%; display:none">
|
||||
<h2 id="levelName"></h2>
|
||||
<p></p>
|
||||
<h3 id="objectCount"></h3>
|
||||
|
||||
<div id="highDetailDiv">
|
||||
|
@ -52,7 +53,7 @@
|
|||
|
||||
<h1 class="topMargin2" id="orbText">Jump Rings</h1>
|
||||
<div class="transparentBox analysis" id="orbs"></div>
|
||||
|
||||
|
||||
<h1 class="topMargin2">Block Types</h1>
|
||||
<div class="transparentBox analysis" id="blocks"></div>
|
||||
|
||||
|
@ -98,7 +99,7 @@ let mirrorPortals = ['mirrorOn', 'mirrorOff']
|
|||
fetch(`../api${window.location.pathname}`).then(res => res.json()).then(res => {
|
||||
if (!res.level) return window.location.href = window.location.href.replace("analyze", "search")
|
||||
$('#levelName').text(res.level.name)
|
||||
$('#objectCount').text(res.objects + " objects")
|
||||
$('#objectCount').text(commafy(res.objects) + " objects")
|
||||
document.title = "Analysis of " + res.level.name
|
||||
|
||||
$('#meta-title').attr('content', "Analysis of " + res.level.name)
|
||||
|
@ -111,7 +112,7 @@ if (res.highDetail == 0 || res.hdPercent == 0) $('#highDetailDiv').hide()
|
|||
else {
|
||||
let offset = hdPercent < 20 ? 1 : hdPercent < 40 ? 2 : hdPercent < 60 ? 3 : hdPercent < 80 ? 4 : 5
|
||||
$('#highdetail').append(`<div class="inline" style="width:${hdPercent + offset}%; height: 100%; background-color: lime; margin-left: -2.25%"></div>`)
|
||||
$('#hdText').text(`${res.highDetail}/${res.objects} marked high detail • ${+hdPercent.toFixed(1)}% optimized`)
|
||||
$('#hdText').text(`${commafy(res.highDetail)}/${commafy(res.objects)} marked high detail • ${+hdPercent.toFixed(1)}% optimized`)
|
||||
}
|
||||
|
||||
let triggerList = Object.keys(res.triggers)
|
||||
|
@ -123,6 +124,8 @@ let colorList = Object.keys(res.colors)
|
|||
|
||||
let portals = res.portals.split(", ").map(x => x.split(" "))
|
||||
|
||||
function commafy(num) { return (+num || 0).toString().replace(/(\d)(?=(\d\d\d)+$)/g, "$1,") }
|
||||
|
||||
function appendPortals() {
|
||||
$('#portals').html("")
|
||||
if (res.settings.gamemode && res.settings.gamemode != "cube" && !disabledPortals.includes('form')) $('#portals').append(`<div class="inline portalDiv"><img class="portalImage" src='../objects/portal-${res.settings.gamemode}.png'><h3>Start</h3></div><img class="divider portalImage" src="../assets/divider.png" style="margin: 1.3% 0.8%">`)
|
||||
|
@ -146,27 +149,27 @@ function appendPortals() {
|
|||
appendPortals()
|
||||
|
||||
triggerList.forEach(x => {
|
||||
if (x == "total") $('#triggerText').text(`Triggers (${res.triggers[x]})`)
|
||||
else $('#triggers').append(`<div class="inline triggerDiv"><img height="50%" src='../objects/trigger-${x}.png'><h3 style="padding-top: 7%">x${res.triggers[x]}</h3></div>`)
|
||||
if (x == "total") $('#triggerText').text(`Triggers (${commafy(res.triggers[x])})`)
|
||||
else $('#triggers').append(`<div class="inline triggerDiv"><img height="50%" src='../objects/trigger-${x}.png'><h3 style="padding-top: 7%">x${commafy(res.triggers[x])}</h3></div>`)
|
||||
})
|
||||
|
||||
|
||||
orbList.forEach(x => {
|
||||
if (x == "total") $('#orbText').text(`Jump Rings (${res.orbs[x]})`)
|
||||
else $('#orbs').append(`<div class="inline orbDiv"><img height="50%" src='../objects/orb-${x}.png'><h3 style="padding-top: 7%">x${res.orbs[x]}</h3></div>`)
|
||||
if (x == "total") $('#orbText').text(`Jump Rings (${commafy(res.orbs[x])})`)
|
||||
else $('#orbs').append(`<div class="inline orbDiv"><img height="50%" src='../objects/orb-${x}.png'><h3 style="padding-top: 7%">x${commafy(res.orbs[x])}</h3></div>`)
|
||||
})
|
||||
|
||||
blockList.forEach(x => {
|
||||
$('#blocks').append(`<div class="inline blockDiv"><img height="45%" src='../blocks/${x}.png'><h3 style="padding-top: 15%">x${res.blocks[x]}</h3></div>`)
|
||||
$('#blocks').append(`<div class="inline blockDiv"><img height="45%" src='../blocks/${x}.png'><h3 style="padding-top: 15%">x${commafy(res.blocks[x])}</h3></div>`)
|
||||
})
|
||||
|
||||
miscList.forEach(x => {
|
||||
if (x == "objects") return;
|
||||
else $('#misc').append(`<div class="inline miscDiv"><img height="40%" src='../objects/obj-${x.slice(0, -1)}.png'><h3 style="padding-top: 15%">x${res.misc[x][0]}<br>${res.misc[x][1]}</h3></div>`)
|
||||
else $('#misc').append(`<div class="inline miscDiv"><img height="40%" src='../objects/obj-${x.slice(0, -1)}.png'><h3 style="padding-top: 15%">x${commafy(res.misc[x][0])}<br>${res.misc[x][1]}</h3></div>`)
|
||||
})
|
||||
|
||||
groupList.forEach(x => {
|
||||
if (x == "total") $('#grouptext').text(`Trigger Groups (${res.triggerGroups[x]})`)
|
||||
else $('#groups').append(`<div class="inline groupDiv"><h1 class="groupID">${x.slice(6)}</h1><h3 style="padding-top: 7%">x${res.triggerGroups[x]}</h3></div>`)
|
||||
if (x == "total") $('#grouptext').text(`Trigger Groups (${commafy(res.triggerGroups[x])})`)
|
||||
else $('#groups').append(`<div class="inline groupDiv"><h1 class="groupID">${x.slice(6)}</h1><h3 style="padding-top: 7%">x${commafy(res.triggerGroups[x])}</h3></div>`)
|
||||
})
|
||||
|
||||
let bgCol = res.colors.find(x => x.channel == "BG")
|
||||
|
@ -188,7 +191,7 @@ appendPortals()
|
|||
let c = res.colors[x]
|
||||
|
||||
$('#colorDiv').append(`${y % 8 == 0 ? "<brr>" : ""}<div class="inline aColor"><div class="color" channel="${c.channel}" style="background-color: rgba(${c.cr || c.r}, ${c.cg || c.g}, ${c.cb || c.b}, ${c.opacity}); border: 0.4vh solid rgb(${c.r}, ${c.g}, ${c.b})">
|
||||
${c.copiedChannel ? `<h3 class='copiedColor'>C:${c.copiedChannel}</h3>` : c.pColor ? `<h3 class='copiedColor'>P${c.pColor}</h3>` : c.blending ? "<h3 class='blendingDot'>•</h3>" : ""}
|
||||
${c.copiedChannel ? `<h3 class='copiedColor'>C:${c.copiedChannel}</h3>` : c.pColor ? `<h3 class='copiedColor'>P${c.pColor}</h3>` : c.blending ? "<h3 class='blendingDot'>•</h3>" : ""}
|
||||
${c.copiedChannel && c.copiedHSV ? `<h3 class='copiedColor copiedHSV'> +HSV</h3>` : ""}
|
||||
${c.opacity != "1" ? `<h3 class='copiedColor'>${c.opacity}%</h3>` : ""}
|
||||
</div><h3 style="padding-top: 7%">${c.channel > 0 ? "Col " + c.channel : c.channel}</h3></div>`)
|
||||
|
@ -199,7 +202,7 @@ appendPortals()
|
|||
$(".portalToggle").click(function() {
|
||||
if ($(this).prop('checked')) disabledPortals = disabledPortals.filter(x => x != $(this).attr('portal'))
|
||||
else disabledPortals.push($(this).attr('portal'))
|
||||
|
||||
|
||||
portals = res.portals.split(", ").map(x => x.split(" "))
|
||||
if (disabledPortals.includes('form')) portals = portals.filter(x => !formPortals.includes(x[0]))
|
||||
if (disabledPortals.includes('speed')) portals = portals.filter(x => !speedPortals.includes(x[0]))
|
||||
|
@ -215,7 +218,10 @@ appendPortals()
|
|||
appendPortals()
|
||||
})
|
||||
|
||||
$('#codeLength').html(`(${res.dataLength} characters)`)
|
||||
let dataSize = [Number((res.dataLength / 1024 / 1024).toFixed(1)), "MB"]
|
||||
if (dataSize[0] < 1) dataSize = [Number((res.dataLength / 1024).toFixed(1)), "KB"]
|
||||
|
||||
$('#codeLength').html(`${commafy(res.dataLength)} characters (${dataSize.join(" ")})`)
|
||||
|
||||
$('#revealCode').click(function() {
|
||||
$('#levelCode').html('<p>Loading...</p>')
|
||||
|
@ -284,4 +290,4 @@ appendPortals()
|
|||
|
||||
});
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -324,11 +324,12 @@
|
|||
<p>Simply returns the top player leaderboard</p>
|
||||
|
||||
<br>
|
||||
<p class="reveal" onclick="$('#params-leaderboard').slideToggle(100)"><b>Parameters (3)</b></p>
|
||||
<p class="reveal" onclick="$('#params-leaderboard').slideToggle(100)"><b>Parameters (4)</b></p>
|
||||
<div class="subdiv" id="params-leaderboard">
|
||||
<p>count: The amount of players to list (default is 100, max is 5000, does not work with accurate leaderboard)</p>
|
||||
<p>creator: Fetches the creator leaderboard</p>
|
||||
<p>accurate: Fetches the accurate leaderboard</p>
|
||||
<p>type: Accurate leaderboard only - the type of stat to sort by (stars, coins, or demons)</p>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
|
|
@ -44,7 +44,13 @@
|
|||
<div style="height: 4.5%"></div>
|
||||
</div>
|
||||
|
||||
<div class="leaderboardBox supercenter gs" style="width: 120vh; height: 80%; pointer-events: none"></div>
|
||||
<div class="leaderboardBox supercenter gs" style="width: 120vh; height: 80%; pointer-events: none">
|
||||
<div id="sortDiv" style="position: relative; right: 10.5%; top: 10.5%; width: 0.1%">
|
||||
<img class="gdButton sortButton" style="margin-bottom: 1vh" sort="stars" src="../assets/sort-stars-on.png" height="11%">
|
||||
<img class="gdButton sortButton" style="margin-bottom: 1vh" sort="coins" src="../assets/sort-coins.png" height="11%">
|
||||
<img class="gdButton sortButton" style="margin-bottom: 1vh" sort="demons" src="../assets/sort-demons.png" height="11%">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="position:absolute; top: 2%; left: 1.5%; width: 10%; height: 25%; pointer-events: none">
|
||||
<img class="gdButton yesClick" id="backButton" src="../assets/back.png" height="30%" onclick="backButton()">
|
||||
|
@ -68,7 +74,7 @@
|
|||
<script type="text/javascript" src="../assets/dragscroll.js"></script>
|
||||
<script>
|
||||
|
||||
let loading = false;
|
||||
let sort = "stars"
|
||||
|
||||
let top250Text =
|
||||
`The <g>Top 250<> leaderboard contains the <g>top 250 players<>, sorted by <y>star<> value. However, due to <o>hackers<> flooding the leaderboard, this leaderboard has been <b>frozen<> for well over 2 years and displays <o>very outdated information<>.`
|
||||
|
@ -97,7 +103,7 @@ function leaderboard(val) {
|
|||
$('#searchBox').html(`<div style="height: 4.5%"></div>`)
|
||||
$('#loading').show()
|
||||
|
||||
fetch(`../api/leaderboard?count=250&${val}`).then(res => res.json()).then(res => {
|
||||
fetch(`../api/leaderboard?count=250&${val}&type=${sort}`).then(res => res.json()).then(res => {
|
||||
|
||||
if (val != type) return;
|
||||
|
||||
|
@ -134,12 +140,20 @@ function leaderboard(val) {
|
|||
appendScroll: '#searchBox'
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
let type = "accurate"
|
||||
leaderboard(type)
|
||||
|
||||
$(document).on('click', '.sortButton', function () {
|
||||
if ($('#loading').is(":visible")) return
|
||||
sort = $(this).attr('sort')
|
||||
$('.sortButton').each(function() {
|
||||
$(this).attr('src', $(this).attr('src').replace('-on', '').replace('.png', '') + ($(this).attr('sort') == sort ? "-on" : "") + ".png")
|
||||
})
|
||||
return leaderboard("accurate")
|
||||
})
|
||||
|
||||
$('#topTabOff').click(function() {
|
||||
if (type == "top") return;
|
||||
type = "top"
|
||||
|
@ -149,6 +163,7 @@ function leaderboard(val) {
|
|||
$('#accurateTabOff').show()
|
||||
$('#creatorTabOff').show()
|
||||
infoText(top250Text)
|
||||
$('#sortDiv').hide()
|
||||
})
|
||||
|
||||
$('#accurateTabOff').click(function() {
|
||||
|
@ -160,6 +175,7 @@ function leaderboard(val) {
|
|||
$('#accurateTabOn').show()
|
||||
$('#creatorTabOff').show()
|
||||
infoText(accurateText)
|
||||
$('#sortDiv').show()
|
||||
})
|
||||
|
||||
$('#creatorTabOff').click(function() {
|
||||
|
@ -171,6 +187,7 @@ function leaderboard(val) {
|
|||
$('#accurateTabOff').show()
|
||||
$('#creatorTabOn').show()
|
||||
infoText(creatorText)
|
||||
$('#sortDiv').hide()
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"canvas": "^2.6.0",
|
||||
"canvas": "^2.6.1",
|
||||
"compression": "^1.7.4",
|
||||
"connect-timeout": "^1.9.0",
|
||||
"express": "^4.17.1",
|
||||
"jimp": "^0.8.4",
|
||||
"jimp": "^0.8.5",
|
||||
"plist": "^3.0.1",
|
||||
"request": "^2.88.0"
|
||||
"request": "^2.88.2"
|
||||
}
|
||||
}
|
||||
|
|