MooMoo.io Custom Store

Customize store

目前为 2023-03-16 提交的版本。查看 最新版本

// ==UserScript==
// @name         MooMoo.io Custom Store
// @description  Customize store
// @author       WEIRD
// @match        *://*.moomoo.io/*
// @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
// @run-at       document-start
// @grant        unsafeWindow
// @license      MIT
// @version      0.2.1
// @namespace    https://gf.qytechs.cn/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 () => {
    const elementID = id => { return document.getElementById(id) }
    var inGame = false
    await new Promise(async resolve => {
        let { send } = WebSocket.prototype

        WebSocket.prototype.send = function (...x) {
            send.apply(this, x)
            this.send = send
            this.addEventListener("message", e => {
                const [packet, data] = msgpack.decode(new Uint8Array(e.data))
                switch (packet) {
                    case "us":
                        if (elementID("storeMenu").style.display == "block") {
                            generateStoreList()
                        }
                        break
                    case "1":
                        myPlayer.sid = data[0]
                        inGame = true
                        break
                    case "11":
                        inGame = false
                        break
                }
            })
            resolve(this)
        }
    })

    unsafeWindow.customStore = true

    const myPlayer = {
        sid: null,
        playerObject: null
    }

    const symbol = Symbol("lockDir")
    Object.defineProperty(Object.prototype, "lockDir", {
        get() { return this[symbol] },
        set(value) {
            this[symbol] = value
            if (this.isPlayer === true && this.sid === myPlayer.sid) {
                myPlayer.playerObject = this
            }
        },
        configurable: true
    })

    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"
    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;
            }
        `
        document.head.appendChild(style)
        storeHolder.parentNode.insertBefore(customStoreHolder, 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
        }]
    }
    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 sortable = null
    function generateStoreList() {
        if (inGame) {
            while (customStoreHolder.hasChildNodes()) {
                customStoreHolder.removeChild(customStoreHolder.lastChild)
            }
            var index = currentStoreIndex
            var tmpArray = index ? customStore.accessories : customStore.hats
            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"
            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.playerObject.tails[ele.id]) : (!myPlayer.playerObject.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.playerObject.tailIndex : myPlayer.playerObject.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)
                    }
                }
            })
            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
            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
            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))
            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()
                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()
        }
    })

    document.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
            }
        }
    })
})()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址