MooMoo.io Custom Store

Customize store

当前为 2023-09-14 提交的版本,查看 最新版本

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