MooMoo.io Custom Store

Customize store

目前为 2023-07-05 提交的版本,查看 最新版本

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