Bullet

moomoo.io visual script + clickgui (Insert to open).

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name        Bullet
// @namespace   Violentmonkey Scripts
// @icon
// @version     2.0
// @match       *://moomoo.io/*
// @match       *://sandbox.moomoo.io/*
// @grant       GM_setValue
// @grant       GM_getValue
// @license     MIT
// @author      wun1zzz
// @description moomoo.io visual script + clickgui (Insert to open).
// ==/UserScript==

(function () {
    'use strict';

    /* =========================================================
       STATE / PERSISTENCE
    ========================================================= */
    const DEFAULT_MODULES = {
        restyle: true,
        watermark: true,
        stats: true,
        timer: true,
        keystrokes: true
    };

    const PRESETS = {
        'Default': '#ffffff',
        'Blood':   '#ff3b3b',
        'Ocean':   '#3d9dff',
        'Toxic':   '#5cff6e',
        'Royal':   '#a066ff',
        'Amber':   '#ffb03d'
    };

    let modules = Object.assign({}, DEFAULT_MODULES, GM_getValue('bullet_modules_v2', {}));
    let accent = GM_getValue('bullet_accent_v2', '#ffffff');

    function persistModules() { GM_setValue('bullet_modules_v2', modules); }
    function persistAccent(c) { GM_setValue('bullet_accent_v2', c); }

    function applyAccent(c) {
        accent = c;
        document.documentElement.style.setProperty('--bullet-accent', c);
        persistAccent(c);
    }

    /* =========================================================
       GLOBAL GUI STYLE
    ========================================================= */
    const guiStyle = document.createElement('style');
    guiStyle.textContent = `
        :root { --bullet-accent: ${accent}; }

        #bullet-gui {
            position: fixed;
            top: 90px;
            left: 90px;
            width: 380px;
            z-index: 999999;
            background: rgba(14,14,17,0.94);
            border: 1px solid rgba(255,255,255,0.08);
            border-radius: 10px;
            box-shadow: 0 8px 24px rgba(0,0,0,0.6);
            font-family: 'Segoe UI', Arial, sans-serif;
            color: #e9e9ec;
            user-select: none;
            display: none;
            overflow: hidden;
        }
        #bullet-gui.open { display: block; }

        #bullet-gui .bg-header {
            padding: 12px 14px;
            display: flex;
            align-items: baseline;
            gap: 8px;
            cursor: move;
            border-bottom: 1px solid rgba(255,255,255,0.07);
            background: linear-gradient(180deg, rgba(255,255,255,0.04), transparent);
        }
        #bullet-gui .bg-title {
            font-weight: 700;
            font-size: 15px;
            letter-spacing: 1px;
            color: var(--bullet-accent);
        }
        #bullet-gui .bg-sub { font-size: 11px; color: #77777d; }
        #bullet-gui .bg-close {
            margin-left: auto;
            cursor: pointer;
            color: #77777d;
            font-size: 13px;
            padding: 2px 6px;
            border-radius: 4px;
        }
        #bullet-gui .bg-close:hover { color: #fff; background: rgba(255,255,255,0.08); }

        #bullet-gui .bg-body {
            display: grid;
            grid-template-columns: 1fr 140px;
        }
        #bullet-gui .bg-col-left {
            padding: 10px 6px;
            border-right: 1px solid rgba(255,255,255,0.06);
        }
        #bullet-gui .bg-col-right { padding: 10px; }

        #bullet-gui .bg-cat-label {
            font-size: 10px;
            letter-spacing: 1.5px;
            color: #6c6c72;
            padding: 4px 8px 8px 8px;
        }

        #bullet-gui .bg-row {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 8px 8px;
            border-radius: 6px;
            font-size: 13px;
            cursor: pointer;
        }
        #bullet-gui .bg-row:hover { background: rgba(255,255,255,0.05); }

        #bullet-gui .bg-toggle {
            width: 32px;
            height: 18px;
            border-radius: 10px;
            background: rgba(255,255,255,0.12);
            position: relative;
            transition: background 0.15s ease;
            flex-shrink: 0;
        }
        #bullet-gui .bg-toggle::after {
            content: '';
            position: absolute;
            top: 2px; left: 2px;
            width: 14px; height: 14px;
            border-radius: 50%;
            background: #d4d4d8;
            transition: transform 0.15s ease, background 0.15s ease;
        }
        #bullet-gui .bg-row.on .bg-toggle { background: rgba(255,255,255,0.2); }
        #bullet-gui .bg-row.on .bg-toggle::after {
            transform: translateX(14px);
            background: var(--bullet-accent);
        }

        #bullet-gui .bg-theme-label {
            font-size: 10px;
            letter-spacing: 1.5px;
            color: #6c6c72;
            margin-bottom: 8px;
        }
        #bullet-gui input[type="color"] {
            width: 100%;
            height: 28px;
            border: 1px solid rgba(255,255,255,0.1);
            border-radius: 6px;
            background: transparent;
            cursor: pointer;
            padding: 0;
            margin-bottom: 10px;
        }
        #bullet-gui .bg-presets {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 6px;
        }
        #bullet-gui .bg-preset {
            width: 100%;
            aspect-ratio: 1;
            border-radius: 6px;
            border: 1.5px solid rgba(255,255,255,0.15);
            cursor: pointer;
        }
        #bullet-gui .bg-preset:hover { border-color: #fff; }

        #bullet-gui .bg-footer {
            padding: 8px 12px;
            font-size: 10px;
            color: #62626a;
            border-top: 1px solid rgba(255,255,255,0.06);
            text-align: center;
        }
    `;
    document.head.appendChild(guiStyle);

    /* =========================================================
       BUILD GUI DOM
    ========================================================= */
    const gui = document.createElement('div');
    gui.id = 'bullet-gui';
    gui.innerHTML = `
        <div class="bg-header">
            <span class="bg-title">BULLET</span>
            <span class="bg-sub">v2.0</span>
            <span class="bg-close">&#10005;</span>
        </div>
        <div class="bg-body">
            <div class="bg-col-left">
                <div class="bg-cat-label">HUD</div>
                <div class="bg-row" data-mod="restyle"><span>UI Restyle</span><div class="bg-toggle"></div></div>
                <div class="bg-row" data-mod="watermark"><span>Watermark</span><div class="bg-toggle"></div></div>
                <div class="bg-row" data-mod="stats"><span>FPS &amp; Ping</span><div class="bg-toggle"></div></div>
                <div class="bg-row" data-mod="timer"><span>Session Timer</span><div class="bg-toggle"></div></div>
                <div class="bg-row" data-mod="keystrokes"><span>Keystrokes</span><div class="bg-toggle"></div></div>
            </div>
            <div class="bg-col-right">
                <div class="bg-theme-label">ACCENT</div>
                <input type="color" id="bg-color-picker" value="${accent}">
                <div class="bg-theme-label">PRESETS</div>
                <div class="bg-presets" id="bg-presets"></div>
            </div>
        </div>
        <div class="bg-footer">Insert &mdash; toggle menu</div>
    `;
    document.body.appendChild(gui);

    const presetsEl = gui.querySelector('#bg-presets');
    Object.entries(PRESETS).forEach(([name, color]) => {
        const sw = document.createElement('div');
        sw.className = 'bg-preset';
        sw.title = name;
        sw.style.background = color;
        sw.addEventListener('click', () => {
            applyAccent(color);
            gui.querySelector('#bg-color-picker').value = color;
        });
        presetsEl.appendChild(sw);
    });

    gui.querySelector('#bg-color-picker').addEventListener('input', (e) => applyAccent(e.target.value));
    gui.querySelector('.bg-close').addEventListener('click', () => closeGui());

    (function makeDraggable() {
        const header = gui.querySelector('.bg-header');
        let dragging = false, sx, sy, ox, oy;
        header.addEventListener('mousedown', (e) => {
            if (e.target.classList.contains('bg-close')) return;
            dragging = true;
            sx = e.clientX; sy = e.clientY;
            const r = gui.getBoundingClientRect();
            ox = r.left; oy = r.top;
        });
        window.addEventListener('mousemove', (e) => {
            if (!dragging) return;
            gui.style.left = (ox + e.clientX - sx) + 'px';
            gui.style.top = (oy + e.clientY - sy) + 'px';
        });
        window.addEventListener('mouseup', () => dragging = false);
    })();

    function openGui() { gui.classList.add('open'); }
    function closeGui() { gui.classList.remove('open'); }
    function toggleGui() { gui.classList.toggle('open'); }

    function isTyping() {
        const id = (document.activeElement && document.activeElement.id || '').toLowerCase();
        return id === 'chatbox' || id === 'allianceinput';
    }

    document.addEventListener('keydown', (e) => {
        if (e.keyCode === 45 && !isTyping()) toggleGui(); // Insert
    });

    /* =========================================================
       MODULES
    ========================================================= */
    const mounted = {};

    function mountRestyle() {
        const style = document.createElement('style');
        style.id = 'bullet-restyle-style';
        style.textContent = `
            #leaderboard, #chatHolder, #storeHolder,
            #allianceHolder, #itemInfoHolder, #mainMenu, .menuCard,
            #chatButton, #storeButton, #allianceButton, .uiElement,
            .actionBarItem, .gameButton, #ageBar {
                background-color: rgba(10, 10, 10, 0.9) !important;
                border-radius: 12px !important;
                border: 1px solid rgba(255, 255, 255, 0.1) !important;
                box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5) !important;
                backdrop-filter: none !important;
            }
            #mapDisplay {
                border-radius: 12px !important;
                border: 1px solid rgba(255, 255, 255, 0.2) !important;
                background:
                    linear-gradient(to bottom, transparent 49%, #3d81b8 49%, #3d81b8 51%, transparent 51%),
                    linear-gradient(to bottom,
                        #fff 0%, #fff 17%,
                        #b5d86c 17%, #b5d86c 83%,
                        #e1c66d 83%, #e1c66d 100%
                    ) !important;
                box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5) !important;
            }
            #enterGame {
                background-color: #fff !important;
                color: #000 !important;
                border-radius: 10px !important;
                font-weight: bold !important;
                border: none !important;
                cursor: pointer;
            }
            #nameInput, #serverBrowser, .storeInput {
                background-color: #151515 !important;
                border-radius: 8px !important;
                border: 1px solid #333 !important;
                color: #fff !important;
                padding: 8px !important;
            }
            #ageBarBody {
                background-color: var(--bullet-accent) !important;
                border-radius: 10px !important;
                transition: width 0.25s ease-out !important;
            }
            #gameName {
                color: #fff !important;
                text-shadow: 2px 2px 10px rgba(0,0,0,0.5) !important;
            }
            #ot-sdk-btn-container, #promoImgHolder, .adCard, #moomooio_728x90_home {
                display: none !important;
            }
        `;
        document.head.appendChild(style);
        mounted.restyle = style;

        const updateLogo = () => {
            const logo = document.getElementById('gameName');
            if (logo) logo.innerText = 'BULLET';
        };
        const checker = setInterval(() => {
            if (document.getElementById('mainMenu')) {
                updateLogo();
                clearInterval(checker);
            }
        }, 100);
        mounted.restyleChecker = checker;
    }
    function unmountRestyle() {
        if (mounted.restyle) mounted.restyle.remove();
        if (mounted.restyleChecker) clearInterval(mounted.restyleChecker);
        mounted.restyle = null;
    }

    function mountWatermark() {
        const wm = document.createElement('div');
        wm.id = 'bullet-watermark';
        wm.innerHTML = 'Bullet <span style="font-size:14px;opacity:.8;">by wun1zz 2.0</span>';
        Object.assign(wm.style, {
            position: 'fixed', top: '15px', left: '15px', zIndex: '10000',
            fontFamily: '"Hammersmith One", sans-serif', fontSize: '26px',
            color: 'var(--bullet-accent)', pointerEvents: 'none',
            textShadow: '-1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000'
        });
        document.body.appendChild(wm);
        mounted.watermark = wm;
    }
    function unmountWatermark() {
        if (mounted.watermark) mounted.watermark.remove();
        mounted.watermark = null;
    }

    function mountStats() {
        const box = document.createElement('div');
        box.id = 'bullet-stats';
        Object.assign(box.style, {
            position: 'fixed', top: '48px', left: '15px', zIndex: '10000',
            fontFamily: '"Hammersmith One", sans-serif', fontSize: '15px',
            color: 'var(--bullet-accent)', pointerEvents: 'none',
            textShadow: '-1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000'
        });
        box.innerHTML = 'FPS: 0 | Ping: 0 ms';
        document.body.appendChild(box);

        let lastTime = performance.now(), frames = 0, fps = 0, raf;
        function loop() {
            frames++;
            const now = performance.now();
            if (now - lastTime >= 1000) { fps = frames; frames = 0; lastTime = now; }
            const pingEl = document.getElementById('pingDisplay');
            const ping = pingEl ? pingEl.innerText.replace(/[^\d]/g, '') : '?';
            box.innerHTML = `FPS: ${fps} | Ping: ${ping} ms`;
            raf = requestAnimationFrame(loop);
        }
        loop();
        mounted.stats = box;
        mounted.statsStop = () => cancelAnimationFrame(raf);
    }
    function unmountStats() {
        if (mounted.statsStop) mounted.statsStop();
        if (mounted.stats) mounted.stats.remove();
        mounted.stats = null;
    }

    function mountTimer() {
        const el = document.createElement('div');
        el.id = 'bullet-session-timer';
        Object.assign(el.style, {
            position: 'fixed', top: '70px', left: '15px', color: 'var(--bullet-accent)',
            fontSize: '13px', fontFamily: 'sans-serif', background: 'rgba(0,0,0,0.4)',
            padding: '2px 8px', borderRadius: '6px', zIndex: '9999'
        });
        el.innerHTML = 'Session: 00:00';
        document.body.appendChild(el);

        const start = performance.now();
        const pad = n => String(n).padStart(2, '0');
        let raf;
        function update() {
            const t = Math.floor((performance.now() - start) / 1000);
            const h = Math.floor(t / 3600), m = Math.floor((t % 3600) / 60), s = t % 60;
            el.innerHTML = h > 0 ? `Session: ${pad(h)}:${pad(m)}:${pad(s)}` : `Session: ${pad(m)}:${pad(s)}`;
            raf = requestAnimationFrame(update);
        }
        update();
        mounted.timer = el;
        mounted.timerStop = () => cancelAnimationFrame(raf);
    }
    function unmountTimer() {
        if (mounted.timerStop) mounted.timerStop();
        if (mounted.timer) mounted.timer.remove();
        mounted.timer = null;
    }

    function mountKeystrokes() {
        const style = document.createElement('style');
        style.id = 'bullet-ks-style';
        style.textContent = `
            #ks-container {
                position: fixed; z-index: 999998; cursor: move; user-select: none;
                display: grid; grid-template-columns: repeat(6, 22px); grid-gap: 4px;
                padding: 6px; background: rgba(0,0,0,0.4); border-radius: 8px;
                font-family: 'Arial', sans-serif; border: 1px solid rgba(255,255,255,0.05);
            }
            .ks-key {
                background: rgba(0,0,0,0.7); color: #fff; border-radius: 4px;
                display: flex; flex-direction: column; align-items: center; justify-content: center;
                height: 42px; border: 1px solid rgba(255,255,255,0.1); pointer-events: none;
            }
            #ks-KeyW { grid-column: 3 / span 2; }
            #ks-KeyA { grid-column: 1 / span 2; }
            #ks-KeyS { grid-column: 3 / span 2; }
            #ks-KeyD { grid-column: 5 / span 2; }
            #ks-Mouse0 { grid-column: 1 / span 3; }
            #ks-Mouse2 { grid-column: 4 / span 3; }
            #ks-Space { grid-column: 1 / span 6; height: 26px; }
            .ks-key.active { background: var(--bullet-accent) !important; color: #000 !important; transform: scale(0.9); }
            .ks-letter { font-size: 12px; font-weight: bold; pointer-events: none; }
            .ks-count { font-size: 9px; opacity: .8; pointer-events: none; margin-top: -1px; }
        `;
        document.head.appendChild(style);
        mounted.ksStyle = style;

        const container = document.createElement('div');
        container.id = 'ks-container';
        container.style.top = GM_getValue('ks_y', '120px');
        container.style.left = GM_getValue('ks_x', '20px');

        const savedCounts = GM_getValue('ks_counts_data', {});
        const keys = [
            {id:'KeyW',txt:'W'},{id:'KeyA',txt:'A'},{id:'KeyS',txt:'S'},{id:'KeyD',txt:'D'},
            {id:'Mouse0',txt:'LMB'},{id:'Mouse2',txt:'RMB'},{id:'Space',txt:'SPACE'}
        ];
        container.innerHTML = keys.map(k => {
            const count = savedCounts[k.id] || 0;
            return `<div id="ks-${k.id}" class="ks-key"><span class="ks-letter">${k.txt}</span><span class="ks-count">${count}</span></div>`;
        }).join('');
        document.body.appendChild(container);
        mounted.ks = container;

        const currentCounts = { ...savedCounts };
        function updateState(code, isDown) {
            const el = document.getElementById(`ks-${code}`);
            if (!el) return;
            if (isDown) {
                if (!el.classList.contains('active')) {
                    currentCounts[code] = (currentCounts[code] || 0) + 1;
                    el.querySelector('.ks-count').textContent = currentCounts[code];
                    el.classList.add('active');
                    GM_setValue('ks_counts_data', currentCounts);
                }
            } else {
                el.classList.remove('active');
            }
        }
        const onKeydown = e => updateState(e.code, true);
        const onKeyup = e => updateState(e.code, false);
        const onMousedown = e => { if (e.button === 0 || e.button === 2) updateState(`Mouse${e.button}`, true); };
        const onMouseup = e => { if (e.button === 0 || e.button === 2) updateState(`Mouse${e.button}`, false); };
        const onContext = e => { if (e.target.closest('#ks-container')) e.preventDefault(); };
        const onReset = e => { if (e.shiftKey && e.altKey && e.code === 'KeyR') { GM_setValue('ks_counts_data', {}); location.reload(); } };

        window.addEventListener('keydown', onKeydown);
        window.addEventListener('keyup', onKeyup);
        window.addEventListener('mousedown', onMousedown);
        window.addEventListener('mouseup', onMouseup);
        window.addEventListener('contextmenu', onContext);
        window.addEventListener('keydown', onReset);

        let isDragging = false, sx, sy, ox, oy;
        container.onmousedown = (e) => {
            isDragging = true; sx = e.clientX; sy = e.clientY;
            ox = container.offsetLeft; oy = container.offsetTop;
        };
        const onMove = (e) => {
            if (!isDragging) return;
            container.style.left = (ox + e.clientX - sx) + 'px';
            container.style.top = (oy + e.clientY - sy) + 'px';
        };
        const onUp = () => {
            if (isDragging) {
                isDragging = false;
                GM_setValue('ks_x', container.style.left);
                GM_setValue('ks_y', container.style.top);
            }
        };
        window.addEventListener('mousemove', onMove);
        window.addEventListener('mouseup', onUp);

        mounted.ksCleanup = () => {
            window.removeEventListener('keydown', onKeydown);
            window.removeEventListener('keyup', onKeyup);
            window.removeEventListener('mousedown', onMousedown);
            window.removeEventListener('mouseup', onMouseup);
            window.removeEventListener('contextmenu', onContext);
            window.removeEventListener('keydown', onReset);
            window.removeEventListener('mousemove', onMove);
            window.removeEventListener('mouseup', onUp);
        };
    }
    function unmountKeystrokes() {
        if (mounted.ksCleanup) mounted.ksCleanup();
        if (mounted.ks) mounted.ks.remove();
        if (mounted.ksStyle) mounted.ksStyle.remove();
        mounted.ks = null;
    }

    const MODULE_FNS = {
        restyle:    { mount: mountRestyle,    unmount: unmountRestyle },
        watermark:  { mount: mountWatermark,  unmount: unmountWatermark },
        stats:      { mount: mountStats,      unmount: unmountStats },
        timer:      { mount: mountTimer,      unmount: unmountTimer },
        keystrokes: { mount: mountKeystrokes, unmount: unmountKeystrokes }
    };

    function setModule(id, on) {
        modules[id] = on;
        persistModules();
        const row = gui.querySelector(`.bg-row[data-mod="${id}"]`);
        if (row) row.classList.toggle('on', on);
        if (on) MODULE_FNS[id].mount(); else MODULE_FNS[id].unmount();
    }

    gui.querySelectorAll('.bg-row').forEach(row => {
        const id = row.dataset.mod;
        row.classList.toggle('on', !!modules[id]);
        row.addEventListener('click', () => setModule(id, !modules[id]));
        if (modules[id]) MODULE_FNS[id].mount();
    });

    /* =========================================================
       CORE (always-on) TWEAKS
    ========================================================= */
    const originalGetContext = HTMLCanvasElement.prototype.getContext;
    HTMLCanvasElement.prototype.getContext = function (type, attribs) {
        const ctx = originalGetContext.call(this, type, attribs);
        if (ctx && type === '2d') ctx.imageSmoothingEnabled = false;
        return ctx;
    };

    function patchMinimap() {
        const mapCanvas = document.getElementById('mapDisplay');
        if (!mapCanvas) return;
        const ctx = mapCanvas.getContext('2d');
        const desc = Object.getOwnPropertyDescriptor(CanvasRenderingContext2D.prototype, 'fillStyle');
        Object.defineProperty(ctx, 'fillStyle', {
            get() { return desc.get.call(this); },
            set(v) {
                if (v === '#fff' || v === '#ffffff') { desc.set.call(this, '#ff3b3b'); return; }
                if (typeof v === 'string' && v.startsWith('rgba(255,255,255')) { desc.set.call(this, 'rgba(80,230,110,0.9)'); return; }
                desc.set.call(this, v);
            }
        });
    }
    const mapChecker = setInterval(() => {
        if (document.getElementById('mapDisplay')) { patchMinimap(); clearInterval(mapChecker); }
    }, 200);

})();