1const WHITE = 0xffffff 2const colorNames = { "1": "Color 1", "2": "Color 2", "g": "Glow", "w": "White", "u": "UFO Dome" } 3const formNames = { "player": "cube", "player_ball": "ball", "bird": "ufo", "dart": "wave" } 4const extendedFormNames = Object.assign({ icon: "cube", special: "trail", death: "deathEffect", color: "color1" }, formNames) 5const achFormName = { "cube": "icon", "ufo": "bird", "wave": "dart", "color1": "color", "deathEffect": "death", "trail": "special" } 6const specialFormNames = { "trail": "Trail", "deathEffect": "Death Effect", "shipFire": "Ship Fire", "item": "Item" } 7const qualities = { low: 'low', sd: 'low', med: 'hd', medium: 'hd', hd: 'hd' } 8const positionMultipliers = { uhd: 4, hd: 2, low: 1 } 9const yOffsets = { player_ball: -10, bird: 30, spider: 7, swing: -15 } 10const cubeOffsets = { 11 ship: { "x": 0, "y": 5, "scale": 0.55 }, 12 bird: { "x": 0, "y": 6, "scale": 0.55 }, 13 jetpack: { "x": 3, "y": 2, "scale": 0.6 } 14} 15 16const loadedAssets = {} 17 18async function loadTexture(name, path) { 19 let loaded = await PIXI.Assets.load(path) 20 if (loaded) loadedAssets[name] = loaded 21 return loaded 22} 23 24function downloadFile(data, name) { 25 let url = window.URL.createObjectURL(data); 26 let downloader = document.createElement('a'); 27 downloader.href = url 28 downloader.setAttribute("download", name); 29 document.body.appendChild(downloader); 30 downloader.click(); 31 document.body.removeChild(downloader); 32} 33 34const loadedIcons = {} 35const iconFrames = {} 36 37function getPositionMultiplier(quality) { 38 return positionMultipliers[quality || 'uhd'] 39} 40 41function positionPart(part, partIndex, layer, formName, isGlow, quality) { 42 let positionMultiplier = getPositionMultiplier(quality) 43 layer.position.x += (part.pos[0] * positionMultiplier) 44 layer.position.y -= (part.pos[1] * positionMultiplier) 45 layer.scale.x = part.scale[0] 46 layer.scale.y = part.scale[1] 47 if (part.flipped[0]) layer.scale.x *= -1 48 if (part.flipped[1]) layer.scale.y *= -1 49 layer.angle = part.rotation 50 layer.zIndex = part.z 51 52 if (!isGlow) { 53 let tintInfo = iconStuff.robotAnimations.info[formName].tints 54 let foundTint = tintInfo[partIndex] 55 if (foundTint > 0) { 56 let darkenFilter = new PIXI.ColorMatrixFilter(); 57 darkenFilter.brightness(0) 58 darkenFilter.alpha = (255 - foundTint) / 255 59 layer.filters = [darkenFilter] 60 } 61 } 62} 63 64function validNum(val, defaultVal) { 65 let colVal = +val 66 return isNaN(colVal) ? defaultVal : colVal 67} 68 69function getGlowColor(colors) { 70 let glowCol = Number.isInteger(colors["g"]) ? colors["g"] : (colors[2] === 0 ? colors[1] : colors[2]) 71 // if (glowCol === 0) glowCol = WHITE // white glow if both colors are black 72 return glowCol 73} 74 75function validateIconID(id, form) { 76 let realID = Math.min(iconStuff.iconCounts[form], Math.abs(validNum(id, 1))) 77 if (realID == 0 && !["player", "player_ball", "ship"].includes(form)) realID = 1 78 return realID 79} 80 81function parseIconColor(col) { 82 if (!col && col != 0) return WHITE 83 else if (typeof col == "string" && col.length == 6) return parseInt(col, 16) 84 let rgb = iconStuff.colors[col] 85 return rgb ? rgbToDecimal(rgb) : WHITE; 86} 87 88function parseIconForm(form, def="player") { 89 let foundForm = iconStuff.forms[form] || iconStuff.forms[formNames] 90 return foundForm ? foundForm.form : def 91} 92 93function loadIconLayers(form, id, cb) { 94 let iconStr = `${form}_${padZero(validateIconID(id, form))}` 95 return loadIconSheet(iconStr, cb) 96} 97 98function loadIconSheet(iconStr, cb) { 99 fetch(`/iconkit/icons/${iconStr}-uhd.plist`).then(pl => pl.text()).then(plist => { 100 101 let data = parsePlist(plist) 102 103 Object.keys(data.frames).forEach(x => { 104 iconFrames[x] = data.frames[x] 105 }) 106 107 let sheetName = iconStr + "-sheet" 108 loadTexture(sheetName, `/iconkit/icons/${iconStr}-uhd.png`).then(texture => { 109 readIconData(texture, data.pos, cb) 110 }) 111 }) 112} 113 114async function extendTexture(texture, endX, endY) { 115 let img = texture.baseTexture.resource.source
116 let canv = document.createElement('canvas') 117 let ctx = canv.getContext('2d') 118 canv.width = endX 119 canv.height = endY 120 ctx.drawImage(img, 0, 0) 121 let url = canv.toDataURL() 122 let loaded = await PIXI.Assets.load({ src: url, alias: "temp_extended_texture_" + url.slice(-32) + String(Math.random()).slice(2) }) 123 return loaded 124} 125 126async function readIconData(texture, data, cb, folder = loadedIcons, warnings = []) { 127 let hasError = false; 128 for (const x of Object.keys(data)) { 129 let bounds = data[x] 130 let size = bounds.rotated ? [bounds.size[1], bounds.size[0]] : bounds.size 131 132 let textureRect = new PIXI.Rectangle(bounds.pos[0], bounds.pos[1], size[0], size[1]) 133 134 let endX = bounds.pos[0] + size[0] 135 let endY = bounds.pos[1] + size[1] 136 if (endX > texture.width || endY > texture.height) { 137 if (warnings.length < 1) alert(`Warning: Frame '${x}' expects pixels outside of the width/height of the image file! This is supported by Geometry Dash, but should never happen. ` + 138 `\n\nThe image will be automatically extended, but please consider fixing your sprite offset.\n\nX pos: ${endX} (width: ${texture.width})\nY pos: ${endY} (height: ${texture.height})`) 139 warnings.push(x) 140 if (warnings.length > 99) return 141 142 hasError = true 143 let newTexture = await extendTexture(texture, Math.max(endX, texture.width), Math.max(endY, texture.height)) 144 readIconData(newTexture, data, cb, folder, warnings) 145 break; 146 } 147 148 let partTexture = new PIXI.Texture(texture, textureRect) 149 folder[x] = partTexture 150 } 151 152 if (cb && !hasError) { 153 if (warnings.length > 1) alert("The same issue also applies to:\n\n" + warnings.join("\n")) 154 cb(texture, loadedAssets, true) 155 } 156} 157 158let dom_parser = new DOMParser() 159function parsePlist(data) { 160 let plist = dom_parser.parseFromString(data, "text/xml") 161 let frames = plist.children[0].children[0].children[1].children 162 let positionData = {} 163 let dataFrames = {} 164 for (let i = 0; i < frames.length; i += 2) { 165 let frameName = frames[i].innerHTML 166 let frameData = frames[i + 1].children 167 let isRotated = false 168 dataFrames[frameName] = {} 169 positionData[frameName] = {} 170 171 for (let n = 0; n < frameData.length; n += 2) { 172 let keyName = frameData[n].innerHTML 173 let keyData = frameData[n + 1].innerHTML 174 if (["spriteOffset", "spriteSize", "spriteSourceSize"].includes(keyName)) { 175 dataFrames[frameName][keyName] = parseWeirdArray(keyData) 176 } 177 178 else if (keyName == "textureRotated") { 179 isRotated = frameData[n + 1].outerHTML.includes("true") 180 dataFrames[frameName][keyName] = isRotated 181 } 182 183 else if (keyName == "textureRect") { 184 let textureArr = keyData.slice(1, -1).split("},{").map(x => parseWeirdArray(x)) 185 positionData[frameName].pos = textureArr[0] 186 positionData[frameName].size = textureArr[1] 187 } 188 } 189 190 if (isRotated) { 191 if (dataFrames[frameName].spriteSize.join(",") == positionData[frameName].size.join(",")) { 192 positionData[frameName].size.reverse() 193 } 194 } 195 196 } 197 return { pos: positionData, frames: dataFrames } 198} 199 200function parseWeirdArray(data) { 201 return data.replace(/[^0-9,-.]/g, "").split(",").map(x => +x) 202} 203 204function padZero(num) { 205 if (isNaN(num)) return num 206 let numStr = num.toString() 207 if (num < 10) numStr = "0" + numStr 208 return numStr 209} 210 211function rgbToDecimal(rgb) { 212 return (rgb.r << 16) + (rgb.g << 8) + rgb.b; 213} 214 215class Icon { 216 constructor(data = {}, cb) { 217 this.app = data.app 218 this.sprite = new PIXI.Container(); 219 this.form = data.form || "player" 220 this.id = data.isCustom ? data.id : validateIconID(data.id, this.form) 221 this.colors = data.rawColors || { 222 "1": validNum(data.col1, 0xafafaf), // primary 223 "2": validNum(data.col2, WHITE), // secondary 224 "g": validNum(data.colG, validNum(+data.colG, null)), // glow 225 "w": validNum(data.colW, validNum(+data.colW, WHITE)), // white 226 "u": validNum(data.colU, validNum(+data.colU, WHITE)), // ufo 227 } 228 229 this.glow = !!data.glow 230 this.layers = [] 231 this.glowLayers = [] 232 this.customFiles = null 233 this.complex = ["spider", "robot"].includes(this.form)
234 this.quality = data.quality ? (qualities[data.quality.toLowerCase()] || 'uhd') : 'uhd' 235 236 if (data.isCustom && data.files) this.customFiles = data.files 237 238 // most forms 239 if (!this.complex) { 240 let extraSettings = {} 241 if (data.noUFODome) extraSettings.noDome = true 242 if (data.isCustom) extraSettings.customFiles = this.customFiles 243 if (this.form == "player_ball") { 244 this.ballSpeed = (data.ballSpeed || 0) 245 if (this.ballSpeed != 0 && !this.ballRolling) this.rollBall() 246 } 247 let basicIcon = new IconPart(this.form, this.id, this.colors, this.glow, extraSettings) 248 this.sprite.addChild(basicIcon.sprite) 249 this.layers.push(basicIcon) 250 251 let foundGlow = basicIcon.sections.find(x => x.colorType == "g") 252 if (foundGlow) this.glowLayers.push(foundGlow) 253 } 254 255 // spider + robot 256 else { 257 let idlePosition = this.getAnimation(data.animation, data.animationForm).frames[0] 258 let cFiles = data.isCustom ? this.customFiles : undefined 259 idlePosition.forEach((x, y) => { 260 x.name = iconStuff.robotAnimations.info[this.form].names[y] 261 let part = new IconPart(this.form, this.id, this.colors, false, { part: x, skipGlow: true, customFiles: cFiles }) 262 positionPart(x, y, part.sprite, this.form, false, this.quality) 263 264 let glowPart = new IconPart(this.form, this.id, this.colors, true, { part: x, onlyGlow: true, customFiles: cFiles }) 265 positionPart(x, y, glowPart.sprite, this.form, true, this.quality) 266 glowPart.sprite.visible = (this.glow || this.colors[1] === 0) 267 this.glowLayers.push(glowPart) 268 269 this.layers.push(part) 270 this.sprite.addChild(part.sprite) 271 }) 272 273 let fullGlow = new PIXI.Container(); 274 this.glowLayers.forEach(x => fullGlow.addChild(x.sprite)) 275 this.sprite.addChildAt(fullGlow, 0) 276 if (typeof Ease !== "undefined") this.ease = new Ease.Ease() 277 this.animationSpeed = Math.abs(Number(data.animationSpeed) || 1) 278 if (data.animation) this.setAnimation(data.animation, data.animationForm) 279 } 280
281 if (this.quality != 'uhd') this.sprite.scale.set(4 / getPositionMultiplier(this.quality)) 282 283 if (!data.dontAdd) { 284 this.app.stage.removeChildren() 285 this.app.stage.addChild(this.sprite) 286 } 287 288 if (cb) cb(this) 289 290 } 291 292 updatePosition() { 293 this.sprite.position.set(this.app.renderer.width / 2, (this.app.renderer.height / 2) + (yOffsets[this.form] || 0)) 294 } 295 296 getAllLayers() { 297 let allLayers = []; 298 (this.complex ? this.glowLayers : []).concat(this.layers).forEach(x => x.sections.forEach(s => allLayers.push(s))) 299 return allLayers 300 } 301 302 getLayerArr() { 303 if (!this.complex) return this.layers 304 else return this.layers.concat({ sections: this.glowLayers.map(x => x.sections[0]), part: { name: "Glow" } }) 305 } 306 307 setColor(colorType, newColor, extra = {}) { 308 let colorStr = String(colorType).toLowerCase() 309 if (!colorType || !Object.keys(this.colors).includes(colorStr)) return 310 else this.colors[colorStr] = newColor 311 let newGlow = getGlowColor(this.colors) 312 this.getAllLayers().forEach(x => { 313 if (colorType != "g" && x.colorType == colorStr) x.setColor(newColor) 314 if (!extra.ignoreGlow && x.colorType == "g") x.setColor(newGlow) 315 }) 316 if (!this.glow && colorStr == "1") { 317 let shouldGlow = newColor == 0 318 this.glowLayers.forEach(x => x.sprite.visible = shouldGlow) 319 } 320 321 if (this.secondaryIcon) this.secondaryIcon.setColor(colorType, newColor, extra) 322 } 323 324 copyColorsFrom(i1) { 325 Object.entries(i1.colors).forEach(c => { 326 this.setColor(c[0], c[1]) 327 }) 328 } 329 330 setGlow(toggle) { 331 this.glow = !!toggle 332 this.glowLayers.forEach(x => x.sprite.visible = (this.colors["1"] == 0 || this.glow)) 333 334 if (this.secondaryIcon) this.secondaryIcon.setGlow(toggle) 335 } 336 337 formName() { 338 return formNames[this.form] || this.form 339 } 340 341 isGlowing() { 342 return this.glowLayers[0] && this.glowLayers[0].sprite.visible 343 } 344 345 // icon inside ships, ufos, etc 346 addSecondaryIcon(id = 1, skipLoad, cb) { 347 let offset = cubeOffsets[this.form] 348 if (!offset) return 349 350 let providedIcon = (typeof id == "object" && id.sprite) 351 352 if (!providedIcon && !skipLoad) return loadIconLayers("player", id, () => this.addSecondaryIcon(id, true, cb)) 353 354 let i2 = providedIcon ? id : new Icon({ 355 app: this.app, form: "player", id, dontAdd: true, 356 rawColors: this.colors, glow: this.glow 357 }) 358 359 // remove existing 360 if (this.secondaryIcon) this.removeSecondaryIcon() 361 362 this.secondaryIcon = i2 363 364 let iconScale = getPositionMultiplier(this.quality) / 4 365 366 i2.sprite.scale.set(offset.scale * iconScale) 367 i2.sprite.position.set(offset.x * 8 * iconScale, offset.y * -8 * iconScale) 368 this.sprite.children[0].addChildAt(i2.sprite, 1) 369 370 if (providedIcon) { 371 i2.copyColorsFrom(this) 372 i2.setGlow(this.glow) 373 } 374 375 if (cb) cb(this) 376 } 377 378 removeSecondaryIcon(destroy) { 379 if (this.secondaryIcon) { 380 if (destroy) this.secondaryIcon.sprite.destroy() 381 else this.sprite.children[0].children = this.sprite.children[0].children.filter(x => x != this.secondaryIcon.sprite) 382 this.secondaryIcon = null 383 } 384 } 385 386 setBallSpeed(speed, stop) { 387 if (this.form != "player_ball") return 388 this.ballSpeed = speed 389 390 if (stop) this.sprite.angle = 0 391 else if (this.ballSpeed != 0 && !this.ballRolling) this.rollBall() 392 } 393 394 rollBall() { 395 if (!this.ballSpeed) return this.ballRolling = false 396 this.ballRolling = true 397 this.sprite.angle += this.ballSpeed 398 requestAnimationFrame(() => this.rollBall()) 399 } 400 401 getAnimation(name, animForm) { 402 let animationList = iconStuff.robotAnimations.animations[animForm || this.form] 403 return animationList[name || "idle"] || animationList["idle"] 404 } 405 406 clearAnimation() { 407 this.ease.removeAll() 408 this.isRecording = false 409 this.recordedFrames = [] 410 this.animationFrame = 0 411 } 412 413 setAnimation(data, animForm) { 414 let animData = this.getAnimation(data, animForm) || this.getAnimation("idle") 415 this.clearAnimation() 416 this.animationName = data 417 this.runAnimation(animData, data) 418 } 419 420 runAnimation(animData, animName, duration) { 421 animData.frames[this.animationFrame].forEach((newPart, index) => { 422 let section = this.layers[index] 423 let glowSection = this.glowLayers[index] 424 let truePosMultiplier = getPositionMultiplier(this.qual
424ity) 425 if (!section) return 426 427 // gd is weird with negative rotations 428 // this isn't perfect (e.g. robot run2) but it's the best i can do 429 let realRot = newPart.rotation 430 if (realRot < -180) realRot += 360 431 432 let movementData = { 433 x: newPart.pos[0] * truePosMultiplier, 434 y: newPart.pos[1] * truePosMultiplier * -1, 435 scaleX: newPart.scale[0], 436 scaleY: newPart.scale[1], 437 angle: realRot 438 } 439 if (newPart.flipped[0]) movementData.scaleX *= -1 440 if (newPart.flipped[1]) movementData.scaleY *= -1 441 442 let dur = (!duration ? 1 : (animData.info.duration / (this.animationSpeed || 1))) 443 let bothSections = [section, glowSection] 444 bothSections.forEach((x, y) => { 445 let easing = this.ease.add(x.sprite, movementData, { duration: duration || 1, ease: 'linear' }) 446 let continueAfterEase = animData.frames.length > 1 && y == 0 && index == 0 && animName == this.animationName 447 if (continueAfterEase) easing.on('complete', () => { 448 this.animationFrame++ 449 if (this.animationFrame >= animData.frames.length) { 450 if (animData.info.loop) { 451 this.animationFrame = 0; 452 } 453 else setTimeout(() => { 454 this.animationFrame = 0; 455 this.runAnimation(animData, animName, dur); 456 }, 1000); 457 } 458 if (this.animationFrame < animData.frames.length) this.runAnimation(animData, animName, dur) 459 }) 460 }) 461 }) 462 } 463 464 recordAnimation(animName = this.animationName) { 465 466 if (!window.UPNG) appendScript("upng") 467 if (!window.pako) appendScript("pako") 468 469 alert("Animation recording is a work in progress!!! If you found this function then feel free to use it, but looping might be a bit buggy.") 470 471 if (!this.animationFrame || this.animationSpeed < 0.1) return console.info("No animation to record!") 472 473 let w = this.app.view.width 474 let h = this.app.view.height 475 476 // needed because so canvas doesn't autocrop :v 477 let bounds = new PIXI.Graphics(); 478 bounds.lineStyle(1, 0xFF0000); 479 bounds.drawRect(0, 0, w - 1, h - 1); 480 bounds.alpha = 0 481 this.app.stage.addChild(bounds); 482 483 const RECORDING_FPS = (this.animationSpeed <= 0.5 ? 30 : 60) 484 const FRAME_DELAY = (1000 / RECORDING_FPS) 485 486 this.setAnimation(animName) 487 this.isRecording = true 488 this.recordedFrames = [] 489 490 let firstFrame = -1 491 let passedFrame = false 492 493 let snapFrame = () => { 494 if (firstFrame == -1 && this.animationFrame == 0) return // no idea why this works but removing it breaks things 495 496 let frame = new Uint8Array(this.app.renderer.extract.pixels(this.app.stage)).buffer 497 // console.log([this.animationFrame, firstFrame]) 498 499 if (firstFrame == -1) firstFrame = this.animationFrame // on first frame 500 else if (firstFrame != this.animationFrame) passedFrame = true // on passing first frame 501 else if (passedFrame) { // on returning to first frame 502 clearInterval(recorder) 503 this.app.stage.removeChild(bounds) 504 this.isRecording = false 505 506 console.info("[animation] Downloading .apng file") 507 let apng = UPNG.encode(this.recordedFrames, w, h, 0, new Array(this.recordedFrames.length).fill(FRAME_DELAY)) 508 let apngBlob = new Blob([apng], { type: "image/apng" }) 509 downloadFile(apngBlob, this.getDownloadString() + "_" + animName + ".apng") 510 511 return 512 } 513 514 this.recordedFrames.push(frame) 515 console.info(`[animation] Recorded frame ${this.animationFrame}`) 516 } 517 518 let recorder = setInterval(async () => { 519 snapFrame() 520 }, FRAME_DELAY); 521 snapFrame() 522 } 523 524 async getDataURL() { 525 let [imgData, pixels] = await Promise.all([ 526 this.app.renderer.extract.image(this.sprite, "image/png", 1), 527 this.app.renderer.extract.pixels(this.sprite) 528 ]); 529 530 await new Promise((res, rej) => { 531 imgData.onload = () => res(imgData); 532 imgData.onerror = (e) => rej(e); 533 }) 534 535 let spriteSize = [imgData.width || Math.ceil(this.sprite.width), imgData.height || Math.ceil(this.sprite.height)] 536 537 let xRange = [spriteSize[0], 0] 538 let yRange = [spriteSize[1], 0] 539 540 for (let i = 3; i < pixels.length; i += 4) { 541 let alpha = pixels[i] 542 let realIndex = (i - 3) / 4 543 let pos = [realIndex % spriteSize[0], Math.floor(realIndex / spriteSize[0])] 544 545 if (alpha > 10) { // if pixel is not blank...
546 if (pos[0] < xRange[0]) xRange[0] = pos[0] // if x pos is < the lowest x pos so far 547 else if (pos[0] > xRange[1]) xRange[1] = pos[0] // if x pos is > the highest x pos so far 548 if (pos[1] < yRange[0]) yRange[0] = pos[1] // if y pos is < the lowest y pos so far 549 else if (pos[1] > yRange[1]) yRange[1] = pos[1] // if y pos is > the highest y pos so far 550 } 551 } 552 553 xRange[1]++ 554 yRange[1]++ 555 556 let canv = document.createElement("canvas") 557 let ctx = canv.getContext('2d') 558 559 canv.width = xRange[1] - xRange[0] 560 canv.height = yRange[1] - yRange[0] 561 ctx.drawImage(imgData, -xRange[0], -yRange[0]) 562 563 return canv.toDataURL("image/png") 564 } 565 566 getDownloadString() { 567 return `${this.formName()}_${this.id}` 568 } 569 570 async pngExport() { 571 let b64data = await this.getDataURL() 572 let downloader = document.createElement('a'); 573 downloader.href = b64data 574 downloader.setAttribute("download", `${this.getDownloadString()}.png`); 575 document.body.appendChild(downloader); 576 downloader.click(); 577 document.body.removeChild(downloader); 578 } 579 580 async copyToClipboard() { 581 let b64data = await this.getDataURL() 582 let blob = await fetch(b64data).then(res => res.blob()) 583 if (typeof ClipboardItem == "undefined") return alert("Clipboard copying is not supported on this browser! :(") 584 let item = new ClipboardItem({ [blob.type]: blob });
585 navigator.clipboard.write([item]); 586 } 587 588 psdExport() { 589 if (!window.agPsd) return appendScript("ag-psd").then(x => this.psdExport()) 590 591 let glowing = this.isGlowing() 592 this.setGlow(true) 593 594 let psd = { width: this.app.stage.width, height: this.app.stage.height, children: [] } 595 596 let ic2 = this.secondaryIcon ? this.secondaryIcon.getAllLayers() : [] 597 let allLayers = this.getAllLayers().concat(ic2) 598 let renderer = this.app.renderer 599 let complex = this.complex 600 601 function addPSDLayer(layer, parent, sprite) { 602 allLayers.forEach(x => x.sprite.alpha = 0) 603 layer.sprite.alpha = 255 604 605 let layerChild = { name: layer.colorName, canvas: renderer.extract.canvas(sprite) } 606 if (layer.colorType == "g") { 607 if (parent.part) layerChild.name = parent.part.name + " glow" 608 if (!complex && !glowing) layerChild.hidden = true 609 } 610 return layerChild 611 } 612 613 let diffIcons = [this] 614 if (this.secondaryIcon) diffIcons.push(this.secondaryIcon) 615 616 diffIcons.forEach((i, idx) => { 617 i.layers.forEach(x => { 618 let iconType = (formNames[i.form] || i.form) 619 620 let partName = x.part ? x.part.name : (iconType[0].toUpperCase() + iconType.slice(1)) 621 let folder = { 622 name: partName, 623 children: x.sections.map(layer => addPSDLayer(layer, x, this.sprite)), 624 opened: true 625 } 626 if (idx == 0) psd.children.push(folder) // main icon 627 else psd.children[0].children.splice(1, 0, folder) // secondary icon 628 }) 629 }) 630 631 if (complex) { 632 let glowFolder = { name: "Glow", children: [], opened: true, hidden: !glowing } 633 glowFolder.children = this.glowLayers.map(x => addPSDLayer(x.sections[0], x, this.sprite)) 634 psd.children.unshift(glowFolder) 635 } 636 637 allLayers.forEach(x => x.sprite.alpha = 255) 638 let output = agPsd.writePsd(psd) 639 let blob = new Blob([output]); 640 downloadFile(blob, `${this.getDownloadString()}.psd`) 641 this.setGlow(glowing) 642 } 643 644 destroy() { 645 if (this.ease) this.ease.destroy() 646 this.sprite.destroy() 647 } 648} 649 650class IconPart { 651 constructor(form, id, colors, glow, misc = {}) { 652 653 if (colors[1] === 0 && !misc.skipGlow) glow = true // add glow if p1 is black 654 655 let iconPath = `${form}_${padZero(id)}` 656 let partString = misc.part ? "_" + padZero(misc.part.part) : "" 657 658 let sections = {} 659 if (misc.part) this.part = misc.part 660 661 this.sprite = new PIXI.Container(); 662 this.sections = [] 663 664 if (!misc.skipGlow) { 665 let glowCol = getGlowColor(colors) 666 let glowPath = `${iconPath}${partString}_glow_001.png` 667 668 if (misc.customFiles) { 669 let foundCustomGlow = misc.customFiles[glowPath] 670 if (!foundCustomGlow) { 671 alert(`Warning: This icon's glow texture (${glowPath}) could not be found! It will be rendered without a glow layer.`) 672 glowPath = null 673 } 674 } 675 676 if (glowPath) { 677 sections.glow = new IconLayer(glowPath, glowCol, "g", misc.customFiles) 678 if (!glow) sections.glow.sprite.visible = false 679 } 680 681 } 682 683 if (!misc.onlyGlow) { 684 if (form == "bird") { // ufo top 685 sections.ufo = new IconLayer(`${iconPath}_3_001.png`, colors["u"] || WHITE, "u", misc.customFiles) 686 if (misc.noDome) sections.ufo.sprite.visible = false 687 } 688 689 sections.col1 = new IconLayer(`${iconPath}${partString}_001.png`, colors["1"], "1", misc.customFiles) 690 sections.col2 = new IconLayer(`${iconPath}${partString}_2_001.png`, colors["2"], "2", misc.customFiles) 691 692 let extraPath = `${iconPath}${partString}_extra_001.png` 693 let hasExtra = misc.customFiles ? misc.customFiles[extraPath] : iconFrames[extraPath] 694 if (hasExtra) { 695 sections.white = new IconLayer(extraPath, colors["w"], "w", misc.customFiles) 696 } 697 } 698 699 let layerOrder = ["glow", "ufo", "col2", "col1", "white"].map(x => sections[x]).filter(x => x) 700 layerOrder.forEach(x => { 701 this.sections.push(x) 702 this.sprite.addChild(x.sprite) 703 }) 704 } 705} 706 707class IconLayer { 708 constructor(path, color, colorType, customFiles) { 709 710 let loadedTexture = loadedIcons[path] 711 712 if (customFiles) { 713 let customPath = customFiles[path]; 714 if (!customPath) throw new Error(`Could not find texture: ${path}`) 715 loadedTexture = customPath.texture 716 } 717 718 let loadedOffsets = iconFrames[path] 719 if (customFiles) loadedOffsets = customFiles[path].frames 720 721 this.offsets = loadedOffsets || { spriteOffset: [0, 0] } 722 this.sprite = new PIXI.Sprite(loadedTexture || PIXI.Texture.EMPTY) 723 this.name = path 724 725 this.colorType = colorType 726 this.colorName = colorNames[colorType] 727 this.setColor(color) 728 this.applyOffset() 729 730 if (this.offsets.textureRotated) { 731 this.sprite.angle = -90 732 } 733 this.angleOffset = this.sprite.angle 734 735 this.sprite.anchor.set(0.5) 736 } 737 738 applyOffset() { 739 this.sprite.position.x = Number(this.offsets.spriteOffset[0] || 0) 740 this.sprite.position.y = Number(this.offsets.spriteOffset[1] || 0) * -1 741 } 742 743 setColor(color) { 744 this.color = validNum(color, WHITE) 745 this.sprite.tint = this.color 746 } 747}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.