Greasy Fork is available in English.

MooMoo.io Custom Store

Customize store

스크립트 설치?
개발자의 추천 스크립트

MooMoo.io Insta Helper는 어떤가요?

스크립트 설치
질문, 리뷰하거나, 이 스크립트를 신고하세요.
// ==UserScript==
// @name         MooMoo.io Custom Store
// @description  Customize store
// @author       KOOKY WARRIOR
// @match        *://*.moomoo.io/*
// @icon         https://moomoo.io/img/favicon.png?v=1
// @require      https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.10.2/Sortable.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/msgpack-lite/0.1.26/msgpack.min.js
// @require 	 https://greasyfork.org/scripts/478839-moomoo-io-packet-code/code/MooMooio%20Packet%20Code.js
// @run-at       document-start
// @grant        unsafeWindow
// @license      MIT
// @version      1.1.1
// @namespace    https://greasyfork.org/users/999838
// ==/UserScript==
/*
- Right-click the Store Button to access the editing options
- Press "B" to toggle store menu
- To change the order of your hats and accessories, simply drag and drop them to the desired position
- Add a blank space to give your collection some extra style
- Don't need a particular hat or accessory? Delete it with just a click
- Export your changes or import customizations
*/

;(async () => {
	unsafeWindow.customStore = true
	const elementID = (id) => {
		return document.getElementById(id)
	}
	const myPlayer = {
		sid: null,
		tails: {},
		skins: {},
		tailIndex: 0,
		skinIndex: 0,
		team: null
	}
	var inGame = false
	var alliances = []
	var alliancePlayers = []
	var totalAllianceEle = 0
	var currentAllianceEle = 0
	let init = false
	await new Promise(async (resolve) => {
		let { send } = WebSocket.prototype

		WebSocket.prototype.send = function (...x) {
			send.apply(this, x)
			this.send = send
			if (!init) {
				init = true
				this.addEventListener("message", (e) => {
					if (!e.origin.includes("moomoo.io") && !unsafeWindow.privateServer) return
					const [packet, data] = msgpack.decode(new Uint8Array(e.data))
					switch (packet) {
						case PACKETCODE.RECEIVE.updateStoreItems:
							if (data[2]) {
								if (!data[0]) {
									myPlayer.tails[data[1]] = 1
								} else {
									myPlayer.tailIndex = data[1]
								}
							} else {
								if (!data[0]) {
									myPlayer.skins[data[1]] = 1
								} else {
									myPlayer.skinIndex = data[1]
								}
							}
							if (elementID("storeMenu").style.display == "block") {
								generateStoreList()
							}
							break
						case PACKETCODE.RECEIVE.setupGame:
							myPlayer.sid = data[0]
							inGame = true
							break
						case PACKETCODE.RECEIVE.killPlayer:
							inGame = false
							break
						case PACKETCODE.RECEIVE.addAlliance:
							alliances.push(data[0])
							totalAllianceEle = myPlayer.team != null ? alliancePlayers.length / 2 : alliances.length
							currentAllianceEle = Math.min(totalAllianceEle - 5, currentAllianceEle + 5)
							if (elementID("allianceMenu").style.display == "block") {
								showAllianceMenu()
							}
							break
						case PACKETCODE.RECEIVE.setPlayerTeam:
							myPlayer.team = data[0]
							myPlayer.isOwner = data[1]
							currentAllianceEle = 0
							totalAllianceEle = myPlayer.team != null ? alliancePlayers.length / 2 : alliances.length
							if (elementID("allianceMenu").style.display == "block") {
								showAllianceMenu()
							}
							break
						case PACKETCODE.RECEIVE.setAlliancePlayers:
							alliancePlayers = data[0]
							totalAllianceEle = myPlayer.team != null ? alliancePlayers.length / 2 : alliances.length
							currentAllianceEle = Math.min(totalAllianceEle - 5, currentAllianceEle + 5)
							if (elementID("allianceMenu").style.display == "block") {
								showAllianceMenu()
							}
							break
						case PACKETCODE.RECEIVE.deleteAlliance:
							for (var i = alliances.length - 1; i >= 0; i--) {
								if (alliances[i].sid == data[0]) {
									alliances.splice(i, 1)
									break
								}
							}
							totalAllianceEle = myPlayer.team != null ? alliancePlayers.length / 2 : alliances.length
							currentAllianceEle = Math.min(totalAllianceEle - 5, currentAllianceEle + 5)
							if (elementID("allianceMenu").style.display == "block") {
								showAllianceMenu()
							}
							break
						case PACKETCODE.RECEIVE.setInitData:
							alliances = data[0].teams
							totalAllianceEle = myPlayer.team != null ? alliancePlayers.length / 2 : alliances.length
							break
					}
				})
			}
			resolve(this)
		}
	})

	function waitForElm(selector) {
		return new Promise((resolve) => {
			if (document.querySelector(selector)) {
				return resolve(document.querySelector(selector))
			}

			const observer = new MutationObserver((mutations) => {
				if (document.querySelector(selector)) {
					resolve(document.querySelector(selector))
					observer.disconnect()
				}
			})

			observer.observe(document.body, {
				childList: true,
				subtree: true
			})
		})
	}

	const customStoreHolder = document.createElement("div")
	customStoreHolder.id = "customStoreHolder"
	const customStoreScrollBar = document.createElement("div")
	customStoreScrollBar.id = "customStoreScrollBar"
	waitForElm("#storeHolder").then((storeHolder) => {
		const style = document.createElement("style")
		style.innerHTML = `
            #customStoreHolder {
                pointer-events: all;
                width: 400px;
                display: inline-block;
                background-color: rgba(0, 0, 0, 0.25);
                -webkit-border-radius: 4px;
                -moz-border-radius: 4px;
                border-radius: 4px;
                color: #fff;
                padding: 10px;
                height: 200px;
                max-height: calc(100vh - 200px);
                overflow-y: scroll;
                -webkit-overflow-scrolling: touch;
            }
			.storeItem {
				font-size: 24px;
			}
			.hatPreview {
				width: 45px;
				height: 45px;
			}
			.joinAlBtn {
				font-size: 24px;
			}
			.itemPrice {
				font-size: 24px;
			}
			#customStoreScrollBar {
				display: none;
				position: absolute;
				width: 3px;
				background: white;
				left: calc(50% + 210px - 3px);
				border-radius: 10px;
			}
        `
		document.head.appendChild(style)
		storeHolder.parentNode.insertBefore(customStoreHolder, storeHolder.nextSibling)
		storeHolder.parentNode.insertBefore(customStoreScrollBar, storeHolder.nextSibling)
		storeHolder.style.display = "none"
	})

	var currentStoreIndex = 0
	var store = {
		hats: [
			{
				id: 51,
				name: "Moo Cap",
				price: 0,
				scale: 120,
				desc: "coolest mooer around"
			},
			{
				id: 50,
				name: "Apple Cap",
				price: 0,
				scale: 120,
				desc: "apple farms remembers"
			},
			{
				id: 28,
				name: "Moo Head",
				price: 0,
				scale: 120,
				desc: "no effect"
			},
			{
				id: 29,
				name: "Pig Head",
				price: 0,
				scale: 120,
				desc: "no effect"
			},
			{
				id: 30,
				name: "Fluff Head",
				price: 0,
				scale: 120,
				desc: "no effect"
			},
			{
				id: 36,
				name: "Pandou Head",
				price: 0,
				scale: 120,
				desc: "no effect"
			},
			{
				id: 37,
				name: "Bear Head",
				price: 0,
				scale: 120,
				desc: "no effect"
			},
			{
				id: 38,
				name: "Monkey Head",
				price: 0,
				scale: 120,
				desc: "no effect"
			},
			{
				id: 44,
				name: "Polar Head",
				price: 0,
				scale: 120,
				desc: "no effect"
			},
			{
				id: 35,
				name: "Fez Hat",
				price: 0,
				scale: 120,
				desc: "no effect"
			},
			{
				id: 42,
				name: "Enigma Hat",
				price: 0,
				scale: 120,
				desc: "join the enigma army"
			},
			{
				id: 43,
				name: "Blitz Hat",
				price: 0,
				scale: 120,
				desc: "hey everybody i'm blitz"
			},
			{
				id: 49,
				name: "Bob XIII Hat",
				price: 0,
				scale: 120,
				desc: "like and subscribe"
			},
			{
				id: 57,
				name: "Pumpkin",
				price: 50,
				scale: 120,
				desc: "Spooooky"
			},
			{
				id: 8,
				name: "Bummle Hat",
				price: 100,
				scale: 120,
				desc: "no effect"
			},
			{
				id: 2,
				name: "Straw Hat",
				price: 500,
				scale: 120,
				desc: "no effect"
			},
			{
				id: 15,
				name: "Winter Cap",
				price: 600,
				scale: 120,
				desc: "allows you to move at normal speed in snow",
				coldM: 1
			},
			{
				id: 5,
				name: "Cowboy Hat",
				price: 1000,
				scale: 120,
				desc: "no effect"
			},
			{
				id: 4,
				name: "Ranger Hat",
				price: 2000,
				scale: 120,
				desc: "no effect"
			},
			{
				id: 18,
				name: "Explorer Hat",
				price: 2000,
				scale: 120,
				desc: "no effect"
			},
			{
				id: 31,
				name: "Flipper Hat",
				price: 2500,
				scale: 120,
				desc: "have more control while in water",
				watrImm: true
			},
			{
				id: 1,
				name: "Marksman Cap",
				price: 3000,
				scale: 120,
				desc: "increases arrow speed and range",
				aMlt: 1.3
			},
			{
				id: 10,
				name: "Bush Gear",
				price: 3000,
				scale: 160,
				desc: "allows you to disguise yourself as a bush"
			},
			{
				id: 48,
				name: "Halo",
				price: 3000,
				scale: 120,
				desc: "no effect"
			},
			{
				id: 6,
				name: "Soldier Helmet",
				price: 4000,
				scale: 120,
				desc: "reduces damage taken but slows movement",
				spdMult: 0.94,
				dmgMult: 0.75
			},
			{
				id: 23,
				name: "Anti Venom Gear",
				price: 4000,
				scale: 120,
				desc: "makes you immune to poison",
				poisonRes: 1
			},
			{
				id: 13,
				name: "Medic Gear",
				price: 5000,
				scale: 110,
				desc: "slowly regenerates health over time",
				healthRegen: 3
			},
			{
				id: 9,
				name: "Miners Helmet",
				price: 5000,
				scale: 120,
				desc: "earn 1 extra gold per resource",
				extraGold: 1
			},
			{
				id: 32,
				name: "Musketeer Hat",
				price: 5000,
				scale: 120,
				desc: "reduces cost of projectiles",
				projCost: 0.5
			},
			{
				id: 7,
				name: "Bull Helmet",
				price: 6000,
				scale: 120,
				desc: "increases damage done but drains health",
				healthRegen: -5,
				dmgMultO: 1.5,
				spdMult: 0.96
			},
			{
				id: 22,
				name: "Emp Helmet",
				price: 6000,
				scale: 120,
				desc: "turrets won't attack but you move slower",
				antiTurret: 1,
				spdMult: 0.7
			},
			{
				id: 12,
				name: "Booster Hat",
				price: 6000,
				scale: 120,
				desc: "increases your movement speed",
				spdMult: 1.16
			},
			{
				id: 26,
				name: "Barbarian Armor",
				price: 8000,
				scale: 120,
				desc: "knocks back enemies that attack you",
				dmgK: 0.6
			},
			{
				id: 21,
				name: "Plague Mask",
				price: 10000,
				scale: 120,
				desc: "melee attacks deal poison damage",
				poisonDmg: 5,
				poisonTime: 6
			},
			{
				id: 46,
				name: "Bull Mask",
				price: 10000,
				scale: 120,
				desc: "bulls won't target you unless you attack them",
				bullRepel: 1
			},
			{
				id: 14,
				name: "Windmill Hat",
				topSprite: true,
				price: 10000,
				scale: 120,
				desc: "generates points while worn",
				pps: 1.5
			},
			{
				id: 11,
				name: "Spike Gear",
				topSprite: true,
				price: 10000,
				scale: 120,
				desc: "deal damage to players that damage you",
				dmg: 0.45
			},
			{
				id: 53,
				name: "Turret Gear",
				topSprite: true,
				price: 10000,
				scale: 120,
				desc: "you become a walking turret",
				turret: {
					proj: 1,
					range: 700,
					rate: 2500
				},
				spdMult: 0.7
			},
			{
				id: 20,
				name: "Samurai Armor",
				price: 12000,
				scale: 120,
				desc: "increased attack speed and fire rate",
				atkSpd: 0.78
			},
			{
				id: 58,
				name: "Dark Knight",
				price: 12000,
				scale: 120,
				desc: "restores health when you deal damage",
				healD: 0.4
			},
			{
				id: 27,
				name: "Scavenger Gear",
				price: 15000,
				scale: 120,
				desc: "earn double points for each kill",
				kScrM: 2
			},
			{
				id: 40,
				name: "Tank Gear",
				price: 15000,
				scale: 120,
				desc: "increased damage to buildings but slower movement",
				spdMult: 0.3,
				bDmg: 3.3
			},
			{
				id: 52,
				name: "Thief Gear",
				price: 15000,
				scale: 120,
				desc: "steal half of a players gold when you kill them",
				goldSteal: 0.5
			},
			{
				id: 55,
				name: "Bloodthirster",
				price: 20000,
				scale: 120,
				desc: "Restore Health when dealing damage. And increased damage",
				healD: 0.25,
				dmgMultO: 1.2
			},
			{
				id: 56,
				name: "Assassin Gear",
				price: 20000,
				scale: 120,
				desc: "Go invisible when not moving. Can't eat. Increased speed",
				noEat: true,
				spdMult: 1.1,
				invisTimer: 1000
			}
		],
		accessories: [
			{
				id: 12,
				name: "Snowball",
				price: 1000,
				scale: 105,
				xOff: 18,
				desc: "no effect"
			},
			{
				id: 9,
				name: "Tree Cape",
				price: 1000,
				scale: 90,
				desc: "no effect"
			},
			{
				id: 10,
				name: "Stone Cape",
				price: 1000,
				scale: 90,
				desc: "no effect"
			},
			{
				id: 3,
				name: "Cookie Cape",
				price: 1500,
				scale: 90,
				desc: "no effect"
			},
			{
				id: 8,
				name: "Cow Cape",
				price: 2000,
				scale: 90,
				desc: "no effect"
			},
			{
				id: 11,
				name: "Monkey Tail",
				price: 2000,
				scale: 97,
				xOff: 25,
				desc: "Super speed but reduced damage",
				spdMult: 1.35,
				dmgMultO: 0.2
			},
			{
				id: 17,
				name: "Apple Basket",
				price: 3000,
				scale: 80,
				xOff: 12,
				desc: "slowly regenerates health over time",
				healthRegen: 1
			},
			{
				id: 6,
				name: "Winter Cape",
				price: 3000,
				scale: 90,
				desc: "no effect"
			},
			{
				id: 4,
				name: "Skull Cape",
				price: 4000,
				scale: 90,
				desc: "no effect"
			},
			{
				id: 5,
				name: "Dash Cape",
				price: 5000,
				scale: 90,
				desc: "no effect"
			},
			{
				id: 2,
				name: "Dragon Cape",
				price: 6000,
				scale: 90,
				desc: "no effect"
			},
			{
				id: 1,
				name: "Super Cape",
				price: 8000,
				scale: 90,
				desc: "no effect"
			},
			{
				id: 7,
				name: "Troll Cape",
				price: 8000,
				scale: 90,
				desc: "no effect"
			},
			{
				id: 14,
				name: "Thorns",
				price: 10000,
				scale: 115,
				xOff: 20,
				desc: "no effect"
			},
			{
				id: 15,
				name: "Blockades",
				price: 10000,
				scale: 95,
				xOff: 15,
				desc: "no effect"
			},
			{
				id: 20,
				name: "Devils Tail",
				price: 10000,
				scale: 95,
				xOff: 20,
				desc: "no effect"
			},
			{
				id: 16,
				name: "Sawblade",
				price: 12000,
				scale: 90,
				spin: true,
				xOff: 0,
				desc: "deal damage to players that damage you",
				dmg: 0.15
			},
			{
				id: 13,
				name: "Angel Wings",
				price: 15000,
				scale: 138,
				xOff: 22,
				desc: "slowly regenerates health over time",
				healthRegen: 3
			},
			{
				id: 19,
				name: "Shadow Wings",
				price: 15000,
				scale: 138,
				xOff: 22,
				desc: "increased movement speed",
				spdMult: 1.1
			},
			{
				id: 18,
				name: "Blood Wings",
				price: 20000,
				scale: 178,
				xOff: 26,
				desc: "restores health when you deal damage",
				healD: 0.2
			},
			{
				id: 21,
				name: "Corrupt X Wings",
				price: 20000,
				scale: 178,
				xOff: 26,
				desc: "deal damage to players that damage you",
				dmg: 0.25
			}
		]
	}
	for (let i = 0; i < store.hats.length; ++i) {
		if (store.hats[i].price <= 0) {
			myPlayer.skins[store.hats[i].id] = 1
		}
	}
	for (let i = 0; i < store.accessories.length; ++i) {
		if (store.accessories[i].price <= 0) {
			myPlayer.tails[store.accessories[i].id] = 1
		}
	}
	var checkRealStore = JSON.parse(localStorage.getItem("realStore"))
	if (checkRealStore == null) {
		localStorage.setItem("realStore", JSON.stringify(store))
	}
	var customStore = JSON.parse(localStorage.getItem("customStore"))
	if (customStore == null) {
		customStore = store
		localStorage.setItem("customStore", JSON.stringify(store))
	}

	var totalShopEle = customStore.hats.length
	var currentShopEle = 0
	function updateScroll() {
		if (customStoreButton.style.background == "red") return
		const elements = document.querySelectorAll("#customStoreHolder > .storeItem")
		const storeArray = []
		for (let i = 0; i < elements.length; i++) {
			if (currentShopEle <= i && i < currentShopEle + 4) {
				elements[i].style.display = null
				storeArray.push(tmpArray[i].blank ? "blank" : tmpArray[i].id)
			} else {
				elements[i].style.display = "none"
			}
		}

		const elementHeight = 220 / elements.length
		customStoreScrollBar.style.height = `${elementHeight * 4}px`
		customStoreScrollBar.style.marginTop = `${elementHeight * currentShopEle}px`
		customStoreScrollBar.style.display = elements.length <= 4 ? "none" : "block"
		if (unsafeWindow.recorder) {
			unsafeWindow.updateStoreData = [currentStoreIndex, storeArray, elements.length, currentShopEle]
			unsafeWindow.sendToLocal("addData", [
				Date.now().toString(),
				{ type: "updateStore", data: [currentStoreIndex, storeArray, elements.length, currentShopEle] }
			])
		}
	}
	customStoreHolder.addEventListener("wheel", (event) => {
		if (event.wheelDelta > 0) {
			currentShopEle = Math.max(0, currentShopEle - 4)
		} else {
			currentShopEle = Math.min(totalShopEle - 4, currentShopEle + 4)
		}
		updateScroll()
	})

	var sortable = null,
		tmpArray
	function generateStoreList() {
		if (inGame) {
			while (customStoreHolder.hasChildNodes()) {
				customStoreHolder.removeChild(customStoreHolder.lastChild)
			}
			var index = currentStoreIndex
			tmpArray = index ? customStore.accessories : customStore.hats
			totalShopEle = tmpArray.length
			addEdit.style.display = customStoreButton.style.background == "red" ? null : "none"
			reloadEdit.style.display = customStoreButton.style.background == "red" ? null : "none"
			importBut.style.display = customStoreButton.style.background == "red" ? null : "none"
			exportBut.style.display = customStoreButton.style.background == "red" ? null : "none"
			customStoreHolder.style.overflowY = customStoreButton.style.background == "red" ? null : "hidden"
			Array.from(tmpArray).forEach((ele) => {
				let tmp = document.createElement("div")
				tmp.id = "storeDisplay" + ele.id
				tmp.className = "storeItem"
				customStoreHolder.appendChild(tmp)

				let childtmp
				if (!ele.blank) {
					tmp.onmouseout = () => {
						unsafeWindow.showItemInfo()
					}
					tmp.onmouseover = () => {
						unsafeWindow.showItemInfo(ele, false, true)
					}

					childtmp = document.createElement("img")
					childtmp.className = "hatPreview"
					childtmp.src = "../img/" + (index ? "accessories/access_" : "hats/hat_") + ele.id + (ele.topSprite ? "_p" : "") + ".png"
					tmp.appendChild(childtmp)

					childtmp = document.createElement("span")
					childtmp.textContent = ele.name
					tmp.appendChild(childtmp)
				} else {
					childtmp = document.createElement("div")
					childtmp.className = "hatPreview"
					tmp.appendChild(childtmp)
				}

				if (customStoreButton.style.background == "red") {
					childtmp = document.createElement("div")
					childtmp.className = "joinAlBtn"
					childtmp.style = "margin-top: 5px"
					childtmp.textContent = "Delete"
					tmp.appendChild(childtmp)
					childtmp.onclick = () => {
						let arr = index ? customStore.accessories : customStore.hats
						const objWithIdIndex = arr.findIndex((obj) => obj.id === ele.id)
						if (objWithIdIndex > -1) {
							arr.splice(objWithIdIndex, 1)
						}
						localStorage.setItem("customStore", JSON.stringify(customStore))
						generateStoreList()
					}
				} else if (!ele.blank) {
					if (index ? !myPlayer.tails[ele.id] : !myPlayer.skins[ele.id]) {
						childtmp = document.createElement("div")
						childtmp.className = "joinAlBtn"
						childtmp.style = "margin-top: 5px"
						childtmp.textContent = "Buy"
						childtmp.onclick = () => {
							unsafeWindow.storeBuy(ele.id, index)
						}
						tmp.appendChild(childtmp)

						childtmp = document.createElement("span")
						childtmp.className = "itemPrice"
						childtmp.textContent = ele.price
						tmp.appendChild(childtmp)
					} else if ((index ? myPlayer.tailIndex : myPlayer.skinIndex) == ele.id) {
						childtmp = document.createElement("div")
						childtmp.className = "joinAlBtn"
						childtmp.style = "margin-top: 5px"
						childtmp.textContent = "Unequip"
						childtmp.onclick = () => {
							unsafeWindow.storeEquip(0, index)
						}
						tmp.appendChild(childtmp)
					} else {
						childtmp = document.createElement("div")
						childtmp.className = "joinAlBtn"
						childtmp.style = "margin-top: 5px"
						childtmp.textContent = "Equip"
						childtmp.onclick = () => {
							unsafeWindow.storeEquip(ele.id, index)
						}
						tmp.appendChild(childtmp)
					}
				}
			})
			updateScroll()
			if (customStoreButton.style.background == "red") {
				if (sortable != null) {
					sortable.destroy()
				}
				sortable = new Sortable.create(customStoreHolder, {
					animation: 150,
					onUpdate: (event) => {
						let arr = index ? customStore.accessories : customStore.hats
						if (event.newIndex >= arr.length) {
							var k = event.newIndex - arr.length + 1
							while (k--) {
								arr.push(undefined)
							}
						}
						arr.splice(event.newIndex, 0, arr.splice(event.oldIndex, 1)[0])
						localStorage.setItem("customStore", JSON.stringify(customStore))
					}
				})
			} else {
				if (sortable != null) {
					sortable.destroy()
					sortable = null
				}
			}
		}
	}

	const customStoreButton = document.createElement("div")
	customStoreButton.id = "customStoreButton"
	customStoreButton.className = "uiElement gameButton"
	customStoreButton.innerHTML = `<i class="material-icons" style="font-size:40px; vertical-align:middle"></i>`
	customStoreButton.onclick = () => {
		if (elementID("storeMenu").style.display != "block") {
			elementID("storeMenu").style.display = "block"
			elementID("allianceMenu").style.display = "none"
			elementID("chatBox").value = ""
			elementID("chatHolder").style.display = "none"
			generateStoreList()
		} else {
			elementID("storeMenu").style.display = "none"
			customStoreButton.style.background = null
		}
	}
	customStoreButton.oncontextmenu = (event) => {
		event.preventDefault()
		if (elementID("storeMenu").style.display != "block") {
			elementID("storeMenu").style.display = "block"
			elementID("allianceMenu").style.display = "none"
			elementID("chatBox").value = ""
			elementID("chatHolder").style.display = "none"
			if (customStoreButton.style.background != "red") {
				customStoreButton.style.background = "red"
			}
			generateStoreList()
		} else {
			elementID("storeMenu").style.display = "none"
			customStoreButton.style.background = null
		}
	}

	waitForElm("#storeButton").then((storeButton) => {
		const style = document.createElement("style")
		style.innerHTML = `
            #customStoreButton {
                right: 330px;
            }
            @media only screen and (max-width: 896px) {
                #customStoreButton {
                    top: inherit;
                    right: 60px;
                }
            }
        `
		document.head.appendChild(style)
		storeButton.parentNode.insertBefore(customStoreButton, storeButton.nextSibling)
		storeButton.hidden = true
	})

	waitForElm("#storeMenu > div:nth-child(1) > div:nth-child(1)").then((storeTab1) => {
		storeTab1.addEventListener("click", () => {
			currentStoreIndex = 0
			currentShopEle = 0
			generateStoreList()
		})
	})
	const addEdit = document.createElement("div")
	addEdit.className = "storeTab"
	addEdit.textContent = "Add Blank"
	addEdit.style.display = "none"
	addEdit.style.marginLeft = "10px"
	const reloadEdit = document.createElement("div")
	reloadEdit.className = "storeTab"
	reloadEdit.textContent = "Reload"
	reloadEdit.style.display = "none"
	reloadEdit.style.marginLeft = "10px"
	const importBut = document.createElement("div")
	importBut.className = "storeTab"
	importBut.textContent = "Import"
	importBut.style.marginLeft = "10px"
	const exportBut = document.createElement("div")
	exportBut.className = "storeTab"
	exportBut.textContent = "Export"
	exportBut.style.marginLeft = "10px"
	waitForElm("#storeMenu > div:nth-child(1) > div:nth-child(2)").then((storeTab2) => {
		storeTab2.addEventListener("click", () => {
			currentStoreIndex = 1
			currentShopEle = 0
			generateStoreList()
		})

		storeTab2.parentNode.appendChild(addEdit)
		addEdit.onclick = () => {
			let arr = currentStoreIndex ? customStore.accessories : customStore.hats
			let id = Math.max(...arr.map((el) => el.id)) + 1001

			let min = customStoreHolder.getBoundingClientRect().top + 10
			let top,
				index = 0
			let childrens = customStoreHolder.childNodes
			for (var i = 0; i < childrens.length; i++) {
				top = Math.abs(childrens[i].getBoundingClientRect().top)
				if (top <= min) {
					index = i + 1
				}
			}
			arr.splice(index, 0, { id: id, blank: true })
			localStorage.setItem("customStore", JSON.stringify(customStore))
			generateStoreList()
		}

		storeTab2.parentNode.appendChild(reloadEdit)
		reloadEdit.onclick = () => {
			let realStore = JSON.parse(localStorage.getItem("realStore"))
			currentStoreIndex ? (customStore.accessories = realStore.accessories) : (customStore.hats = realStore.hats)
			localStorage.setItem("customStore", JSON.stringify(customStore))
			currentShopEle = 0
			generateStoreList()
		}

		storeTab2.parentNode.appendChild(importBut)
		importBut.onclick = () => {
			const tmpEle = document.createElement("input")
			tmpEle.type = "file"
			tmpEle.style.display = "none"
			document.body.appendChild(tmpEle)
			tmpEle.addEventListener("change", async () => {
				let data = await new Response(tmpEle.files[0]).json()
				customStore = data
				localStorage.setItem("customStore", JSON.stringify(data))
				tmpEle.remove()
				currentShopEle = 0
				generateStoreList()
			})
			tmpEle.click()
		}

		storeTab2.parentNode.appendChild(exportBut)
		exportBut.onclick = () => {
			let dataStr = JSON.stringify(customStore)
			let dataUri = "data:application/jsoncharset=utf-8," + encodeURIComponent(dataStr)

			let exportFileDefaultName = `customStore_${Date.now()}.json`

			let linkElement = document.createElement("a")
			linkElement.setAttribute("href", dataUri)
			linkElement.setAttribute("download", exportFileDefaultName)
			linkElement.click()
		}
	})

	unsafeWindow.addEventListener("keydown", (event) => {
		if (event.code == "Escape") {
			customStoreButton.style.background = null
		} else if (event.code == "KeyB" && document.activeElement.tagName != "INPUT" && inGame) {
			if (elementID("storeMenu").style.display != "block") {
				elementID("storeMenu").style.display = "block"
				elementID("allianceMenu").style.display = "none"
				elementID("chatBox").value = ""
				elementID("chatHolder").style.display = "none"
				generateStoreList()
			} else {
				elementID("storeMenu").style.display = "none"
				customStoreButton.style.background = null
			}
		}
	})

	const customAllianceHolder = document.createElement("div")
	customAllianceHolder.id = "customAllianceHolder"
	const customAllianceScrollBar = document.createElement("div")
	customAllianceScrollBar.id = "customAllianceScrollBar"
	waitForElm("#allianceHolder").then((allianceHolder) => {
		const style = document.createElement("style")
		style.innerHTML = `
            #customAllianceHolder {
                pointer-events: all;
				height: 200px;
				max-height: calc(100vh - 260px);
				overflow-y: hidden;
				-webkit-overflow-scrolling: touch;
				width: 350px;
				display: inline-block;
				text-align: left;
				padding: 10px;
				background-color: rgba(0, 0, 0, 0.25);
				-webkit-border-radius: 4px;
				-moz-border-radius: 4px;
				border-radius: 4px;
            }
			.allianceItem {
				height: 30px;
				font-size: 24px;
			}
			#allianceNoTribe {
				height: 30px;
				font-size: 24px;
				padding: 5px;
				color: #fff;
			}
			#customAllianceScrollBar {
				display: none;
				position: absolute;
				width: 3px;
				background: white;
				left: calc(50% + 185px - 3px);
				border-radius: 10px;
			}
        `
		document.head.appendChild(style)
		allianceHolder.parentNode.insertBefore(customAllianceHolder, allianceHolder.nextSibling)
		allianceHolder.parentNode.insertBefore(customAllianceScrollBar, allianceHolder.nextSibling)
		allianceHolder.style.display = "none"
	})

	customAllianceHolder.addEventListener("wheel", (event) => {
		if (event.wheelDelta > 0) {
			currentAllianceEle = Math.max(0, currentAllianceEle - 5)
		} else {
			currentAllianceEle = Math.min(totalAllianceEle - 5, currentAllianceEle + 5)
		}
		updateAllianceScroll()
	})
	function updateAllianceScroll() {
		const elements = document.querySelectorAll("#customAllianceHolder > .allianceItem")
		const allianceArray = []
		var tmpi = 0
		for (let i = 0; i < elements.length; i++) {
			if (currentAllianceEle <= i && i < currentAllianceEle + 5) {
				elements[i].style.display = null
				allianceArray.push({
					sid: myPlayer.team ? alliancePlayers[tmpi] : null,
					text: myPlayer.team ? alliancePlayers[tmpi + 1] : alliances[i]
				})
			} else {
				elements[i].style.display = "none"
			}

			tmpi += 2
		}

		if (allianceArray.length <= 0 && document.getElementById("allianceNoTribe") == null) {
			let tmp = document.createElement("div")
			tmp.id = "allianceNoTribe"
			tmp.textContent = "No Tribes Yet"
			customAllianceHolder.appendChild(tmp)
		}

		const elementHeight = 220 / elements.length
		customAllianceScrollBar.style.height = `${elementHeight * 5}px`
		customAllianceScrollBar.style.marginTop = `${elementHeight * currentAllianceEle}px`
		customAllianceScrollBar.style.display = elements.length <= 5 ? "none" : "block"

		if (unsafeWindow.recorder) {
			unsafeWindow.updateAllianceData = [myPlayer.team, allianceArray, elements.length, currentAllianceEle]
			unsafeWindow.sendToLocal("addData", [
				Date.now().toString(),
				{ type: "updateAlliance", data: [myPlayer.team, allianceArray, elements.length, currentAllianceEle] }
			])
		}
	}

	waitForElm("#allianceButton").then((ele) => {
		ele.addEventListener("click", () => {
			showAllianceMenu()
		})
	})

	function showAllianceMenu() {
		if (inGame) {
			if (unsafeWindow.recorder) {
				unsafeWindow.sendToLocal("addData", [Date.now().toString(), { type: "changeInputText", data: ["allianceInput", ""] }])
			}
			while (customAllianceHolder.hasChildNodes()) {
				customAllianceHolder.removeChild(customAllianceHolder.lastChild)
			}
			if (myPlayer.team) {
				for (let i = 0; i < alliancePlayers.length; i += 2) {
					let tmp = document.createElement("div")
					tmp.id = "allianceItem" + alliancePlayers[i]
					tmp.className = "allianceItem"
					tmp.style = "color:" + (alliancePlayers[i] == myPlayer.sid ? "#fff" : "rgba(255,255,255,0.6)")
					let tmp2 = document.createElement("span")
					tmp2.innerText = alliancePlayers[i + 1]
					tmp2.style.position = "absolute"
					tmp.appendChild(tmp2)
					customAllianceHolder.appendChild(tmp)

					if (myPlayer.isOwner && alliancePlayers[i] != myPlayer.sid) {
						let alliancePlayersArray = alliancePlayers
						let childtmp = document.createElement("div")
						childtmp.className = "joinAlBtn"
						childtmp.textContent = "Kick"
						childtmp.onclick = function () {
							unsafeWindow.kickFromClan(alliancePlayersArray[i])
						}
						tmp.appendChild(childtmp)
					}
				}
			} else if (alliances.length) {
				for (let i = 0; i < alliances.length; ++i) {
					let tmp = document.createElement("div")
					tmp.id = "allianceItem" + alliances[i].owner
					tmp.className = "allianceItem"
					tmp.style = "color:" + (alliances[i].sid == myPlayer.team ? "#fff" : "rgba(255,255,255,0.6)")
					let tmp2 = document.createElement("span")
					tmp2.innerText = alliances[i].sid
					tmp2.style.position = "absolute"
					tmp.appendChild(tmp2)
					customAllianceHolder.appendChild(tmp)

					let childtmp = document.createElement("div")
					childtmp.className = "joinAlBtn"
					childtmp.textContent = "Join"
					childtmp.onclick = function () {
						unsafeWindow.sendJoin(i)
					}
					tmp.appendChild(childtmp)
				}
			}
			updateAllianceScroll()
		}
	}
})()