MooMoo.io Custom Store

Customize store

安裝腳本?
作者推薦腳本

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

安裝腳本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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()
		}
	}
})()