MooMoo.io Custom Store

Customize store

安裝腳本?
作者推薦腳本

您可能也會喜歡 MooMoo.io Insta Helper

安裝腳本
  1. // ==UserScript==
  2. // @name MooMoo.io Custom Store
  3. // @description Customize store
  4. // @author KOOKY WARRIOR
  5. // @match *://*.moomoo.io/*
  6. // @icon https://moomoo.io/img/favicon.png?v=1
  7. // @require https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.10.2/Sortable.min.js
  8. // @require https://cdnjs.cloudflare.com/ajax/libs/msgpack-lite/0.1.26/msgpack.min.js
  9. // @require https://greasyfork.org/scripts/478839-moomoo-io-packet-code/code/MooMooio%20Packet%20Code.js
  10. // @run-at document-start
  11. // @grant unsafeWindow
  12. // @license MIT
  13. // @version 1.1.1
  14. // @namespace https://greasyfork.org/users/999838
  15. // ==/UserScript==
  16. /*
  17. - Right-click the Store Button to access the editing options
  18. - Press "B" to toggle store menu
  19. - To change the order of your hats and accessories, simply drag and drop them to the desired position
  20. - Add a blank space to give your collection some extra style
  21. - Don't need a particular hat or accessory? Delete it with just a click
  22. - Export your changes or import customizations
  23. */
  24.  
  25. ;(async () => {
  26. unsafeWindow.customStore = true
  27. const elementID = (id) => {
  28. return document.getElementById(id)
  29. }
  30. const myPlayer = {
  31. sid: null,
  32. tails: {},
  33. skins: {},
  34. tailIndex: 0,
  35. skinIndex: 0,
  36. team: null
  37. }
  38. var inGame = false
  39. var alliances = []
  40. var alliancePlayers = []
  41. var totalAllianceEle = 0
  42. var currentAllianceEle = 0
  43. let init = false
  44. await new Promise(async (resolve) => {
  45. let { send } = WebSocket.prototype
  46.  
  47. WebSocket.prototype.send = function (...x) {
  48. send.apply(this, x)
  49. this.send = send
  50. if (!init) {
  51. init = true
  52. this.addEventListener("message", (e) => {
  53. if (!e.origin.includes("moomoo.io") && !unsafeWindow.privateServer) return
  54. const [packet, data] = msgpack.decode(new Uint8Array(e.data))
  55. switch (packet) {
  56. case PACKETCODE.RECEIVE.updateStoreItems:
  57. if (data[2]) {
  58. if (!data[0]) {
  59. myPlayer.tails[data[1]] = 1
  60. } else {
  61. myPlayer.tailIndex = data[1]
  62. }
  63. } else {
  64. if (!data[0]) {
  65. myPlayer.skins[data[1]] = 1
  66. } else {
  67. myPlayer.skinIndex = data[1]
  68. }
  69. }
  70. if (elementID("storeMenu").style.display == "block") {
  71. generateStoreList()
  72. }
  73. break
  74. case PACKETCODE.RECEIVE.setupGame:
  75. myPlayer.sid = data[0]
  76. inGame = true
  77. break
  78. case PACKETCODE.RECEIVE.killPlayer:
  79. inGame = false
  80. break
  81. case PACKETCODE.RECEIVE.addAlliance:
  82. alliances.push(data[0])
  83. totalAllianceEle = myPlayer.team != null ? alliancePlayers.length / 2 : alliances.length
  84. currentAllianceEle = Math.min(totalAllianceEle - 5, currentAllianceEle + 5)
  85. if (elementID("allianceMenu").style.display == "block") {
  86. showAllianceMenu()
  87. }
  88. break
  89. case PACKETCODE.RECEIVE.setPlayerTeam:
  90. myPlayer.team = data[0]
  91. myPlayer.isOwner = data[1]
  92. currentAllianceEle = 0
  93. totalAllianceEle = myPlayer.team != null ? alliancePlayers.length / 2 : alliances.length
  94. if (elementID("allianceMenu").style.display == "block") {
  95. showAllianceMenu()
  96. }
  97. break
  98. case PACKETCODE.RECEIVE.setAlliancePlayers:
  99. alliancePlayers = data[0]
  100. totalAllianceEle = myPlayer.team != null ? alliancePlayers.length / 2 : alliances.length
  101. currentAllianceEle = Math.min(totalAllianceEle - 5, currentAllianceEle + 5)
  102. if (elementID("allianceMenu").style.display == "block") {
  103. showAllianceMenu()
  104. }
  105. break
  106. case PACKETCODE.RECEIVE.deleteAlliance:
  107. for (var i = alliances.length - 1; i >= 0; i--) {
  108. if (alliances[i].sid == data[0]) {
  109. alliances.splice(i, 1)
  110. break
  111. }
  112. }
  113. totalAllianceEle = myPlayer.team != null ? alliancePlayers.length / 2 : alliances.length
  114. currentAllianceEle = Math.min(totalAllianceEle - 5, currentAllianceEle + 5)
  115. if (elementID("allianceMenu").style.display == "block") {
  116. showAllianceMenu()
  117. }
  118. break
  119. case PACKETCODE.RECEIVE.setInitData:
  120. alliances = data[0].teams
  121. totalAllianceEle = myPlayer.team != null ? alliancePlayers.length / 2 : alliances.length
  122. break
  123. }
  124. })
  125. }
  126. resolve(this)
  127. }
  128. })
  129.  
  130. function waitForElm(selector) {
  131. return new Promise((resolve) => {
  132. if (document.querySelector(selector)) {
  133. return resolve(document.querySelector(selector))
  134. }
  135.  
  136. const observer = new MutationObserver((mutations) => {
  137. if (document.querySelector(selector)) {
  138. resolve(document.querySelector(selector))
  139. observer.disconnect()
  140. }
  141. })
  142.  
  143. observer.observe(document.body, {
  144. childList: true,
  145. subtree: true
  146. })
  147. })
  148. }
  149.  
  150. const customStoreHolder = document.createElement("div")
  151. customStoreHolder.id = "customStoreHolder"
  152. const customStoreScrollBar = document.createElement("div")
  153. customStoreScrollBar.id = "customStoreScrollBar"
  154. waitForElm("#storeHolder").then((storeHolder) => {
  155. const style = document.createElement("style")
  156. style.innerHTML = `
  157. #customStoreHolder {
  158. pointer-events: all;
  159. width: 400px;
  160. display: inline-block;
  161. background-color: rgba(0, 0, 0, 0.25);
  162. -webkit-border-radius: 4px;
  163. -moz-border-radius: 4px;
  164. border-radius: 4px;
  165. color: #fff;
  166. padding: 10px;
  167. height: 200px;
  168. max-height: calc(100vh - 200px);
  169. overflow-y: scroll;
  170. -webkit-overflow-scrolling: touch;
  171. }
  172. .storeItem {
  173. font-size: 24px;
  174. }
  175. .hatPreview {
  176. width: 45px;
  177. height: 45px;
  178. }
  179. .joinAlBtn {
  180. font-size: 24px;
  181. }
  182. .itemPrice {
  183. font-size: 24px;
  184. }
  185. #customStoreScrollBar {
  186. display: none;
  187. position: absolute;
  188. width: 3px;
  189. background: white;
  190. left: calc(50% + 210px - 3px);
  191. border-radius: 10px;
  192. }
  193. `
  194. document.head.appendChild(style)
  195. storeHolder.parentNode.insertBefore(customStoreHolder, storeHolder.nextSibling)
  196. storeHolder.parentNode.insertBefore(customStoreScrollBar, storeHolder.nextSibling)
  197. storeHolder.style.display = "none"
  198. })
  199.  
  200. var currentStoreIndex = 0
  201. var store = {
  202. hats: [
  203. {
  204. id: 51,
  205. name: "Moo Cap",
  206. price: 0,
  207. scale: 120,
  208. desc: "coolest mooer around"
  209. },
  210. {
  211. id: 50,
  212. name: "Apple Cap",
  213. price: 0,
  214. scale: 120,
  215. desc: "apple farms remembers"
  216. },
  217. {
  218. id: 28,
  219. name: "Moo Head",
  220. price: 0,
  221. scale: 120,
  222. desc: "no effect"
  223. },
  224. {
  225. id: 29,
  226. name: "Pig Head",
  227. price: 0,
  228. scale: 120,
  229. desc: "no effect"
  230. },
  231. {
  232. id: 30,
  233. name: "Fluff Head",
  234. price: 0,
  235. scale: 120,
  236. desc: "no effect"
  237. },
  238. {
  239. id: 36,
  240. name: "Pandou Head",
  241. price: 0,
  242. scale: 120,
  243. desc: "no effect"
  244. },
  245. {
  246. id: 37,
  247. name: "Bear Head",
  248. price: 0,
  249. scale: 120,
  250. desc: "no effect"
  251. },
  252. {
  253. id: 38,
  254. name: "Monkey Head",
  255. price: 0,
  256. scale: 120,
  257. desc: "no effect"
  258. },
  259. {
  260. id: 44,
  261. name: "Polar Head",
  262. price: 0,
  263. scale: 120,
  264. desc: "no effect"
  265. },
  266. {
  267. id: 35,
  268. name: "Fez Hat",
  269. price: 0,
  270. scale: 120,
  271. desc: "no effect"
  272. },
  273. {
  274. id: 42,
  275. name: "Enigma Hat",
  276. price: 0,
  277. scale: 120,
  278. desc: "join the enigma army"
  279. },
  280. {
  281. id: 43,
  282. name: "Blitz Hat",
  283. price: 0,
  284. scale: 120,
  285. desc: "hey everybody i'm blitz"
  286. },
  287. {
  288. id: 49,
  289. name: "Bob XIII Hat",
  290. price: 0,
  291. scale: 120,
  292. desc: "like and subscribe"
  293. },
  294. {
  295. id: 57,
  296. name: "Pumpkin",
  297. price: 50,
  298. scale: 120,
  299. desc: "Spooooky"
  300. },
  301. {
  302. id: 8,
  303. name: "Bummle Hat",
  304. price: 100,
  305. scale: 120,
  306. desc: "no effect"
  307. },
  308. {
  309. id: 2,
  310. name: "Straw Hat",
  311. price: 500,
  312. scale: 120,
  313. desc: "no effect"
  314. },
  315. {
  316. id: 15,
  317. name: "Winter Cap",
  318. price: 600,
  319. scale: 120,
  320. desc: "allows you to move at normal speed in snow",
  321. coldM: 1
  322. },
  323. {
  324. id: 5,
  325. name: "Cowboy Hat",
  326. price: 1000,
  327. scale: 120,
  328. desc: "no effect"
  329. },
  330. {
  331. id: 4,
  332. name: "Ranger Hat",
  333. price: 2000,
  334. scale: 120,
  335. desc: "no effect"
  336. },
  337. {
  338. id: 18,
  339. name: "Explorer Hat",
  340. price: 2000,
  341. scale: 120,
  342. desc: "no effect"
  343. },
  344. {
  345. id: 31,
  346. name: "Flipper Hat",
  347. price: 2500,
  348. scale: 120,
  349. desc: "have more control while in water",
  350. watrImm: true
  351. },
  352. {
  353. id: 1,
  354. name: "Marksman Cap",
  355. price: 3000,
  356. scale: 120,
  357. desc: "increases arrow speed and range",
  358. aMlt: 1.3
  359. },
  360. {
  361. id: 10,
  362. name: "Bush Gear",
  363. price: 3000,
  364. scale: 160,
  365. desc: "allows you to disguise yourself as a bush"
  366. },
  367. {
  368. id: 48,
  369. name: "Halo",
  370. price: 3000,
  371. scale: 120,
  372. desc: "no effect"
  373. },
  374. {
  375. id: 6,
  376. name: "Soldier Helmet",
  377. price: 4000,
  378. scale: 120,
  379. desc: "reduces damage taken but slows movement",
  380. spdMult: 0.94,
  381. dmgMult: 0.75
  382. },
  383. {
  384. id: 23,
  385. name: "Anti Venom Gear",
  386. price: 4000,
  387. scale: 120,
  388. desc: "makes you immune to poison",
  389. poisonRes: 1
  390. },
  391. {
  392. id: 13,
  393. name: "Medic Gear",
  394. price: 5000,
  395. scale: 110,
  396. desc: "slowly regenerates health over time",
  397. healthRegen: 3
  398. },
  399. {
  400. id: 9,
  401. name: "Miners Helmet",
  402. price: 5000,
  403. scale: 120,
  404. desc: "earn 1 extra gold per resource",
  405. extraGold: 1
  406. },
  407. {
  408. id: 32,
  409. name: "Musketeer Hat",
  410. price: 5000,
  411. scale: 120,
  412. desc: "reduces cost of projectiles",
  413. projCost: 0.5
  414. },
  415. {
  416. id: 7,
  417. name: "Bull Helmet",
  418. price: 6000,
  419. scale: 120,
  420. desc: "increases damage done but drains health",
  421. healthRegen: -5,
  422. dmgMultO: 1.5,
  423. spdMult: 0.96
  424. },
  425. {
  426. id: 22,
  427. name: "Emp Helmet",
  428. price: 6000,
  429. scale: 120,
  430. desc: "turrets won't attack but you move slower",
  431. antiTurret: 1,
  432. spdMult: 0.7
  433. },
  434. {
  435. id: 12,
  436. name: "Booster Hat",
  437. price: 6000,
  438. scale: 120,
  439. desc: "increases your movement speed",
  440. spdMult: 1.16
  441. },
  442. {
  443. id: 26,
  444. name: "Barbarian Armor",
  445. price: 8000,
  446. scale: 120,
  447. desc: "knocks back enemies that attack you",
  448. dmgK: 0.6
  449. },
  450. {
  451. id: 21,
  452. name: "Plague Mask",
  453. price: 10000,
  454. scale: 120,
  455. desc: "melee attacks deal poison damage",
  456. poisonDmg: 5,
  457. poisonTime: 6
  458. },
  459. {
  460. id: 46,
  461. name: "Bull Mask",
  462. price: 10000,
  463. scale: 120,
  464. desc: "bulls won't target you unless you attack them",
  465. bullRepel: 1
  466. },
  467. {
  468. id: 14,
  469. name: "Windmill Hat",
  470. topSprite: true,
  471. price: 10000,
  472. scale: 120,
  473. desc: "generates points while worn",
  474. pps: 1.5
  475. },
  476. {
  477. id: 11,
  478. name: "Spike Gear",
  479. topSprite: true,
  480. price: 10000,
  481. scale: 120,
  482. desc: "deal damage to players that damage you",
  483. dmg: 0.45
  484. },
  485. {
  486. id: 53,
  487. name: "Turret Gear",
  488. topSprite: true,
  489. price: 10000,
  490. scale: 120,
  491. desc: "you become a walking turret",
  492. turret: {
  493. proj: 1,
  494. range: 700,
  495. rate: 2500
  496. },
  497. spdMult: 0.7
  498. },
  499. {
  500. id: 20,
  501. name: "Samurai Armor",
  502. price: 12000,
  503. scale: 120,
  504. desc: "increased attack speed and fire rate",
  505. atkSpd: 0.78
  506. },
  507. {
  508. id: 58,
  509. name: "Dark Knight",
  510. price: 12000,
  511. scale: 120,
  512. desc: "restores health when you deal damage",
  513. healD: 0.4
  514. },
  515. {
  516. id: 27,
  517. name: "Scavenger Gear",
  518. price: 15000,
  519. scale: 120,
  520. desc: "earn double points for each kill",
  521. kScrM: 2
  522. },
  523. {
  524. id: 40,
  525. name: "Tank Gear",
  526. price: 15000,
  527. scale: 120,
  528. desc: "increased damage to buildings but slower movement",
  529. spdMult: 0.3,
  530. bDmg: 3.3
  531. },
  532. {
  533. id: 52,
  534. name: "Thief Gear",
  535. price: 15000,
  536. scale: 120,
  537. desc: "steal half of a players gold when you kill them",
  538. goldSteal: 0.5
  539. },
  540. {
  541. id: 55,
  542. name: "Bloodthirster",
  543. price: 20000,
  544. scale: 120,
  545. desc: "Restore Health when dealing damage. And increased damage",
  546. healD: 0.25,
  547. dmgMultO: 1.2
  548. },
  549. {
  550. id: 56,
  551. name: "Assassin Gear",
  552. price: 20000,
  553. scale: 120,
  554. desc: "Go invisible when not moving. Can't eat. Increased speed",
  555. noEat: true,
  556. spdMult: 1.1,
  557. invisTimer: 1000
  558. }
  559. ],
  560. accessories: [
  561. {
  562. id: 12,
  563. name: "Snowball",
  564. price: 1000,
  565. scale: 105,
  566. xOff: 18,
  567. desc: "no effect"
  568. },
  569. {
  570. id: 9,
  571. name: "Tree Cape",
  572. price: 1000,
  573. scale: 90,
  574. desc: "no effect"
  575. },
  576. {
  577. id: 10,
  578. name: "Stone Cape",
  579. price: 1000,
  580. scale: 90,
  581. desc: "no effect"
  582. },
  583. {
  584. id: 3,
  585. name: "Cookie Cape",
  586. price: 1500,
  587. scale: 90,
  588. desc: "no effect"
  589. },
  590. {
  591. id: 8,
  592. name: "Cow Cape",
  593. price: 2000,
  594. scale: 90,
  595. desc: "no effect"
  596. },
  597. {
  598. id: 11,
  599. name: "Monkey Tail",
  600. price: 2000,
  601. scale: 97,
  602. xOff: 25,
  603. desc: "Super speed but reduced damage",
  604. spdMult: 1.35,
  605. dmgMultO: 0.2
  606. },
  607. {
  608. id: 17,
  609. name: "Apple Basket",
  610. price: 3000,
  611. scale: 80,
  612. xOff: 12,
  613. desc: "slowly regenerates health over time",
  614. healthRegen: 1
  615. },
  616. {
  617. id: 6,
  618. name: "Winter Cape",
  619. price: 3000,
  620. scale: 90,
  621. desc: "no effect"
  622. },
  623. {
  624. id: 4,
  625. name: "Skull Cape",
  626. price: 4000,
  627. scale: 90,
  628. desc: "no effect"
  629. },
  630. {
  631. id: 5,
  632. name: "Dash Cape",
  633. price: 5000,
  634. scale: 90,
  635. desc: "no effect"
  636. },
  637. {
  638. id: 2,
  639. name: "Dragon Cape",
  640. price: 6000,
  641. scale: 90,
  642. desc: "no effect"
  643. },
  644. {
  645. id: 1,
  646. name: "Super Cape",
  647. price: 8000,
  648. scale: 90,
  649. desc: "no effect"
  650. },
  651. {
  652. id: 7,
  653. name: "Troll Cape",
  654. price: 8000,
  655. scale: 90,
  656. desc: "no effect"
  657. },
  658. {
  659. id: 14,
  660. name: "Thorns",
  661. price: 10000,
  662. scale: 115,
  663. xOff: 20,
  664. desc: "no effect"
  665. },
  666. {
  667. id: 15,
  668. name: "Blockades",
  669. price: 10000,
  670. scale: 95,
  671. xOff: 15,
  672. desc: "no effect"
  673. },
  674. {
  675. id: 20,
  676. name: "Devils Tail",
  677. price: 10000,
  678. scale: 95,
  679. xOff: 20,
  680. desc: "no effect"
  681. },
  682. {
  683. id: 16,
  684. name: "Sawblade",
  685. price: 12000,
  686. scale: 90,
  687. spin: true,
  688. xOff: 0,
  689. desc: "deal damage to players that damage you",
  690. dmg: 0.15
  691. },
  692. {
  693. id: 13,
  694. name: "Angel Wings",
  695. price: 15000,
  696. scale: 138,
  697. xOff: 22,
  698. desc: "slowly regenerates health over time",
  699. healthRegen: 3
  700. },
  701. {
  702. id: 19,
  703. name: "Shadow Wings",
  704. price: 15000,
  705. scale: 138,
  706. xOff: 22,
  707. desc: "increased movement speed",
  708. spdMult: 1.1
  709. },
  710. {
  711. id: 18,
  712. name: "Blood Wings",
  713. price: 20000,
  714. scale: 178,
  715. xOff: 26,
  716. desc: "restores health when you deal damage",
  717. healD: 0.2
  718. },
  719. {
  720. id: 21,
  721. name: "Corrupt X Wings",
  722. price: 20000,
  723. scale: 178,
  724. xOff: 26,
  725. desc: "deal damage to players that damage you",
  726. dmg: 0.25
  727. }
  728. ]
  729. }
  730. for (let i = 0; i < store.hats.length; ++i) {
  731. if (store.hats[i].price <= 0) {
  732. myPlayer.skins[store.hats[i].id] = 1
  733. }
  734. }
  735. for (let i = 0; i < store.accessories.length; ++i) {
  736. if (store.accessories[i].price <= 0) {
  737. myPlayer.tails[store.accessories[i].id] = 1
  738. }
  739. }
  740. var checkRealStore = JSON.parse(localStorage.getItem("realStore"))
  741. if (checkRealStore == null) {
  742. localStorage.setItem("realStore", JSON.stringify(store))
  743. }
  744. var customStore = JSON.parse(localStorage.getItem("customStore"))
  745. if (customStore == null) {
  746. customStore = store
  747. localStorage.setItem("customStore", JSON.stringify(store))
  748. }
  749.  
  750. var totalShopEle = customStore.hats.length
  751. var currentShopEle = 0
  752. function updateScroll() {
  753. if (customStoreButton.style.background == "red") return
  754. const elements = document.querySelectorAll("#customStoreHolder > .storeItem")
  755. const storeArray = []
  756. for (let i = 0; i < elements.length; i++) {
  757. if (currentShopEle <= i && i < currentShopEle + 4) {
  758. elements[i].style.display = null
  759. storeArray.push(tmpArray[i].blank ? "blank" : tmpArray[i].id)
  760. } else {
  761. elements[i].style.display = "none"
  762. }
  763. }
  764.  
  765. const elementHeight = 220 / elements.length
  766. customStoreScrollBar.style.height = `${elementHeight * 4}px`
  767. customStoreScrollBar.style.marginTop = `${elementHeight * currentShopEle}px`
  768. customStoreScrollBar.style.display = elements.length <= 4 ? "none" : "block"
  769. if (unsafeWindow.recorder) {
  770. unsafeWindow.updateStoreData = [currentStoreIndex, storeArray, elements.length, currentShopEle]
  771. unsafeWindow.sendToLocal("addData", [
  772. Date.now().toString(),
  773. { type: "updateStore", data: [currentStoreIndex, storeArray, elements.length, currentShopEle] }
  774. ])
  775. }
  776. }
  777. customStoreHolder.addEventListener("wheel", (event) => {
  778. if (event.wheelDelta > 0) {
  779. currentShopEle = Math.max(0, currentShopEle - 4)
  780. } else {
  781. currentShopEle = Math.min(totalShopEle - 4, currentShopEle + 4)
  782. }
  783. updateScroll()
  784. })
  785.  
  786. var sortable = null,
  787. tmpArray
  788. function generateStoreList() {
  789. if (inGame) {
  790. while (customStoreHolder.hasChildNodes()) {
  791. customStoreHolder.removeChild(customStoreHolder.lastChild)
  792. }
  793. var index = currentStoreIndex
  794. tmpArray = index ? customStore.accessories : customStore.hats
  795. totalShopEle = tmpArray.length
  796. addEdit.style.display = customStoreButton.style.background == "red" ? null : "none"
  797. reloadEdit.style.display = customStoreButton.style.background == "red" ? null : "none"
  798. importBut.style.display = customStoreButton.style.background == "red" ? null : "none"
  799. exportBut.style.display = customStoreButton.style.background == "red" ? null : "none"
  800. customStoreHolder.style.overflowY = customStoreButton.style.background == "red" ? null : "hidden"
  801. Array.from(tmpArray).forEach((ele) => {
  802. let tmp = document.createElement("div")
  803. tmp.id = "storeDisplay" + ele.id
  804. tmp.className = "storeItem"
  805. customStoreHolder.appendChild(tmp)
  806.  
  807. let childtmp
  808. if (!ele.blank) {
  809. tmp.onmouseout = () => {
  810. unsafeWindow.showItemInfo()
  811. }
  812. tmp.onmouseover = () => {
  813. unsafeWindow.showItemInfo(ele, false, true)
  814. }
  815.  
  816. childtmp = document.createElement("img")
  817. childtmp.className = "hatPreview"
  818. childtmp.src = "../img/" + (index ? "accessories/access_" : "hats/hat_") + ele.id + (ele.topSprite ? "_p" : "") + ".png"
  819. tmp.appendChild(childtmp)
  820.  
  821. childtmp = document.createElement("span")
  822. childtmp.textContent = ele.name
  823. tmp.appendChild(childtmp)
  824. } else {
  825. childtmp = document.createElement("div")
  826. childtmp.className = "hatPreview"
  827. tmp.appendChild(childtmp)
  828. }
  829.  
  830. if (customStoreButton.style.background == "red") {
  831. childtmp = document.createElement("div")
  832. childtmp.className = "joinAlBtn"
  833. childtmp.style = "margin-top: 5px"
  834. childtmp.textContent = "Delete"
  835. tmp.appendChild(childtmp)
  836. childtmp.onclick = () => {
  837. let arr = index ? customStore.accessories : customStore.hats
  838. const objWithIdIndex = arr.findIndex((obj) => obj.id === ele.id)
  839. if (objWithIdIndex > -1) {
  840. arr.splice(objWithIdIndex, 1)
  841. }
  842. localStorage.setItem("customStore", JSON.stringify(customStore))
  843. generateStoreList()
  844. }
  845. } else if (!ele.blank) {
  846. if (index ? !myPlayer.tails[ele.id] : !myPlayer.skins[ele.id]) {
  847. childtmp = document.createElement("div")
  848. childtmp.className = "joinAlBtn"
  849. childtmp.style = "margin-top: 5px"
  850. childtmp.textContent = "Buy"
  851. childtmp.onclick = () => {
  852. unsafeWindow.storeBuy(ele.id, index)
  853. }
  854. tmp.appendChild(childtmp)
  855.  
  856. childtmp = document.createElement("span")
  857. childtmp.className = "itemPrice"
  858. childtmp.textContent = ele.price
  859. tmp.appendChild(childtmp)
  860. } else if ((index ? myPlayer.tailIndex : myPlayer.skinIndex) == ele.id) {
  861. childtmp = document.createElement("div")
  862. childtmp.className = "joinAlBtn"
  863. childtmp.style = "margin-top: 5px"
  864. childtmp.textContent = "Unequip"
  865. childtmp.onclick = () => {
  866. unsafeWindow.storeEquip(0, index)
  867. }
  868. tmp.appendChild(childtmp)
  869. } else {
  870. childtmp = document.createElement("div")
  871. childtmp.className = "joinAlBtn"
  872. childtmp.style = "margin-top: 5px"
  873. childtmp.textContent = "Equip"
  874. childtmp.onclick = () => {
  875. unsafeWindow.storeEquip(ele.id, index)
  876. }
  877. tmp.appendChild(childtmp)
  878. }
  879. }
  880. })
  881. updateScroll()
  882. if (customStoreButton.style.background == "red") {
  883. if (sortable != null) {
  884. sortable.destroy()
  885. }
  886. sortable = new Sortable.create(customStoreHolder, {
  887. animation: 150,
  888. onUpdate: (event) => {
  889. let arr = index ? customStore.accessories : customStore.hats
  890. if (event.newIndex >= arr.length) {
  891. var k = event.newIndex - arr.length + 1
  892. while (k--) {
  893. arr.push(undefined)
  894. }
  895. }
  896. arr.splice(event.newIndex, 0, arr.splice(event.oldIndex, 1)[0])
  897. localStorage.setItem("customStore", JSON.stringify(customStore))
  898. }
  899. })
  900. } else {
  901. if (sortable != null) {
  902. sortable.destroy()
  903. sortable = null
  904. }
  905. }
  906. }
  907. }
  908.  
  909. const customStoreButton = document.createElement("div")
  910. customStoreButton.id = "customStoreButton"
  911. customStoreButton.className = "uiElement gameButton"
  912. customStoreButton.innerHTML = `<i class="material-icons" style="font-size:40px; vertical-align:middle"></i>`
  913. customStoreButton.onclick = () => {
  914. if (elementID("storeMenu").style.display != "block") {
  915. elementID("storeMenu").style.display = "block"
  916. elementID("allianceMenu").style.display = "none"
  917. elementID("chatBox").value = ""
  918. elementID("chatHolder").style.display = "none"
  919. generateStoreList()
  920. } else {
  921. elementID("storeMenu").style.display = "none"
  922. customStoreButton.style.background = null
  923. }
  924. }
  925. customStoreButton.oncontextmenu = (event) => {
  926. event.preventDefault()
  927. if (elementID("storeMenu").style.display != "block") {
  928. elementID("storeMenu").style.display = "block"
  929. elementID("allianceMenu").style.display = "none"
  930. elementID("chatBox").value = ""
  931. elementID("chatHolder").style.display = "none"
  932. if (customStoreButton.style.background != "red") {
  933. customStoreButton.style.background = "red"
  934. }
  935. generateStoreList()
  936. } else {
  937. elementID("storeMenu").style.display = "none"
  938. customStoreButton.style.background = null
  939. }
  940. }
  941.  
  942. waitForElm("#storeButton").then((storeButton) => {
  943. const style = document.createElement("style")
  944. style.innerHTML = `
  945. #customStoreButton {
  946. right: 330px;
  947. }
  948. @media only screen and (max-width: 896px) {
  949. #customStoreButton {
  950. top: inherit;
  951. right: 60px;
  952. }
  953. }
  954. `
  955. document.head.appendChild(style)
  956. storeButton.parentNode.insertBefore(customStoreButton, storeButton.nextSibling)
  957. storeButton.hidden = true
  958. })
  959.  
  960. waitForElm("#storeMenu > div:nth-child(1) > div:nth-child(1)").then((storeTab1) => {
  961. storeTab1.addEventListener("click", () => {
  962. currentStoreIndex = 0
  963. currentShopEle = 0
  964. generateStoreList()
  965. })
  966. })
  967. const addEdit = document.createElement("div")
  968. addEdit.className = "storeTab"
  969. addEdit.textContent = "Add Blank"
  970. addEdit.style.display = "none"
  971. addEdit.style.marginLeft = "10px"
  972. const reloadEdit = document.createElement("div")
  973. reloadEdit.className = "storeTab"
  974. reloadEdit.textContent = "Reload"
  975. reloadEdit.style.display = "none"
  976. reloadEdit.style.marginLeft = "10px"
  977. const importBut = document.createElement("div")
  978. importBut.className = "storeTab"
  979. importBut.textContent = "Import"
  980. importBut.style.marginLeft = "10px"
  981. const exportBut = document.createElement("div")
  982. exportBut.className = "storeTab"
  983. exportBut.textContent = "Export"
  984. exportBut.style.marginLeft = "10px"
  985. waitForElm("#storeMenu > div:nth-child(1) > div:nth-child(2)").then((storeTab2) => {
  986. storeTab2.addEventListener("click", () => {
  987. currentStoreIndex = 1
  988. currentShopEle = 0
  989. generateStoreList()
  990. })
  991.  
  992. storeTab2.parentNode.appendChild(addEdit)
  993. addEdit.onclick = () => {
  994. let arr = currentStoreIndex ? customStore.accessories : customStore.hats
  995. let id = Math.max(...arr.map((el) => el.id)) + 1001
  996.  
  997. let min = customStoreHolder.getBoundingClientRect().top + 10
  998. let top,
  999. index = 0
  1000. let childrens = customStoreHolder.childNodes
  1001. for (var i = 0; i < childrens.length; i++) {
  1002. top = Math.abs(childrens[i].getBoundingClientRect().top)
  1003. if (top <= min) {
  1004. index = i + 1
  1005. }
  1006. }
  1007. arr.splice(index, 0, { id: id, blank: true })
  1008. localStorage.setItem("customStore", JSON.stringify(customStore))
  1009. generateStoreList()
  1010. }
  1011.  
  1012. storeTab2.parentNode.appendChild(reloadEdit)
  1013. reloadEdit.onclick = () => {
  1014. let realStore = JSON.parse(localStorage.getItem("realStore"))
  1015. currentStoreIndex ? (customStore.accessories = realStore.accessories) : (customStore.hats = realStore.hats)
  1016. localStorage.setItem("customStore", JSON.stringify(customStore))
  1017. currentShopEle = 0
  1018. generateStoreList()
  1019. }
  1020.  
  1021. storeTab2.parentNode.appendChild(importBut)
  1022. importBut.onclick = () => {
  1023. const tmpEle = document.createElement("input")
  1024. tmpEle.type = "file"
  1025. tmpEle.style.display = "none"
  1026. document.body.appendChild(tmpEle)
  1027. tmpEle.addEventListener("change", async () => {
  1028. let data = await new Response(tmpEle.files[0]).json()
  1029. customStore = data
  1030. localStorage.setItem("customStore", JSON.stringify(data))
  1031. tmpEle.remove()
  1032. currentShopEle = 0
  1033. generateStoreList()
  1034. })
  1035. tmpEle.click()
  1036. }
  1037.  
  1038. storeTab2.parentNode.appendChild(exportBut)
  1039. exportBut.onclick = () => {
  1040. let dataStr = JSON.stringify(customStore)
  1041. let dataUri = "data:application/jsoncharset=utf-8," + encodeURIComponent(dataStr)
  1042.  
  1043. let exportFileDefaultName = `customStore_${Date.now()}.json`
  1044.  
  1045. let linkElement = document.createElement("a")
  1046. linkElement.setAttribute("href", dataUri)
  1047. linkElement.setAttribute("download", exportFileDefaultName)
  1048. linkElement.click()
  1049. }
  1050. })
  1051.  
  1052. unsafeWindow.addEventListener("keydown", (event) => {
  1053. if (event.code == "Escape") {
  1054. customStoreButton.style.background = null
  1055. } else if (event.code == "KeyB" && document.activeElement.tagName != "INPUT" && inGame) {
  1056. if (elementID("storeMenu").style.display != "block") {
  1057. elementID("storeMenu").style.display = "block"
  1058. elementID("allianceMenu").style.display = "none"
  1059. elementID("chatBox").value = ""
  1060. elementID("chatHolder").style.display = "none"
  1061. generateStoreList()
  1062. } else {
  1063. elementID("storeMenu").style.display = "none"
  1064. customStoreButton.style.background = null
  1065. }
  1066. }
  1067. })
  1068.  
  1069. const customAllianceHolder = document.createElement("div")
  1070. customAllianceHolder.id = "customAllianceHolder"
  1071. const customAllianceScrollBar = document.createElement("div")
  1072. customAllianceScrollBar.id = "customAllianceScrollBar"
  1073. waitForElm("#allianceHolder").then((allianceHolder) => {
  1074. const style = document.createElement("style")
  1075. style.innerHTML = `
  1076. #customAllianceHolder {
  1077. pointer-events: all;
  1078. height: 200px;
  1079. max-height: calc(100vh - 260px);
  1080. overflow-y: hidden;
  1081. -webkit-overflow-scrolling: touch;
  1082. width: 350px;
  1083. display: inline-block;
  1084. text-align: left;
  1085. padding: 10px;
  1086. background-color: rgba(0, 0, 0, 0.25);
  1087. -webkit-border-radius: 4px;
  1088. -moz-border-radius: 4px;
  1089. border-radius: 4px;
  1090. }
  1091. .allianceItem {
  1092. height: 30px;
  1093. font-size: 24px;
  1094. }
  1095. #allianceNoTribe {
  1096. height: 30px;
  1097. font-size: 24px;
  1098. padding: 5px;
  1099. color: #fff;
  1100. }
  1101. #customAllianceScrollBar {
  1102. display: none;
  1103. position: absolute;
  1104. width: 3px;
  1105. background: white;
  1106. left: calc(50% + 185px - 3px);
  1107. border-radius: 10px;
  1108. }
  1109. `
  1110. document.head.appendChild(style)
  1111. allianceHolder.parentNode.insertBefore(customAllianceHolder, allianceHolder.nextSibling)
  1112. allianceHolder.parentNode.insertBefore(customAllianceScrollBar, allianceHolder.nextSibling)
  1113. allianceHolder.style.display = "none"
  1114. })
  1115.  
  1116. customAllianceHolder.addEventListener("wheel", (event) => {
  1117. if (event.wheelDelta > 0) {
  1118. currentAllianceEle = Math.max(0, currentAllianceEle - 5)
  1119. } else {
  1120. currentAllianceEle = Math.min(totalAllianceEle - 5, currentAllianceEle + 5)
  1121. }
  1122. updateAllianceScroll()
  1123. })
  1124. function updateAllianceScroll() {
  1125. const elements = document.querySelectorAll("#customAllianceHolder > .allianceItem")
  1126. const allianceArray = []
  1127. var tmpi = 0
  1128. for (let i = 0; i < elements.length; i++) {
  1129. if (currentAllianceEle <= i && i < currentAllianceEle + 5) {
  1130. elements[i].style.display = null
  1131. allianceArray.push({
  1132. sid: myPlayer.team ? alliancePlayers[tmpi] : null,
  1133. text: myPlayer.team ? alliancePlayers[tmpi + 1] : alliances[i]
  1134. })
  1135. } else {
  1136. elements[i].style.display = "none"
  1137. }
  1138.  
  1139. tmpi += 2
  1140. }
  1141.  
  1142. if (allianceArray.length <= 0 && document.getElementById("allianceNoTribe") == null) {
  1143. let tmp = document.createElement("div")
  1144. tmp.id = "allianceNoTribe"
  1145. tmp.textContent = "No Tribes Yet"
  1146. customAllianceHolder.appendChild(tmp)
  1147. }
  1148.  
  1149. const elementHeight = 220 / elements.length
  1150. customAllianceScrollBar.style.height = `${elementHeight * 5}px`
  1151. customAllianceScrollBar.style.marginTop = `${elementHeight * currentAllianceEle}px`
  1152. customAllianceScrollBar.style.display = elements.length <= 5 ? "none" : "block"
  1153.  
  1154. if (unsafeWindow.recorder) {
  1155. unsafeWindow.updateAllianceData = [myPlayer.team, allianceArray, elements.length, currentAllianceEle]
  1156. unsafeWindow.sendToLocal("addData", [
  1157. Date.now().toString(),
  1158. { type: "updateAlliance", data: [myPlayer.team, allianceArray, elements.length, currentAllianceEle] }
  1159. ])
  1160. }
  1161. }
  1162.  
  1163. waitForElm("#allianceButton").then((ele) => {
  1164. ele.addEventListener("click", () => {
  1165. showAllianceMenu()
  1166. })
  1167. })
  1168.  
  1169. function showAllianceMenu() {
  1170. if (inGame) {
  1171. if (unsafeWindow.recorder) {
  1172. unsafeWindow.sendToLocal("addData", [Date.now().toString(), { type: "changeInputText", data: ["allianceInput", ""] }])
  1173. }
  1174. while (customAllianceHolder.hasChildNodes()) {
  1175. customAllianceHolder.removeChild(customAllianceHolder.lastChild)
  1176. }
  1177. if (myPlayer.team) {
  1178. for (let i = 0; i < alliancePlayers.length; i += 2) {
  1179. let tmp = document.createElement("div")
  1180. tmp.id = "allianceItem" + alliancePlayers[i]
  1181. tmp.className = "allianceItem"
  1182. tmp.style = "color:" + (alliancePlayers[i] == myPlayer.sid ? "#fff" : "rgba(255,255,255,0.6)")
  1183. let tmp2 = document.createElement("span")
  1184. tmp2.innerText = alliancePlayers[i + 1]
  1185. tmp2.style.position = "absolute"
  1186. tmp.appendChild(tmp2)
  1187. customAllianceHolder.appendChild(tmp)
  1188.  
  1189. if (myPlayer.isOwner && alliancePlayers[i] != myPlayer.sid) {
  1190. let alliancePlayersArray = alliancePlayers
  1191. let childtmp = document.createElement("div")
  1192. childtmp.className = "joinAlBtn"
  1193. childtmp.textContent = "Kick"
  1194. childtmp.onclick = function () {
  1195. unsafeWindow.kickFromClan(alliancePlayersArray[i])
  1196. }
  1197. tmp.appendChild(childtmp)
  1198. }
  1199. }
  1200. } else if (alliances.length) {
  1201. for (let i = 0; i < alliances.length; ++i) {
  1202. let tmp = document.createElement("div")
  1203. tmp.id = "allianceItem" + alliances[i].owner
  1204. tmp.className = "allianceItem"
  1205. tmp.style = "color:" + (alliances[i].sid == myPlayer.team ? "#fff" : "rgba(255,255,255,0.6)")
  1206. let tmp2 = document.createElement("span")
  1207. tmp2.innerText = alliances[i].sid
  1208. tmp2.style.position = "absolute"
  1209. tmp.appendChild(tmp2)
  1210. customAllianceHolder.appendChild(tmp)
  1211.  
  1212. let childtmp = document.createElement("div")
  1213. childtmp.className = "joinAlBtn"
  1214. childtmp.textContent = "Join"
  1215. childtmp.onclick = function () {
  1216. unsafeWindow.sendJoin(i)
  1217. }
  1218. tmp.appendChild(childtmp)
  1219. }
  1220. }
  1221. updateAllianceScroll()
  1222. }
  1223. }
  1224. })()