GDBrowser/html/analyze.html
2022-05-19 22:30:54 -04:00

348 lines
18 KiB
HTML

<head>
<title>Level Analysis</title>
<meta charset="utf-8">
<meta property="og:type" content="website">
<meta id="meta-title" property="og:title" content="Level Analysis">
<meta id="meta-desc" property="og:description" content="Analyze a Geometry Dash level and view it's objects, portals, color channels, code, and more!">
<meta id="meta-image" name="og:image" itemprop="image" content="https://gdbrowser.com/assets/cp.png">
<meta name="twitter:card" content="summary">
<link href="../assets/css/browser.css?v=1" type="text/css" rel="stylesheet">
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-135255146-3"></script><script>window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);}gtag('js', new Date());gtag('config', 'UA-135255146-3');</script>
<link rel="icon" href="../assets/cp.png">
</head>
<body class="levelBG" style="overflow-y:auto;" onbeforeunload="saveUrl()">
<div id="everything" class="center">
<div class="popup" id="analyzeError">
<div class="fancybox bounce center supercenter" style="width: 80vh; top: 46%">
<h2 class="smaller center" style="font-size: 5.5vh">Something Went Wrong!</h2>
<p class="bigger center" id="errorMessage" style="margin-bottom: 2vh; line-height: 5vh; margin-top: 1.5vh;">An <cr>unknown error</cr> occured while trying to analyze this level.</p>
<img onclick='window.location.replace(window.location.href.replace("analyze/", ""))' src="../assets/btn-back.png" width=26%; class="gdButton center closeWindow">
</div>
</div>
<div class="supercenter" id="loadingDiv" style="height:50%">
<h1 style="transform:scale(1.2)">Analyzing level...</h1>
<img id="loading" class="spin noSelect" src="../assets/loading.png" height="25%" style="margin-top: 7%">
</div>
<div class="popup" id="colorInfo">
<div id="colorStuff" class="brownbox bounce center supercenter" style="height: 60%; width: 90vh">
</div>
</div>
<div id="analysisDiv" style="margin-top: 2%; display:none">
<h2 class="pre" id="levelName"></h2>
<p></p>
<h3 id="objectCount"></h3>
<div id="highDetailDiv">
<h1 class="topMargin">Low Detail Mode</h1>
<div class="transparentBox analysis roundBottom" id="highdetail" style="height: 10%; text-align: left; overflow: hidden; background-color: darkblue;"></div>
<h3 style="margin-top: -0.25%" id="hdText"></h3>
</div>
<h1 class="topMargin">Portal Order</h1>
<div class="transparentBox analysis" id="portals" style="height: 23%"></div>
<div>
<div class="portalSetting"><h3><input checked type="checkbox" class="portalToggle" id="box1" portal="form"> <label for="box1" class="gdcheckbox gdButton portalButton"></label>Form Portals</h3></div>
<div class="portalSetting"><h3><input checked type="checkbox" class="portalToggle" id="box2" portal="size"> <label for="box2" class="gdcheckbox gdButton portalButton"></label>Size Portals</h3></div>
<div class="portalSetting"><h3><input checked type="checkbox" class="portalToggle" id="box3" portal="speed"><label for="box3" class="gdcheckbox gdButton portalButton"></label>Speed Portals</h3></div>
<br><div style="height: 1.5%"></div>
<div class="portalSetting"><h3><input checked type="checkbox" class="portalToggle" id="box4" portal="mirror"><label for="box4" class="gdcheckbox gdButton portalButton"></label>Mirror Portals</h3></div>
<div class="portalSetting"><h3><input checked type="checkbox" class="portalToggle" id="box5" portal="dual"> <label for="box5" class="gdcheckbox gdButton portalButton"></label>Dual Portals</h3></div>
<div class="portalSetting"><h3><input checked type="checkbox" class="portalToggle" id="box6" portal="dupe"> <label for="box6" class="gdcheckbox gdButton portalButton"></label>Duplicates</h3></div>
</div>
<h1 class="topMargin" id="coinText">User Coins</h1>
<div class="transparentBox analysis" id="coins"></div>
<h1 class="topMargin2" id="triggerText">Triggers</h1>
<div class="transparentBox analysis" id="triggers" style="margin-bottom: 0.6%"></div>
<h3 style="margin-right: 1.2vh; font-size: 4.2vh; display: none" id="alphaGroup" class="inline">Invisible Group: <span id="alphaGroupNum" class="gold"></span></h3>
<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>
<h1 class="topMargin2">Misc Objects</h1>
<div class="transparentBox analysis" id="misc" style="height: 25%"></div>
<h1 class="topMargin2">Level Style</h1>
<div class="transparentBox analysis" id="style" style="height: 20%"></div>
<h1 class="topMargin2" id="grouptext">Trigger Groups</h1>
<div class="transparentBox analysis" id="groups" style="height: 18%"></div>
<h3 style="margin-right: 1.2vh" class="inline">Sort:</h3><h3 id="triggerSort" class="inline gdButton gold" style="text-decoration: underline;">Ascending</h3>
<h1 class="topMargin2">Color Channels</h1>
<div class="transparentBox analysis" id="colorDiv"></div>
<h1 class="topMargin2">Level Data</h1>
<div class="transparentBox levelCode" id="levelCode">
<p class="menuLink" id="revealCode">Reveal level data</p>
<p id="codeLength"></p>
</div>
<div style="height: 7%"></div>
</div>
<div style="position:absolute; top: 2%; left: -1.95%; width: 10%; height: 25%; pointer-events: none">
<img class="gdButton yesClick" id="backButton" src="../assets/back.png" height="30%" onclick="backButton()">
</div>
</div>
</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script type="text/javascript" src="../global.js"></script>
<script>
function clean(text) {return text.toString().replace(/&/g, "&#38;").replace(/</g, "&#60;").replace(/>/g, "&#62;").replace(/=/g, "&#61;").replace(/"/g, "&#34;").replace(/'/g, "&#39;")}
let disabledPortals = []
let altTriggerSort = false
let formPortals = ['cube', 'ship', 'ball', 'ufo', 'wave', 'robot', 'spider']
let speedPortals = ['-1x', '1x', '2x', '3x', '4x']
let sizePortals = ['mini', 'big']
let dualPortals = ['dual', 'single']
let mirrorPortals = ['mirrorOn', 'mirrorOff']
fetch(`../api${window.location.pathname}`).then(res => res.json()).then(res => {
if (!res.level) {
switch(res) {
case -1: $('#errorMessage').html("This level could not be be <cr>downloaded</cr>. Either the servers rejected the request, or the level simply <co>doesn't exist at all</co>."); break;
case -2: $('#errorMessage').html("This level's data appears to be <cr>corrupt</cr> and cannot be <cy>parsed</cy>. It most likely won't load in GD."); break;
case -3: $('#errorMessage').html("This level's data could not be obtained because <ca>RobTop</ca> has disabled <cg>level downloading</cg>."); break;
}
popupEsc = false
$('#analyzeError').show()
return $('#loadingDiv').hide()
}
$('#levelName').text(res.level.name)
$('#objectCount').text(commafy(res.objects) + " objects")
document.title = "Analysis of " + res.level.name
$('#meta-title').attr('content', "Analysis of " + res.level.name)
$('#meta-desc').attr('content', `${res.portals.split(",").length}x portals, ${res.orbs.total || 0}x orbs, ${res.triggers.total || 0}x triggers, ${res.misc.glow || 0}x glow...`)
let hdPercent = (res.highDetail / res.objects) * 100
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(`${commafy(res.highDetail)}/${commafy(res.objects)} marked high detail • ${+hdPercent.toFixed(1)}% optimized`)
}
let triggerList = Object.keys(res.triggers)
let orbList = Object.keys(res.orbs)
let miscList = Object.keys(res.misc)
let blockList = Object.keys(res.blocks)
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='../assets/objects/portals/${res.settings.gamemode}.png'><h3>Start</h3></div><img class="divider portalImage" src="../assets/divider.png" style="margin: 1.3% 0.8%">`)
if (res.settings.startMini && !disabledPortals.includes('size')) $('#portals').append(`<div class="inline portalDiv"><img class="portalImage" src='../assets/objects/portals/mini.png'><h3>Start</h3></div><img class="divider portalImage" src="../assets/divider.png" style="margin: 1.3% 0.8%">`)
if (res.settings.speed && res.settings.speed != "1x" && !disabledPortals.includes('speed')) $('#portals').append(`<div class="inline portalDiv"><img class="portalImage speedPortal" src='../assets/objects/portals/${res.settings.speed}.png'><h3>Start</h3></div><img class="divider portalImage" src="../assets/divider.png" style="margin: 1.3% 0.8%">`)
if (res.settings.startDual && !disabledPortals.includes('dual')) $('#portals').append(`<div class="inline portalDiv"><img class="portalImage" src='../assets/objects/portals/dual.png'><h3>Start</h3></div><img class="divider portalImage" src="../assets/divider.png" style="margin: 1.3% 0.8%">`)
let dividerCount = $('.divider').length - 1
$('.divider').each(function(y) {
if (y != dividerCount) $(this).remove()
})
portals.forEach(x => {
if (!x || x[0] == "") return;
$('#portals').append(`<div class="inline portalDiv"><img class="portalImage ${x[0].match(/[0-9]x/) ? "speedPortal" : ""}" src='../assets/objects/portals/${x[0]}.png'><h3>${x[1]}</h3></div>`)
}
)}
function appendTriggerGroups() {
$('#groups').html("")
let groupList = Object.keys(res.triggerGroups)
if (!altTriggerSort) groupList = groupList.sort((a, b) => Number(a.slice(6)) - Number(b.slice(6)))
groupList.forEach(x => {
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>`)
})
}
appendPortals()
appendTriggerGroups()
if (!res.coins || !res.coins.length) {
$('#coinText').remove()
$('#coins').remove()
}
else {
$('#coinText').text(`User Coins (${res.coins.length})`)
res.coins.forEach(x => {
$('#coins').append(`<div class="inline orbDiv"><img height="50%" src='../assets/objects/${res.coinsVerified ? "coin" : "browncoin"}.png'><h3 style="padding-top: 7%">${x}%</h3></div>`)
})
}
triggerList.forEach(x => {
if (x == "total") $('#triggerText').text(`Triggers (${commafy(res.triggers[x])})`)
else $('#triggers').append(`<div class="inline triggerDiv"><img height="50%" src='../assets/objects/triggers/${x}.png'><h3 style="padding-top: 7%">x${commafy(res.triggers[x])}</h3></div>`)
})
if (res.invisibleGroup) {
$('#alphaGroupNum').text(res.invisibleGroup)
$('#alphaGroup').show()
}
orbList.forEach(x => {
if (x == "total") $('#orbText').text(`Jump Rings (${commafy(res.orbs[x])})`)
else $('#orbs').append(`<div class="inline orbDiv"><img height="50%" src='../assets/objects/orbs/${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='../assets/objects/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='../assets/objects/${x.slice(0, -1)}.png'><h3 style="padding-top: 15%">x${commafy(res.misc[x][0])}<br>${res.misc[x][1]}</h3></div>`)
})
let bgCol = res.colors.find(x => x.channel == "BG")
let grCol = res.colors.find(x => x.channel == "G")
if (!bgCol) bgCol = {r: 40, g: 125, b: 255}
else if (bgCol.r < 35 && bgCol.g < 35 && bgCol.b < 35) bgCol = {r: 75, g: 75, b: 75}
if (!grCol) grCol = {r: 0, g: 102, b: 255}
else if (grCol.r < 35 && grCol.g < 35 && grCol.b < 35) grCol = {r: 75, g: 75, b: 75}
$('#style').append(`<div class="inline styleDiv styleBG" style='background-color: rgb(${clean(bgCol.r)}, ${clean(bgCol.g)}, ${clean(bgCol.b)})'><img height="60%" src='../assets/levelstyle/bg-${res.settings.background}.png'></div>`)
$('#style').append(`<div class="inline styleDiv styleBG" style='background-color: rgb(${clean(grCol.r)}, ${clean(grCol.g)}, ${clean(grCol.b)})'><img height="60%" src='../assets/levelstyle/gr-${res.settings.ground}.png'></div>`)
$('#style').append(`<div class="inline styleDiv"><img height="55%" src='../assets/levelstyle/font-${res.settings.font}.png'></div>`)
$('#style').append(`<div class="inline styleDiv"><img height="60%" src='../assets/levelstyle/line-${res.settings.alternateLine ? 2 : 1}.png'></div>`)
if (res.settings.twoPlayer) $('#style').append(`<div class="inline styleDiv"><img height="60%" src='../assets/levelstyle/mode-2p.png'></div>`)
colorList.forEach((x, y) => {
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(${clean(c.cr || c.r)}, ${clean(c.cg || c.g)}, ${clean(c.cb || c.b)}, ${clean(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 && 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>`)
})
if (colorList.length == 0) $('#colorDiv').append('<h3 style="margin-top: 7vh">Could not get color info!</h3>')
$('#triggerSort').click(function() {
altTriggerSort = !altTriggerSort
$('#triggerSort').text(altTriggerSort ? "Most Used" : "Ascending")
appendTriggerGroups()
})
$(".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]))
if (disabledPortals.includes('size')) portals = portals.filter(x => !sizePortals.includes(x[0]))
if (disabledPortals.includes('dual')) portals = portals.filter(x => !dualPortals.includes(x[0]))
if (disabledPortals.includes('mirror')) portals = portals.filter(x => !mirrorPortals.includes(x[0]))
if (disabledPortals.includes('dupe')) {
portals.reverse().forEach((x, y) => {if (portals[y+1] && portals[y+1][0] == x[0]) portals[y][0] = null;})
portals = portals.reverse().filter(x => x[0] != null)
}
appendPortals()
})
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>')
window.setTimeout(function () { //small delay so "loading" message appears
$('#levelCode').html(`<p class="codeFont">${clean(res.data).replace(/\n/g, "<br>")}</p>`)}, 50);
$('#levelCode').focus().select()
})
$(document).on('click', '.color', function() {
let col = res.colors.find(x => x.channel == $(this).attr('channel'))
let hsv = col.copiedHSV
if (hsv) {
hsv.s = Number(hsv.s).toFixed(2)
hsv.v = Number(hsv.v).toFixed(2)
}
let hex = "#" + ((1 << 24) + (+col.r << 16) + (+col.g << 8) + +col.b).toString(16).slice(1)
$('#colorStuff').html(`
<h2 class="slightlySmaller">${isNaN(col.channel) ? col.channel : "Color " + col.channel}</h2>
<div class="colorSection">
<h3>Hex Code</h3>
<p>${hex}</p>
</div>
<div class="colorSection">
<h3>RGB</h3>
<p>${clean(col.r)}, ${clean(col.g)}, ${clean(col.b)}</p>
</div>
<div class="colorSection">
<h3>Opacity</h3>
<p>${Number(col.opacity).toFixed(2)}</p>
</div>
<br>
<div class="colorSection2" style="width: 40%; ${col.copiedChannel ? "" : "margin-right:55.4%"}">
<div class="colorCheckbox"><h3><input ${col.pColor == 1 ? "checked" : ""} type="checkbox"> <label class="gdcheckbox gdButton"></label>Player 1</h3></div>
<div class="colorCheckbox"><h3><input ${col.pColor == 2 ? "checked" : ""} type="checkbox"> <label class="gdcheckbox gdButton"></label>Player 2</h3></div>
<div class="colorCheckbox"><h3><input ${col.blending ? "checked" : ""} type="checkbox"> <label class="gdcheckbox gdButton"></label>Blending</h3></div>
<div class="colorCheckbox"><h3><input ${col.copiedChannel ? "checked" : ""} type="checkbox"> <label class="gdcheckbox gdButton"></label>Copy Color</h3></div>
<div class="colorCheckbox"><h3><input ${col.copyOpacity ? "checked" : ""} type="checkbox"> <label class="gdcheckbox gdButton"></label>Copy Opacity</h3></div>
</div>
${col.copiedChannel ? `
<div class="colorSection2">
<div class="colorSection copyDetails">
<h3>Copied</h3>
<p>${isNaN(col.copiedChannel) ? col.copiedChannel : "Col " + col.copiedChannel}</p>
</div>
<div class="colorSection copyDetails">
<h3>Hue</h3>
<p>${!hsv ? 0 : hsv.h > 0 ? "+" + hsv.h : hsv.h}</p>
</div>
<div class="colorSection copyDetails">
<h3>Saturation</h3>
<p>${!hsv ? "x1.00" : !hsv['s-checked'] ? "x" + hsv.s : hsv.s > 0 ? "+" + hsv.s : hsv.s}</p>
</div>
<div class="colorSection copyDetails">
<h3>Brightness</h3>
<p>${!hsv ? "x1.00" : !hsv['v-checked'] ? "x" + hsv.v : hsv.v > 0 ? "+" + hsv.v : hsv.v}</p>
</div>
</div>`
: `<div class="colorBox" style="background-color: rgba(${clean(col.r)}, ${clean(col.g)}, ${clean(col.b)}, ${clean(col.opacity)}); border-color: ${hex}"></div>`}
<br><img src="../assets/ok.png" width=14%; style="margin-top: 4%" class="gdButton center" onclick="$('.popup').hide()">`)
$('#colorInfo').show()
})
$('#loadingDiv').hide()
$('#analysisDiv').show()
});
</script>